Viktor Public API | Viktor Docs

Documentation

Learn how to set up and get the most out of Viktor.

Use the Viktor Public API to give Viktor work from your own applications and automations. The API is asynchronous: create a thread with a message, track the resulting run, then fetch the final result.

Quickstart

1. Create an API key

Open Settings → API Keys, select Generate Key, and grant only the scopes your integration needs. Personal keys act as you. Team keys are for shared automations and can be created by team admins.

The secret is shown once. Store it in a secret manager and never put it in source control or client-side code. Keys begin with zt_live_sk_.

2. Check the key

Send the key as a Bearer token. The x-api-key header is also supported.

Copy

export VIKTOR_API_KEY="zt_live_sk_..."

curl "https://api.viktor.com/api/public/v1/me" \
  -H "Authorization: Bearer $VIKTOR_API_KEY"

GET /api/public/v1/me returns the key type, granted scopes, the workspace's rate-limit tier, and the active rate-limit policies scaled to that tier. It requires no scope, so it is the best first request after creating or rotating a key.

3. Give Viktor a task

Creating a thread also queues its first agent run. Use an idempotency key so a retry cannot start the same task twice.

Copy

curl -X POST "https://api.viktor.com/api/public/v1/threads" \
  -H "Authorization: Bearer $VIKTOR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: monthly-revenue-2026-07" \
  -d '{
    "message": "Analyze this month'\'s revenue and summarize the biggest changes.",
    "speed": "smarter"
  }'

The API returns 202 Accepted with thread.id, message.id, and run.id. Save the run ID.

speed selects the agent configuration: smarter (the default) maximizes quality, while faster trades some depth for lower latency on simple tasks.

4. Wait for the result

Poll the run until its status is terminal: completed, requires_action, failed, cancelled, or timed_out.

requires_action means Viktor finished the run by asking you for something — missing input, a decision, or an approval. The result is available like a completed run: read it, then answer by sending a follow-up message on the same thread.

Copy

curl "https://api.viktor.com/api/public/v1/runs/<run_id>" \
  -H "Authorization: Bearer $VIKTOR_API_KEY"

When result.available is true, fetch the final result:

Copy

curl "https://api.viktor.com/api/public/v1/runs/<run_id>/result" \
  -H "Authorization: Bearer $VIKTOR_API_KEY"

Results can contain Markdown, structured JSON, and file artifacts. To continue the same conversation, send another message to the existing thread.

Copy

curl -X POST "https://api.viktor.com/api/public/v1/threads/<thread_id>/messages" \
  -H "Authorization: Bearer $VIKTOR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: monthly-revenue-follow-up-1" \
  -d '{"message":"Turn the analysis into a one-page executive brief."}'

Endpoint reference

Method Endpoint Purpose Required scopes
GET /api/public/v1/me Identify the key and inspect scopes and rate limits None
GET /api/public/v1/test Lightweight authentication check None
POST /api/public/v1/threads Create a thread and start a run threads:create, runs:create
GET /api/public/v1/threads List threads threads:read
GET /api/public/v1/threads/{thread_id} Get a thread threads:read
POST /api/public/v1/threads/{thread_id}/messages Continue a thread and start a run messages:create, runs:create
GET /api/public/v1/threads/{thread_id}/messages List user-visible messages messages:read
GET /api/public/v1/threads/{thread_id}/runs List runs in a thread runs:read
GET /api/public/v1/runs/{run_id} Get run status runs:read
GET /api/public/v1/runs/{run_id}/result Get a terminal run result runs:read
POST /api/public/v1/runs/{run_id}/cancel Cancel an in-flight run runs:create
POST /api/public/v1/scripts/runs Run a Python script directly in the workspace sandbox scripts:execute
GET /api/public/v1/scripts/runs/{script_run_id} Get a script run's status and result scripts:execute
POST /api/public/v1/scripts/runs/{script_run_id}/cancel Cancel a queued script run scripts:execute
GET /api/public/v1/integrations List connected integrations and their SDK modules integrations:read
GET /api/public/v1/integrations/{sdk_module}/tools List an integration's SDK tools with schemas and snippets integrations:read
GET /api/public/v1/files/{file_token}/download-url Get a short-lived artifact download URL files:read
GET /api/public/v1/credits Get the workspace credit balance and runout forecast usage:read
GET /api/public/v1/usage/summary Get windowed credit usage totals by spend bucket usage:read
GET /api/public/v1/usage/daily Get per-day credit usage for a window usage:read
GET /api/public/v1/audit/events Pull workspace audit events, cursor-paginated (team keys only) audit:read

The full interactive reference — every parameter, schema, response body, and a downloadable spec for generated clients — lives in the dashboard under Settings → API Docs.

Scopes

A key with no scopes can only call /me and /test. Scope changes take effect on new requests immediately.

Script runs

Sometimes your own code is the orchestrator and you do not want an agent. Script runs execute one Python script directly in your workspace sandbox. Your script keeps full control of the logic, and Viktor supplies the sandbox, the connected integrations, and the file plumbing.

The script runs as the key owner's identity. It sees the same workspace files the same person sees in chat, and its integration calls pass the same permission checks.

