Skip to content

SDKs

The TypeScript and Python clients, what each adds over raw fetch, and how their types stay honest.

Two official clients. Both generate their types from /docs/openapi.yaml, which is itself generated from the schemas the server validates with, and both fail their build if the generated tree drifts from the published document. If it compiles, it speaks the deployed contract.

Both are published, both are prerelease. Install them with the commands below. They stay prerelease while the developer product settles, which means 0.1.x can still move under you: pin the version you tested against, and read the changelog before you take a new one.

TypeScript

Published as @cuvo-health-us/api under the next tag:

pnpm add @cuvo-health-us/api@next

Node 20 or newer. verifySignature uses node:crypto; the client runs anywhere there is a fetch.

import { createCuvoClient, unwrap } from "@cuvo-health-us/api";

const cuvo = createCuvoClient({
  apiKey: process.env.CUVO_API_KEY!,
  // organization: "org_…",  // only when the credential holds more than one grant
});

const patient = unwrap(
  await cuvo.POST("/v1/patients", { body: { first_name: "Ada", /* … */ } }),
);

createCuvoClient takes an API key or an OAuth access token in the same option, since both arrive as a bearer.

The exports are small on purpose:

ExportWhat it does
createCuvoClientThe typed client
unwrap, CuvoApiErrorTurn a problem body into a thrown error carrying code and issues
verifySignature, parseEventThe webhook recipe, constant time, with a timestamp tolerance
paginateAn async iterator over a list, so you never touch a cursor
typedEvent, expandEventNarrow an event by its type, then read the resource it names
paths, components, operationsThe generated contract types

typedEvent is the piece worth knowing about. It pairs an event's type with the resource type that event can name, so case.approved.v1 narrows to a Case and nothing else, and a mismatch is caught at runtime rather than becoming an undefined field three lines later.

Python

Published as cuvo, at version 0.1.0a1. It is a prerelease, so pip needs to be told to take one:

pip install --pre cuvo

Python 3.11 or newer. httpx under the hood.

import os
from cuvo import CuvoClient

cuvo = CuvoClient(os.environ["CUVO_API_KEY"])

The same helpers by the same names: verify_signature, parse_event, paginate, expand_event, CuvoApiError. Every generated endpoint has an asyncio twin, and it carries the same guarantees as the sync one: the same headers, the same minted idempotency keys, the same backoff. Await cuvo.aclose() when you are done with an async client.

The command line

@cuvo-health-us/cli is the same contract as a cuvo command, generated from the same document and carried over the TypeScript client, so it inherits the headers, the minted idempotency keys and the retries described below. cuvo login signs a person in through the browser and cuvo login --client-id signs a server in with its secret. Output is JSON on stdout by default, which is what makes it usable from a script or an agent as well as by hand. See CLI.

What both add over raw fetch

Three things, and they are most of why the clients exist.

  1. Headers. Authorization, and Cuvo-Organization when you named one.
  2. An Idempotency-Key on every write. Required by the API, minted per call, and reused across the client's own retries. Pass your own when the retry comes from your job queue rather than from theirs. See Rate limits and idempotency.
  3. Retries that are safe. On 429 and on the server faults a second attempt can clear, with exponential backoff, jitter, and Retry-After honoured when the server names a delay. A write with no idempotency key is never retried, because a timeout and a success are indistinguishable.

Verifying a webhook

import { parseEvent } from "@cuvo-health-us/api";

const raw = await request.text();
const event = parseEvent(raw, request.headers.get("cuvo-signature"), process.env.CUVO_WEBHOOK_SECRET!);

It verifies before it parses, accepts any of the signatures in the header so a rotation grace works without a code change, and throws rather than returning a half-trusted object.

No SDK for your language

The contract is an OpenAPI 3.1 document, so a generator gets you most of the way. Read Errors, Pagination and Rate limits and idempotency first: those three are what a generated client will not give you, and they are what makes a client correct rather than merely typed.

A Postman collection is generated from the same document if you would rather click through it first.