Skip to main content
REST API · v1

API Reference

REST API. JSON-only. Versioned at /api/v1/.

Base URL: https://api.onetimelogin.com
Tutorials & quickstart

Authentication flow

Two header patterns. Pick the one that matches the call you're making.

User actions (Bearer JWT)

Anything done as a logged-in user. JWTs are HS256 (default) or RS256 (dual-signed when configured).

Authorization: Bearer <access_token>
Site-level actions (Site Key)

Server-to-server. Example: a partner site posting /auth/login on behalf of a user.

X-OTL-Site-Key: otr_<...>

End-to-end flow

  1. Your server calls POST /api/v1/auth/login with X-OTL-Site-Key and the user's credentials.
  2. OneTimeLogin returns access_token (JWT, 30 min), refresh_token (opaque, 30 days), and the user object.
  3. Send access_token as Authorization: Bearer <token> on every authenticated call.
  4. When you get a 401, exchange the refresh token at POST /api/v1/auth/refresh — you receive a fresh access token and a rotated refresh token. The old refresh token is immediately revoked (one-time-use rotation, replay-protected).
  5. To force-logout a user everywhere, call POST /api/v1/auth/logout or DELETE /api/v1/sessions/{id}. This bumps the user's session_version and every existing access token becomes invalid on next call.

Token storage

  • Access tokens: store in sessionStorage or memory. Never localStorage.
  • Refresh tokens: store in HttpOnly Secure SameSite=Strict cookies, server-side only.
  • Site Keys: server-side secret manager. Never ship to a browser.
  • Transport: HTTPS only in production. Localhost is allowed in development.

Working code in the 15-minute quickstart. SAML / OIDC enterprise SSO available on the Business and Enterprise tiers — see Enterprise SSO.

Resources

Eight resource groups. Each card lists the most-used endpoints.

Auth

All auth endpoints require X-OTL-Site-Key — they create or mutate user sessions for one site.

POST/api/v1/auth/registerCreate a new user
POST/api/v1/auth/loginIssue access + refresh tokens
POST/api/v1/auth/logoutRevoke current session
POST/api/v1/auth/refreshRotate refresh token, mint new access token
POST/api/v1/auth/mfa/verifySubmit MFA code to complete login

Users

Manage users registered on your site. Bearer auth.

GET/api/v1/usersList users (paginated)
GET/api/v1/users/{user_id}Retrieve one user
POST/api/v1/usersCreate a user
PATCH/api/v1/users/{user_id}Update user fields
DEL/api/v1/users/{user_id}Delete a user

Sites

A user can belong to many partner sites. Site-level register requires Bearer auth.

GET/api/v1/sitesList the current user's sites
POST/api/v1/sitesRegister a new site & receive its site_key

Roles

Roles are scoped to one site. The catalog is read-only; assignment requires admin Bearer.

GET/api/v1/rolesList role catalog
POST/api/v1/users/{user_id}/rolesAssign a role to a user

Sessions

List active sessions and revoke them. Revocation bumps the user's session version — every existing access token for that user is invalid on next call.

GET/api/v1/sessionsList the current user's sessions
DEL/api/v1/sessions/{session_id}Revoke a specific session

Webhook flow

