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.
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.
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.
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.
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.
Authentication
/api/v1/auth/signupCreate 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
| 400 | validation_error — malformed fields |
| 401 | invalid_api_key — unknown/revoked key |
| 403 | route_forbidden — route not in the org allowedRoutes |
| 409 | identity_conflict — username/email already exists (sign in instead) |
| 429 | rate_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" }'/api/v1/auth/verifySign 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
| 400 | validation_error — missing method/identity/password |
| 401 | invalid_credentials — wrong password or QuickHash |
| 401 | invalid_api_key — unknown key |
| 403 | route_forbidden — not allowed for the org |
| 429 | rate_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" }'/api/v1/auth/meResolve 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
| 401 | invalid_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>"
/api/v1/auth/refreshSlide 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
| 401 | invalid_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>"
/api/v1/auth/logoutRevoke 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
| 401 | invalid_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>"
/api/v1/auth/google/authorizeReturn 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
| 400 | validation_error — redirectUrl missing |
| 503 | google_not_configured — GOOGLE_CLIENT_ID/SECRET not set |
| 401 | invalid_api_key |
| 403 | route_forbidden |
| 429 | rate_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)
/api/v1/keysPOSTList 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
| 400 | validation_error — POST without a key name |
| 401 | invalid_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" }'/api/v1/usersList 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
| 401 | invalid_session |
Use cases
- Admin user directory
- Team management screens
Example
curl https://selfiam.site/api/v1/users -H "Authorization: Bearer <jwt>"
/api/v1/clientsList 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
| 401 | invalid_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)
/api/v1/clients/unamehashVerify 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
| 400 | username_error / password_error — missing headers |
| 401 | password_error — bad credentials |
| 404 | username_error — no such client user in this org |
| 401 | invalid_api_key |
| 403 | route_forbidden |
| 429 | rate_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"
/api/v1/clients/emailhashEmail 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
| 400 | email_error / password_error |
| 401 | password_error — bad credentials |
| 404 | email_error — no such client user |
| 401 | invalid_api_key |
| 403 | route_forbidden |
| 429 | rate_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"
/api/v1/clients/whatsapp-otpRequest 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
| 400 | username_error / otp_error — missing input |
| 422 | otp_error — no phone number linked |
| 401 | otp_error — invalid/expired/used code |
| 404 | username_error — unknown client user |
| 401 | invalid_api_key |
| 403 | route_forbidden |
| 429 | rate_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
/api/v1/unamehashOriginal 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
| 400 | username_error / password_error |
| 401 | password_error |
| 404 | username_error |
| 429 | rate_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!"
/api/v1/emailhashEmail-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
| 400 | email_error / password_error |
| 401 | password_error |
| 404 | email_error |
| 429 | rate_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!"
/api/v1/whatsapp-otpOriginal 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
| 400 | username_error / otp_error |
| 401 | otp_error |
| 422 | otp_error — no phone linked |
| 404 | username_error |
| 429 | rate_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" }'/api/v1/contact/messagesAccept 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
| 400 | validation_error / invalid_payload |
| 401 | invalid_api_key |
| 403 | route_forbidden |
| 429 | rate_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?" }'/api/v1/orgPublic 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
| 404 | organization_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"