Scopes and organizations
What a credential may do, who it may do it for, and how a clinic grants you access.
Two things decide whether a call is allowed: the scopes on the credential, and the scopes the organization granted your integration. The API uses the intersection of the two, and never more.
The scope vocabulary
Scopes are resource:verb. There are twenty.
| Scope | What it opens |
|---|---|
organization:read | GET /v1/organization, GET /v1/organizations |
catalog:read | Medications, pharmacies, states |
patients:read | Reading patients, exports, case lists by patient |
patients:write | Creating and updating patients, deletion requests |
files:read | Reading file metadata |
files:write | Reserving, confirming and deleting files |
consents:read | Reading a patient's consents |
consents:write | Recording a consent |
cases:read | Cases, their events, status history and prescriptions |
cases:write | Filing, cancelling and releasing a case |
messages:read | The message thread on a case |
messages:write | Posting to that thread |
orders:read | Orders raised from an approved case |
visits:read | Reading visits, their slots and a join link |
visits:write | Booking, cancelling and rescheduling a visit |
webhooks:manage | Endpoints, deliveries, redelivery |
events:read | The event feed |
realtime:subscribe | Minting a realtime token |
billing:read | Usage and invoices |
test:manage | The sandbox controls, test mode only |
Every operation in the reference names the one scope it requires.
The intersection rule
A credential carries the scopes you ticked when you minted it. A grant carries the scopes the organization approved. The effective set is what both agree on.
A key with cases:write acting for an organization that granted only cases:read may read cases
and nothing more. GET /v1/organization returns the effective set, so you can see what this
credential can actually do here rather than infer it from a refusal.
A call outside the effective set answers 403 forbidden.
Naming the organization
An integration can hold approved grants to several organizations. The credential is the same; the organization is chosen per request.
- One approved grant: it is used automatically and the header is optional.
- More than one: send
Cuvo-Organization: org_…. Without it the request answers400 invalid_request, because guessing would be worse than refusing. - Naming an organization you hold no approved grant to answers
403 forbidden, which is the same answer as naming one that does not exist. - No approved grant at all answers
403 forbidden.
curl https://api.cuvo.co/v1/organizations \
-H "Authorization: Bearer cuvo_sk_test_…"
{
"data": [
{
"id": "org_c8k2q1w9",
"name": "Northgate Health",
"slug": "northgate",
"kind": "DIRECT",
"status": "active",
"scopes": ["organization:read", "patients:write", "cases:write"]
}
],
"has_more": false
}
kind is how the organization reaches Cuvo: DIRECT, RESELLER, RESELLER_CLIENT, or
INTEGRATION for a headless brand that exists only behind an API. It does not change what you can
call. status is active or suspended.
Asking a clinic for access
A grant is requested in the portal, by an integration member with the ADMIN role or above. You need three things:
- The organization's public
org_…id, which the clinic gives you. It is not guessable and cannot be looked up, so an id naming no organization is refused with a clear message rather than turned into a directory. - The scopes you are asking for. Ask for what you need; a request for everything is a slower conversation.
- A note saying who you are and what you are building. The person approving it reads this.
The request appears as a pending grant. Once it is approved, the organization's name appears on your grant and the credential can act for it. Until then the clinic is a bare id on your side, so the form can never become an id-to-name lookup.
None of this blocks you from starting. Every new integration is created with an approved grant on the shared sandbox organization, so the whole loop is reachable before a clinic has heard of you.