Start building

Docs: one connection, end to end

Every response on this page is the server’s actual reply from production, with per-client values replaced by placeholders. Values in angle brackets are placeholders for secrets or per-client values.

0. Instructions for your coding agent

Paste this into the agent that writes your integration. Every endpoint, scope and rule in it is live in production; the sections below show each call’s actual response.

Add AllClear to this app so it can read a user's records with their consent.
Everything below is live in production. Do not invent endpoints or parameters.

1. Register this app once as a public OAuth client (no secret):
   POST https://oauth.app.allclearid.com/register
   { "client_name": "<app name>",
     "redirect_uris": ["<https redirect uri>"],
     "token_endpoint_auth_method": "none",
     "grant_types": ["authorization_code", "refresh_token"],
     "response_types": ["code"] }
   Keep the returned client_id.

2. To connect a user, send them to https://oauth.app.allclearid.com/authorize with
   response_type=code, client_id, redirect_uri, scope="records:read offline_access",
   resource=https://mcp.app.allclearid.com, code_challenge (S256), code_challenge_method=S256,
   state. On return, check state and iss before using the code.

3. Exchange the code: POST https://oauth.app.allclearid.com/token (form-encoded)
   grant_type=authorization_code, code, redirect_uri, client_id, code_verifier.
   Store access_token (1 hour) and refresh_token (30 days). Renew with
   grant_type=refresh_token. Treat both tokens as opaque. Never log them.

4. Read records over MCP: POST https://mcp.app.allclearid.com/mcp
   Authorization: Bearer <access_token>
   Accept: application/json, text/event-stream
   Call tools/list, then tools/call with get_medications, get_conditions, get_allergies,
   get_lab_results, get_vital_signs, get_immunizations, get_encounters, get_procedures,
   get_care_plans, get_patient_insurance, list_patient_documents, search_medical_data.
   Records come back as the source holds them: structured (fhir) plus document excerpts.

5. On a 401, refresh the access token and retry once. If the refresh is refused too,
   the user ended the connection: drop both tokens, clear anything cached, and offer
   to reconnect.

Discovery, if you need it:
   https://oauth.app.allclearid.com/.well-known/oauth-authorization-server
   https://mcp.app.allclearid.com/.well-known/oauth-protected-resource

1. Discovery

The MCP server tells a client where to get authorized. Verbatim:

GET https://mcp.app.allclearid.com/.well-known/oauth-protected-resource
{
  "resource": "https://mcp.app.allclearid.com",
  "authorization_servers": ["https://oauth.app.allclearid.com"],
  "bearer_methods_supported": ["header"]
}

The authorization server describes itself. Verbatim:

GET https://oauth.app.allclearid.com/.well-known/oauth-authorization-server
{
  "issuer": "https://oauth.app.allclearid.com",
  "authorization_endpoint": "https://oauth.app.allclearid.com/authorize",
  "token_endpoint": "https://oauth.app.allclearid.com/token",
  "revocation_endpoint": "https://oauth.app.allclearid.com/revoke",
  "revocation_endpoint_auth_methods_supported": ["none"],
  "registration_endpoint": "https://oauth.app.allclearid.com/register",
  "jwks_uri": "https://oauth.app.allclearid.com/.well-known/jwks.json",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code", "refresh_token"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"],
  "authorization_response_iss_parameter_supported": true
}

What it says in words: public clients, PKCE with S256, authorization code and refresh grants, self-registration, revocation. No client secrets anywhere.

2. The 401 that starts the flow

An unauthenticated MCP call. Verbatim:

POST https://mcp.app.allclearid.com/mcp
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"your-agent","version":"1.0"}}}
HTTP/2 401
WWW-Authenticate: Bearer realm="oauth", resource_metadata="https://mcp.app.allclearid.com/.well-known/oauth-protected-resource"

A spec-conformant MCP client follows that header on its own. This is the whole reason “point your client at one server” is true.

3. Point an MCP client at it

{
  "mcpServers": {
    "allclear": {
      "url": "https://mcp.app.allclearid.com/mcp"
    }
  }
}

No auth block is needed. The client discovers the authorization server from the 401.

4. Register a client

Public client, RFC 7591. Request:

POST https://oauth.app.allclearid.com/register
Content-Type: application/json

{
  "client_name": "Your Agent",
  "redirect_uris": ["https://yourapp.example/callback"],
  "token_endpoint_auth_method": "none",
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"]
}

Response (values illustrative, shape exact):

