API Introduction

API Introduction

The Seclai REST API allows you to programmatically create and manage AI agents, knowledge bases, sources, solutions, runs, alerts, and AI-assisted configuration. This page provides an overview of how to get started with the API.

Base URL

All API requests should be made to:

https://api.seclai.com/

Authentication

The Seclai API supports two authentication methods: OAuth (recommended) and API keys.

OAuth uses your Seclai account credentials via Cognito. Include a Bearer token in the Authorization header:

Authorization: Bearer YOUR_ACCESS_TOKEN

OAuth tokens are obtained through the standard OAuth 2.0 Authorization Code flow with PKCE. MCP clients handle this automatically — just enter the server URL and sign in when prompted.

To target a different organization's account, include the X-Account-Id header:

Authorization: Bearer YOUR_ACCESS_TOKEN
X-Account-Id: ORGANIZATION_ACCOUNT_ID

API Key Authentication

API keys are best suited for server-to-server integrations. Include your API key in the X-API-Key header:

X-API-Key: YOUR_API_KEY

Get Your API Key

  1. Log in to your Seclai account
  2. Navigate to API Keys in the left sidebar
  3. Click "Create API Key"
  4. Copy your key and store it securely right away. The key will not be shown again.

Important: Treat your API keys like passwords. Never share them or commit them to version control. Each user should create their own key.

Calling from a Browser

You can't. The API sends no CORS headers, and neither credential type can be narrowed for a browser: there are no per-resource or read-only scopes, so a leaked key or token carries every permission its identity holds. Browser-facing apps call their own backend, which calls Seclai. See Calling from a Browser for the reasoning and the proxy checklist.

Making Requests

Example Request

curl https://api.seclai.com/agents \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json"

Request Headers

HeaderRequiredDescription
Authorization*OAuth Bearer token: Bearer YOUR_ACCESS_TOKEN
X-API-Key*Your API key (alternative to OAuth)
X-Account-IdNoTarget a different organization account (OAuth only)
Seclai-VersionNoOpt into dated API changes — see API Versioning
Content-TypeYesSet to application/json for POST/PUT requests

* One of Authorization or X-API-Key is required.

Response Format

All responses are returned as JSON. Successful responses will have a 200 status code and include the requested data:

{
  "id": "agent_abc123",
  "name": "My Agent",
  "status": "active",
  "created_at": "2026-01-12T10:00:00Z"
}

Error Responses

Error responses carry an error object with a machine-readable code and a human-readable message (plus occasional extra fields, e.g. retry_after, callers):

{
  "error": {
    "code": "agent_in_use",
    "message": "This agent is still called by other live agents."
  }
}

Request-validation failures (a missing or malformed body field, a bad query-parameter type, or an unknown query parameter) historically returned a different { "detail": [...] } envelope. On Seclai-Version: 2026-07-27 or later they are unified into the same error envelope, with the per-field breakdown under details:

{
  "error": {
    "code": "validation_error",
    "message": "field required",
    "details": [
      { "type": "missing", "loc": ["body", "name"], "msg": "field required" }
    ]
  }
}

Clients on an older version keep the { "detail": [...] } shape for validation errors. One exception on every version: a malformed Seclai-Version header is rejected with { "detail": [...] } (it is rejected before a version can be resolved, so the response cannot be version-gated). Always branch on HTTP status first; then parse error (or, for validation errors on a legacy version, detail).

API Versioning

The API evolves without breaking existing integrations by making backward-incompatible changes opt-in through a dated version. The version a request runs under is resolved in this order:

  1. Seclai-Version request header (if present) — overrides everything for that one request, so you can test a new version on demand:

    curl https://api.seclai.com/agents \
      -H "X-API-Key: YOUR_API_KEY" \
      -H "Seclai-Version: 2026-07-27"
    
  2. Your account's pinned version — a sticky default for every request you make without the header. New accounts are pinned to the latest version at signup; accounts that predate a change stay on their existing version, so a release never changes behavior under you.

  3. The baseline — if your account is unpinned, requests fall back to a stable legacy baseline.

Sending (or pinning) a date opts into every change released on or before it. Every response echoes the version it was served with in the Seclai-Version response header; a malformed value (not YYYY-MM-DD) returns 422. The published OpenAPI spec carries an x-seclai-versions object listing the default, latest, and all known versions.

Managing your account's version

GET /version reports your pinned version, the version the current request resolved to, and the known versions. An account owner or admin can pin (or clear) it with PUT /version:

