Sandbox coming soon.This reference previews the API; it is not live yet.
API reference
Graft API
introduction
Graft is one API for prior authorization across every payer. You describe the member, the service, and the provider; Graft answers whether an auth is required, what the payer wants, and carries the submission through to a decision. JSON in, JSON out, across every payer - whether the payer speaks FHIR, a portal, or a fax machine.
All requests go to https://api.usegraft.dev/v1 over HTTPS. Request and response bodies are JSON.
curl https://api.usegraft.dev/v1/check \
--request POST \
--header "Authorization: Bearer grft_sandbox_51kd83hf..." \
--header "Content-Type: application/json" \
--data @check.json{
"auth_required": true,
"method": "api",
"requirements_available": true,
"check_id": "chk_9f2ab1"
}authentication
Authenticate with a Bearer token in the Authorizationheader. Sandbox keys are prefixed grft_sandbox_ and hit simulated payers only; live keys are prefixed grft_live_and reach real payer rails. The two environments share an API surface, so switching is a key swap.
curl https://api.usegraft.dev/v1/status/auth_5c11 \
--header "Authorization: Bearer grft_live_9k2m47xq..."{
"error": {
"type": "authentication_error",
"message": "Invalid API key. Check the key prefix: sandbox keys
start with grft_sandbox_, live keys with grft_live_."
}
}POST /check
Ask whether a service needs prior authorization for a given member, plan, and date. This is the entry point for everything else: the returned check_id threads through requirements and submission.
{
"payer_id": "uhc",
"plan_id": "uhc-choice-plus",
"member": {
"first_name": "Maria",
"last_name": "Alvarez",
"member_id": "UHC88214537",
"dob": "1987-06-14"
},
"service": {
"cpt": "70553",
"icd10": ["M54.12"],
"service_date": "2026-08-02",
"place_of_service": "11"
},
"provider": {
"npi": "1568493201",
"organization_npi": "1811940372"
}
}{
"auth_required": true,
"method": "api",
"requirements_available": true,
"check_id": "chk_9f2ab1"
}request fields
- payer_id string, required
- Graft's identifier for the payer, e.g. uhc. The full list lives at GET /payers.
- plan_id string, optional
- Narrows the check to a specific plan when the payer's rules differ by plan. Omit to use payer-level rules.
- member.first_name string, required
- Member's legal first name as it appears on the insurance card.
- member.last_name string, required
- Member's legal last name.
- member.member_id string, required
- Member ID from the insurance card. In sandbox, magic values simulate payer behavior (see Sandbox).
- member.dob date, required
- Date of birth, ISO 8601 (YYYY-MM-DD).
- service.cpt string, required
- CPT or HCPCS code for the service being checked.
- service.icd10 string[], required
- One or more ICD-10 diagnosis codes justifying the service.
- service.service_date date, required
- Planned date of service. Rules are evaluated as of this date.
- service.place_of_service string, optional
- CMS place-of-service code (e.g. 11 for office). Some payers key rules off it.
- provider.npi string, required
- Rendering provider's 10-digit NPI.
- provider.organization_npi string, optional
- Billing organization's NPI, when different from the rendering provider.
response fields
- auth_required boolean
- Whether this payer requires prior authorization for this service, member, and date.
- method "api" | "portal" | "fax"
- How the payer accepts submissions. Graft handles all three behind POST /submit.
- requirements_available boolean
- Whether a structured questionnaire is available via GET /requirements.
- check_id string
- Reference to this check. Pass it to GET /requirements and POST /submit.
GET /requirements
Fetch the payer's questionnaire for a check as structured items - the questions, their types, and which are required. Answer them inanswers[] on POST /submit.
curl "https://api.usegraft.dev/v1/requirements?check_id=chk_9f2ab1" \
--header "Authorization: Bearer grft_sandbox_51kd83hf..."{
"check_id": "chk_9f2ab1",
"source": "payer",
"items": [
{
"link_id": "q1",
"text": "Symptom duration",
"type": "choice",
"required": true,
"options": ["<6 weeks", "6-12 weeks", ">12 weeks"]
}
]
}item fields
- link_id string
- Stable identifier for the item. Echo it back in answers[] on POST /submit.
- text string
- Human-readable question, taken from the payer's own questionnaire wherever possible.
- type "boolean" | "text" | "choice" | "number" | "attachment"
- Expected answer shape. attachment items expect an entry in attachments[] instead of answers[].
- required boolean
- Whether the payer rejects submissions missing this item.
- options string[], optional
- Allowed values; present only when type is choice.
- source "payer" | "simulated"
- Top-level field: payer means the questionnaire came from the payer's API; simulated means Graft reconstructed it from published policy while the payer's API is not yet live.
POST /submit
Submit the answered questionnaire to the payer. The call returns a synchronous acknowledgment - usually pended - and the decision arrives later as a webhook. PollGET /status/{id} if you can't receive webhooks. Send an Idempotency-Key header so network retries never double-submit.
curl https://api.usegraft.dev/v1/submit \
--request POST \
--header "Authorization: Bearer grft_sandbox_51kd83hf..." \
--header "Idempotency-Key: 4d2ba8c0-6c1f-4d3e-9b1a-2f83a01e6b7d" \
--header "Content-Type: application/json" \
--data '{
"check_id": "chk_9f2ab1",
"answers": [
{ "link_id": "q1", "value": true },
{ "link_id": "q2", "value": "6-12 weeks" }
],
"attachments": [
{ "filename": "mri-order.pdf", "url": "https://files.example.com/mri-order.pdf" }
],
"urgency": "standard"
}'{
"auth_id": "auth_5c11",
"status": "pended",
"auth_number": null,
"tracking_id": "trk_77e2c9",
"payer_decision_deadline": "2026-08-09T00:00:00Z"
}request fields
- check_id string, required
- The check this submission answers. Requirements are validated against it.
- answers[] array, required
- One { link_id, value } per questionnaire item. Values must match the item's type.
- attachments[] array, optional
- Supporting documents as { filename, url }. Graft fetches, virus-scans, and forwards them.
- urgency "standard" | "urgent"
- Urgent requests follow the payer's expedited path (72-hour decisions under CMS-0057-F).
- Idempotency-Key header, recommended
- Any unique string. Retries with the same key return the original result instead of double-submitting.
response fields
- auth_id string
- Graft's identifier for the authorization. Use it with GET /status/{id}.
- status "pended" | "approved" | "denied"
- Initial state. Most payers pend first; some sandbox flows decide synchronously.
- auth_number string | null
- The payer's authorization number. null until approved.
- tracking_id string
- The payer-side tracking reference, when the payer issues one.
- payer_decision_deadline datetime
- When the payer must decide under CMS-0057-F timelines, given the urgency.
GET /status/{id}
The current state of an authorization plus its fullhistory - every transition with a timestamp, in order. The same payload shape a webhook delivers, so status handling code can be shared.
curl https://api.usegraft.dev/v1/status/auth_5c11 \
--header "Authorization: Bearer grft_sandbox_51kd83hf..."{
"auth_id": "auth_5c11",
"status": "approved",
"auth_number": "A2210-8834",
"valid_until": "2026-11-02",
"history": [
{ "status": "submitted", "at": "2026-08-02T14:11:08Z" },
{ "status": "pended", "at": "2026-08-02T14:11:09Z" },
{ "status": "approved", "at": "2026-08-04T09:27:41Z" }
]
}webhooks
Decisions are pushed to your endpoint as JSON. Every delivery is signed: the Graft-Signature header carries a timestamp and an HMAC-SHA256 of timestamp.body computed with your webhook secret. Verify it (constant-time compare, reject stale timestamps) before trusting the payload.
POST https://yourapp.example.com/webhooks/graft
Graft-Signature: t=1754140061,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
{
"event": "auth.approved",
"auth_id": "auth_5c11",
"auth_number": "A2210-8834",
"valid_until": "2026-11-02"
}// Verify Graft-Signature before trusting a delivery.
const [t, v1] = signature.split(",").map((p) => p.split("=")[1]);
const expected = crypto
.createHmac("sha256", process.env.GRAFT_WEBHOOK_SECRET)
.update(`${t}.${rawBody}`)
.digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(v1), Buffer.from(expected));events
- auth.approved event
- Decision made in the member's favor. Carries auth_number and valid_until.
- auth.denied event
- Denied. Carries a structured denial_reason plus the payer's own codes.
- auth.pended event
- The payer needs more time. Carries an updated payer_decision_deadline.
- auth.info_requested event
- The payer wants more information. Respond with POST /submit using the same check_id and the new items.
Future events - such as auth.expiring ahead of avalid_until date - will carry suggested-next hints in the payload, so handlers should ignore unknown fields.
sandbox
The sandbox simulates payer behavior end to end - checks, questionnaires, submissions, webhooks - with no real PHI and no real payers. Magic member_id values steer the outcome so every path is testable on demand.
- GRAFT-APPROVE-NOW member_id
- Approves within seconds: auth.approved webhook with a populated auth_number.
- GRAFT-PEND-N member_id
- Pends, then delivers a decision webhook after N minutes, e.g. GRAFT-PEND-5. Useful for testing the async path.
- GRAFT-DENY-001 member_id
- Denies with a structured denial_reason, for exercising denial handling.
- GRAFT-INFO-001 member_id
- Emits auth.info_requested asking for one attachment; approves after you resubmit with it.
Submitting a check for GRAFT-APPROVE-NOW and carrying it through POST /submit produces an approval webhook within seconds:
{
"payer_id": "uhc",
"member": {
"first_name": "Test",
"last_name": "Member",
"member_id": "GRAFT-APPROVE-NOW",
"dob": "1990-01-01"
},
"service": {
"cpt": "70553",
"icd10": ["M54.12"],
"service_date": "2026-08-02"
},
"provider": { "npi": "1568493201" }
}{
"event": "auth.approved",
"auth_id": "auth_sim_4f01",
"auth_number": "A0000-0001",
"valid_until": "2026-11-01"
}