API Design Guidelines
Schema designs
This document covers general API structure. For data schema design of specific vertical, check out the following:
General
Vital API and Org Management API
Core concepts:
Each Vital customer is represented as an Org.
Vital API comprises of multiple regional environments (US, EU, etc)
Within each Vital API regional environment, an Org can have one or more groups of Users, represented as Teams.
Vital API is the SaaS application plane:
✅ It provides user management and order management capabilities for each Team.
✅ It provides access to all structured data associated with Users and Orders.
✅ It is responsible for ingesting new data, both via regular polling and receiving pushed data from providers and labs.
❌ It cannot be used to inspect or change any settings of the Teams or the Orgs.
Org Management API is the SaaS control plane:
✅ It provides administrative capability for the Orgs themselves, such as Billing and (Admin) Memberships.
✅ It provides the means to create and delete Teams, as well as
✅ It manages all aspects of Team configurations and settings.
❌ It cannot be used to access users, orders, and associated data of any specific Team.
Case studies:
Considering the requirement for programmatically customizing Brand Information:
Brand Information is a team configuration.
Therefore, it should be exposed through the Org Management API.
Considering the requirement to expose Body Temperature data:
Body Temperature data are associated data of a User in a Team.
Therefore, it should be exposed through the Vital API.
Field naming
General
Prefer full name over abbreviation. Use abbreviation sparingly, and only when it is an established, unambiguous terms of art.
✅ HRV instead of heart rate variability
✅ API instead of application programming interface
❌ HR instead of heart rate
❌ DoB instead of date of birth
Standard fields
Pagination
General
Prefer cursor-based pagination whenever possible. For example:
The dataset only needs to be forward paginated.
The endpoint provides a comprehensive filtering and sorting options.
Use offset-based pagination only when the use case strictly requires:
bidirectional pagination; and
the ability to jump between pages.
… which is rarely observed in practice.
It is always preferrable to provide more filtering options — so that the result set can be narrowed down — over providing precise offset-based pagination to walk over the whole dataset.
Cursor-based pagination (Recommended)
Query parameter:
next_cursor
(optional): The cursor for fetching the next page.
Response field:
The top-level item array (of the current page) should be named after the entity in plural form.
The cursor for the next page must be provided at
$.next_cursor
. If there is no more data,$.next_cursor
must benull
.
Offset-based pagination
Query parameters:
page
(optional): The page index to fetch; one-based.size
(optional): The page size to use.
Response field:
The top-level item array (of the current page) should be named after the entity in plural form.
$.page
: The current page index; one-based.$.size
: The page size used.$.pages
: The total number of pages.$.total
: The total number of items.
Resource Paths
Pathing hierarchical resources
General
Use underscore when a resource name comprises of multiple words. Do not use break it into multiple subpaths to refer to the one particular resource or one RPC method.
✅
/team_etl_pipelines
❌
/team/etl/pipelines
✅
/introspect/historical_pull
❌
/introspect/historical/pull
See also: RPC-style resources
Custom RPC method should be an imperative clause (starting with a verb).
✅
POST /billing/check_checkout_session
❌
POST /billing/checkout_session
See also: Custom RPC method on a resource
Cancellation should be a Custom RPC method.
It must not use the
DELETE
verb.For cancellations that would result in resource deletion, consider calling these deletions instead of cancellations.
Top-level resources
A specific uniquely identifiable resource must be pathed as:
**resource**/{resource_id}
e.g., **user**/a13f0e7d-18a1-4262-a096-ee7319fa4692
Resource name should be singular. See Singular or Plural?.
Nested resources
If it is a nested resource, unless there is a compelling API ergonomic argument, it should be nested under its parent as:
**parent**/{parent_id}/**child**/{child_id}
e.g., **org**/{parent_id}/**team**/{team_id}
in Org Management API
Resource name should be singular. See Singular or Plural?.
✅ A compelling API ergonomic argument should include these factors:
A Death Valley in cardinality: The cardinality of the child resource is many orders of magnitude higher than that of the parent resource.
e.g., Vital API: team ↔ user & order
Requestor is the parent: The parent resource can be consistently inferred from the requestor authentication.
e.g., Vital API: team ↔ user & order — both can consistently infer the parent Team ID from requestor AuthN.
❌ An example of a non-compelling argument could be:
“Org Management
/v1/org/{}/team/{}
should be shortened to/v1/team/{}
.”The domain was provisioned to support multiple Orgs, despite banning it at UX level.
We cannot consistently infer the Org from (at least) the Auth0 authentication, because an Auth0 user can be associated with multiple Orgs.
Singular or Plural?
RESTful collection resources
If a domain entity being exposed is representable as a RESTful collection resource, where you can:
List some or all resources, with optional filtering and sorting queries.
Retrieve one specific resource by its unique identifier or an alias like
latest
.(Optionally) Create one new resource
(Optionally) Delete one existing resource by a unique identifier
(Optionally) Patch one existing resource by a unique identifier
You should use a singular path.
For example, for an API that exposes a collection of apples:
List all apples:
GET /v3/apple
Get a specific apple:
GET /v3/apple/{apple_id}
Create a new apple:
POST /v3/apple
Delete a specific apple:
DELETE /v3/apple/{apple_id}
Patch a specific apple:
PATCH /v3/apple/{apple_id}
Using a singular noun ensures that all methods manipulating apples share a consistent URL prefix.
RPC-style resources
When in doubt, prefer singular.
Having said that, sometimes APIs cannot be represented as a RESTful collection resource. For example:
We have a deliberate reason not to provide the ability to retrieve individual resources.
The endpoint is an action (verb), rather than a resource (countable noun).
In these scenarios, you may use a plural name.
For example, in the Org Management API, the Team ETL Pipeline endpoints (as well as Team Custom Credentials, and Team API Keys) are all designed to be a set of RPC-style, **batch-**centric operations:
List all active Team ETL Pipeline config
GET /v1/org/{}/team_etl_pipelines/{env}/{region}
Set active Team ETL Pipeline config
POST /v1/org/{}/team_etl_pipelines/{env}/{region}
It has create-or-replace semantic, as well as broadcast semantic (set this config against multiple teams).
This endpoint does not satisfy the RESTful collection style.
Clear Team ETL Pipeline config
DELETE /v1/org/{}/team_etl_pipelines/{env}/{region}
Likewise, this endpoint has broadcast semantic for targeting multiple teams.
This endpoint does not satisfy the RESTful collection style.
So these endpoints use a /team_etl_pipeline**s**/**
(plural) common prefix. This creates a subtle yet important distintion from RESTful collection resources which — as defined above — use singular noun in URL paths.
Custom RPC method on a resource
For endpoints that perform an action not fitting the RESTful verbs, name the endpoint with the verb or an imperative clause, and place it as a subpath under the subject being actioned. For example:
POST /v1/org/{}/billing/check_checkout_session
(imperative clause)POST /v3/order/testkit/register
(verb)POST /v3/order/{}/cancel
(verb)
These endpoints must use the POST
HTTP verb.
Standard Terminology
Last updated