API reference

self-IAM documentation

Every route with its authentication, request shape, status codes, limits, and use cases. Base URL: https://selfiam.site

Guides

Rate limits & usage

How quota is enforced — and why failures still count.

Every API key has a daily op budget that resets at UTC midnight: **Free 100**, **Pro 98,765**, **Enterprise unlimited**. The counter is incremented atomically so concurrent requests cannot overshoot.

**Usage is counted even when the request fails.** All keyed routes run `verifyApiKeyAndQuota` before body/header validation, so malformed, rejected, and unauthorized requests still consume quota. Success and failure responses both echo `remainingOps` / `resetDate`.

When the budget is exhausted every further call returns `429 rate_limit_exceeded` until the reset. Generate additional keys (each has its own budget) or upgrade to the Pro/Enterprise plan for more headroom.

100 ops/day (Free)98,765 ops/day (Pro)Unlimited (Enterprise)

Authentication & sessions

The hybrid JWT + cookie session model.

Sign-in/sign-up returns a short-lived **HS256 JWT (15 min)**. The token is mirrored in an **HttpOnly cookie `ck_session`** (SameSite=Lax, Secure in production) so server pages and routes can authenticate without JavaScript, while the ContactKit widget keeps the token in localStorage for its own calls.

Sessions are stored centrally in the `sessions` collection and are revocable: logout deletes the record so the JWT can never be refreshed. The widget revalidates via `/me` on load and slides the session forward before expiry.

The JWT carries a `kind` field (`accounts` | `clients`) that selects the identity collection the token resolves against — the dashboard always requires `accounts` sessions.

15-min JWT TTLHttpOnly `ck_session` cookieCentral revocation

Identities & organizations

Separate collections and one-account-everywhere membership.

Platform logins (self-IAM dashboard) live in the **`accounts`** collection, isolated so client API keys can never touch them. End-users created by client websites live in one shared **`clients`** collection across all organizations.

One account works across organizations: signing in through another org's key adds the org to `organizationIds` automatically. Usernames/emails are globally unique per collection — duplicate sign-ups get `409 identity_conflict` and must sign in instead.

A Google account is **linked once**: the same Google identity can join multiple organizations, but no organization's API can re-bind an already-linked Google account.

`accounts` = platform`clients` = shared end-users`organizationIds` membership

Security model

How credentials and keys are protected.

API keys are stored only as **SHA-256 hashes** and shown once at creation; keys require a name/note. Passwords are hashed with bcrypt (cost 10). QuickHash tokens are compared in constant time.

All request input is coerced to strings before entering queries (injection-safe), and a dummy bcrypt comparison runs when a user is not found so response timing does not leak account existence.

Google OAuth state is stored per-flow; WhatsApp OTPs are SHA-256-hashed, single-use, and expire after 5 minutes.

Hashed API keysTiming-safe credential checksSingle-use OTP

Pricing

Pricing plans

Free · Pro · Enterprise — see the pricing page for full details.

**Free — ₹0.** 100 API calls/day, core auth + contact endpoints, shared identity across apps, community support.

**Pro — ₹69/month.** 98,765 API calls/day, Google OAuth + WhatsApp OTP, multi-organization memberships, priority support.

**Enterprise — contact us.** Unlimited API calls, dedicated support engineer, SLA, custom onboarding & SSO.

Free: 100 calls/dayPro: 98,765 calls/dayEnterprise: unlimited

Authentication

POST/api/v1/auth/signup

Create a user and start a session. Routes to the `accounts` collection for the self-IAM organization and the shared `clients` collection for every other organization.

Auth
Publishable API key (`Authorization: Bearer org_live_…`)
Request
  • `username` — 3–32 chars, `[a-zA-Z0-9_]` (required)
  • `email` — valid email (required)
  • `password` — at least 8 characters (required)
  • `phoneNumber` — optional, `^\+?[0-9\s\-()]{7,20}$`
Success
`201` — `{ status, token, user, session, organization, remainingOps, resetDate }`
Limits
One identity per collection; duplicate usernames/emails are rejected globally.

Status codes

400validation_error — malformed fields
401invalid_api_key — unknown/revoked key
403route_forbidden — route not in the org allowedRoutes
409identity_conflict — username/email already exists (sign in instead)
429rate_limit_exceeded — daily quota used

