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:
| Export | What it does |
|---|---|
createCuvoClient | The typed client |
unwrap, CuvoApiError | Turn a problem body into a thrown error carrying code and issues |
verifySignature, parseEvent | The webhook recipe, constant time, with a timestamp tolerance |
paginate | An async iterator over a list, so you never touch a cursor |
typedEvent, expandEvent | Narrow an event by its type, then read the resource it names |
paths, components, operations | The 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.
- Headers.
Authorization, andCuvo-Organizationwhen you named one. - An
Idempotency-Keyon 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. - Retries that are safe. On 429 and on the server faults a second attempt can clear, with
exponential backoff, jitter, and
Retry-Afterhonoured 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.