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

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:

  1. bidirectional pagination; and

  2. 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.

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 be null.

GET /v2/data?next_cursor=bm90IGlwc3VtIGxvcmVuIGlwc3Vt

{
  "credentials": [
    { ... },
    { ... },
  ],
  "next_cursor": "bG9yZW0gaXBzdW0gbG9yZW0gaXBzdW0="
}

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.

GET /v2/lab_tests/markers?page=2

{
  "markers": [
    { ... },
    { ... },
  ],
  "total": 1000,
  "page": 2,
  "size": 50,
  "pages": 20
}

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

  • Custom RPC method should be an imperative clause (starting with a verb).

  • 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:

  1. POST /v1/org/{}/billing/check_checkout_session (imperative clause)

  2. POST /v3/order/testkit/register (verb)

  3. POST /v3/order/{}/cancel (verb)

These endpoints must use the POST HTTP verb.

Standard Terminology

Terminology

Last updated