How delivery works

  1. You register a webhook_url and receive a webhook_secret on your site.
  2. When an event fires, we POST a JSON body to that URL.
  3. Each request carries X-OTL-Signature: sha256=<hex> where the hex is HMAC-SHA256(body, webhook_secret), and X-OTL-Event-Id for idempotency.
  4. Verify the signature with hmac.compare_digest() (or your language's constant-time equivalent) before parsing the body.
  5. Respond 2xx within 5 seconds. Anything else — or a timeout — triggers a retry.

Retry / backoff

Up to 3 attempts for back-channel events (SLO, deletion) with delays of 1 s, 5 s, 15 s. Lifecycle events (user.*, session.*) retry up to 5 times with exponential backoff (30 s, 2 min, 10 min, 1 h, 6 h) until you respond 2xx. Use X-OTL-Event-Id as your idempotency key — the same id will be sent on every retry.

Events emitted

Event Fires when
user.createdA new user registers anywhere in the network.
user.updatedProfile fields change on any partner site.
user.account.deletedUser invokes account deletion (CCPA / GDPR erasure). Implemented.
session.createdA user logs in to one of your sites.
session.revokedA session is logged out or admin-revoked.
back_channel_logoutSingle Logout (SLO) broadcast to every partner site holding the session.
role.assignedA user is granted a role on your site.
payment.completedA paid plan order is fulfilled.

Payload schema

All events share the same envelope. data varies by event type.

{
  "event_type": "user.account.deleted",
  "timestamp": "2026-04-25T18:30:00Z",
  "data": {
    "otl_subject_id": "01HKZ...",
    "reason_code": "user_initiated_erasure",
    "compliance_flags": {
      "ccpa_drop_request": false,
      "gdpr_erasure_request": true
    }
  },
  "metadata": {
    "partner_client_id": "<your_site_domain>",
    "action_required": "Sever SSO link and execute local data retention/deletion policies."
  }
}

Signature verification (Python example)

import hmac, hashlib

def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
    # signature_header looks like "sha256=<hex>"
    if not signature_header.startswith("sha256="):
        return False
    received = signature_header.split("=", 1)[1]
    expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(received, expected)

Endpoints

POST/api/v1/webhooksRegister an endpoint
GET/api/v1/webhooks/eventsList recent delivery attempts
POST/api/v1/webhooks/events/{event_id}/replayReplay a single event

Review delivery semantics in the webhooks reference before pointing at your prod endpoint.

Sandbox keys

Sandbox is a separate site you create alongside your prod site — same dashboard, distinct site_key and webhook_secret. Use it for local dev, CI, and load tests.

How to get sandbox credentials

  1. Sign in to your dashboard at /owner/dashboard.
  2. Open Settings → Sites and click Add a sandbox site. Use a name like acme-sandbox and a callback URL on localhost or a staging host.
  3. Copy the site_key (prefix otr_) into your dev environment's secret manager. The sandbox webhook_secret is shown once on the same screen.
  4. Sandbox sites are free of charge — no per-registration fee — and live alongside your prod site under the same account.

Sandbox vs production

Capability Sandbox Production
All REST endpointsYesYes
Webhook deliveriesYesYes
Localhost / HTTP callback URLsYesNo (HTTPS only)
Per-registration billingNo (free)$0.05 / new reg, capped at $499/mo
Real email / SMS deliveryNo (logged only)Yes
Rate limit60 req / min / site_key60 req / min / site_key
Data retention30 days, may be wiped between releasesPer your retention policy

Sandbox keys never authenticate against the production database, and production keys never authenticate against sandbox — the boundary is enforced by site_key.

Integration checklist

Step-by-step pre-go-live tasks. Work through these in order against your sandbox site.

  1. Register your prod site and a sandbox site — receive both site_keys.
  2. Store keys securely — AWS Secrets Manager, Vault, or your equivalent. Never commit to git.
  3. Implement /auth/login — call with X-OTL-Site-Key, parse the access + refresh token response.
  4. Implement refresh-token rotation — on 401, call /auth/refresh and replace the stored refresh token with the new one.
  5. Configure your callback URL — the URL OneTimeLogin redirects to after login. HTTPS in prod; localhost allowed in sandbox.
  6. Stand up a webhook receiver — verify X-OTL-Signature with constant-time HMAC compare, and respond 2xx in under 5 seconds.
  7. Handle user.account.deleted — sever the SSO link and run your local retention / deletion policies. Required for CCPA / GDPR.
  8. Wire up logout — call /auth/logout on user action. Optionally subscribe to back_channel_logout for cross-site SLO.
  9. Add error logging — record the request_id on every non-2xx response. We use it to look up the matching server log.
  10. Bulk-import existing users (optional)POST /api/v1/bulk/register with a CSV. Imported users do not count toward billing.
  11. Run end-to-end tests — happy path, expired token, revoked session, webhook signature mismatch, rate-limit (60 / min / site_key).

Go-live checklist

Final checks before flipping the production site_key on. Run all of these against your prod site, not sandbox.

  • Callback URL is HTTPS — cert valid, no mixed content.
  • Production site_key is in your secret manager — never in app code, never in env files committed to git.
  • Webhook signature verification is on — tests pass with the correct secret and fail with a tampered one.
  • Idempotency on webhooks — replays of the same X-OTL-Event-Id are no-ops.
  • Refresh-token rotation is wired — old refresh tokens are discarded after a successful refresh.
  • Rate-limit handling — on 429, you honor Retry-After rather than retrying immediately.
  • Account-deletion handler is liveuser.account.deleted triggers your local user-record purge.
  • Existing users imported (if migrating) — via /bulk/register so re-logins don't count as new registrations.
  • Monitoring is on — alert on 5xx rates from OneTimeLogin and on signature-verification failures from your webhook receiver.
  • Privacy policy + DPA reflect the new processor — if applicable to your jurisdiction. Email legal@onetimelogin.com for our DPA.
  • Rollback plan documented — how you revert to the legacy login flow if something regresses in the first hour.

Want a 20-minute go-live review with an engineer? Email hello@onetimelogin.com.

Bulk Registration

Migrate users from your existing system. Upload a CSV; we return a job_id you can poll.

POST/api/v1/bulk/registerUpload CSV (multipart/form-data)
GET/api/v1/bulk/jobs/{job_id}Status, success count, error rows

Payments

Read-only access to plan orders for the authenticated user.

GET/api/v1/payments/ordersList orders
GET/api/v1/payments/orders/{order_id}Retrieve one order

Rate limits

  • Default: 60 requests / minute / site_key.
  • Auth endpoints: 10 requests / minute / IP.
  • Burst: 2x for 5 seconds.
  • When exceeded: we return 429 Too Many Requests. Honor the Retry-After header.

Need higher limits? Contact hello@onetimelogin.com.

Errors

Every error response is JSON in this shape:

{
  "detail": "<human-readable message>",
  "request_id": "<uuid>"
}

Always log request_id — we use it to look up the matching server log when you write to support.

Code Meaning
400Bad Request — malformed JSON or missing field
401Unauthorized — token missing, expired, or revoked
403Forbidden — token valid but insufficient role
404Not Found
409Conflict — duplicate key or stale version
422Validation Error — field-level failure
429Too Many Requests — honor Retry-After
500Internal Server Error
502Bad Gateway — upstream IdP unreachable
503Service Unavailable — check /status

Versioning

  • /api/v1/ is the current stable version.
  • /v1/ is a permanent alias of /api/v1/.
  • Breaking changes ship as /v2/; non-breaking additions land in /v1/.
  • Deprecation policy: 6 months minimum notice before any version is removed, communicated via email and the Deprecation response header.

OpenAPI spec

The machine-readable OpenAPI 3.1 spec is available to authenticated customers on request. We don't expose it on a public URL because the full route surface includes admin / internal endpoints we audit separately.

Email support@onetimelogin.com with your account ID for the customer-scoped spec, or open a ticket from /support.

Have a question?

Email developers@onetimelogin.com — real engineers, no ticket triage.