Documentation
On this page

API overview

Two HTTP APIs on two listeners. Requests and responses are JSON, credentials are bearer tokens, and errors share one shape.

Organization APIManagement API
Default address0.0.0.0:8080127.0.0.1:9090 (keep it private)
Who calls ityour organizations’ users, agents, apps, MCP clientsyou, the operator
Credentialssession tokens, external JWTs, agent API tokens, OAuth access tokens (MCP only); identity tokens (heid_) are refused everywhere but the identity exchange and sign-upEd25519 JWTs signed by a key in MANAGEMENT_JWT_PUBLIC_KEYS, or operator sessions opened with a password or a security key
JWT audiencehappy-enterprise:org (REST), happy-enterprise:mcp (MCP)happy-enterprise:management
Database accessa role confined by row level security to the request’s organizationa role that sees only the list of organizations and their identity provider keys

The two listeners share no routes, credentials, or database roles. A token for one audience is rejected by the others.

Reading this reference

Each endpoint lists:

  • Auth: none (the login endpoints, which name the organization in the path), partial (a session still waiting for its second factor is enough), or full (a fully authenticated credential; partial sessions get 403 mfa_required).
  • Permission: what the caller must hold, see Users, teams, permissions. owner means a member of owners using a first-party credential (not an OAuth grant).

Examples use these shell variables:

Shell
BASE=https://enterprise.example.com     # PUBLIC_BASE_URL
ORG=c8kq2m7xw3n5p9r1t4v6y0za            # organization id
TOKEN=eyJhbGciOiJFZERTQSIsImtpZCI6ImhlLXNlc3Npb24tdjEifQ...   # session token

Conventions

  • Bodies are JSON (Content-Type: application/json), except image uploads (raw bytes) and the OAuth token and revocation endpoints (form-encoded).
  • Ids are cuid2 strings, 24 characters, ^[a-z][a-z0-9]{23}$. A malformed id in a path is a 404, like an unknown one.
  • {org} in a URL (/v1/orgs/{org}/..., /orgs/{org}/...) is the organization’s slug or its id.
  • Timestamps are RFC 3339 in UTC.
  • Unknown JSON fields are refused by the ontology and security-posture endpoints.
  • DELETEs and other writes without a body answer 204 No Content.

Errors

Every error has the same shape:

JSON
{ "error": { "code": "not_found", "message": "not found" } }

Validation and schema errors add details, a list of { path?, object_id?, message }:

JSON
{
  "error": {
    "code": "validation_failed",
    "message": "the properties do not satisfy the object type",
    "details": [
      { "path": "email", "message": "must be an email address" },
      { "path": "age", "message": "must be at least 16" }
    ]
  }
}
StatuscodeWhen
400bad_requestmalformed input; the message says what
401unauthorizedany authentication failure, always identical; carries WWW-Authenticate: Bearer
403forbiddenauthenticated, but missing a permission
403mfa_requiredthe organization requires 2FA and this session has not completed it
404not_foundunknown, or not visible to you
409conflicta uniqueness, version, or team-rule conflict
409version_conflictan object an action reads is not at its expected version, or changed before its edits ran
409idempotency_conflictan action’s idempotency key was used for a different request
413payload_too_largean image over 5 MB
422validation_failedcontent that breaks its rules, with details
422criteria_failedan action’s criteria do not hold, one detail per criterion
429rate_limitedsee Rate limits; carries Retry-After
502upstreamthe email provider or object store failed
503setup_requiredthe admin has no password yet; see First-run setup
503validators_unavailablea write owes custom validator or format calls but no sandbox is configured
500internala bug; details are only in the server log

Resources you are not allowed to see answer 404 rather than 403 where admitting they exist would leak something: other members’ avatars, restricted object types, other users’ sessions.

Health

GET /health

AuthnoneListenerboth

JSON
{ "status": "ok" }

Example resource: objects

/v1/objects is a minimal per-organization resource kept as an example of the pattern every table of organization data follows (a transaction scoped to the organization, an explicit organization filter, RLS underneath). For real data, use the ontology.

GET /v1/objects

Authfull

Every object of the organization, oldest first.

POST /v1/objects

Authfull

JSON
{ "name": "first", "data": { "any": "json" } }

201 with { id, name, data, created_at, updated_at }. name is 1-200 characters and unique per organization (409 otherwise).

GET /v1/objects/{id}

Authfull

DELETE /v1/objects/{id}

Authfull

204.

Esc
↑ ↓ navigate↵ open