Clinical API Reference
Programmatic access to GeneOS Token Vault for EHR and CRM integration. Requires a clinical account and an API key.
Authentication
All API endpoints are authenticated via the X-GeneOS-Key HTTP header. API keys are available to accounts with Clinical Access enabled. Generate keys from the Token Vault → API Access section.
gxk_ followed by 48 hex characters (52 chars total).Keys are shown once at creation — store them securely in your environment variables.
# Every request must include:
X-GeneOS-Key: gxk_a1b2c3d4e5f6...
Base URL
https://geneos.app/api/ideas
All endpoint paths below are relative to this base URL. For example: https://geneos.app/api/ideas/geneos/api/v1/tokens
Rate Limits
Each API key is limited to 1,000 requests per day (UTC). Exceeding this returns HTTP 429.
# 429 response body: { "ok": false, "error": "Rate limit exceeded: 1000 requests/day per key." }
Error Format
All responses include "ok": true on success and "ok": false on error. HTTP status codes follow REST conventions.
| Status | Meaning |
|---|---|
| 200 | Success |
| 400 | Bad request (missing or invalid field) |
| 401 | Missing or invalid API key |
| 403 | Access denied (wrong owner or no clinical access) |
| 404 | Resource not found |
| 429 | Rate limit exceeded |
| 500 | Internal server error |
List API Keys
{
"ok": true,
"keys": [
{
"prefix": "gxk_a1b2c3d4",
"label": "EHR Integration",
"created_at": "2026-07-14T12:00:00+00:00",
"last_used": "2026-07-14T15:30:00+00:00"
}
]
}
Generate API Key
{ "label": "EHR Integration" }
{
"ok": true,
"key": "gxk_a1b2c3d4e5f6...", // full key — shown ONCE
"prefix": "gxk_a1b2c3d4",
"label": "EHR Integration",
"created_at": "2026-07-14T12:00:00+00:00",
"note": "Store this key securely — it will not be shown again."
}
Revoke API Key
{ "ok": true, "revoked": "gxk_a1b2c3d4" }
List Tokens
| Param | Default | Description |
|---|---|---|
| status | (all) | Filter: created · kit_issued · sample_received · processing · complete |
| q | (none) | Search alias, token fragment (first 6 chars), or lab accession |
| limit | 50 | Max 200 |
| offset | 0 | Pagination offset |
{
"ok": true,
"total": 142,
"limit": 50,
"offset": 0,
"tokens": [
{
"token": "a1b2c3d4e5f6...",
"alias": "Client #001",
"status": "complete",
"fulgent_accession": "FUL-2026-001",
"created_at": "2026-07-14T12:00:00+00:00",
"health_data_loaded": true,
"order_id": "WHL-ABC123"
}
]
}
Create Token
{ "alias": "Client #001" }
{
"ok": true,
"token": "a1b2c3d4...",
"alias": "Client #001",
"status": "created",
"created_at": "2026-07-14T12:00:00+00:00",
"health_data_loaded": false
}
Bulk Create Tokens
{
"count": 25,
"alias_prefix": "Client #" // optional → Client #1, Client #2, ...
}
{
"ok": true,
"created": 25,
"tokens": [...]
}
Get Token
created → kit_issued → sample_received → processing → complete
{
"ok": true,
"token": "a1b2c3d4e5f6...",
"alias": "Client #001",
"owner_email": "[email protected]",
"status": "complete",
"fulgent_accession": "FUL-2026-001",
"health_data_loaded": true,
"created_at": "2026-07-01T09:00:00+00:00",
"sample_received_at": "2026-07-05T14:22:00+00:00",
"complete_at": "2026-07-14T10:00:00+00:00",
"notes": ""
}
Update Token
{
"alias": "Jane Doe — Rm 12",
"notes": "Expedited processing requested"
}
{ "ok": true, "token": "a1b2...", "alias": "Jane Doe — Rm 12", ... }
Link Lab Accession
{ "fulgent_accession": "FUL-2026-001" }
{
"ok": true,
"token": "a1b2c3d4...",
"status": "sample_received",
"fulgent_accession": "FUL-2026-001"
}
Also fires registered sample_received webhooks.
Get Report
{
"ok": true,
"report": { /* full report JSON — structure varies */ }
}
{ "ok": false, "error": "No report available yet" }
Get Health Profile
{
"ok": true,
"profile": { /* biomarkers, conditions, medications, ... */ }
}
Get QR Code
Returns a image/png QR code encoding the patient wizard URL for this token. The token itself is the credential, so this endpoint can be used without an API key (for printing kits).
https://geneos.app/geneos/health-wizard-v2.html?token={token}
Register Webhook
{
"url": "https://your-ehr.example.com/webhook/geneos",
"events": ["results_ready", "sample_received", "kit_issued"]
}
| Event | Fired when |
|---|---|
| sample_received | Lab accession linked to token |
| results_ready | Report processed and loaded |
| kit_issued | Token moves to kit_issued status |
{ "ok": true, "id": "abc12345", "url": "https://...", "events": [...] }
List Webhooks
{
"ok": true,
"webhooks": [
{ "id": "abc12345", "url": "https://...", "events": [...], "created_at": "..." }
]
}
Delete Webhook
{ "ok": true, "deleted": "abc12345" }
Webhook Payload Schema
All webhook payloads share the same envelope. Delivery is attempted twice with an 8-second timeout per attempt. Failed deliveries are logged server-side.
{
"event": "sample_received",
"timestamp": "2026-07-14T15:00:00+00:00",
"token": "a1b2c3d4...",
"alias": "Client #001",
"status": "sample_received",
"fulgent_accession": "FUL-2026-001" // present for sample_received event
}
Your endpoint should respond with HTTP 2xx within 8 seconds. If the first attempt fails, one retry is made immediately. Subsequent failures are not retried.
Code Examples
List tokens with pagination:
BASE=https://geneos.app/api/ideas
KEY=gxk_your_key_here
# List tokens
curl -H "X-GeneOS-Key: $KEY" \
"$BASE/geneos/api/v1/tokens?limit=50&offset=0"
# Create a token
curl -X POST -H "X-GeneOS-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"alias":"Client #001"}' \
"$BASE/geneos/api/v1/tokens"
# Bulk create 25 tokens
curl -X POST -H "X-GeneOS-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"count":25,"alias_prefix":"Client #"}' \
"$BASE/geneos/api/v1/tokens/bulk"
# Link a lab accession
curl -X POST -H "X-GeneOS-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"fulgent_accession":"FUL-2026-001"}' \
"$BASE/geneos/api/v1/tokens/TOKEN_HERE/link-accession"
# Poll for report
curl -H "X-GeneOS-Key: $KEY" \
"$BASE/geneos/api/v1/tokens/TOKEN_HERE/report"
# Register webhook
curl -X POST -H "X-GeneOS-Key: $KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://your-ehr.com/webhook","events":["results_ready","sample_received"]}' \
"$BASE/geneos/api/v1/webhooks"
Using the requests library:
import requests
BASE = "https://geneos.app/api/ideas"
API_KEY = "gxk_your_key_here"
HEADERS = {"X-GeneOS-Key": API_KEY, "Content-Type": "application/json"}
# List all complete tokens
resp = requests.get(
f"{BASE}/geneos/api/v1/tokens",
headers=HEADERS,
params={"status": "complete", "limit": 100},
)
data = resp.json()
print(f"Total: {data['total']}, returned: {len(data['tokens'])}")
# Create 10 tokens with prefix
resp = requests.post(
f"{BASE}/geneos/api/v1/tokens/bulk",
headers=HEADERS,
json={"count": 10, "alias_prefix": "Study-2026-"},
)
tokens = resp.json()["tokens"]
print(f"Created {len(tokens)} tokens")
# Poll for results (EHR integration pattern)
def get_report(token_id):
r = requests.get(
f"{BASE}/geneos/api/v1/tokens/{token_id}/report",
headers=HEADERS,
)
if r.status_code == 200:
return r.json()["report"]
return None # not ready yet
Using fetch (Node.js or browser):
const BASE = 'https://geneos.app/api/ideas';
const API_KEY = 'gxk_your_key_here';
const headers = {
'X-GeneOS-Key': API_KEY,
'Content-Type': 'application/json',
};
// List tokens
async function listTokens(status = '', query = '') {
const params = new URLSearchParams({ limit: '100' });
if (status) params.set('status', status);
if (query) params.set('q', query);
const res = await fetch(`${BASE}/geneos/api/v1/tokens?${params}`, { headers });
return res.json();
}
// Create token
async function createToken(alias) {
const res = await fetch(`${BASE}/geneos/api/v1/tokens`, {
method: 'POST', headers,
body: JSON.stringify({ alias }),
});
return res.json();
}
// Bulk create
async function bulkCreate(count, prefix = '') {
const res = await fetch(`${BASE}/geneos/api/v1/tokens/bulk`, {
method: 'POST', headers,
body: JSON.stringify({ count, alias_prefix: prefix }),
});
return res.json();
}
// Register webhook
async function registerWebhook(url, events) {
const res = await fetch(`${BASE}/geneos/api/v1/webhooks`, {
method: 'POST', headers,
body: JSON.stringify({ url, events }),
});
return res.json();
}