API Design Principles: What Makes an SDK Feel Good
Lessons from building the Velora SDK and consuming dozens of APIs. Naming, errors, pagination, and the small decisions that separate an SDK people love from one they tolerate.
I have built one SDK that people use, the Velora SDK, and consumed dozens that other people built. The difference between an SDK that feels good and one that feels hostile is not features. It is a handful of small decisions made consistently. Here are the ones I care about most.
Names that read like sentences
A good API call reads like a sentence. velora.jobs.create() reads as 'Velora, jobs, create.' The verb is last, the resource is in the middle, the namespace is first. This is the convention in Stripe, in the GitHub SDK, and in every API I have enjoyed using. The alternative is methods named createJob, getJob, listJobs, which puts the verb first and reads like a database driver. It works, but it does not feel like language.
If your API call does not read like a sentence, you are designing for the machine, not the human who has to type it.
Errors that tell you what to do next
An error that says 'Invalid request' is useless. An error that says 'schedule must be a valid cron expression, received "every 5 minutes"' is useful. The second one tells the developer exactly what is wrong and what shape the input should be. Every error in the Velora SDK includes a code, a human message, and the field that failed. The code is for the program, the message is for the developer, the field is for the form.
throw new VeloraError({
code: "INVALID_CRON",
message: 'schedule must be a valid cron expression, received "every 5 minutes"',
field: "schedule",
docs: "https://docs.velora.dev/cron-syntax",
});Pagination that does not make you think
There are two kinds of pagination: offset and cursor. Offset is easier to understand and breaks at scale. Cursor is harder to explain and works at any scale. The Velora SDK uses cursor pagination everywhere and exposes it as an async iterator, so the caller does not have to think about the cursor at all. They write a for-await loop and get every job. The pagination is invisible, which is the goal.
Types are documentation
If your SDK is typed, the types are the documentation that is always up to date. A developer who types velora.jobs.create( and sees the parameter shape does not need to open a browser. Keep the types honest. Do not use any. Do not use string where an enum would do. The moment a developer hits a runtime error that the types said was impossible, they stop trusting the types, and the types are the whole point.