Rivon Health — Public REST API
Base URL: https://app.rivon.health/api/v1
Version: 2026-04-22
This is the integration API for partner engineers building on top of Rivon. It's a read-focused, scope-gated REST API with HMAC-signed webhooks for real-time event sync.
Table of Contents
- Authentication
- Scopes
- Conventions
- Rate limiting
- Errors
- Endpoints
- Webhooks — Consuming events
- Versioning
- Support
1. Authentication
Every request requires a Bearer token in the Authorization header:
Authorization: Bearer rvn_live_1a2b3c...
Keys are issued by Rivon staff, not self-serve. Email api@rivon.health (or open a support request inside Rivon) with:
- The scopes you need (see §2 below)
- The org name the key should be tied to
- A short description of your integration
We'll review, provision a scoped key, and email it to the requester. One key = one organization's data. Keys are hashed server-side — if you lose one, request a new one and we'll revoke the old. This provisioning model lets us audit partner access and keeps PHI-scope keys behind a human review.
curl -H "Authorization: Bearer $RIVON_API_KEY" \
https://app.rivon.health/api/v1/credentialing/summary
2. Scopes
Each key is granted a set of scopes at creation. Request only what you need.
| Scope | Grants |
|---|---|
providers:read |
List / read providers |
providers:write |
Create / update providers |
credentialing:read |
Enrollment summaries, lists, and details |
credentialing:write |
Create enrollments, advance stages |
licensing:read |
Licensing pipeline data |
licensing:write |
Create and advance licensing pipelines |
tasks:read |
List and read tasks |
tasks:write |
Create and complete tasks |
documents:read |
List documents + generate signed download URLs |
monitoring:read |
Expiration reports |
webhooks:admin |
Create / edit / delete webhook endpoints |
phi:read |
Include sensitive fields (DOB, SSN/EIN, birthplace, full address). Requires a signed BAA between your org and Rivon. |
A request missing a required scope returns 403 insufficient_scope.
3. Conventions
- Encoding: All bodies are JSON. Responses use UTF-8.
- Timestamps: ISO-8601 UTC (
2026-04-21T12:34:56.789Z). - IDs: 25-character CUIDs (
cmnpkacda0001123f1wjtif6j). - Pagination: Cursor-based. List endpoints accept
?limit=<1-200>(default 50) and?cursor=<id>. Responses include{ data, pagination: { limit, nextCursor } }. PassnextCursorback as?cursor=for the next page; whennextCursorisnullyou've reached the end. - Versioning header (optional): Pin to a specific date with
X-Rivon-Version: 2026-04-20. Omit to always get the latest.
4. Rate limiting
100 requests per minute per API key. Exceeding returns 429 rate_limited. Design retries with exponential backoff and respect the 429 status. Need a higher limit for bulk backfills? Reach out.
5. Errors
All errors have the shape:
{ "error": { "code": "invalid_key", "message": "Unknown or invalid API key." } }
| HTTP | Code | Meaning |
|---|---|---|
| 400 | invalid_url |
Validation failed on the request body |
| 400 | invalid_body |
JSON body was malformed |
| 401 | missing_auth |
No Bearer token in the Authorization header |
| 401 | invalid_key |
Unknown key |
| 401 | revoked_key |
Key was revoked |
| 401 | expired_key |
Key's expiresAt has passed |
| 403 | insufficient_scope |
Key is missing a required scope |
| 404 | not_found |
Resource doesn't exist in your org |
| 429 | rate_limited |
More than 100 requests in the last 60 seconds |
| 5xx | server_error |
We broke something — please include the request ID when reporting |
6. Endpoints
Providers
GET /providers
List providers in your organization.
Scope: providers:read (+ phi:read for DOB / SSN / full address)
Query params:
| Name | Type | Description |
|---|---|---|
status |
string | onboarding | active | inactive | archived |
clientGroupId |
string | Restrict to one client group |
limit |
integer | 1-200 (default 50) |
cursor |
string | Pagination cursor |
Example:
curl -H "Authorization: Bearer $KEY" \
"https://app.rivon.health/api/v1/providers?status=active&limit=25"
Response:
{
"data": [
{
"id": "cmnpk...",
"type": "individual",
"status": "active",
"clientGroupId": "cmclt...",
"clientGroup": { "id": "cmclt...", "name": "Acme Medical Group" },
"assignedSpecialist": { "name": "Elle Hong", "email": "elle@acme.com" },
"personal": {
"firstName": "Jane", "lastName": "Doe", "middleName": null,
"orgName": null,
"credentials": "MD", "specialty": "Internal Medicine",
"npi": "1234567890", "taxonomyCode": "207R00000X"
},
"contact": {
"email": "jane@acme.com", "phone": "614-555-0110",
"city": "Columbus", "state": "OH", "zip": "43215", "country": "US"
},
"createdAt": "2026-04-01T12:34:56.000Z",
"updatedAt": "2026-04-20T09:12:14.000Z"
}
],
"pagination": { "limit": 25, "nextCursor": "cmnpk..." }
}
GET /providers/{id}
Retrieve a single provider with credentials (state licenses, DEA, insurance).
Scope: providers:read (+ phi:read for sensitive fields)
Response shape: same as list, plus credential records (state licenses, DEA, liability insurance) and government-payer enrollments (Medicare and Medicaid). Government enrollments live in dedicated tables — surfaced here so you can pull effective dates and member IDs in one fetch.
{
"stateLicenses": [
{ "id": "...", "state": "OH", "licenseType": "RN", "licenseId": "RN12345",
"issueDate": "2024-07-01", "expirationDate": "2027-06-30" }
],
"deaCertificates": [
{ "id": "...", "state": "OH", "expirationDate": "2028-03-31" }
],
"liabilityInsurance": [
{ "id": "...", "carrierName": "The Doctors Company", "policyNumber": "POL-2024-00123",
"expirationDate": "2026-12-01" }
],
"medicareEnrollments": [
{ "id": "...", "state": "OH", "medicareId": "M12345",
"status": "approved", "effectiveDate": "2025-09-15", "expirationDate": null }
],
"medicaidEnrollments": [
{ "id": "...", "state": "OH", "medicaidId": "MD98765",
"status": "approved", "effectiveDate": "2025-08-01",
"expirationDate": null, "reattestationDate": "2026-08-01" }
]
}
Status enum (Medicare/Medicaid):
submitted,under_review,approved,denied,expired,revalidating.
Credentialing
GET /credentialing/summary
Aggregate counts for dashboards.
Scope: credentialing:read
Response:
{
"byStatus": { "active": 28, "credentialed": 142, "denied": 3, "panel_closed": 5 },
"byStage": { "Pre-Credentialing": 4, "Application": 8, "Committee Review": 16 },
"totalEnrollments": 178,
"avgDaysToCredentialed": 67.4
}
GET /credentialing/enrollments
List enrollments. Filters: ?status=, ?payerId=, ?providerId=.
Scope: credentialing:read
Response row (abbreviated):
{
"id": "cmenr...",
"status": "active",
"attemptNumber": 1,
"initiatedAt": "2026-02-10T00:00:00.000Z",
"credentialedDate": null,
"providerPayerNumber": null,
"currentStage": "Application",
"daysInStage": 24,
"payer": { "id": "cmins...", "insuranceName": "BCBS of Ohio",
"state": "OH", "productName": "HMO" },
// ↑ once the payer credentials the provider, `status` flips to
// "credentialed", `credentialedDate` populates with the effective
// date, and `providerPayerNumber` carries the payer-issued
// member / provider ID (e.g. PIN, supplier #).
"provider": { "id": "cmnpk...", "firstName": "Jane", "lastName": "Doe",
"npi": "1234567890", "credentials": "MD" },
"assignedSpecialist": { "name": "Elle Hong", "email": "elle@acme.com" },
"stages": [
{ "name": "Pre-Credentialing", "status": "complete", "startedAt": "...", "completedAt": "..." },
{ "name": "Application", "status": "in_progress", "startedAt": "...", "completedAt": null }
]
}
GET /credentialing/enrollments/{id}
Full enrollment detail including notes history.
Scope: credentialing:read
Licensing
GET /licensing/summary
Scope: licensing:read
{
"byStatus": { "active": 42, "complete": 118, "denied": 2, "paused": 3 },
"byState": { "OH": 31, "CA": 18, "TX": 22 },
"byStage": { "Application Preparation": 6, "Under Board Review": 14 },
"totalPipelines": 165
}
GET /licensing/pipelines
List pipelines. Filters: ?status=, ?state=, ?providerId=.
Scope: licensing:read
Response row (abbreviated):
{
"id": "cmlpln...",
"state": "OH",
"providerType": "RN",
"status": "complete",
"submittedDate": "2025-11-08T00:00:00.000Z",
"trackingNumber": "OH-RN-2025-44219",
"tempLicenseNumber": null,
"tempLicenseExpiration": null,
"currentStage": null,
"daysInStage": null,
"provider": { "id": "cmnpk...", "firstName": "Jane", "lastName": "Doe",
"npi": "1234567890", "credentials": "MD" },
"issuedLicense": {
"id": "cmsl...",
"state": "OH",
"licenseType": "RN",
"licenseId": "RN445566",
"issueDate": "2026-01-15",
"expirationDate": "2028-01-15"
},
"stages": [ /* … */ ]
}
issuedLicenseisnulluntil the pipeline reachescompleteand aStateLicenserow is created for the provider. Once present,issueDateis the license's effective date andlicenseIdis the permanent license number.
GET /licensing/pipelines/{id}
Full pipeline with stage checklists, plus the same issuedLicense field as the list response.
Scope: licensing:read
{
"id": "cmlic...",
"state": "OH",
"providerType": "RN",
"status": "active",
"trackingNumber": "OH-2026-12345",
"tempLicenseNumber": null,
"currentStage": "Under Board Review",
"daysInStage": 31,
"stageDetails": [
{
"name": "Pre-Application",
"status": "complete",
"checklists": [
{
"state": "OH",
"items": [
{ "description": "Verify provider NPI number", "isRequired": true,
"isCollected": true },
{ "description": "Confirm provider type and specialty", "isRequired": true,
"isCollected": true }
]
}
]
}
]
}
Tasks
GET /tasks
Scope: tasks:read
Query params:
| Name | Type | Description |
|---|---|---|
status |
string | open | completed | overdue |
assignedTo |
string | Filter by assignee user ID |
providerId |
string | Filter by linked provider |
limit |
integer | 1-200 (default 50) |
cursor |
string | Pagination cursor |
Monitoring — Expirations
GET /monitoring/expirations
Unified list of upcoming credential expirations (state licenses, DEA, CS, liability, Medicare, Medicaid, certifications).
Scope: monitoring:read
Query params:
| Name | Type | Description |
|---|---|---|
within |
integer | Days to look ahead, 1-365 (default 90) |
Response:
{
"withinDays": 60,
"count": 12,
"data": [
{ "providerId": "cmnpk...", "credentialType": "state_license",
"label": "OH RN", "expirationDate": "2026-05-15", "daysUntil": 24,
"recordId": "cm..." }
]
}
credentialType values: state_license · dea · controlled_substance · liability_insurance · medicare · medicaid · certification.
Webhooks — Management
GET /webhooks
List webhook endpoints.
Scope: webhooks:admin
POST /webhooks
Create an endpoint.
Scope: webhooks:admin
Body:
{
"url": "https://your-app.com/hooks/rivon",
"events": ["credentialing.stage_changed", "credential.expiring"],
"description": "Main integration endpoint"
}
Response (201): the endpoint + a secret field that is only returned at creation time. Store it securely — you'll use it to verify signatures on incoming webhooks.
{
"id": "cmweb...",
"url": "https://your-app.com/hooks/rivon",
"events": ["credentialing.stage_changed", "credential.expiring"],
"active": true,
"secret": "a1b2c3...f0",
"secretMessage": "Store this secret now — we will not show it again."
}
PATCH /webhooks/{id}
Update url, events, active, or description. Same body shape as create (any subset).
DELETE /webhooks/{id}
Soft-delete an endpoint.
7. Webhooks — Consuming events
When an event fires, Rivon sends a POST to your endpoint with:
Headers:
Content-Type: application/json
User-Agent: Rivon-Webhooks/1.0
X-Rivon-Version: 2026-04-20
X-Rivon-Event: credentialing.stage_changed
X-Rivon-Event-Id: 7a4e6c5e-2a7b-4b0d-9e8f-1c2d3e4f5a6b
X-Rivon-Signature: sha256=3c4f...a1
X-Rivon-Delivery-Id: cmdel...
Body:
{
"id": "7a4e6c5e-2a7b-4b0d-9e8f-1c2d3e4f5a6b",
"type": "credentialing.stage_changed",
"apiVersion": "2026-04-20",
"createdAt": "2026-04-21T15:30:22.123Z",
"orgId": "cmorg...",
"data": {
"enrollmentId": "cmenr...",
"providerId": "cmnpk...",
"payerId": "cmins...",
"stageName": "Committee Review",
"status": "in_progress"
}
}
Verifying the signature
import crypto from "crypto";
function verifyRivonSignature(rawBody: string, header: string, secret: string): boolean {
const expected =
"sha256=" + crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
// Constant-time comparison
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header));
}
Always verify before trusting the payload. Use the raw request body string — not a re-stringified JSON object.
Event catalog
| Event | When it fires |
|---|---|
provider.created |
New provider added |
provider.status_changed |
Provider status transitions (onboarding → active, etc.) |
credentialing.stage_changed |
Any stage advance on an enrollment |
credentialing.credentialed |
Enrollment resolves to credentialed |
credentialing.denied |
Enrollment resolves to denied |
licensing.stage_changed |
Any stage advance on a licensing pipeline |
licensing.license_issued |
Pipeline resolves to issued |
credential.expiring |
Fires at 60 / 30 / 7-day thresholds before a credential expires |
task.completed |
Task marked complete |
Delivery guarantees
- At-least-once delivery — return
2xxon receipt; theX-Rivon-Event-Idis stable across retries, use it for idempotency. - Retries: non-2xx triggers retries at 1 min, 5 min, 30 min, 2 hr, 12 hr (exponential backoff). After 5 failures the delivery is marked
failed. - Timeout: we give your endpoint 10 seconds to respond. Acknowledge quickly and queue background work asynchronously.
- Order: events are delivered in roughly chronological order but we don't guarantee strict ordering. Rely on
data.*fields + your own re-fetch via the REST API when order matters.
8. Versioning
APIs are versioned by release date, not v1 / v2. To pin to a specific version, send:
X-Rivon-Version: 2026-04-20
Pinning shields you from breaking changes. When we release a new version we keep the old one working for at least 12 months. Omit the header to always receive the latest. Deprecations are announced in-app and via email at least 90 days in advance.
Changelog: https://rivon.health/changelog
9. Support
- Dev support: Email
api@rivon.health— response within 1 business day. - Bug reports: Include the request ID (from
X-Rivon-Request-Idon any response) so we can trace it in our logs. - Status:
https://status.rivon.health— live platform status + historical incidents. - Rate-limit increase / bulk backfill: Email us with expected volume and we'll bump you.
Quick-start — 10-minute integration
# 1. Request an API key — email api@rivon.health with your scopes + org name
export RIVON_API_KEY="rvn_live_..."
# 2. Fetch your credentialing snapshot
curl -H "Authorization: Bearer $RIVON_API_KEY" \
https://app.rivon.health/api/v1/credentialing/summary
# 3. Subscribe to real-time events
curl -X POST -H "Authorization: Bearer $RIVON_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/hooks/rivon",
"events": ["credentialing.stage_changed", "credentialing.credentialed", "credential.expiring"]
}' \
https://app.rivon.health/api/v1/webhooks
# 4. Verify the first webhook and ship it
Welcome to Rivon. 🚀