If you’re not building a client, use
slideless auth ... — it wraps these endpoints, handles --json, and saves the resulting cko_ key as a local profile.Overview
| Endpoint | Purpose | Auth |
|---|---|---|
POST /cliRequestSignupOtp | Email a signup OTP to a brand-new email | Public (no Authorization header) |
POST /cliCompleteSignup | Verify the OTP, create user + org + API key | Public |
POST /cliRequestLoginOtp | Email a login OTP to an existing account | Public |
POST /cliCompleteLogin | Verify the OTP, mint a fresh API key | Public |
https://europe-west1-slideless-ai.cloudfunctions.net/
All four endpoints return the same discriminated-union shape:
400 for validation, 404 for missing user/code, 409 for state conflicts, 410 for expired code, 429 for rate limits, 500 for internals.
Common rules
- Resend cooldown: the
-requestendpoints reject withOTP_RESEND_COOLDOWNif called twice within 30 seconds for the same email. - Abuse caps: 20 requests per email per hour, 60 per IP per hour, across both signup and login.
- Code lifetime: 10 minutes. 5 bad attempts locks the code out.
- Purpose isolation: a code issued for signup cannot be consumed by
cliCompleteLogin(and vice versa) — returnsOTP_PURPOSE_MISMATCH.
POST /cliRequestSignupOtp
When to use
Brand-new user; no Slideless account yet. Pre-flight check: if a Firebase Auth user already exists for the email and has ≥1 organization, the request returnsUSER_ALREADY_HAS_ORGANIZATION — the client should switch to /cliRequestLoginOtp.
Endpoint
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
email | string | yes | Lowercased server-side for rate-limit keying |
Response (200)
Examples
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | EMAIL_REQUIRED / EMAIL_INVALID | Missing or malformed email |
| 429 | OTP_RESEND_COOLDOWN | Called again within 30 s; details.retryInSeconds set |
| 429 | EMAIL_RATE_LIMITED / IP_RATE_LIMITED | Abuse caps hit |
| 409 | USER_ALREADY_HAS_ORGANIZATION | Switch to /cliRequestLoginOtp |
POST /cliCompleteSignup
When to use
Second half of signup. Consumes the emailed code, creates the Firebase Auth user (if missing), creates ausers/{uid} doc, creates a single-owner organization, optionally uploads the logo to GCS, mints a cko_ API key with full presentation scopes, and returns the raw key.
Endpoint
Request body
| Field | Required | Notes |
|---|---|---|
email, code | yes | 6-digit numeric code from the OTP email |
company.name | no | Defaults to "My Organization". Max 100 chars |
company.description, company.tone | no | Free text |
company.brandPrimary / brandSecondary / brandAccent | no | 6-digit hex, with or without leading # |
logo.data | no | Base64-encoded bytes, max 2 MB decoded |
logo.contentType | no | image/png, image/jpeg, image/webp, image/svg+xml |
apiKey.name | no | Defaults to "CLI default key" |
apiKey.expiresInDays | no | 1–365; omit for no expiration |
Response (200)
apiKey.raw is only returned in this single response — the server stores a SHA-256 hash.
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | EMAIL_REQUIRED / OTP_INVALID | Missing/malformed inputs |
| 400 | COMPANY_NAME_TOO_LONG / BRAND_COLOR_INVALID | Optional fields failed validation |
| 400 | LOGO_TOO_LARGE / LOGO_INVALID_FORMAT / LOGO_DECODE_FAILED | Logo rejected |
| 400 | INVALID_EXPIRES_IN_DAYS | apiKey.expiresInDays outside 1–365 |
| 404 | OTP_NOT_FOUND | No pending code; run /cliRequestSignupOtp first |
| 410 | OTP_EXPIRED | > 10 min old |
| 409 | OTP_ALREADY_USED / OTP_PURPOSE_MISMATCH | Request a new signup code |
| 429 | OTP_LOCKED_OUT | 5 failed attempts; request a fresh code |
| 409 | USER_ALREADY_HAS_ORGANIZATION | Happened in the race window; switch to login |
| 500 | INTERNAL | Retry; contact support if persistent |
POST /cliRequestLoginOtp
When to use
Existing user on a new machine (or with a revoked key). Pre-flight check: if the email has no Firebase Auth user, returnsUSER_NOT_FOUND; if it exists but has no organization, returns USER_HAS_NO_ORGANIZATION. Both push the client back to /cliRequestSignupOtp.
Endpoint
Request body
Response (200)
Same shape as/cliRequestSignupOtp:
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | EMAIL_REQUIRED / EMAIL_INVALID | Missing or malformed |
| 429 | OTP_RESEND_COOLDOWN / EMAIL_RATE_LIMITED / IP_RATE_LIMITED | Rate limits |
| 404 | USER_NOT_FOUND | No account; switch to signup |
| 409 | USER_HAS_NO_ORGANIZATION | Switch to signup |
POST /cliCompleteLogin
When to use
Consume a login OTP and mint a freshcko_ key for the account’s existing organization.
Endpoint
Request body
apiKey is optional (same defaults as /cliCompleteSignup).
Response (200)
Errors
| Status | Code | Meaning |
|---|---|---|
| 400 | EMAIL_REQUIRED / OTP_INVALID | Missing/malformed inputs |
| 400 | INVALID_EXPIRES_IN_DAYS | apiKey.expiresInDays outside 1–365 |
| 404 | OTP_NOT_FOUND | Run /cliRequestLoginOtp first |
| 410 | OTP_EXPIRED | > 10 min old |
| 409 | OTP_ALREADY_USED / OTP_PURPOSE_MISMATCH | Request a new login code |
| 429 | OTP_LOCKED_OUT / MAX_API_KEYS_REACHED | Revoke unused keys from the dashboard |
| 409 | USER_HAS_NO_ORGANIZATION | Happens if org was deleted between request + complete |
| 500 | INTERNAL | Retry |
Agent recipe
For a script or Claude Code skill: run the signup path optimistically; if the server says the user already has an organization, retry via login. This handles every case in two or three HTTP calls.setup-slideless marketplace skill encodes this logic.
See also
cli/auth— the CLI wrapper around these four endpoints.- Authentication — how the resulting
cko_key is used on subsequent calls. concepts/api-keys— lifecycle and security model forcko_keys.