Validation API
The REST API behind every Interoperall tool. Authenticated by agent token, returns structured pass/fail reports.
Overview
Authenticated agent endpoints (require Authorization: Bearer iob_…):
Public device-authorization endpoints (no bearer token; used by an unconfigured agent to obtain one):
Base URL: https://interoperall.com (override with --api-base on tools, or your self-hosted URL).
Authentication
Every /api/v1/agent/* endpoint requires an agent token in the Authorization header:
Authorization: Bearer iob_xxxxxxxxxxxxxxxxxxxxxxxxxxx
Tokens are org-scoped, show their first 12 characters in the UI for identification, and are stored server-side as a SHA-256 hash — the plaintext is delivered to the agent exactly once (either via the device-authorization flow below, or via the "Issue token manually" flow in Settings).
X-Agent-Version: <semver> on every request. This appears in your validation telemetry and helps us diagnose version mismatches.Device authorization flow
How agents and CLIs obtain a token without you ever copy-pasting one. This is the same pattern used by gh auth login, the Stripe CLI, and cloudflared — modelled on RFC 8628.
POST /api/agent-tokens/device-init
Called by the agent when it has no credentials yet. No auth required.
curl -X POST https://interoperall.com/api/agent-tokens/device-init \
-H "Content-Type: application/json" \
-d '{"agent_type":"bridge","device_hint":"prod-mirth-01"}'Response 200
{
"device_code": "opaque-32-byte-base64url-string",
"user_code": "K7T2-NP3X",
"verification_url": "https://interoperall.com/connect",
"verification_url_complete":"https://interoperall.com/connect?code=K7T2-NP3X",
"interval": 5,
"expires_in": 600
}The agent prints verification_url_complete (or the short URL + user_code) to the user, then polls.
POST /api/agent-tokens/device-poll
Agent polls this every interval seconds with its device_code. Returns one of four discriminated shapes:
curl -X POST https://interoperall.com/api/agent-tokens/device-poll \
-H "Content-Type: application/json" \
-d '{"device_code":"..."}'# Still waiting on user
{ "status": "pending" }
# User clicked Deny
{ "status": "denied" }
# 10-minute window elapsed
{ "status": "expired" }
# User clicked Authorize — agent saves this token and uses it from now on
{
"status": "authorized",
"token": "iob_REAL_TOKEN_RETURNED_ONCE",
"label": "Bridge agent (prod-mirth-01) · 2026-05-20",
"org": { "id": "uuid", "name": "Acme Health" }
}HTTP 410 Gone. If the agent crashes between receiving the token and saving it, the user has to start a new device-flow session.Rate limits
device-init: 10 requests/minute per IP.device-poll: 20 requests/minute per device_code (agents are told to poll atintervalseconds, default 5).
Revocation
The token issued via the device flow appears in Settings → HL7 Bridge agent like any other agent token. Clicking the trash icon revokes it immediately — the agent will start failing heartbeats and you can run it again to re-authorize.
POST /api/v1/agent/heartbeat
Updates last_seen_aton the server for your token (drives the "online now" indicator in Settings) and returns the server's current time plus the minimum agent version it supports.
curl -X POST https://interoperall.com/api/v1/agent/heartbeat \ -H "Authorization: Bearer iob_..." \ -H "X-Agent-Version: 0.1.0"
Response 200
{
"server_time": "2026-05-20T10:00:00.000Z",
"min_version": "0.1.0"
}GET /api/v1/agent/specs
Lists every spec your token can see. Use it to discover what slugs are available, what type each is (hl7v2 or fhir), and when they last changed.
curl https://interoperall.com/api/v1/agent/specs \ -H "Authorization: Bearer iob_..."
Response 200
{
"specs": [
{
"slug": "adt-strict",
"name": "ADT Strict",
"version": "1.2.0",
"spec_type": "hl7v2",
"updated_at": "2026-05-19T18:23:14.000Z"
},
{ "slug": "patient-binding", "name": "Patient Strict",
"version": "0.4.0", "spec_type": "fhir",
"updated_at": "2026-05-20T09:01:33.000Z" }
]
}POST /api/v1/agent/validate/[slug]
Validate a single HL7v2 message or FHIR resource against one of your specs (identified by slug in the URL).
HL7v2
Send the raw HL7v2 message as the request body:
curl -X POST https://interoperall.com/api/v1/agent/validate/adt-strict \ -H "Authorization: Bearer iob_..." \ -H "Content-Type: text/plain" \ --data-binary @./message.hl7
Or wrapped in JSON (for environments where binary uploads are awkward):
curl -X POST https://interoperall.com/api/v1/agent/validate/adt-strict \
-H "Authorization: Bearer iob_..." \
-H "Content-Type: application/json" \
-d '{ "message": "MSH|^~\\&|TEST|...|2.5.1\rPID|1||MRN1" }'FHIR
For FHIR specs, send the resource as JSON with Content-Type: application/fhir+json (or just application/json — both accepted):
curl -X POST https://interoperall.com/api/v1/agent/validate/patient-binding \ -H "Authorization: Bearer iob_..." \ -H "Content-Type: application/fhir+json" \ --data-binary @./patient.json
Limits
- Maximum body size: 1 MB.
- Rate limit: 600 requests/minute per token.
- Requests time out at ~30 seconds (server-side; usually validation completes in <200ms).
Response shapes
Successful validation returns HTTP 200 with this shape (defined as a Zod schema in @interoperall/agent-client):
{
"status": "pass" | "pass-with-warnings" | "fail",
"score": 0..100,
"spec": { "slug": string, "name": string, "version": string },
"summary": {
"total": number, // total rules evaluated
"passed": number,
"failed": number,
"skipped": number, // optional fields not present
"errors": number, // failed AND severity == "error"
"warnings": number, // failed AND severity == "warning"
"info": number // failed AND severity == "info"
},
"results": [
{
"ruleId": string,
"passed": boolean,
"skipped": boolean | undefined,
"severity": "error" | "warning" | "info",
"message": string,
"location": {
"segment": "PID",
"segmentIndex": 1,
"field": 3,
"component": 1 | null,
"subcomponent": null,
"path": "Patient.identifier" // for FHIR results
},
"actualValue": string | undefined,
"expectedValue": string | undefined,
"rationale": string | undefined,
"example": string | undefined
}
],
"msh": { // HL7v2 only — null for FHIR or when MSH missing
"sending_app": string | null,
"sending_facility": string | null,
"receiving_app": string | null,
"receiving_facility": string | null,
"trigger_event": string | null, // e.g. "ADT^A01"
"message_control_id": string | null,
"processing_id": string | null, // P / T / D
"version": string | null // e.g. "2.5.1"
}
}The mshobject is what the Bridge agent uses to build an ACK reply. You don't need it for the CLI use case but it's included for completeness so you can route replies if you build your own integration.
Errors
Rate limiting
Each token is allowed up to 600 requests/minute. The response includes:
X-RateLimit-Remaining: 583 X-RateLimit-Reset: 1716200400 # epoch seconds
When exceeded the response is 429 Too Many Requests. The Bridge agent and CLI both surface this with a clear error message; custom integrations should back off and retry after the reset time.
Schema & types
Every endpoint above is backed by a strict Zod schema and inferred TypeScript types in the @interoperall/agent-client workspace package. Server route handlers return these types; the Bridge agent and CLI parse responses through the matching schema at runtime. If the server and a deployed client ever drift, the client fails loudly with a descriptive ContractError instead of misbehaving silently.
If you're writing your own client in another language, treat the response shapes above as the canonical contract. We'll always add fields rather than rename or remove them — breaking changes (if ever needed) get a deprecation window of at least one minor version.
Security & data handling
Every /api/v1/agent/validate/* request is processed in memory and discarded. The only persisted record per request is a small telemetry tuple (counts, timings, hashed IP, agent version) — no message bodies, no rule values, no patient data. Full details on /trust.
All endpoints are TLS-only in production. The token is sensitive (it grants full validation access for your org) — treat it like an API key, store in secrets managers, and rotate via the Settings page if compromised.