Use cases

  • Self-serve sign-up on a client website
  • Creating a platform (dashboard) account
  • Pre-registering users with phone numbers for later WhatsApp OTP

Example

curl -X POST https://selfiam.site/api/v1/auth/signup \
  -H "Authorization: Bearer org_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "username": "jane", "email": "jane@example.com", "password": "SuperSecret!23", "phoneNumber": "+14155550123" }'
POST/api/v1/auth/verify

Sign a user in by username or email with a password or QuickHash. Adds the calling organization to the identity's memberships (`organizationIds`) so one account works across organizations.

Auth
Publishable API key
Request
  • `method` — `"username"` or `"email"` (required)
  • `identity` — the username or email (required)
  • `password` — plaintext password (required unless `quickHash` given)
  • `quickHash` — optional replayable hash (`bcrypt(identity + ":" + password)`)
Success
`200` — `{ status, token, user, session, organization, remainingOps, resetDate }`
Limits
Token TTL 15 min; membership add is idempotent.

Status codes

400validation_error — missing method/identity/password
401invalid_credentials — wrong password or QuickHash
401invalid_api_key — unknown key
403route_forbidden — not allowed for the org
429rate_limit_exceeded

Use cases

  • Sign-in on any client website
  • Cross-organization login — joining a new org with an existing account
  • Server-to-server verification of credentials before issuing your own token

Example

curl -X POST https://selfiam.site/api/v1/auth/verify \
  -H "Authorization: Bearer org_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "method": "username", "identity": "jane", "password": "SuperSecret!23" }'
GET/api/v1/auth/me

Resolve the current user behind a session token (JWT). Used by the widget on load to revalidate sessions and by server helpers for page-level auth.

Auth
Session token (`Authorization: Bearer <jwt>` or `ck_session` cookie)
Success
`200` — `{ status, user, session: { expiresAt } }`
Limits
Public fields only (no hashes, no internal ids).

Status codes

401invalid_session — missing/expired/revoked token

Use cases

  • Middleware / route guards
  • Validating sessions in Server Components via `self-iam/server`
  • Hydrating a UI with the signed-in user

Example

curl https://selfiam.site/api/v1/auth/me -H "Authorization: Bearer <jwt>"
POST/api/v1/auth/refresh

Slide a still-valid session forward and return a fresh token + cookie.

Auth
Session token
Success
`200` — fresh `{ status, token, user, session }`
Limits
Only renews before the 15-min TTL; revoked sessions cannot be refreshed.

Status codes

401invalid_session — expired or revoked (must sign in again)

Use cases

  • Automatic background token renewal in the widget
  • Server-side sliding sessions for long-running pages

Example

curl -X POST https://selfiam.site/api/v1/auth/refresh -H "Authorization: Bearer <jwt>"
POST/api/v1/auth/logout

Revoke the current session centrally (deletes the session record, so the JWT can never be refreshed). With `?all=1` it revokes every session for the user.

Auth
Session token
Request
  • Query: `?all=1` (optional) — log out of all devices
Success
`200` — `{ status: "success" }`
Limits
Idempotent; already-revoked tokens return 401.

Status codes

401invalid_session

Use cases

  • Sign-out buttons and the `ContactAuthSignout` component
  • "Log out everywhere" security actions

Example

curl -X POST https://selfiam.site/api/v1/auth/logout?all=1 -H "Authorization: Bearer <jwt>"
POST/api/v1/auth/google/authorize

Return a Google OAuth consent URL for the calling organization. On success the callback redirects back with `?contactkit_token=` or `?contactkit_error=`.

Auth
Publishable API key
Request
  • `redirectUrl` — where the browser should return after consent (required)
Success
`200` — `{ status, authUrl }`
Limits
A Google account is linked once and never re-bound by another org's API.

Status codes

400validation_error — redirectUrl missing
503google_not_configured — GOOGLE_CLIENT_ID/SECRET not set
401invalid_api_key
403route_forbidden
429rate_limit_exceeded

Use cases

  • One-tap "Continue with Google" sign-in
  • Joining a new organization with an existing Google account

Example

curl -X POST https://selfiam.site/api/v1/auth/google/authorize \
  -H "Authorization: Bearer org_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "redirectUrl": "https://my-app.com/auth/callback" }'

