Skip to main content

AI Interpretation

The assistant that interprets results in the Partner Dashboard is available over the API. There are two ways to use it, and you can use both.

EndpointUse it for
One-shotPOST /ai/interpretationsA written interpretation of a result, in a single request.
ConversationPOST /ai/conversations + messagesA back-and-forth about a patient, with follow-up questions.

Both draw on the same data: the patient's GlycanAge results, their health questionnaire, and a curated corpus of glycan and IgG literature.

One-shot interpretation

Send a kit code, get an interpretation back:

POST /ai/interpretations
Content-Type: application/json

{
"kitCode": "GA-AB-123456"
}
{
"kitCode": "GA-AB-123456",
"patientId": "5a1c...",
"interpretation": "The glycan age of 48 sits four years above the chronological age of 44...",
"resultsConsidered": 3,
"generatedOn": 1769558460000,
"cached": false,
"sources": []
}

You do not assemble any context yourself. The endpoint automatically pulls the patient's five most recent results and their health questionnaire, so a repeat tester gets a trend-aware interpretation rather than a reading of one result in isolation. resultsConsidered tells you how many results informed the text.

interpretation is Markdown, structured as an overview, a per-index section, a trend section (omitted when the patient has only one result), and considerations.

Caching

Interpretations are cached per result. The first call generates one; every later call for the same kit returns the stored text.

Cached responses have "cached": true, cost nothing, and do not count against your rate limit or monthly budget — poll the endpoint as freely as you like.

To regenerate, for example after the patient updated their health questionnaire:

{ "kitCode": "GA-AB-123456", "refresh": true }

You can also fetch a cached interpretation without ever risking a generation:

GET /ai/interpretations/kit/GA-AB-123456

This returns 404 if none has been generated yet.

Steering the output

An optional instructions field (up to 2000 characters) lets you focus a single interpretation:

{
"kitCode": "GA-AB-123456",
"refresh": true,
"instructions": "The patient started a 12-week anti-inflammatory protocol in March. Focus on whether the indexes moved."
}

Account-wide tone and guidance is configured by your GlycanAge account manager rather than per request.

Conversations

A conversation is always about one patient, so open it with either a kit code or a patient ID:

POST /ai/conversations
Content-Type: application/json

{ "kitCode": "GA-AB-123456" }
{
"id": "9f2b...",
"patientId": "5a1c...",
"kitCode": "GA-AB-123456",
"title": "Patient Jane Doe",
"createdOn": 1769558460000,
"lastActivity": 1769558460000
}

Opening with kitCode pins the discussion to that result; opening with patientId follows the patient's most recent released result. Either way the patient's five most recent results come along as history.

The assistant is seeded with the patient's record when the conversation opens, so you never repeat context on a message:

POST /ai/conversations/9f2b.../messages
Content-Type: application/json

{ "message": "How does the B index compare with their previous test?" }
{
"id": "c31d...",
"conversationId": "9f2b...",
"role": "assistant",
"content": "The bisecting GlcNAc index moved from the 71st to the 64th percentile...",
"sources": [],
"createdOn": 1769558520000
}

Read the transcript back at any time with GET /ai/conversations/{id}/messages, list conversations with GET /ai/conversations (filter with ?patientId=), and remove one with DELETE /ai/conversations/{id}.

Streaming

For a chat interface, post to /messages/stream instead. The response is application/x-ndjson — one JSON object per line:

{"type":"text","content":"The bisecting GlcNAc index "}
{"type":"text","content":"moved from the 71st to the 64th percentile..."}
{"type":"complete","message":{"id":"c31d...","content":"...full reply...","sources":[]}}

Handle three line types:

  • text — append content to what you are displaying.
  • complete — the finished, persisted message. Use this as the authoritative version.
  • error — generation failed after the stream opened. The stream ends here.

The turn is persisted regardless, so a dropped connection loses nothing: re-read it from GET /ai/conversations/{id}/messages.

note

Rate limit and budget rejections are evaluated before the stream opens, so they arrive as an ordinary JSON error with an HTTP status — not as an error line.

Rate limits and budget

AI endpoints are metered per partner account.

ControlDefaultError code when exceeded
Requests per minute10rate_limit_exceeded
Requests per day500daily_rate_limit_exceeded
Spend per calendar month100 USDmonthly_budget_exhausted

Spend is the token cost of every model call your account makes, summed over the calendar month (UTC) and reset on the 1st. Cached interpretations are free and are not counted.

All three return 429 with a Retry-After header and a machine-readable code:

{
"error": "Monthly AI budget of 100.00 USD has been used. It resets at the start of next month; contact support@glycanage.com to raise it.",
"code": "monthly_budget_exhausted"
}

Any of these limits can be raised for your account — contact support@glycanage.com or your account manager.

Knowing where you stand

Every AI response carries the current state in headers, so you can back off before you are rejected:

HeaderMeaning
X-RateLimit-Limit / X-RateLimit-RemainingRequests allowed / left in the current minute
X-RateLimit-Limit-Day / X-RateLimit-Remaining-DayRequests allowed / left in the trailing day
X-AI-Budget-Limit-USDYour monthly budget
X-AI-Budget-Used-USDSpend so far this month
X-AI-Budget-Remaining-USDWhat is left

GET /ai/usage returns the same figures plus a breakdown, and does not consume quota:

{
"enabled": true,
"limits": {
"requestsPerMinute": 10,
"requestsPerDay": 500,
"monthlyBudgetUsd": 100
},
"currentMonth": {
"spendUsd": 12.4831,
"remainingUsd": 87.5169,
"requests": 214,
"interpretations": 176,
"conversationMessages": 38,
"inputTokens": 4192883,
"outputTokens": 214902
}
}

Errors

Alongside the standard error message, AI endpoints return a stable code you can branch on.

StatusCodeMeaning
400invalid_requestMissing or malformed field.
403ai_disabledAI access is not enabled for your account.
404not_foundKit, patient, conversation or interpretation does not exist, or is not yours.
409result_not_readyThe kit has no released result yet.
409kit_unassignedThe kit is not assigned to a patient.
409no_resultsThe patient has no released results to discuss.
429rate_limit_exceeded, daily_rate_limit_exceeded, monthly_budget_exhaustedSee above.
502empty_responseThe model returned nothing. Retry.

Clinical use

Responses are generated from the patient's GlycanAge data and the literature corpus. They are decision support for a qualified practitioner — not a diagnosis, and not a substitute for clinical judgement.

  • Review generated text before passing it to a patient, as you would any generated content.
  • Do not use it as the sole basis for a clinical decision.
  • The assistant does not prescribe, and will frame uncertain mechanisms as hypotheses rather than fact.

Sandbox

The AI endpoints work in the sandbox against simulated results, so you can build and test your integration end-to-end. Sandbox usage is metered separately from production.