API Reference

Every route Selyf's API actually exposes, re-verified against source: method, auth, rate limits, validation, and real request/response examples.

See Authentication for the OAuth model these routes share, and Errors and Rate Limits for the shared error and throttling contract every route below follows. No example on this page uses a real account, email address, token, or credential.

Authentication

GET/auth/google

Starts the Google OAuth sign-in flow by redirecting to Google's consent screen.

Auth
None
CSRF
Not applicable -- a browser navigation, not a state-changing API call
Rate limit
20 requests / 60s

Response

302 Found -- redirects to accounts.google.com

Errors

  • SERVICE_UNAVAILABLE (503) -- Google OAuth is not configured server-side

Example

Example request
curl -i https://api.selyf.com/auth/google
Example response
HTTP/1.1 302 Found
Location: https://accounts.google.com/o/oauth2/v2/auth?client_id=...&redirect_uri=...&scope=openid+email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fgmail.readonly&state=...
GET/auth/google/callback

Google redirects here after consent. Exchanges the authorization code, establishes a session, and redirects to the web app.

Auth
None
CSRF
The OAuth state parameter is validated (single-use, 10-minute lifetime) -- a separate mechanism from the app's own X-CSRF-Token
Rate limit
20 requests / 60s

Request

Query / params: code (string, from Google), state (string, from Google), error (string, optional)

Validation: An invalid, expired, or mismatched state redirects to the web app with ?error=oauth_failed rather than returning a JSON error -- this endpoint is only ever reached via Google's own redirect, never called directly

Response

302 Found -- redirects to the web app with ?connected=1

Errors

  • Redirects with ?error=oauth_failed on any failure (invalid state, missing code, token exchange failure)
  • SERVICE_UNAVAILABLE (503) -- Google OAuth is not configured server-side

Example

