API Reference
Paths, parameters, the Bearer key gate, the shared rate limit, and the response envelope for every live endpoint. Every endpoint requires a key.
Every path on this page resolves to a route serving production traffic, and every one needs a key. Keys are provisioned manually while the program is in private preview. How to get one.
Conventions
Every endpoint is GET and returns application/json. There are no write endpoints.
Authentication
Send Authorization with the Bearer scheme on every request. A missing, malformed, revoked, or expired key returns 401 with code UNAUTHORIZED.
Authorization: Bearer <your-key>
Rate limiting
120 requests/minute Per IP, shared across every /api/v1 endpoint. The limiter runs before the key check, so an over-limit request returns 429 even with a valid key. Over the limit you get a Retry-After header in seconds and this body — note it is not the standard envelope. There are no X-RateLimit-* headers.
HTTP/1.1 429 Too Many Requests
Retry-After: 4
{"error":"rate_limited"}Success envelope
{
"data": { ... },
"meta": {
"version": "1.0",
"generated": "2026-08-13T09:41:07.201Z"
}
}Error envelope
Four codes are emitted. There is no error.details field.
| Code | HTTP | When |
|---|---|---|
| UNAUTHORIZED | 401 | Missing, malformed, revoked, or expired key. |
| BAD_REQUEST | 400 | A required parameter is missing or fails validation. |
| NOT_FOUND | 404 | The slug or PSX ID matches no published record. |
| INTERNAL_ERROR | 500 | The query failed. Nothing partial is returned. |
{
"error": {
"code": "NOT_FOUND",
"message": "Substance 'not-a-substance' not found"
},
"meta": {
"version": "1.0",
"generated": "2026-08-13T09:41:07.201Z"
}
}Pagination
Only the substance list paginates, and it is offset-based: page and limit. There are no cursors. Every other endpoint returns its full result set.
"pagination": {
"page": 1,
"limit": 50,
"total": <int>,
"total_pages": <int>
}Curiosity — General Data
Read access to published substance and effect records. Draft and unpublished records are never returned.
/api/v1/curiosity/substancesPublished substances, paginated. Identity fields only — no dose or interaction data.
| Parameter | Type | Description |
|---|---|---|
| page | int | 1-based page number. Default 1. |
| limit | int | Items per page. Default 50, max 100. |
/api/v1/curiosity/substances/{slug}One substance in full — classes, dosage, duration, tolerance, withdrawal, toxicity, dependence, chemistry, legal status, effects, interactions.
/api/v1/curiosity/substances/{slug}/dosageDose tiers for one substance. Every tier is keyed by route of administration; a dose without its ROA is not returned.
/api/v1/curiosity/substances/{slug}/interactionsInteractions for one substance — the same resolved set the substance page states: pharmacological class rules and curated pairs, approved rows only, one row per named partner. Every row carries its severity classification and its review status.
/api/v1/curiosity/effectsThe full effect taxonomy — name, slug, category, description.
/api/v1/curiosity/effects/{slug}One effect with the substances reported to produce it.
/api/v1/curiosity/interactions/checkPairwise interactions across a set of substances, each with its severity and review status.
| Parameter | Type | Description |
|---|---|---|
| substances* | string | Comma-separated slugs. Minimum 2, maximum 10. An unknown slug returns 404. |
Substance list response
{
"data": {
"substances": [
{
"id": "<uuid>",
"psx_id": "PSX-0001",
"slug": "psilocybin",
"common_name": "Psilocybin",
"systematic_name": "4-phosphoryloxy-N,N-dimethyltryptamine",
"common_names": ["magic mushrooms", "shrooms"],
"substance_type": "<string>",
"characterization": "<string>"
}
],
"pagination": { "page": 1, "limit": 50, "total": <int>, "total_pages": <int> }
},
"meta": { "version": "1.0", "generated": "<ISO 8601>" }
}Doses carry their route
The dosage payload is keyed by route of administration, and each tier belongs to one route. A dose read without its ROA is not a dose — do not merge or average tiers across routes when you display them.
Interactions carry their severity
Every interaction row carries a severity classification. Never render one without it. The values are exactly: lethal, dangerous, caution, low_risk, unknown. Relay lethal as lethal; it has no softer synonym. unknown means the combination is unclassified, not that it is safe.
An empty interaction list means nothing is recorded for that substance, which is not evidence that a combination is safe. Both interaction endpoints return a safety_note saying so — pass it through rather than dropping it.
Effect categories
category is one of: perception, body, thinking, feeling, self, time, transpersonal, awareness.
Atlas — Legal Status
Legal status for one substance in one jurisdiction. Coverage is thin: entries are curated per version, so most substance/jurisdiction pairs have none.
/api/v1/atlas/statusLegal status for one substance in one jurisdiction. Substances with no entry for that jurisdiction answer with status 'unknown' and the list of jurisdictions that do have entries.
| Parameter | Type | Description |
|---|---|---|
| substance* | string | PSX ID (PSX-0001) or slug (psilocybin). |
| jurisdiction* | string | ISO 3166-1/-2 code (US, US-OR, GB) or the literal 'International'. |
{
"data": {
"substance_id": "PSX-0001",
"substance_name": "Psilocybin",
"jurisdiction": "US-OR",
"status": "<string>",
"schedule": "<string | null>",
"note": "<string | null>",
"available_jurisdictions": ["US", "US-OR"]
},
"meta": { "version": "1.0", "generated": "<ISO 8601>" }
}status is curated free text, not a fixed enum. When no entry exists for the requested jurisdiction the route answers unknown and available_jurisdictions lists the ones that do have entries. Legal status changes faster than any dataset; treat a value as a pointer to the primary source, not as legal advice.
Not built
These names are reserved. No route exists for any of them, and no schema is published here, because a schema for an unwritten route is a guess. Where the capability already ships under another path, that path is named.
No Nexus route. Pairwise checking across up to 10 substances ships today at /api/v1/curiosity/interactions/check.
No Prism route. Effect lookup and effect-to-substance links ship today under /api/v1/curiosity/effects.
No route. Slug and PSX ID are the only accepted identifiers today.
No route. Provenance and version history are published as pages instead: /substances/<slug>/evidence and /substances/<slug>/history.
No route and no data sources contracted. See /sentinel for the design and scope.
No route. Emergency guidance is published as pages at /harm-reduction/emergency.
Versioning
The version is the path prefix: /api/v1. Breaking changes get a new prefix rather than mutating this one; additive fields do not.
No deprecation policy is published yet, and no Sunset header is emitted. While the program is a private preview, changes are announced to key holders by email — there are few enough to write to.
Routes under /api/public/ support the Psychedex app, which is being rebuilt. They are app-support endpoints, not part of this API: their shapes change with the app and carry no compatibility promise.