The path a third party follows to integrate without anyone from shteg.ai in the loop: create a key, get a sandbox tenant, subscribe to events, and read a claim.
503 developer_api_disabled.Keys start with sk_sandbox_. Pick only the scopes you need; anything outside the allowlist below is refused.
# 1. Create a sandbox key pair (signed-in, verified organization).
# The response shows the secret key once. A fresh synthetic-data sandbox
# tenant is created for the pair; no real patient data is in it.
curl -X POST https://your-sandbox-origin.example/api/developer/keys \
-H "Content-Type: application/json" \
-H "Cookie: $SESSION" \
-d '{ "name": "my-integration", "scopes": ["claims:read"] }'# 2. Exchange the key for a short-lived access token (15 minutes).
curl -X POST https://your-sandbox-origin.example/api/v1/token \
-H "Authorization: Bearer $SHTEG_API_KEY"{
"access_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"scope": "claims:read"
}Endpoint management is a dashboard-session call today; managing endpoints with an access token (webhooks:manage) follows.
# 3. Register a webhook endpoint (signed-in session with the
# integrations:write permission). The signing secret is returned once.
curl -X POST https://your-sandbox-origin.example/api/webhook-subscriptions \
-H "Content-Type: application/json" \
-H "Cookie: $SESSION" \
-d '{ "url": "https://example.com/shteg/webhooks", "event_types": ["claim.*"] }'# 4. Read a claim from your sandbox tenant (scope: claims:read).
curl https://your-sandbox-origin.example/api/v1/claims/$CLAIM_ID \
-H "Authorization: Bearer $ACCESS_TOKEN"Both are generated from /openapi.json; CI fails when either falls behind the spec. Both use the same retry rule.
# TypeScript (@shteg/sdk) — generated from /openapi.json
npm install @shteg/sdkimport { ShtegClient } from "@shteg/sdk";
const shteg = new ShtegClient({
baseUrl: "https://your-sandbox-origin.example",
token: () => getAccessToken(), // called before every attempt
});
const status = await shteg.clearinghouse.claimStatus({ path: { id: claimId } });# Python (shteg) — standard library only, Python 3.9+
pip install shtegfrom shteg import Shteg
client = Shteg(base_url="https://your-sandbox-origin.example", token=get_access_token)
status = client.clearinghouse.claim_status(claim_id)Retry-After (seconds or HTTP-date) up to 30 seconds.Idempotency-Key and the operation dedupes on it. Retry eligibility comes from the generated operation table, not from a call option.{ object: "list", data, next_cursor } envelope page with paginate / listAll.The packages are not on a public registry yet; until they are, install from the repository checkout (packages/sdk, packages/sdk-python).
SBX-. No real person is in a sandbox.Format resource:verb. write does not imply read. No scope charges, refunds, collects, releases or settles money, and none crosses tenants.
| Resource | Scopes | Note |
|---|---|---|
| Patients | patients:readpatients:write | |
| Appointments | appointments:readappointments:write | |
| Encounters & notes | encounters:readnotes:readnotes:write | |
| Forms & care plans | forms:readforms:writecare_plans:readcare_plans:write | |
| Documents | documents:readdocuments:write | |
| Tasks & messages | tasks:readtasks:writemessages:readmessages:write | |
| Referrals & orders | referrals:readreferrals:writeorders:readorders:write | |
| Device studies | device_studies:read | |
| Eligibility | eligibility:readeligibility:run | |
| Claims & remittances | claims:readclaims:writeremittances:read | |
| Invoices | invoices:readinvoices:write | Drafting and reading invoices; collecting on one is not an API-key action. |
| Attestations | attestations:read | Read-only. No settlement or payment scope is issuable to an API key. |
| Automations | automations:readautomations:writeautomations:publish | |
| Webhooks & custom fields | webhooks:managecustom_fields:readcustom_fields:manage | |
| FHIR | fhir:readfhir:write |
Payloads are thin: the event id, the object id and type, and a status. They carry no patient data and no amounts. Fetch the object with your token when you need more.
{
"id": "evt_...",
"object": "event",
"type": "claim.paid",
"api_version": "2026-10-20",
"created_at": "2026-10-20T12:00:00.000Z",
"clinic_id": "cln_...",
"data": {
"object_id": "clm_...",
"object_type": "claim",
"status": "PAID",
"previous_status": "SUBMITTED"
}
}| Object | Event types |
|---|---|
patient | patient.createdpatient.updated |
coverage | coverage.createdcoverage.updated |
appointment | appointment.createdappointment.updatedappointment.cancelledappointment.completed |
encounter | encounter.createdencounter.signed |
note | note.signed |
task | task.createdtask.updatedtask.completed |
referral | referral.createdreferral.updated |
claim | claim.createdclaim.scrubbedclaim.submittedclaim.acceptedclaim.deniedclaim.paid |
remittancestatus only, no amounts | remittance.receivedremittance.posted |
eligibility_snapshot | eligibility_snapshot.created |
invoicestatus only, no amounts | invoice.createdinvoice.updatedinvoice.void |
settlementstatus only, no amounts | settlement.instruction.createdsettlement.reconciledsettlement.halt |
attestation | attestation.createdattestation.verified |
automation_run | automation_run.completedautomation_run.failed |
<object>.*; an empty filter receives every type.Each delivery carries X-Shteg-Timestamp (unix seconds) and X-Shteg-Signature-256: sha256=<hex HMAC-SHA256 of timestamp + '.' + raw body>.
import { verifyWebhookSignature } from "@shteg/sdk";
const raw = await request.text(); // the exact raw body, never re-serialized
const signingSecret = loadSecret("shteg-webhook"); // your secret store
const result = await verifyWebhookSignature({
body: raw,
signature: request.headers.get("X-Shteg-Signature-256"),
timestamp: request.headers.get("X-Shteg-Timestamp"),
secret: [signingSecret], // pass old + new while rotating
});
if (!result.ok) return new Response(null, { status: 401 });from shteg import verify_webhook_signature
result = verify_webhook_signature(
raw_body, # bytes exactly as received
headers.get("X-Shteg-Signature-256"),
headers.get("X-Shteg-Timestamp"),
[secret], # pass old + new while rotating
)
if not result.ok:
return 401Inbound partner webhooks (payment processor, EMR bridge, fax) use a different header; see Webhooks.
POST /api/v1/token) allows 30 requests per minute per client.429 with Retry-After and X-RateLimit-Limit / -Remaining / -Reset. Both SDKs wait for Retry-After.