Rivon

Rivon Health

API Documentation

Download .md

Last updated: October 4, 2026 · Source of truth: docs/API.md in the Rivon repo

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

  1. Authentication
  2. Scopes
  3. Conventions
  4. Rate limiting
  5. Errors
  6. Endpoints
  7. Webhooks — Consuming events
  8. Versioning
  9. 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 } }. Pass nextCursor back as ?cursor= for the next page; when nextCursor is null you'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": [ /* … */ ]
}

issuedLicense is null until the pipeline reaches complete and a StateLicense row is created for the provider. Once present, issueDate is the license's effective date and licenseId is 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 2xx on receipt; the X-Rivon-Event-Id is 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-Id on 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. 🚀