Example request
(Called only by Google's own redirect -- not intended to be called directly.)
Example response
HTTP/1.1 302 Found
Location: https://www.selyf.com/?connected=1
GET/auth/session

Returns whether the current request is authenticated, and if so, the account email and a CSRF token for subsequent requests.

Auth
None required to call -- this is how a client checks whether it is authenticated
CSRF
Not applicable -- read-only
Rate limit
Global default (100 requests / 60s)

Response

200 OK

Response body
{
  "authenticated": true,
  "csrfToken": "<csrf-token>",
  "email": "you@example.com"
}

Example

Example request
curl -i --cookie "<session-cookie>" https://api.selyf.com/auth/session
Example response
{
  "authenticated": true,
  "csrfToken": "<csrf-token>",
  "email": "you@example.com"
}
POST/auth/logout

Ends the current session.

Auth
None required -- a request with no session simply has nothing to clear
CSRF
Not required
Rate limit
Global default (100 requests / 60s)

Request

Body: None

Validation: None

Response

204 No Content

Example

Example request
curl -i -X POST --cookie "<session-cookie>" https://api.selyf.com/auth/logout
Example response
HTTP/1.1 204 No Content
POST/auth/google/disconnect

Revokes Gmail access with Google, cancels the Gmail watch, clears stored credentials, and ends the session.

Auth
Required (session cookie)
CSRF
Required (X-CSRF-Token header)
Rate limit
20 requests / 60s

Request

Body: None

Validation: None beyond auth/CSRF

Response

204 No Content

Errors

  • AUTH_REQUIRED (401) -- no active session
  • CSRF_FAILED (403) -- missing or invalid X-CSRF-Token, or Origin not in the configured web origin
  • SERVICE_UNAVAILABLE (503) -- no database configured

Example

Example request
curl -i -X POST \
  -H "X-CSRF-Token: <csrf-token>" \
  --cookie "<session-cookie>" \
  https://api.selyf.com/auth/google/disconnect
Example response
HTTP/1.1 204 No Content
DELETE/auth/account

Permanently deletes the account and all associated data.

Auth
Required (session cookie)
CSRF
Required (X-CSRF-Token header)
Rate limit
20 requests / 60s

Request

Body: { "confirm": true } -- required, must be the literal boolean true

Validation: VALIDATION_FAILED if the body is missing or confirm is not exactly true

Response

204 No Content

Errors

  • AUTH_REQUIRED (401) -- no active session
  • CSRF_FAILED (403) -- missing or invalid X-CSRF-Token
  • VALIDATION_FAILED (400) -- body is not { "confirm": true }
  • SERVICE_UNAVAILABLE (503) -- no database configured

Example

Example request
curl -i -X DELETE \
  -H "X-CSRF-Token: <csrf-token>" \
  -H "Content-Type: application/json" \
  --cookie "<session-cookie>" \
  -d '{"confirm": true}' \
  https://api.selyf.com/auth/account
Example response
HTTP/1.1 204 No Content

Mail & sync

POST/api/mail/sync

Triggers a bounded Gmail inbox sync for the authenticated account (up to 50 new messages per call).

Auth
Required
CSRF
Required
Rate limit
10 requests / 60s

Request

Body: None

Validation: None beyond auth/CSRF. A sync already in progress for this account returns RATE_LIMITED rather than starting a second, overlapping one.

Response

200 OK

Response body
{
  "messagesSynced": 12,
  "hasMore": false,
  "lastSyncedAt": "2026-08-30T12:00:00.000Z"
}

Errors

  • AUTH_REQUIRED (401)
  • CSRF_FAILED (403)
  • RATE_LIMITED (429) -- a sync is already in progress for this account
  • SYNC_FAILED (502) -- the Gmail API request failed
  • SERVICE_UNAVAILABLE (503) -- no database configured

Example

Example request
curl -i -X POST \
  -H "X-CSRF-Token: <csrf-token>" \
  --cookie "<session-cookie>" \
  https://api.selyf.com/api/mail/sync
Example response
{
  "messagesSynced": 12,
  "hasMore": false,
  "lastSyncedAt": "2026-08-30T12:00:00.000Z"
}
GET/api/mail/sync-state

Returns the last successful sync time without triggering a new sync or spending Gmail API quota.

Auth
Required
CSRF
Not applicable -- read-only
Rate limit
Global default (100 requests / 60s)

Response

200 OK

Response body
{
  "lastSyncedAt": "2026-08-30T12:00:00.000Z"
}

Errors

  • AUTH_REQUIRED (401)
  • SERVICE_UNAVAILABLE (503) -- no database configured

Example

Example request
curl -i --cookie "<session-cookie>" https://api.selyf.com/api/mail/sync-state
Example response
{
  "lastSyncedAt": "2026-08-30T12:00:00.000Z"
}
GET/api/mail/threads

Lists synced threads for the authenticated account, newest first, paginated.

Auth
Required
CSRF
Not applicable -- read-only
Rate limit
Global default (100 requests / 60s)

Request

Query / params: limit (integer, 1-100, default 20), offset (integer, >= 0, default 0)

Validation: VALIDATION_FAILED if limit/offset are out of range or not integers

Response

200 OK

Response body
{
  "threads": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "gmailThreadId": "18c9f2a1b3d4e5f6",
      "subject": "Quarterly report",
      "snippet": "Attached is the draft for review...",
      "lastMessageAt": "2026-08-29T09:15:00.000Z",
      "isUnread": true
    }
  ],
  "total": 143,
  "hasMore": true
}

Errors

  • AUTH_REQUIRED (401)
  • VALIDATION_FAILED (400)
  • SERVICE_UNAVAILABLE (503)

Example

Example request
curl -i --cookie "<session-cookie>" \
  "https://api.selyf.com/api/mail/threads?limit=20&offset=0"
Example response
{
  "threads": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "gmailThreadId": "18c9f2a1b3d4e5f6",
      "subject": "Quarterly report",
      "snippet": "Attached is the draft for review...",
      "lastMessageAt": "2026-08-29T09:15:00.000Z",
      "isUnread": true
    }
  ],
  "total": 143,
  "hasMore": true
}
GET/api/mail/threads/:threadId

Returns one thread's messages, fetching and caching each message's full body from Gmail on demand.

Auth
Required
CSRF
Not applicable -- read-only
Rate limit
Global default (100 requests / 60s)

Request

Query / params: threadId (path parameter, UUID)

Validation: VALIDATION_FAILED if threadId is not a UUID; NOT_FOUND if it does not belong to the authenticated account