{
  "client_id": "mcp_…",
  "client_name": "Your Agent",
  "redirect_uris": ["https://yourapp.example/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "application_type": "web",
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "client_id_issued_at": 1788854719
}
HTTP/2 201
Access-Control-Allow-Origin: *

5. Ask for permission

Real scopes. Two are enough for an agent that reads records and keeps a standing connection:

Scope What the user is asked What it does
records:read Share my records with Your Agent Read access over MCP
records:add Receive my records from Your Agent Your app can contribute records
account:assist Authorize Your Agent to assist with sharing and ordering my records Act on the account’s behalf
messages:send Allow Your Agent to send me messages and notifications Notifications to the user
offline_access (standing connection) Refresh token
openid profile email address phone Identity Identity claims (not yet on in production)

Authorize URL, built by the client:

https://oauth.app.allclearid.com/authorize
  ?response_type=code
  &client_id=mcp_…
  &redirect_uri=https%3A%2F%2Fyourapp.example%2Fcallback
  &scope=records%3Aread%20offline_access
  &resource=https%3A%2F%2Fmcp.app.allclearid.com
  &code_challenge=<S256 of code_verifier>
  &code_challenge_method=S256
  &state=<random>

The user signs in to AllClear, sees the request in plain words, and taps Authorize. The redirect back carries code, state, and iss (the server supports the iss response parameter, so the client can check who answered).

Node, no SDK needed:

const verifier = crypto.randomBytes(32).toString("base64url");
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");

const url = new URL("https://oauth.app.allclearid.com/authorize");
url.search = new URLSearchParams({
  response_type: "code",
  client_id: CLIENT_ID,
  redirect_uri: "https://yourapp.example/callback",
  scope: "records:read offline_access",
  resource: "https://mcp.app.allclearid.com",
  code_challenge: challenge,
  code_challenge_method: "S256",
  state,
});

Which scopes for which flow

The scope string you request decides what the user approves and what you get back. Four common flows:

Flow Scopes to request What it gives you
Identity openid profile email A signed id_token that identifies a verified person, plus their details from /userinfo. No record access.
One-time data share records:read Read access to the user’s records over MCP. Omit offline_access when you only need to read once.
Standing connection records:read offline_access Read access plus a standing connection you can refresh without the user, until they disconnect.
Login and standing records openid records:read offline_access Identity and ongoing record access in one grant: the shape a full connector uses.
  • openid turns on OpenID Connect: you get an id_token, which is Identity. The token identifies the person and nothing more: registered claims only (iss, sub, aud, azp, auth_time, at_hash, iat, exp, plus nonce when you send one). sub is pairwise, so the same person is a different sub for every client. Their details are not in it. Add profile email address phone for the claims you need and read them from GET /userinfo with the access token. (In final rollout; confirm availability before you depend on it.)
  • records:read is read access to the user’s records over MCP. On its own it is a one-time share, exactly what the connection panel at the top of the home page uses.
  • offline_access is the standing connection: include it when you want to keep access and refresh it without the user (see sections 6 and 8), and leave it out when a single read is all you need.
  • Add records:add, account:assist, or messages:send on top of any of these when your integration also contributes records, acts on the account, or notifies the user. The user approves each as its own line on the consent screen (section 10) and can decline any one of them.

6. Exchange the code

POST https://oauth.app.allclearid.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=<code>
&redirect_uri=https%3A%2F%2Fyourapp.example%2Fcallback
&client_id=mcp_…
&code_verifier=<verifier>

Response shape, from the server code (field names exact, values illustrative):

{
  "token_type": "Bearer",
  "access_token": "<token, 1 hour>",
  "expires_in": 3600,
  "refresh_token": "<token, 30 days>",
  "refresh_token_expires_in": 2592000
}

Treat both tokens as opaque; the format is the server’s business and may change. What a client needs to know: the access token is bound to the grant the user gave, to your client, and to the MCP server you named in resource; it lasts one hour; the refresh token renews it under the same grant without the user; and when the user disconnects, both stop working at once.

7. Call a tool

POST https://mcp.app.allclearid.com/mcp
Authorization: Bearer <access_token>
Content-Type: application/json
Accept: application/json, text/event-stream

{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_medications","arguments":{"status":"active"}}}

Tools the server advertised on 2026-09-08 (the live answer is tools/list): get_patient_basic_info, get_medications, get_conditions, get_allergies, get_lab_results, get_vital_signs, get_immunizations, get_encounters, get_procedures, get_care_plans, get_patient_insurance, list_patient_documents, summarize_document, search_medical_data, search_faqs, search_faqs, list_connections, connect_provider.

Response envelope from an account with no active medications (structure is the point; a populated account fills fhir_records and retrieved_context):

<tool_output>
  <fhir_output>
    <fhir_records source="fhir" format="columnar-json" />
  </fhir_output>
  <document_output>
    <retrieved_context />
  </document_output>
  <pagination>
    <page>1</page>
    <page_size>20</page_size>
    <has_more>false</has_more>
  </pagination>
</tool_output>

list_connections (values illustrative, shape exact):

{
  "status": "ok",
  "connections": [
    {
      "connection_id": "…",
      "provider_name": "Example Health System",
      "status": "CONNECTED",
      "npi": "1234567893",
      "provider_type": "ORGANIZATION",
      "connection_type": "IN_NETWORK_CONNECTION",
      "share_provider_to_patient": true,
      "fasten_connected": false,
      "can_order_records": true,
      "can_order_electronically": false
    }
  ]
}

Two notes for the page: records come back as they are, structured where the source had structure (the fhir half) and as the clinical documents themselves where it did not (the document half), which keeps the context a conversion would lose; and there is no per-record source or consent id in the output today.

8. Refresh

POST https://oauth.app.allclearid.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
&refresh_token=<refresh_token>

Same response shape as section 6. Access tokens live one hour; the standing connection is the refresh token, which the user can end at any time from the app. client_id is optional on the refresh grant (the token identifies the grant); if you do send it, it must match your registered client.

9. Revoke

From your side:

POST https://oauth.app.allclearid.com/revoke
Content-Type: application/x-www-form-urlencoded

token=<refresh_token>
&client_id=mcp_…

From the user’s side: they open Connections in the AllClear app and disconnect. Either way, the next MCP call answers 401 and the flow in section 2 starts again. No webhook exists; the page shows the 401 instead of a webhook.

10. What the user sees

Real screens from the consent flow, in order:

  1. Header “Connection Request”, title “Choose Your Settings”, card “You’re connecting with Your Agent”. Verified clients show a shield and their domain; unverified ones show “Not verified by Health Bank One”.
  2. Four toggles, worded as the user reads them: “Share my records with Your Agent”, “Receive my records from Your Agent”, “Authorize Your Agent to assist with sharing and ordering my records”, “Allow Your Agent to send me messages and notifications”.
  3. “Confirm Your Settings” with the same lines read back, then Authorize or Cancel.