Profile Health API
GraphQL is the canonical surface; a thin REST facade is exposed for partners that don't speak GraphQL. All cross-cutting concerns — auth, rate limits, audit, resilience — are enforced once in the gateway.
Quick Start
You need a partner API key (Bearer pk_live_*). Profile Health system administrators issue keys via the admin UI. Ask your contact to provision one for the sandbox first.
curl -X POST https://api.profilehealth.com/graphql \
-H "Authorization: Bearer pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.yyyyyyyyyyyy" \
-H "Content-Type: application/json" \
-d '{"query":"{ health { status } }"}'
Returns {"data":{"health":{"status":"ok"}}}. The health query needs no scope — use it to verify connectivity before making scope-gated calls. Add an Idempotency-Key header on mutations; it must be ≤128 chars and unique per logical operation.
What you can do
GraphQL is the canonical surface. Every field is scope-gated — your API key carries only the scopes your partner agreement grants. All data access is clinic-scoped: a key can only read or write resources that belong to the clinics in its allowedClinicIds list.
Explore the live schema: GraphQL Voyager — schema.graphql (SDL)
| Use case | GraphQL operations | Required scope(s) |
|---|---|---|
| Patient & Intake |
Queries: patient, patients, patientDossierMutations: createIntakeInvitation
|
patient:read, intake:write |
| Labs & Documents |
Queries: labResults, documentsMutations: uploadDocument
|
patient:read, document:read, document:write |
| Clinical Notes (SOAP) |
Queries: composition, compositionsMutations: createComposition, updateCompositionSection, signComposition, amendComposition, deleteComposition
|
note:read, note:write, note:sign, note:amend |
| Encounters |
Queries: encounter, encountersMutations: createEncounter, updateEncounter, deleteEncounter
|
encounter:read, encounter:write |
| Scratch Notes |
Queries: scratchNote, scratchNotesMutations: createScratchNote, updateScratchNote, deleteScratchNote
|
scratch-note:read, scratch-note:write |
| AI Synthesis |
Queries: synthesisMutations: requestSynthesis
|
synthesis:read, synthesis:run |
| Health Information Exchange (Carequality / KNO2) |
Queries: hieQueryMutations: hieDocuments
|
hie:query; hie:query + document:write (hieDocuments) |
Partner and API-key administration (admin:partners:*) and audit-event queries (admin:audit:read) exist in the schema but require admin scopes restricted to Profile Health administrators. Partners do not receive these scopes.
patientDossier is the one-call aggregate: a single query returns patient demographics, encounters, documents, observations, conditions, medications, allergies, procedures, family history, questionnaire responses, and consents assembled server-side. The return type is a union: PatientDossier when the chart is ready, DossierPending (poll again) when the patient has not yet completed intake or the dossier is still assembling.
Error Codes
Errors carry a code in GraphQL extensions or RFC 7807 title for REST. Always include the x-correlation-id response header in support requests — server logs are searchable by it.
| Code | HTTP | Meaning |
|---|---|---|
UNAUTHENTICATED | 401 | No credentials, malformed bearer, or invalid signature. |
FORBIDDEN | 403 | Authenticated, but missing required scope or trying to act on a clinic the partner does not own. |
NOT_FOUND | 404 | Resource does not exist or is not visible to this principal. |
METHOD_NOT_ALLOWED | 405 | HTTP method not supported on this endpoint (e.g. GET on /graphql). Honor the Allow response header. |
CONFLICT | 409 | Duplicate resource, or stale precondition (e.g. updating a revoked key). |
PAYLOAD_TOO_LARGE | 413 | Request body exceeds the size limit. Reduce the payload (e.g. submit a smaller Bundle). |
VALIDATION_FAILED | 422 | Input failed schema validation. Response includes which field failed. |
RATE_LIMITED | 429 | Per-principal quota exceeded. Honor Retry-After / RateLimit-Reset headers. |
IDEMPOTENCY_KEY_REUSED | 422 | Same Idempotency-Key seen with a different payload, or replay detected. |
UPSTREAM_TIMEOUT | 504 | Upstream (Medplum, Firestore, etc.) responded too slowly. |
UPSTREAM_UNAVAILABLE | 503 | Upstream circuit breaker open, bulkhead full, or service not configured. |
INTERNAL | 500 | Unhandled server error. Detail is never sent to clients — quote the correlationId in support requests. |
KIT_ALREADY_ACTIVATED | 409 | This kit code has already been activated. Do not retry with the same code; have the patient contact support quoting the correlationId. |
Sample error responses
Three of the most common error shapes — full reference + every code is in openapi.json (Swagger UI shows them under each endpoint's "Responses" tab).
401 — UNAUTHENTICATED (missing/wrong Bearer)
HTTP/1.1 401 Unauthorized
content-type: application/problem+json
x-correlation-id: 7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4
{
"type": "https://errors.profilehealth.com/unauthenticated",
"title": "UNAUTHENTICATED",
"status": 401,
"detail": "Authentication required to access Mutation.createIntakeInvitation",
"correlationId": "7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"
}
422 — VALIDATION_FAILED (e.g. missing Idempotency-Key)
HTTP/1.1 422 Unprocessable Entity
content-type: application/problem+json
{
"type": "https://errors.profilehealth.com/validation-failed",
"title": "VALIDATION_FAILED",
"status": 422,
"detail": "Idempotency-Key header is required for this operation",
"correlationId": "7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"
}
429 — RATE_LIMITED (honor Retry-After)
HTTP/1.1 429 Too Many Requests
content-type: application/problem+json
ratelimit-limit: 50
ratelimit-remaining: 0
ratelimit-reset: 1735000060
retry-after: 1
{
"type": "https://errors.profilehealth.com/rate-limited",
"title": "RATE_LIMITED",
"status": 429,
"detail": "Rate limit exceeded for partner par_001 (50 req/s)",
"correlationId": "7e8c3a2b-19d4-4f6e-b551-aab1f9c2d3e4"
}
Reference
- Swagger UI — interactive REST reference
- Voyager — visual GraphQL schema
- openapi.json · openapi.yaml — raw OpenAPI spec
- schema.graphql — raw GraphQL SDL
- GraphiQL — interactive playground (dev environments only)
Operations
GET /v1/health— liveness/readiness probe
Architecture
Partners writing integrations should read the Integration Guide, which covers credential provisioning, the error contract, idempotency, rate-limit tiers, and clinic scoping end-to-end.