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.

bash
curl https://api.usegraft.dev/v1/check \
  --request POST \
  --header "Authorization: Bearer grft_sandbox_51kd83hf..." \
  --header "Content-Type: application/json" \
  --data @check.json
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.

bash
curl https://api.usegraft.dev/v1/status/auth_5c11 \
  --header "Authorization: Bearer grft_live_9k2m47xq..."
json
{
  "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.

json
{
  "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"
  }
}
json
{
  "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.

bash
curl "https://api.usegraft.dev/v1/requirements?check_id=chk_9f2ab1" \
  --header "Authorization: Bearer grft_sandbox_51kd83hf..."
json
{
  "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.

bash
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"
  }'
json
{
  "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.

bash
curl https://api.usegraft.dev/v1/status/auth_5c11 \
  --header "Authorization: Bearer grft_sandbox_51kd83hf..."
json
{
  "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.

http
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"
}
js
// 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:

json
{
  "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" }
}
json
{
  "event": "auth.approved",
  "auth_id": "auth_sim_4f01",
  "auth_number": "A0000-0001",
  "valid_until": "2026-11-01"
}