Skip to main content

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.

/api/v1 base path8 endpoints

Private preview

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.

CodeHTTPWhen
UNAUTHORIZED401Missing, malformed, revoked, or expired key.
BAD_REQUEST400A required parameter is missing or fails validation.
NOT_FOUND404The slug or PSX ID matches no published record.
INTERNAL_ERROR500The 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.

GET/api/v1/curiosity/substances

Published substances, paginated. Identity fields only — no dose or interaction data.

ParameterTypeDescription
pageint1-based page number. Default 1.
limitintItems per page. Default 50, max 100.
GET/api/v1/curiosity/substances/{slug}

One substance in full — classes, dosage, duration, tolerance, withdrawal, toxicity, dependence, chemistry, legal status, effects, interactions.

GET/api/v1/curiosity/substances/{slug}/dosage

Dose tiers for one substance. Every tier is keyed by route of administration; a dose without its ROA is not returned.

GET/api/v1/curiosity/substances/{slug}/interactions

Interactions 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.

GET/api/v1/curiosity/effects

The full effect taxonomy — name, slug, category, description.

GET/api/v1/curiosity/effects/{slug}

One effect with the substances reported to produce it.

GET/api/v1/curiosity/interactions/check

Pairwise interactions across a set of substances, each with its severity and review status.

ParameterTypeDescription
substances*stringComma-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.

GET/api/v1/atlas/status

Legal 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.

ParameterTypeDescription
substance*stringPSX ID (PSX-0001) or slug (psilocybin).
jurisdiction*stringISO 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.

NexusMulti-substance interaction checking

No Nexus route. Pairwise checking across up to 10 substances ships today at /api/v1/curiosity/interactions/check.

PrismEffects taxonomy

No Prism route. Effect lookup and effect-to-substance links ship today under /api/v1/curiosity/effects.

EntitySubstance name resolution

No route. Slug and PSX ID are the only accepted identifiers today.

TracerProvenance and version history

No route. Provenance and version history are published as pages instead: /substances/<slug>/evidence and /substances/<slug>/history.

SentinelReal-time supply monitoring

No route and no data sources contracted. See /sentinel for the design and scope.

BeaconEmergency response protocols

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.

AuthAPI key, every endpoint — Send Authorization: Bearer <key>. There is no anonymous tier.
Getting a keyManual, private preview — There is no self-serve signup. Write to contact@psychedex.org with what you are building and we will issue a key or tell you we cannot yet.
Rate limit120 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 limit429 + Retry-After — Body is {"error":"rate_limited"}. There are no X-RateLimit-* headers.
Success shape{ data, meta } — meta carries version and a generated timestamp.
Error shape{ error: { code, message }, meta } — Four codes: UNAUTHORIZED, BAD_REQUEST, NOT_FOUND, INTERNAL_ERROR.
VersioningIn the path — Breaking changes get a new prefix. /api/v1 stays as it is.
LicensingNot finalized — User-contributed content is CC BY 4.0 (see Terms). A licence for the dataset as a whole has not been set, so treat reuse terms as open questions and attribute Psychedex.

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.

For education and harm reduction only — not medical advice. Terms