Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.slideless.ai/llms.txt

Use this file to discover all available pages before exploring further.

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

EndpointPurposeAuth
POST /cliRequestSignupOtpEmail a signup OTP to a brand-new emailPublic (no Authorization header)
POST /cliCompleteSignupVerify the OTP, create user + org + API keyPublic
POST /cliRequestLoginOtpEmail a login OTP to an existing accountPublic
POST /cliCompleteLoginVerify the OTP, mint a fresh API keyPublic
Base URL: https://europe-west1-slideless-ai.cloudfunctions.net/ All four endpoints return the same discriminated-union shape:
{ "success": true,  "data": { ... } }
{ "success": false, "error": { "code": "...", "message": "...", "nextAction": "...", "details": { ... } } }
HTTP status mirrors the error category: 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 -request endpoints reject with OTP_RESEND_COOLDOWN if 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) — returns OTP_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 returns USER_ALREADY_HAS_ORGANIZATION — the client should switch to /cliRequestLoginOtp.

Endpoint

POST https://europe-west1-slideless-ai.cloudfunctions.net/cliRequestSignupOtp

Request body

{ "email": "you@example.com" }
FieldTypeRequiredNotes
emailstringyesLowercased server-side for rate-limit keying

Response (200)

{
  "success": true,
  "data": {
    "email": "you@example.com",
    "expiresInSeconds": 600
  }
}

Examples

curl -sS -X POST \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com"}' \
  https://europe-west1-slideless-ai.cloudfunctions.net/cliRequestSignupOtp

Errors

StatusCodeMeaning
400EMAIL_REQUIRED / EMAIL_INVALIDMissing or malformed email
429OTP_RESEND_COOLDOWNCalled again within 30 s; details.retryInSeconds set
429EMAIL_RATE_LIMITED / IP_RATE_LIMITEDAbuse caps hit
409USER_ALREADY_HAS_ORGANIZATIONSwitch to /cliRequestLoginOtp

POST /cliCompleteSignup

When to use

