How the API is structured, how authentication works, and how multi-tenant requests are scoped
4 min readThe API is a multi-tenant REST service. Every endpoint speaks JSON, returns the same response envelope, and uses the same error contract. Tenant-scoped resources live under an organization id carried in the URL path. This page is the mental model you need before calling any endpoint.
All examples in these docs use the production base URL:
https://api.indexhog.comHTTPS only: All requests must use HTTPS. The API sends HSTS with a one-year max-age and rejects mixed-content flows.
Use these unauthenticated resources to configure API clients, code generators, and agents:
The API accepts three authentication methods. Pick based on who is calling.
Users sign in through one of the auth endpoints (email + password, magic link, passkey, social, or SSO). A successful sign-in sets an HTTP-only session cookie. Browsers send it automatically when you make requests with credentials: 'include'; a server acting on behalf of a signed-in user must echo the cookie back on each call.
Use this method for browser apps and any server-rendered UI calling the API on behalf of a signed-in user.
A Personal Access Token (PAT) is a long-lived credential created while signed in (see Quick Start). Send it on every request as:
X-Api-Key: tkn_AbCdEf0123456789...PATs carry an explicit scope list (a subset of the user's permissions) and can optionally be pinned to a single organization.
Third-party applications use the OAuth 2.1 Authorization Code flow against the built-in Better Auth OAuth Provider and call the API with the resulting access token:
Authorization: Bearer eyJhbGciOi...Use this method when your app acts on behalf of users who are not your own — i.e. a public OAuth integration. There is no password grant.
Most endpoints are tenant-scoped. The authenticated principal may belong to several organizations, so tenant routes carry the active organization id directly in the URL path:
/api/user/organizations/{organizationId}/projects/api/user/me, /api/user/oauth-clients, ...) are not tenant-scoped./api/user/organizations/{organizationId}/...) require a valid organization id in the URL. Requests without it fail with TENANT_CONTEXT_MISSING./api/public/*) and auth routes (/api/auth/*) are not tenant-scoped.:organizationId URL segment does not match the pin.See API Structure for the full request/response contract.
Every route belongs to exactly one tier. The tier determines which auth it demands.
| Tier | Mount | Auth | Purpose |
|---|---|---|---|
| Public | /api/public/* | none | Health, invite preview |
| Auth | /api/auth/* | per endpoint | Sign-in, sign-up, MFA, passkeys, OAuth, SSO |
| User Account | /api/user/* | session, PAT, or OAuth token | /me, /oauth-clients, notifications, ... |
| User Tenant | /api/user/organizations/:organizationId/* | session, PAT, or OAuth token + org membership | Projects, payments, members, settings |
| Webhook | /api/webhook/* | provider-signed (inbound only) | Inbound provider callbacks — never called by you |
Every JSON response uses the same shape:
{
"success": true,
"status": 200,
"code": "OK",
"message": "User fetched successfully",
"data": { "...": "..." },
"meta": { "...": "..." }
}success — boolean, mirrors status < 400.status — HTTP status code as a number.code — machine-readable code (OK on success, VALIDATION_ERROR, NOT_FOUND, RATE_LIMIT_ERROR, ... on failure).message — human-readable summary.data — the resource or resource list on success.meta — pagination, validation error details, or other structured metadata.See API Structure for pagination and query conventions and Error Handling for the full error code list.
/api/user/organizations/{organizationId}/.... The API never guesses from a default.code, not just HTTP status: The code field is stable; HTTP status classes can shift as the API evolves.401 means re-authenticate (session expired / token invalid); 403 means your credential cannot access this resource no matter how you retry.Sign in, issue a Personal Access Token, and make your first authenticated call
Point any MCP-capable AI client at the hosted MCP server and drive Indexhog over OAuth — no token to paste.
Request headers, response envelope, pagination, and the query conventions shared by every endpoint
Error envelope, full code list, and the retry strategies that actually work