Norvya Developers
Get a sandbox
API reference · Foundation

Platform

Built — gated4 endpoints · OpenAPI tag platform

Tokens, your member list, and your own usage. Every integration starts here. EMR vendors use one credential for every practice that approves them in Norvya: GET /v1/practices lists those practices, and a token is requested per practice.

Who calls it
Every partner: common to all markets.
Before production
No gate of its own; your market's “Before production” applies.
Sandbox
Build against synthetic members first. What each sandbox holds

Scopes

ScopeAllows
members.readList the organisation's linked members as opaque member references.
vendor.practices.readEMR vendors: list the practices that have approved your integration (a vendor-level token, requested without a practice; no patient data).

Endpoints

Exchange client credentials for a bearer token

POST/v1/oauth/tokenno scope

OAuth2 client_credentials (RFC 6749 §4.4). Credentials in the form body or HTTP Basic. The token lives 15 minutes, carries the client's clinic and scopes, and stops working at once if the client is revoked.

EMR vendors (client ids starting pvc_) serve many practices with one credential: with practice=<practice_ref> the token is bound to that practice and carries the scopes you request that the practice approved (all of them when you request none); revoking the approval stops it at once. Without practice you get a vendor-level token for GET /v1/practices only. An unknown, revoked or someone else's practice_ref is invalid_grant.

Request body

Content type: application/x-www-form-urlencoded (required)

grant_typestringrequired

Must be client_credentials.

client_idstring | null
client_secretstring | null
scopestring | null

Optional space-separated subset of the client's scopes.

practicestring | null

EMR vendors only: the practice_ref of a practice that approved you (GET /v1/practices). Omit for a vendor-level token (scope vendor.practices.read).

All responses

200Successful Response400Bad Request401Unauthorized422Validation Error

Codes every call can return are in Errors & limits.

curl -s -X POST "https://norvya.fly.dev/v1/oauth/token" \
  -d grant_type=client_credentials \
  -d client_id=$CLIENT_ID \
  -d client_secret=$CLIENT_SECRET \
  -d scope=$SCOPE
Response 200
{
  "access_token": "string",
  "token_type": "string",
  "expires_in": 0,
  "scope": "string"
}

The response is the shape from the schema, not live data.

List your organisation's linked members

GET/v1/membersmembers.read

Active members linked to your organisation, as opaque member_refs (plus your own id when the member is linked through a system sync). Nothing else about the member is returned here.

All responses

200Successful Response401Unauthorized403Forbidden404Not Found429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/members" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "count": 0,
  "members": [
    {
      "member_ref": "string",
      "external_id": "string",
      "linked_at": "string"
    }
  ]
}

The response is the shape from the schema, not live data.

Your client's call counts, last 30 days

GET/v1/usageno scope

Calls and errors per endpoint per UTC day for the calling client, for usage-based pricing. Counts every authenticated call, including refusals (counted in errors).

All responses

200Successful Response401Unauthorized403Forbidden404Not Found429Too Many Requests503Service Unavailable

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/usage" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "client_id": "string",
  "from": "string",
  "to": "string",
  "total_calls": 0,
  "by_endpoint": {},
  "days": [
    {
      "day": "string",
      "endpoint": "string",
      "calls": 0,
      "errors": 0
    }
  ],
  "environment": "string",
  "billable_calls": 0
}

The response is the shape from the schema, not live data.

EMR vendors: the practices that approved your integration

GET/v1/practicesvendor.practices.read

Every practice with an ACTIVE approval of your integration: its practice_ref (ask POST /v1/oauth/token for practice=<practice_ref>), its name, the scopes it approved and when. Nothing else about the practice and nothing about its patients. Needs a vendor-level token (requested without practice); a practice-bound token gets 403 insufficient_scope.

Errors specific to this call

403insufficient_scope

All responses

200Successful Response401Unauthorized403Forbidden429Too Many Requests

Codes every call can return are in Errors & limits.

curl -s "https://norvya.fly.dev/v1/practices" \
  -H "Authorization: Bearer $TOKEN"
Response 200
{
  "vendor_id": "pv_Hk2m9QzR4tYv",
  "count": 0,
  "practices": [
    {
      "practice_ref": "pr_x2Wq9vT0bLr3sK8mN1aZ4g",
      "practice_name": "Riverside Family Medicine",
      "scopes": [
        "clinical.brief.read",
        "clinical.patient.match"
      ],
      "granted_at": "2026-10-15T14:02:11Z"
    }
  ]
}

The response is the shape from the schema, not live data.

Generated from the Partner API router and developers_catalog.py. The router docstring is the source of each description.

↑ ↓ to moveEnter to open