Response

200 OK

Response body
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "gmailThreadId": "18c9f2a1b3d4e5f6",
  "subject": "Quarterly report",
  "isUnread": true,
  "messages": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "threadId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "fromName": "Jordan Lee",
      "fromAddress": "jordan@example.com",
      "subject": "Quarterly report",
      "snippet": "Attached is the draft for review...",
      "sentAt": "2026-08-29T09:15:00.000Z",
      "isUnread": true,
      "bodyText": "Hi, attached is the draft for review...",
      "bodyHtml": "<p>Hi, attached is the draft for review...</p>",
      "attachments": []
    }
  ]
}

Errors

  • AUTH_REQUIRED (401)
  • VALIDATION_FAILED (400)
  • NOT_FOUND (404)
  • SERVICE_UNAVAILABLE (503)

Example

Example request
curl -i --cookie "<session-cookie>" \
  https://api.selyf.com/api/mail/threads/3fa85f64-5717-4562-b3fc-2c963f66afa6
Example response
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "gmailThreadId": "18c9f2a1b3d4e5f6",
  "subject": "Quarterly report",
  "isUnread": true,
  "messages": [ ... ]
}

AI analysis & drafting

POST/api/mail/threads/:threadId/analyze

Returns a cached analysis for the thread's latest message, or generates and stores a new one.

Auth
Required
CSRF
Required
Rate limit
20 requests / 60s

Request

Query / params: threadId (path parameter, UUID)

Validation: VALIDATION_FAILED if threadId is not a UUID

Response

200 OK

Response body
{
  "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "messageId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "priority": "IMPORTANT",
  "replyStatus": "NEEDS_REPLY",
  "confidence": 0.86,
  "reason": "Asks a direct question about the attached draft.",
  "suggestedAction": "Review the attachment and reply with feedback.",
  "model": "configured-provider-model",
  "createdAt": "2026-08-29T09:16:02.000Z",
  "updatedAt": "2026-08-29T09:16:02.000Z"
}

Errors

  • AUTH_REQUIRED (401)
  • CSRF_FAILED (403)
  • VALIDATION_FAILED (400)
  • NOT_FOUND (404) -- thread not found, or has no synced message yet
  • AI_BUDGET_EXCEEDED (429) -- this account's daily or monthly AI token budget is exhausted
  • AI_RATE_LIMITED (429) -- the AI provider itself rate-limited the request
  • AI_REQUEST_FAILED (502)
  • SERVICE_UNAVAILABLE (503) -- no database or no AI provider configured

Example

Example request
curl -i -X POST \
  -H "X-CSRF-Token: <csrf-token>" \
  --cookie "<session-cookie>" \
  https://api.selyf.com/api/mail/threads/3fa85f64-5717-4562-b3fc-2c963f66afa6/analyze
Example response
{
  "priority": "IMPORTANT",
  "replyStatus": "NEEDS_REPLY",
  "confidence": 0.86,
  "reason": "Asks a direct question about the attached draft.",
  "suggestedAction": "Review the attachment and reply with feedback.",
  "model": "configured-provider-model",
  "createdAt": "2026-08-29T09:16:02.000Z",
  "updatedAt": "2026-08-29T09:16:02.000Z"
}
POST/api/mail/threads/:threadId/draft

Generates an editable reply draft for the thread.

Auth
Required
CSRF
Required
Rate limit
20 requests / 60s

Request

Query / params: threadId (path parameter, UUID)

Body: Optional JSON: { tone?: "default" | "shorter" | "professional" | "friendly", language?: string (max 60 chars), preferredTone?: string (max 60 chars), signature?: string (max 300 chars) }

Validation: VALIDATION_FAILED if threadId is not a UUID, or the body does not match the schema above

Response

200 OK

Response body
{
  "draft": {
    "subject": "Re: Quarterly report",
    "body": "Hi Jordan,\n\nThanks for sending this over...",
    "language": "English"
  },
  "model": "configured-provider-model"
}

Errors

  • AUTH_REQUIRED (401)
  • CSRF_FAILED (403)
  • VALIDATION_FAILED (400)
  • NOT_FOUND (404) -- thread not found, or has no synced message yet
  • AI_BUDGET_EXCEEDED (429)
  • AI_RATE_LIMITED (429)
  • AI_REQUEST_FAILED (502)
  • SERVICE_UNAVAILABLE (503)