# Pin the whole account to a version (applies to every future request)
curl -X PUT https://api.seclai.com/version \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"version": "2026-07-27"}'

# Clear the pin (revert to the baseline)
curl -X PUT https://api.seclai.com/version \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"version": null}'

Changes by version

VersionWhat it changes
2026-07-27Unknown query parameters are rejected with 422 instead of silently ignored. Every list endpoint returns the canonical { data, pagination } envelope (see Pagination) — previously some returned a bare array, a flat { data, total, page, limit }, a { configs, total }, or another per-resource key. Request-validation errors use the unified { error: { code, message, details } } envelope instead of { detail: [...] }. GET /agents/evaluation-results/non-manual-summary also gains an agent_id scoping parameter.
2026-08-03Memory banks: the retired compaction threshold max_age_days is rejected with 400 and reads back as null — 0 stays accepted, since it is the sentinel that clears a value stored earlier. An omitted retention_days on create resolves per bank type — 90 days for a conversation bank, indefinite for a general bank — instead of a flat 30 days. An explicit retention_days (including null) is honoured on every version. See Memory Banks.
2026-08-21POST /sources rejects a dimensions value the chosen embedding_model does not support, with 400 and the allowed values, instead of storing it. Such a source could never be indexed — every later ingest failed with "No embedding model for dimensions N", far from the request that caused it. GET /models/embedders reports the supported dimensions per embedder. Omitting dimensions still uses the default on every version. See Embedding Models.
2026-09-28Agent definitions use explicit file lists. On POST /agents, PUT /agents/{id} and PUT /agents/{id}/definition: an omitted attachments keeps the step's stored list instead of resetting it; [] on send_email, webhook_call, write_aws_s3_object or call_agent means no files instead of every input file; and {{attachments…}} in a list means the step's input files. A new outbound step with no list is saved with ["{{attachments}}"] and a warning. Earlier versions keep the old meanings: their writes are converted before saving. See Attachments.
2026-09-30A run's output is its text. On GET /agents/runs/{run_id}, the run list, run, cancel and the stream's init and done events, output on the run and on each step (and a step's input) is the text, and output_content_type describes that text. Files are in attachments, which every version returns. Earlier versions are still sent the JSON envelope {schema, text, attachments: [{storage_key, mime, name, label, bytes}]} with content type application/vnd.seclai.manifest+json whenever the output has files (bytes can be null on runs made before this version shipped; for an output with files and no text, text is null, or "" on a display_result or call_agent step; a for_each step's output is its array on every version). See Downloading attachments.
2026-10-03New steps follow what they reference. On POST /agents, PUT /agents/{id} and PUT /agents/{id}/definition: a new prompt_call, extract_data or evaluate_step written without attachments receives the files of each source its templates reference, where {{input}} brings the files of the step feeding it (the trigger uploads on a first step) instead of the trigger uploads everywhere; and a new retrieval step's matched media are its files unless attach_matched_media is false. Earlier versions keep the old defaults: a step they add is saved with the explicit list, and with attach_matched_media: false. Steps already stored are unchanged, and a payload that declares export_version follows that version instead. See Attachments.
2026-10-05A source item held for governance review reports completed. On GET /sources/{id}/contents and GET /sources/{id}/contents/{content_version_id}, an item an Enforce governance policy blocked at import has held_for_review: true, a null source_connection_content_version_id, and content_status: completed. Earlier versions report the same item as failed, with the hold as the error, so a poller waiting for an upload to finish stops. The item is held on every version; only how its status is reported differs, and held_for_review is returned on every version. The status filter matches a held item under completed on every version. See Held Source Content.
2026-10-13media_types is validated on POST /sources and PUT /sources/{id}. A kind other than images or video, and any kind on a source that is not a website or RSS feed, is rejected with 400. Earlier versions drop an unknown kind, and store a kind sent for another source type, where it has no effect. A source that is not a website or RSS feed reports an empty media_types on every version, and an empty list is not rejected by this check.
2026-10-16An uploaded file of any type is accepted and ready at once, and no file's text is written into input. POST /agents/{id}/upload-input accepts any file type, it and GET /agents/{id}/input-uploads/{upload_id} report ready as soon as the file is stored, and on POST /agents/{id}/runs each upload contributes only its # filename heading to input; steps receive the file itself, or a conversion of it when their model cannot read it. Earlier versions accept only the types the endpoint description lists and write each file's extracted text under its heading: a file is ready when the upload responds unless it is a scanned PDF that needs OCR or a recording that needs a transcript, in which case it reports processing until that text is made; a non-image file with no text reports failed. From this version the agent_config of POST /agents/{id}/ai-assistant/generate-steps can carry an email agent's email_run_on_attachment_mismatch; earlier versions receive null.
2026-10-18An omitted document_images turns document images on for a text-only embedding model. On POST /sources, a source that is not a website or RSS feed, created without document_images on an embedding model that does not index images, stores it as true, so the images inside its PDF, DOCX, PPTX and XLSX files are indexed by the text in them and billed. Earlier versions store it as false there.
2026-10-19polling_max_items is rejected on a website, custom index or cloud drive source. On POST /sources, polling_max_items on one of those source types is rejected with 400. Only an RSS feed's scheduled polls read it. Earlier versions store it there, where it has no effect.
2026-10-21An evaluation result with status error counts as no verdict. GET /agents/evaluation-criteria/{criteria_id}/summary averages only passed and failed results in average_score. GET /agents/evaluation-results/non-manual-summary counts them in error and not in failed, and computes pass_rate and failure_rate over passed and failed results only. POST /agents/{agent_id}/evaluation-criteria/test-draft returns null for score and passed when the evaluator returns no verdict. Earlier versions also average an error result's stored score into average_score, count it as failed and compute the rates over all results, and return a no-verdict test-draft as score 0.5 and passed false. Every version returns the test-draft status and the summary's error count.

