Use the xCures® API with AI agents
This page is the starting point for AI agents and for the developers who direct them. It collects the rules an agent needs to call the xCures API safely, in one place.
Last reviewed: October 28, 2026.
Version: checked against API version V1, the version the OpenAPI description states. The changelog records each change to this page.
Every rule here comes from the API reference, the changelog, or another page on this site. When this page and the API reference disagree, the API reference wins.
Give your agent these sources
| Source | URL | What it holds |
|---|---|---|
| This page as Markdown | https://docs.xcures.com/ai-agents.md | The rules on this page, as plain Markdown. |
| llms.txt | https://docs.xcures.com/llms.txt | An index for language models that lists this page and the API reference. |
| API reference index | https://docs.xcures.com/apis/current.md | Every endpoint, with a Markdown link per operation. |
| OpenAPI description | https://docs.xcures.com/_bundle/apis/current.yaml | The full machine-readable API description. |
| Agent skills | https://docs.xcures.com/.well-known/agent-skills/index.json | Step-by-step workflow guides in SKILL.md format. |
| Changelog | https://docs.xcures.com/changelog | API changes by date, including new endpoints, removed fields, planned deprecations, and changes to this page. |
The SDKs, FAQ, Platform User Guide, and Data Dictionary pages require a portal sign-in. An agent without a sign-in sees only the public pages, the API reference, and the agent skills. This page restates the facts from those pages that an agent needs most.
Ground rules for agents
- Read the reference before every call. Use the exact path, method, headers, and required fields. Never guess an endpoint or a field name.
- Keep secrets out of the conversation. Read
client_idandclient_secretfrom environment variables or a secret store. Never paste them into a prompt, a log, or a file in a repository. - Start in a non-production project. Non-production projects do not connect to live networks and should not hold real patient data. Use the de-identified demo patients to explore retrieved data.
- Keep patient data out of prompts and logs. Print IDs and counts, not names, birth dates, or record content. Do not log or store signed document URLs.
- Ask before you write. Reads are safe to repeat. Creating subjects, dispatching queries, and publishing documents change state, and a query reaches real health information networks in production.
Terms
The API uses one name for each concept. Use these names in code and in prompts.
| Term | Meaning |
|---|---|
| Subject | The API's name for a patient record. Pages say "patient" in prose; paths and fields say subject and subjectId. |
| Project | A workspace that holds subjects and their data. Almost every call sends the project's ID in the ProjectId header. |
| Organization | Your xCures customer account. It holds one or more production projects and a sandbox project, plus any other test projects. Not the same as the organizationIds list on Create Query, which names a health-network organization. |
| Query | One records request for one subject across connected health information networks. Its progress is in ccdaStatus. |
| Document | A record retrieved for a subject or uploaded for one. Downloads and uploads use short-lived signed URLs. |
| Clinical Concepts | Structured data xCures extracts from a subject's documents, one endpoint per concept type. |
| Checklist | A set of questions xCures answers from a subject's records, with supporting evidence. |
Base URL and authentication
| Item | Value |
|---|---|
| Base URL | https://partner.xcures.com |
| Token URL | POST https://partner.xcures.com/oauth/token |
| Grant type | client_credentials, sent as a JSON body |
| Token response | access_token and token_type, which is Bearer. No expires_in field is returned. |
| Token lifetime | 1 hour. Request a new token when a call returns 401. |
| Headers on every call | Authorization: Bearer <access_token> |
| Project header | ProjectId: <project UUID> on every call except Get Token and List Projects |
Create API credentials in the portal, as the API Introduction shows. Each credential is scoped to production or non-production projects, never both.
The safe call order
The examples use curl and jq. Set XCURES_CLIENT_ID and XCURES_CLIENT_SECRET in your shell from your secret store first. Run the blocks in order, in one shell session: each block uses the variables the blocks before it set.
1Get a token
XCURES_BASE_URL="https://partner.xcures.com"
XCURES_TOKEN="$(curl -sS --fail-with-body -X POST "$XCURES_BASE_URL/oauth/token" \
-H 'content-type: application/json' \
-d "$(jq -n --arg id "$XCURES_CLIENT_ID" --arg secret "$XCURES_CLIENT_SECRET" \
'{client_id: $id, client_secret: $secret, grant_type: "client_credentials"}')" \
| jq -r '.access_token')"Treat any 2xx status as success, and check that access_token is present. The response holds two fields:
{
"access_token": "<access token>",
"token_type": "Bearer"
}The response has no expires_in field, so track the 1-hour lifetime yourself or request a new token when a call returns 401.
The Get Token reference documents a JSON body (application/json), and the JSON call above is the one to use. The endpoint also accepts the standard OAuth form-encoded body (application/x-www-form-urlencoded) with the same three fields, but the reference does not document it.
2List your projects
List Projects is the one data call that needs no ProjectId header.
curl -sS --fail-with-body "$XCURES_BASE_URL/api/v1/patient-registry/project" \
-H "authorization: Bearer $XCURES_TOKEN" \
| jq '.[] | {id, name}'Ask the person you work for which project to use, by name, and set XCURES_PROJECT_NAME to it. Never pick a production project on your own. This block sets XCURES_PROJECT_ID from that name:
XCURES_PROJECT_ID="$(curl -sS --fail-with-body "$XCURES_BASE_URL/api/v1/patient-registry/project" \
-H "authorization: Bearer $XCURES_TOKEN" \
| jq -r --arg name "$XCURES_PROJECT_NAME" '.[] | select(.name == $name) | .id')"
echo "ProjectId: $XCURES_PROJECT_ID"3Read before you write
Search Subjects is read-only. This call prints the total and the first page of subject IDs.
curl -sS --fail-with-body -G "$XCURES_BASE_URL/api/v1/patient-registry/subject" \
--data-urlencode "pageNumber=1" \
--data-urlencode "pageSize=10" \
-H "authorization: Bearer $XCURES_TOKEN" \
-H "ProjectId: $XCURES_PROJECT_ID" \
| jq '{totalCount, subjectIds: [.results[].id]}'4Create a subject
You generate the subject id yourself, as a UUID. Only id, firstName, and lastName are required, but more demographics improve patient matching. The example uses synthetic values. By default, creating a subject also dispatches its first query. The example sets options.initiateEhrQuery to false, so the call creates only the subject and the query becomes its own step that you ask about first. The flag defaults to true when you omit it.
SUBJECT_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
curl -sS --fail-with-body -X POST "$XCURES_BASE_URL/api/v2/patient-registry/subject" \
-H "authorization: Bearer $XCURES_TOKEN" \
-H "ProjectId: $XCURES_PROJECT_ID" \
-H 'content-type: application/json' \
-d "$(jq -n --arg id "$SUBJECT_ID" '{
id: $id,
firstName: "Avery",
lastName: "Synthetic",
birthDate: "1980-01-15",
gender: "F",
addressPostalCode: "55401",
options: { initiateEhrQuery: false }
}')" \
| jq '{id, identifier}'To create many subjects, use POST /api/v1/patient-registry/subject with at most 10 subjects per request. To turn off the queries for a whole batch, send {"subjects": [...], "options": {"initiateEhrQueries": false}}. That flag is initiateEhrQueries, plural, and it also defaults to true. The bulk onboarding skill covers that path.
5Dispatch a query
A query reaches real health information networks in production. Ask the person you work for before you run this block.
QUERY_ID="$(curl -sS --fail-with-body -X POST "$XCURES_BASE_URL/api/v1/patient-registry/query" \
-H "authorization: Bearer $XCURES_TOKEN" \
-H "ProjectId: $XCURES_PROJECT_ID" \
-H 'content-type: application/json' \
-d "$(jq -n --arg id "$SUBJECT_ID" '{subjectId: $id}')" \
| jq -r '.id')"
echo "QueryId: $QUERY_ID"Every call to Create Query starts a new records request. Before you retry a failed call, check whether a query already exists. If you created the subject without turning the flag off, its query already exists, so read its ID instead of dispatching a second one:
QUERY_ID="$(curl -sS --fail-with-body -G "$XCURES_BASE_URL/api/v1/patient-registry/query" \
--data-urlencode "subjectId=$SUBJECT_ID" \
-H "authorization: Bearer $XCURES_TOKEN" \
-H "ProjectId: $XCURES_PROJECT_ID" \
| jq -r '.results[0].id')"To refresh records later, dispatch a new query the same way, after you ask.
6Poll until the query finishes
while :; do
status="$(curl -sS --fail-with-body "$XCURES_BASE_URL/api/v1/patient-registry/query/$QUERY_ID" \
-H "authorization: Bearer $XCURES_TOKEN" \
-H "ProjectId: $XCURES_PROJECT_ID" \
| jq -r '.ccdaStatus')"
echo "ccdaStatus: $status"
case "$status" in completed|exhausted|error) break ;; esac
sleep 30
doneA production query usually takes 20 to 30 minutes. The API documents no maximum wait for a query. Agree on a time budget with the person you work for before you start. When it runs out, stop polling, report the query ID and its last ccdaStatus, and resume later from the stored ID. Request a new token if the loop runs past the token's 1-hour lifetime.
7Read the results
List the subject's documents:
curl -sS --fail-with-body -G "$XCURES_BASE_URL/api/v1/patient-registry/document" \
--data-urlencode "subjectId=$SUBJECT_ID" \
-H "authorization: Bearer $XCURES_TOKEN" \
-H "ProjectId: $XCURES_PROJECT_ID" \
| jq '{totalCount, documentIds: [.results[].id]}'Check that Clinical Concepts are ready, then read one concept type:
curl -sS --fail-with-body "$XCURES_BASE_URL/api/v1/patient-registry/subject/$SUBJECT_ID/status/clinical-concepts" \
-H "authorization: Bearer $XCURES_TOKEN" \
-H "ProjectId: $XCURES_PROJECT_ID" \
| jq '.loaded'
curl -sS --fail-with-body -G "$XCURES_BASE_URL/api/v1/patient-registry/clinical-concepts/condition" \
--data-urlencode "subjectId=$SUBJECT_ID" \
-H "authorization: Bearer $XCURES_TOKEN" \
-H "ProjectId: $XCURES_PROJECT_ID" \
| jq '{totalCount}'Long-running work
Nothing in the API blocks while records are retrieved or processed. A create call answers at once with the new record, and you poll for progress. The API reference documents no 202 Accepted responses.
| Work | How to wait | Stop when |
|---|---|---|
| Query | Poll GET /api/v1/patient-registry/query/{id} every 30 seconds. | ccdaStatus is completed, exhausted, or error. |
| Clinical Concepts processing | Poll GET /api/v1/patient-registry/subject/{id}/status/clinical-concepts. | loaded is true. |
| Checklist evaluation | Poll Clinical Concepts status first. Evaluating before loaded is true returns empty or partial results. | loaded is true; then evaluate. |
None of these waits has a documented maximum. Agree on a time budget with the person you work for before you start. When the budget runs out, stop, report the query or subject ID and its last status, and resume later.
The other ccdaStatus values are delayed, initiated, pending, and inProgress, and mean the query is still running. exhausted is one of the seven ccdaStatus values in the API reference, which gives it no further description. Treat it as finished, like completed: stop polling, then wait for Clinical Concepts as you would after completed. Read the subject's documents to see what the query retrieved before you decide, with the person you work for, whether to query again. When a query ends in error, ask before you dispatch one fresh query and poll it. If that query also ends in error, stop and report both query IDs. Store query IDs as you create them, so an interrupted polling pass can resume.
Checklist operations
The API has four checklist operations. Each one takes the ProjectId header.
- Get Checklists lists the project's checklists. Read-only.
GET /api/v1/patient-registry/checklist - Get Checklist returns one checklist with each item's questions. Read-only.
GET /api/v1/patient-registry/checklist/{checklistId} - Evaluate Checklist evaluates a checklist for one subject and returns
201. The body takessubjectId, required, andregenerate, optional.POST /api/v1/patient-registry/checklist/{checklistId}/evaluate - Submit Checklist Item Feedback records feedback on one item. It changes state, so ask first.
POST /api/v1/patient-registry/checklist/{checklistId}/item/{checklistItemId}/feedback
Evaluate a checklist only after Clinical Concepts loaded is true. The October 27, 2026 changelog says an evaluation answers the item definition version it was evaluated with; when that differs from the version the checklist now uses, read the checklist again.
The October 23, 2026 changelog removed two response fields: eligibilitySatisfied from ChecklistEvaluationResult, and meetsCriteria from ChecklistItemEvaluationResult. Both fields are no longer returned, so do not read them.
Pagination
Search endpoints take pageNumber (starting at 1, default 1) and pageSize (default 50). Each page returns results and totalCount. The API reference documents no maximum pageSize, so use the default of 50 and page until you have totalCount results. The FHIR resource endpoints require both pageNumber and pageSize.
page=1
size=50
while :; do
resp="$(curl -sS --fail-with-body -G "$XCURES_BASE_URL/api/v1/patient-registry/subject" \
--data-urlencode "pageNumber=$page" \
--data-urlencode "pageSize=$size" \
-H "authorization: Bearer $XCURES_TOKEN" \
-H "ProjectId: $XCURES_PROJECT_ID")"
echo "$resp" | jq -r '.results[].id'
total="$(echo "$resp" | jq -r '.totalCount')"
[ $((page * size)) -ge "$total" ] && break
page=$((page + 1))
doneRate limits, retries, and repeated calls
- Concurrency. Keep at most 10 requests in flight, the ceiling the xCures SDKs use. Beyond that, expect
429responses. - Rate limits. Limits adapt to load and are not published. Traffic near the limit is slowed, so rising latency is the first sign.
- Backoff. Retry
429,502,503, and504with exponential backoff and jitter. Honor aRetry-Afterheader when the response has one. The xCures SDKs do both. - Retry count. The API documents no retry limit. Set a fixed cap on retries per call before you start, as the SDKs do with
maxAttempts. When a call reaches the cap, stop and report the call, its last status code, and the IDs involved. - No idempotency keys. The API documents no idempotency header, so make retries safe yourself.
- Subjects. You choose each subject
id, so a retried create targets the same record. If a retry returns409, read the subject withGET /api/v1/patient-registry/subject/{id}before you do anything else. - Queries. Each Create Query call starts a new network request. Check for an existing query before you retry.
- Checklists. Re-running an evaluation is safe. Set
regeneratetotrueto force a fresh evaluation.
| Limit | Value | When you reach it |
|---|---|---|
| Requests in flight | 10 | Wait for a request to finish before you send the next one. |
| Retries per call | No documented limit | Stop at your own cap and report. |
| Subjects per create request | 10 | Split the roster into batches of 10. |
pageSize | No documented limit. The default is 50. | Use 50 and request the next page. |
| Query wait | No documented limit. A production query usually takes 20 to 30 minutes. | Stop at the agreed time budget, report the query ID and last ccdaStatus, and resume later. |
| Clinical Concepts and checklist wait | No documented limit | Stop at the agreed time budget, report the subject ID, and resume later. |
Fresh queries after error | No documented limit | Ask before one fresh query. If it also ends in error, stop and report both query IDs. |
| Token lifetime | 1 hour | Request a new token. |
Status codes to branch on
Where the API reference documents an error body, it is JSON with status, title, and detail fields.
| Code | Meaning | What the agent does |
|---|---|---|
200, 201 | Success. | Continue. |
400 | The request is invalid, often a missing or malformed field. | Fix the request against the reference. Do not retry it unchanged. |
401 | The bearer token is missing, invalid, or expired. | Request a new token and retry once. |
403 | Authenticated but not allowed. | Check the ProjectId header and the credential's projects and permissions. Some features, such as Summary and Reciprocity, must be enabled on the project. Do not retry. |
404 | The resource was not found. | Check the ID in the path and the project in the header. |
409 | The request conflicts with the current state of a resource. | Read the resource's current state before you act again. |
429 | Too many requests. | Back off, lower concurrency, then retry. |
500 | An error on the xCures side. | Stop and report it. Contact xCures support if it persists. |
503 | The service is unavailable. | Retry with backoff. Check status.xcures.com. |
Pick a workflow
Each agent skill lists the calls for one workflow, in order.
| Goal | Skill | Diagram |
|---|---|---|
| Learn the shared rules: auth, headers, paging, polling, errors | getting-started | This page |
| Register a new patient and retrieve their records | standard-treatment-workflow | Standard Treatment Workflow |
| Upload records you already hold | byod-ingestion | BYOD Workflow |
| Retrieve raw documents with no xCures processing | retrieval-only | Retrieval Only Workflow |
| Refresh records for a patient already registered | requery-patient | Existing Patient / Requery Workflow |
| Register and query a large roster | bulk-patient-onboarding | Bulk Patient Onboarding |
| Publish a document back to the network | reciprocity | Reciprocity Workflow |
Clinical Concepts types
Each type has a search endpoint at GET /api/v1/patient-registry/clinical-concepts/{type} and a get-by-ID endpoint at GET /api/v1/patient-registry/clinical-concepts/{type}/{id}. Both require the subjectId query parameter. The 16 types use these exact path segments:
allergy, biomarker, care_team, condition, coverage, demographic, encounter, family_history, imaging, lab, medication, procedure, radiation, social_history, survey, vital
Report a problem with these docs
To report a wrong or incomplete page, email help@xcures.com or use the Support page.