Dashboard (session-guarded)

GET/api/v1/keysPOST

List the session organization's API keys with usage stats (GET) or issue a new key (POST). Key hashes never leave the server.

Auth
Accounts session token (dashboard login)
Request
  • POST body: `name` — key name/note, 1–64 chars (required; rejected otherwise)
Success
GET `200` — key list with `preview`, `dailyUsage`, `remainingOps`, `resetDate`. POST `201` — `{ apiKey, name, organizationId }` (raw key shown once).
Limits
Daily quota per key (100 Free / 98,765 Pro / unlimited Enterprise).

Status codes

400validation_error — POST without a key name
401invalid_session — not signed in to an accounts session

Use cases

  • The `/dashboard` API-key management UI
  • Programmatic key lifecycle for multi-app teams

Example

curl -X POST https://selfiam.site/api/v1/keys \
  -H "Authorization: Bearer <jwt>" -H "Content-Type: application/json" \
  -d '{ "name": "Production web app" }'
GET/api/v1/users

List legacy IAM users belonging to the session organization — public profile fields only.

Auth
Accounts session token
Success
`200` — `{ status, users: [{ id, username, email, phoneNumber, createdAt }] }`
Limits
Projection excludes passwordHash/quickHash/googleId.

Status codes

401invalid_session

Use cases

  • Admin user directory
  • Team management screens

Example

curl https://selfiam.site/api/v1/users -H "Authorization: Bearer <jwt>"
GET/api/v1/clients

List client users (end-users created on client websites) that are members of the session organization. Uses the shared `clients` collection.

Auth
Accounts session token
Success
`200` — `{ status, count, clients: [{ id, username, email, phoneNumber, createdAt }] }`
Limits
Public fields only; org-scoped via `organizationIds`.

Status codes

401invalid_session

Use cases

  • Seeing who has signed up on your site
  • Auditing cross-org memberships

Example

curl https://selfiam.site/api/v1/clients -H "Authorization: Bearer <jwt>"

External access (client users)

GET/api/v1/clients/unamehash

Verify a client user's username + password/QuickHash. Mirrors the legacy IAM route against the shared `clients` collection, scoped to the calling organization.

Auth
Publishable API key + `X-Username` + `X-Password` or `X-Quick-Hash`
Success
`200` — `{ status, authenticated, username, usernameHash, remainingOps, resetDate }`
Limits
Client must be a member of the calling org; dummy bcrypt runs for timing safety.

Status codes

400username_error / password_error — missing headers
401password_error — bad credentials
404username_error — no such client user in this org
401invalid_api_key
403route_forbidden
429rate_limit_exceeded

Use cases

  • Server-side login validation on a client website
  • Deriving a SHA-256 `usernameHash` for your own user tables

Example

curl https://selfiam.site/api/v1/clients/unamehash \
  -H "Authorization: Bearer org_live_..." \
  -H "X-Username: jane" -H "X-Password: SuperSecret!23"
GET/api/v1/clients/emailhash

Email variant of the client verification endpoint — validates `X-Email` + password/QuickHash.

Auth
Publishable API key + `X-Email` + `X-Password` or `X-Quick-Hash`
Success
`200` — `{ status, authenticated, email, emailHash, remainingOps, resetDate }`
Limits
Client must belong to the calling org.

Status codes

400email_error / password_error
401password_error — bad credentials
404email_error — no such client user
401invalid_api_key
403route_forbidden
429rate_limit_exceeded

Use cases

  • Email-based login
  • Pre-signup email verification

Example

curl https://selfiam.site/api/v1/clients/emailhash \
  -H "Authorization: Bearer org_live_..." \
  -H "X-Email: jane@example.com" -H "X-Password: SuperSecret!23"
POST/api/v1/clients/whatsapp-otp

Request or verify a 6-digit WhatsApp OTP for a client user (requires a linked `phoneNumber`). Codes are stored SHA-256-hashed with a 5-minute TTL and are single-use.

Auth
Publishable API key
Request
  • `action: "request"` + header `X-Username` → sends the code
  • `action: "verify"` + headers `X-Username` + `X-Otp` → validates it
Success
request `200` — `{ status, message, deliveredVia, remainingOps }`. verify `200` — `{ status, authenticated, username, usernameHash }`.
Limits
Requires WHATSAPP_API_KEY + WHATSAPP_PHONE_NUMBER_ID; otherwise delivery is skipped (devCode returned in non-production).