Second half of signup. Consumes the emailed code, creates the Firebase Auth user (if missing), creates a users/{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

POST https://europe-west1-slideless-ai.cloudfunctions.net/cliCompleteSignup

Request body

{
  "email": "you@example.com",
  "code": "123456",
  "user": {
    "firstName": "Alex",
    "lastName": "Morgan"
  },
  "company": {
    "name": "Acme",
    "description": "We make widgets",
    "brandPrimary": "#0a0a0a",
    "brandSecondary": "#888888",
    "brandAccent": "#e53935",
    "tone": "Pragmatic, concise, a bit dry"
  },
  "logo": {
    "data": "<base64-encoded bytes>",
    "contentType": "image/png"
  },
  "apiKey": {
    "name": "CI",
    "expiresInDays": 30
  }
}
FieldRequiredNotes
email, codeyes6-digit numeric code from the OTP email
user.firstNameyesStored as the user’s display name and used in onboarding emails. Max 60 chars
user.lastNamenoAppended to display name when present. Max 60 chars
company.namenoDefaults to "<user.firstName>'s workspace". Max 100 chars
company.description, company.tonenoFree text
company.brandPrimary / brandSecondary / brandAccentno6-digit hex, with or without leading #
logo.datanoBase64-encoded bytes, max 2 MB decoded
logo.contentTypenoimage/png, image/jpeg, image/webp, image/svg+xml
apiKey.namenoDefaults to "CLI default key"
apiKey.expiresInDaysno1–365; omit for no expiration

Response (200)

{
  "success": true,
  "data": {
    "organizationId": "4XYwOrZ8QMyyELm1310R",
    "organizationName": "Acme",
    "apiKey": {
      "keyId": "019da6...",
      "raw": "cko_O_Q8...",
      "keyPrefix": "cko_O_Q8",
      "name": "CLI default key",
      "scopes": ["presentations:write", "presentations:read"],
      "createdAt": "2026-04-19T14:21:03.000Z"
    },
    "isNewUser": true
  }
}
apiKey.raw is only returned in this single response — the server stores a SHA-256 hash.

Errors

StatusCodeMeaning
400EMAIL_REQUIRED / OTP_INVALIDMissing/malformed inputs
400USER_FIRST_NAME_REQUIREDMissing user.firstName
400USER_NAME_TOO_LONGuser.firstName or user.lastName over 60 chars
400COMPANY_NAME_TOO_LONG / BRAND_COLOR_INVALIDOptional fields failed validation
400LOGO_TOO_LARGE / LOGO_INVALID_FORMAT / LOGO_DECODE_FAILEDLogo rejected
400INVALID_EXPIRES_IN_DAYSapiKey.expiresInDays outside 1–365
404OTP_NOT_FOUNDNo pending code; run /cliRequestSignupOtp first
410OTP_EXPIRED> 10 min old
409OTP_ALREADY_USED / OTP_PURPOSE_MISMATCHRequest a new signup code
429OTP_LOCKED_OUT5 failed attempts; request a fresh code
409USER_ALREADY_HAS_ORGANIZATIONHappened in the race window; switch to login
500INTERNALRetry; 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, returns USER_NOT_FOUND; if it exists but has no organization, returns USER_HAS_NO_ORGANIZATION. Both push the client back to /cliRequestSignupOtp.

Endpoint

POST https://europe-west1-slideless-ai.cloudfunctions.net/cliRequestLoginOtp

Request body

{ "email": "you@example.com" }

Response (200)

Same shape as /cliRequestSignupOtp:
{
  "success": true,
  "data": { "email": "you@example.com", "expiresInSeconds": 600 }
}

Errors

StatusCodeMeaning
400EMAIL_REQUIRED / EMAIL_INVALIDMissing or malformed
429OTP_RESEND_COOLDOWN / EMAIL_RATE_LIMITED / IP_RATE_LIMITEDRate limits
404USER_NOT_FOUNDNo account; switch to signup
409USER_HAS_NO_ORGANIZATIONSwitch to signup

POST /cliCompleteLogin

When to use

Consume a login OTP and mint a fresh cko_ key for the account’s existing organization.

Endpoint

POST https://europe-west1-slideless-ai.cloudfunctions.net/cliCompleteLogin

Request body

{
  "email": "you@example.com",
  "code": "123456",
  "apiKey": { "name": "CI", "expiresInDays": 30 }
}
apiKey is optional (same defaults as /cliCompleteSignup).

Response (200)

{
  "success": true,
  "data": {
    "organizationId": "4XYwOrZ8QMyyELm1310R",
    "organizationName": "Acme",
    "apiKey": {
      "keyId": "019db0...",
      "raw": "cko_9v...",
      "keyPrefix": "cko_9v4k",
      "name": "CLI default key",
      "scopes": ["presentations:write", "presentations:read"],
      "createdAt": "2026-04-19T16:00:00.000Z"
    }
  }
}
Each successful call mints a new key. Previous keys stay valid until revoked from the dashboard.

Errors

StatusCodeMeaning
400EMAIL_REQUIRED / OTP_INVALIDMissing/malformed inputs
400INVALID_EXPIRES_IN_DAYSapiKey.expiresInDays outside 1–365
404OTP_NOT_FOUNDRun /cliRequestLoginOtp first
410OTP_EXPIRED> 10 min old
409OTP_ALREADY_USED / OTP_PURPOSE_MISMATCHRequest a new login code
429OTP_LOCKED_OUT / MAX_API_KEYS_REACHEDRevoke unused keys from the dashboard
409USER_HAS_NO_ORGANIZATIONHappens if org was deleted between request + complete
500INTERNALRetry

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.
# Pseudo-code
RESP=$(curl -sS ... /cliRequestSignupOtp)
CODE=$(echo $RESP | jq -r 'select(.success == false) | .error.code')
if [ "$CODE" = "USER_ALREADY_HAS_ORGANIZATION" ]; then
  curl -sS ... /cliRequestLoginOtp
  # then cliCompleteLogin
else
  # ask user for the 6-digit code, then cliCompleteSignup
fi
The 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 for cko_ keys.