Authentication Error Codes

HTTP StatusError CodeMeaning
401API_NOT_AUTHENTICATEDMissing or invalid API key / Bearer token
401USER_NOT_AUTHENTICATEDOAuth Bearer token could not be verified
429RATE_LIMIT_EXCEEDEDToo many requests — see Retry-After header

Pagination

Every list endpoint returns the same envelope: the items under data, and a pagination object. Paginate with page/limit (or limit/offset, depending on the endpoint):

curl "https://api.seclai.com/agents?page=1&limit=20" \
  -H "X-API-Key: YOUR_API_KEY"

Response (on Seclai-Version: 2026-07-27 or later):

{
  "data": [...],
  "pagination": {
    "page": 1,
    "limit": 20,
    "total": 150,
    "pages": 8,
    "has_next": true,
    "has_prev": false
  }
}

Because the shape is uniform, one helper can unwrap data and read pagination.has_next for any list endpoint. Naturally-bounded lists (reference data, relationship lists) return the whole set as a single page (has_next: false). The search endpoints (/search, /docs-search) are the exception — they return ranked results as { "results": [...] } (not a paginated collection). Clients on an older version keep each endpoint's legacy shape until they opt in.

SDKs

We provide official SDKs to make integration easier:

  • Python SDK - pip install seclai
  • JavaScript SDK - npm install @seclai/sdk
  • Go SDK - go get github.com/seclai/seclai-go
  • C# SDK - dotnet add package Seclai
  • CLI Tool - npm install -g @seclai/cli — includes built-in skills for common workflows

Rate Limits

The API enforces rate limits to protect service quality. Limits apply at two levels:

Global Limits

All API requests are subject to a per-IP limit of 120 requests per minute and a per-account limit of 300 requests per minute, regardless of plan.

Plan-Specific Limits

Your plan defines the maximum API requests allowed per minute. When you exceed this limit, the API returns a 429 Too Many Requests response.

Rate Limit Headers

Every successful API response includes rate-limit headers:

HeaderDescription
X-RateLimit-LimitMaximum requests allowed per window
X-RateLimit-RemainingRequests remaining in the current window
X-RateLimit-ResetUnix timestamp when the rate limit window resets

429 Too Many Requests

When rate-limited, the response includes a Retry-After header and a structured error body:

{
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "API request quota exceeded: 60/60 requests per minute. Retry after 45s.",
    "retry_after": 45
  }
}

Best practices:

  • Check X-RateLimit-Remaining to anticipate limits before hitting them.
  • On a 429 response, wait for the Retry-After duration before retrying.
  • Use exponential backoff for transient failures.

Webhooks

Configure webhooks to receive real-time notifications about events in your account:

{
  "event": "agent.run.completed",
  "data": {
    "run_id": "run_xyz789",
    "agent_id": "agent_abc123",
    "status": "completed"
  }
}

Set up webhooks in your account settings.

Next Steps