Status codes

400username_error / otp_error — missing input
422otp_error — no phone number linked
401otp_error — invalid/expired/used code
404username_error — unknown client user
401invalid_api_key
403route_forbidden
429rate_limit_exceeded

Use cases

  • Passwordless WhatsApp login
  • Second-factor verification

Example

curl -X POST https://selfiam.site/api/v1/clients/whatsapp-otp \
  -H "Authorization: Bearer org_live_..." -H "X-Username: jane" \
  -H "Content-Type: application/json" -d '{ "action": "request" }'

Legacy IAM

GET/api/v1/unamehash

Original username + password/QuickHash verification against the legacy `users` collection.

Auth
Publishable API key + `X-Username` + `X-Password` or `X-Quick-Hash`
Success
`200` — `{ status, authenticated, username, usernameHash }`
Limits
Same 100/day quota; gate runs before validation so failures count.

Status codes

400username_error / password_error
401password_error
404username_error
429rate_limit_exceeded

Use cases

  • Backward-compatible integrations
  • Existing satellite apps

Example

curl https://selfiam.site/api/v1/unamehash \
  -H "Authorization: Bearer org_live_..." \
  -H "X-Username: john_doe" -H "X-Password: CorrectHorseBatteryStaple1!"
GET/api/v1/emailhash

Email-based password/QuickHash verification against legacy `users`.

Auth
Publishable API key + `X-Email` + `X-Password` or `X-Quick-Hash`
Success
`200` — `{ status, authenticated, email, emailHash }`
Limits
Same quota behavior as above.

Status codes

400email_error / password_error
401password_error
404email_error
429rate_limit_exceeded

Use cases

  • Legacy email login
  • Migration integrations

Example

curl https://selfiam.site/api/v1/emailhash \
  -H "Authorization: Bearer org_live_..." \
  -H "X-Email: john@selfiam.example" -H "X-Password: CorrectHorseBatteryStaple1!"
POST/api/v1/whatsapp-otp

Original WhatsApp OTP request/verify against legacy `users`.

Auth
Publishable API key
Request
  • `action: "request"` / `"verify"` + `X-Username` (+ `X-Otp`)
Success
`200` — request: OTP sent; verify: `{ status, authenticated, username }`
Limits
Same WhatsApp env requirements as the clients variant.

Status codes

400username_error / otp_error
401otp_error
422otp_error — no phone linked
404username_error
429rate_limit_exceeded

Use cases

  • Legacy passwordless flows

Example

curl -X POST https://selfiam.site/api/v1/whatsapp-otp \
  -H "Authorization: Bearer org_live_..." -H "X-Username: john_doe" \
  -H "Content-Type: application/json" -d '{ "action": "request" }'
POST/api/v1/contact/messages

Accept a contact-form submission and persist it to the `messages` collection. This is the endpoint the `ContactForm` widget posts to.

Auth
Publishable API key
Request
  • `name` (≤200), `email` (valid, ≤320), `subject` (≤200, optional), `message` (≤5000)
Success
`201` — `{ status, messageId, createdAt, remainingOps, resetDate }`
Limits
Field length caps; gate runs before body validation so bad requests still count.

Status codes

400validation_error / invalid_payload
401invalid_api_key
403route_forbidden
429rate_limit_exceeded

Use cases

  • Contact form submissions
  • Lead capture

Example

curl -X POST https://selfiam.site/api/v1/contact/messages \
  -H "Authorization: Bearer org_live_..." -H "Content-Type: application/json" \
  -d '{ "name": "Jane Doe", "email": "jane@example.com", "subject": "Pricing", "message": "Do you offer a free tier?" }'
GET/api/v1/org

Public organization profile (name, slug, allowedRoutes) used for top-bar branding.

Auth
None
Request
  • Query: `organizationSlug` (optional, defaults to `self-iam`)
Success
`200` — `{ status, organization: { id, name, slug, allowedRoutes } }`
Limits
Public read-only.

Status codes

404organization_error — no such slug

Use cases

  • Branding the navigation bar
  • Discovering an org's allowed routes

Example

curl "https://selfiam.site/api/v1/org?organizationSlug=self-iam"