Example

Example request
curl -i -X POST \
  -H "X-CSRF-Token: <csrf-token>" \
  -H "Content-Type: application/json" \
  --cookie "<session-cookie>" \
  -d '{"tone": "professional"}' \
  https://api.selyf.com/api/mail/threads/3fa85f64-5717-4562-b3fc-2c963f66afa6/draft
Example response
{
  "draft": {
    "subject": "Re: Quarterly report",
    "body": "Hi Jordan,\n\nThanks for sending this over...",
    "language": "English"
  },
  "model": "configured-provider-model"
}

Analytics

POST/api/analytics/event

Records a single product-analytics event.

Auth
Required, unless the named event is on a small public allowlist (e.g. page views)
CSRF
Not required -- a forged call here costs nothing and changes no account state beyond an analytics counter
Rate limit
60 requests / 60s

Request

Body: event (string, max 64 chars), properties (optional, validated against a fixed per-event schema), anonymousId (optional UUID, used only when no session exists)

Validation: VALIDATION_FAILED if the body does not match the schema above. An event name the server does not recognize is accepted but silently ignored -- this keeps an older deployed frontend from erroring on a since-removed event.

Response

200 OK

Response body
{ "ok": true }

Errors

  • VALIDATION_FAILED (400)
  • AUTH_REQUIRED (401) -- a non-public event with no session

Example

Example request
curl -i -X POST \
  -H "Content-Type: application/json" \
  -d '{"event": "page_view"}' \
  https://api.selyf.com/api/analytics/event
Example response
{ "ok": true }

Status

GET/api/status/incidents

Public incident and maintenance feed, consumed by status.selyf.com.

Auth
None
CSRF
Not applicable
Rate limit
Global default (100 requests / 60s)

Response

200 OK

Response body
{
  "incidents": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "kind": "incident",
      "state": "resolved",
      "title": "Elevated API latency",
      "message": "Investigated and resolved; no data was affected.",
      "affectedComponents": ["api"],
      "startsAt": "2026-08-20T10:00:00.000Z",
      "resolvedAt": "2026-08-20T10:45:00.000Z"
    }
  ]
}

Errors

  • SERVICE_UNAVAILABLE (503) -- no database configured

Example

Example request
curl -i https://api.selyf.com/api/status/incidents
Example response
{
  "incidents": [
    {
      "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
      "kind": "incident",
      "state": "resolved",
      "title": "Elevated API latency",
      "message": "Investigated and resolved; no data was affected.",
      "affectedComponents": ["api"],
      "startsAt": "2026-08-20T10:00:00.000Z",
      "resolvedAt": "2026-08-20T10:45:00.000Z"
    }
  ]
}

System

GET/health

Liveness/readiness probe reporting database, Gmail, and AI-provider reachability.

Auth
None -- reachable by monitoring infrastructure with no session, from any origin
CSRF
Not applicable
Rate limit
Global default (100 requests / 60s)

Response

200 OK (or 503 if the database is unreachable -- the body still describes every component either way)

Response body
{
  "status": "ok",
  "service": "mail-ai-api",
  "time": "2026-08-30T12:00:00.000Z",
  "database": "ok",
  "gmail": { "state": "operational", "detail": "..." },
  "ai": { "state": "operational", "detail": "..." }
}

Example

Example request
curl -i https://api.selyf.com/health
Example response
{
  "status": "ok",
  "service": "mail-ai-api",
  "time": "2026-08-30T12:00:00.000Z",
  "database": "ok",
  "gmail": { "state": "operational", "detail": "..." },
  "ai": { "state": "operational", "detail": "..." }
}
GET/

A human-facing HTML page -- not a JSON endpoint -- showing the same live status as /health, for anyone who lands on the bare API domain in a browser.

Auth
None
CSRF
Not applicable
Rate limit
Global default (100 requests / 60s)

Response

200 OK -- text/html

Example

Example request
curl -i https://api.selyf.com/
Example response
(An HTML page. See it directly at https://api.selyf.com/.)