Discover what a script can call

List the connected integrations your key can use and the SDK module each one is imported from:

Copy

curl "https://api.viktor.com/api/public/v1/integrations" \
  -H "Authorization: Bearer $VIKTOR_API_KEY"

Copy

{
  "integrations": [\
    { "sdk_module": "mcp_linear", "name": "Linear", "kind": "mcp", "account_label": null, "tool_count": 12 },\
    { "sdk_module": "pd_gmail", "name": "Gmail", "kind": "pipedream", "account_label": "ops@example.com", "tool_count": 31 }\
  ]
}

Then list one integration's tools. Every tool comes back exactly as the sandbox SDK exposes it: the function name, the parameters with types and required flags, whether a call needs human approval, and a runnable snippet you can submit to POST /scripts/runs as-is:

Copy

curl "https://api.viktor.com/api/public/v1/integrations/mcp_linear/tools" \
  -H "Authorization: Bearer $VIKTOR_API_KEY"

Copy

{
  "name": "linear_create_issue",
  "sdk_module": "mcp_linear",
  "description": "Create a Linear issue.",
  "requires_approval": false,
  "parameters": [\
    { "name": "title", "type": "str", "required": true, "description": "Issue title", "default": null }\
  ],
  "snippet": "import asyncio\n\nfrom sdk.tools.mcp_linear import linear_create_issue\n\n\nasync def main() -> None:\n    result = await linear_create_issue(\n        title=...,  # str\n    )\n    print(result)\n\n\nasyncio.run(main())\n"
}

The discovery set matches execution: a personal key sees the connections its owner can use in chat, and a team key without a chat identity does not see access-restricted connections. Built-in modules (default_tools, utils_tools, ...) vary per workspace; a script can list /work/sdk/tools/ to discover them in place.

Start a run

Start a run with the script source inline. Short scripts can return their result in one round trip with wait_seconds:

Copy

curl -X POST "https://api.viktor.com/api/public/v1/scripts/runs" \
  -H "Authorization: Bearer $VIKTOR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: sync-crm-2026-08-28" \
  -d '{
    "code": "print(\"hello from the sandbox\")",
    "timeout_seconds": 120,
    "wait_seconds": 30
  }'

The response is the run object. While the run is queued or in_progress, poll it:

Copy

curl "https://api.viktor.com/api/public/v1/scripts/runs/<script_run_id>" \
  -H "Authorization: Bearer $VIKTOR_API_KEY"

A terminal run carries the result: the exit code, captured stdout and stderr, and artifacts for output files. Statuses are queued, in_progress, completed, failed, cancelled, and timed_out.

Rules and limits:

Workspace usage and credits

The usage endpoints report workspace-wide spend in credits — the same numbers, buckets, and cron attribution as the dashboard's Usage page. Because that data covers the whole workspace, visibility follows the dashboard's admin rule: team keys with usage:read qualify as-is (only workspace admins can create them or edit their scopes), while a personal key also requires its owner to currently be a workspace admin — otherwise requests fail with 403 admin_role_required.

Windows are selected with either a named period (today, last_7_days, last_30_days, this_month, last_month, current_billing_period — the default — or previous_billing_period) or an explicit start_time/end_time pair of at most 366 days. Days are bucketed in UTC unless you pass an IANA timezone.

GET /credits needs no window: it returns the current balance across all credit pools, the billing period, and a burn-rate runout forecast — useful to check before dispatching a batch of runs instead of discovering an empty balance through 402 errors.

Structured output

Set response_format to a JSON Schema when your application needs a predictable object instead of Markdown. Viktor returns the validated value in the result's json field. The complete response_format schema is in the API reference.

Pagination

Thread and run lists use keyset pagination. Pass the last item's ID as starting_after. Message lists use after.

Files

Run results return file artifacts as tokens, not permanent public URLs. Exchange an artifact's id at /api/public/v1/files/{file_token}/download-url. The returned URL is served from Viktor's API domain, requires no further authentication, and is valid for about 15 minutes.

Rate limits and retries

Limits apply per route and per key owner: all personal keys of the same user share one quota, and all team keys of the same team share one quota, so creating additional keys does not increase throughput.

Limits scale with your workspace plan. Every workspace resolves to a rate-limit tier — free, entry, mid, high, or enterprise — and higher tiers get proportionally higher limits on every route. Read your tier and the exact values that apply to your keys from GET /api/public/v1/me (rate_limit_tier and limits); a plan change takes effect within a few minutes. Upgrading your plan is the supported way to raise your limits.

A 429 response includes Retry-After and X-RateLimit-* headers, plus a plan_tier field in the error detail. Wait for the advertised interval before retrying.

Use Idempotency-Key on requests that start runs. Reusing the same key and request returns the originally accepted operation instead of spending credits on a duplicate.

Errors

Errors use one JSON envelope:

Copy

{
  "detail": {
    "error": "machine_readable_code",
    "message": "Human-readable explanation"
  }
}

Some errors add fields such as scope or retry_after_seconds. Treat the machine-readable error value as stable application logic and the message as display text.