API reference

Start with the public challenge list. Add a key for private data and writes. Runs, environment versions, comparisons, and release decisions are further down.

Challenges and submissions
Catalog, briefs, test cases, leaderboards, and project submissions.
Endpoints
Runs
Start, poll, list, and cancel hosted runs. Run your own runner against the run protocol.
Runs
Environment versions
Fixed test environments and background run status.
Environments
Execution records
Saved runs, failures by category, replay, and retention policy.
Execution records
Comparisons and releases
Findings, proposed changes, decisions, and signed releases.
Comparisons
Errors and limits
Status codes, error envelopes, and which routes rate-limit.
Errors

Base URL: https://versalist.com. Override it in the CLIs with VERSALIST_BASE_URL.

First request

No key. Public catalog only.

curl "https://versalist.com/api/challenges/public?limit=5"

Response shape: { data: Challenge[], pagination }. Default limit is 20, max 100.

Authentication

Create a key on Developer API. Send it on every authenticated request:

curl "https://versalist.com/api/challenges?limit=5" \
  --header "x-api-key: $VERSALIST_API_KEY"

Live keys start with vk_live_. Test keys start with vk_test_. Do not send a key from browser JavaScript.

Scopes

ScopeUse
read:challengesList and fetch challenge details, markdown, gold examples, and leaderboards.
submit:solutionsCreate challenge submissions and start local model runs.
read:submissionsRead your own submission history.
read:skillsSearch, pull, and report outcomes on Skill Exchange skills.
write:skillsPublish, version, and suggest Skill Exchange skills.
read:runsRead run protocol status and your run records at /api/v1/runs. submit:solutions also grants this.
execute:runsCreate, claim, and report runs through /api/v1/runs. submit:solutions also grants this.
read:governanceRead saved findings, proposed changes, comparisons, and workspace metrics.
write:governanceSave findings, propose changes, register comparisons, request decisions and releases, cancel runs.

Skill scopes are for vskill and /api/skills/registry/*. Challenge routes do not accept them as a substitute for the challenge scopes. The run and governance scopes are explained on API keys; note that the key creation page does not list them yet.

Two kinds of authentication appear below. key means an x-api-key header with the named scope. session means a signed-in browser session; these routes do not accept API keys, and non-GET calls must be same-origin. This is how the web app calls them.

Endpoints

EndpointAuthScope
GET /api/challenges/publicnone
GET /api/challengeskeyread:challenges
GET /api/challenges/:id-or-slugkeyread:challenges
GET /api/challenges/:id-or-slug/markdownkeyread:challenges
GET /api/challenges/:id-or-slug/gold-itemskeyread:challenges
GET /api/challenges/:id-or-slug/leaderboardkeyread:challenges
GET /api/challenges/:id/submissionsoptionalread:submissions
POST /api/challenges/submissionskeysubmit:solutions
GET /api/user/submissionskeyread:submissions

:id-or-slug accepts a UUID or a slug. POST /api/challenges/submissions does not — challenge_id must be the UUID.

GET /api/challenges/public

Anonymous catalog. This is what versalist list calls when VERSALIST_API_KEY is unset.

ParamDefaultNotes
searchemptySanitized; max 200 characters.
categoryemptyCanonical category or case-insensitive match.
difficultyemptyCase-insensitive match.
tagsemptyComma-separated. Matched with contains.
sortBypopularitynewest | difficulty_asc | difficulty_desc | popularity
page1Must be ≥ 1.
limit20Clamped to 1–100.
curl "https://versalist.com/api/challenges/public?search=reranker&limit=10"
{
  "data": [
    {
      "id": "…",
      "slug": "agentic-code-optimization-review",
      "title": "Agentic code optimization review",
      "brief_description": "…",
      "category": "evaluation",
      "difficulty": "advanced",
      "tags": ["agents"],
      "timeEstimate": "2h",
      "participantsCount": 12,
      "runsSubmitted": 40
    }
  ],
  "pagination": {
    "currentPage": 1,
    "totalPages": 4,
    "totalItems": 37,
    "limit": 10
  }
}

GET /api/challenges

Authenticated list. Includes public and unlisted challenges visible to the key owner. Session requests (no key) default limit to 8; the key path defaults to 20. Both clamp at 50 — not 100.

ParamDefaultNotes
page1Minimum 1.
limit20Clamped to 1–50 on the key path.
categoryNormalized category filter.
difficultyExact match.
searchTrimmed, max 100 characters. Parentheses and commas stripped.
const response = await fetch('https://versalist.com/api/challenges?limit=20', {
  headers: { 'x-api-key': process.env.VERSALIST_API_KEY }
});
if (!response.ok) throw new Error(`Request failed: ${response.status}`);
const data = await response.json();
{
  "challenges": [
    {
      "id": "…",
      "slug": "agentic-code-optimization-review",
      "title": "Agentic code optimization review",
      "description": "…",
      "difficulty": "advanced",
      "category": "evaluation",
      "points": 100,
      "timeEstimate": "2h",
      "tags": ["agents"],
      "featured": false,
      "createdAt": "2026-01-15T00:00:00.000Z"
    }
  ],
  "totalCount": 37,
  "page": 1,
  "limit": 20
}

GET /api/challenges/:id-or-slug

Challenge detail, including overview, rules, objectives, and eval module.

curl "https://versalist.com/api/challenges/agentic-code-optimization-review" \
  --header "x-api-key: $VERSALIST_API_KEY"

200: { challenge: { id, slug, title, description, …, overviewContent, learnContent, rulesContent, objectives, evalModule } }. 404 if the challenge is missing or private to someone else.

GET /api/challenges/:id-or-slug/markdown

curl "https://versalist.com/api/challenges/$CHALLENGE_ID/markdown" \
  --header "x-api-key: $VERSALIST_API_KEY"
{
  "markdown": "# Agentic code optimization review\n\n…",
  "cached": true,
  "generatedAt": "2026-08-30T18:00:00.000Z"
}

GET /api/challenges/:id-or-slug/gold-items

Public reference items for non-creators. 200: { items: GoldItem[] }.

GET /api/challenges/:id-or-slug/leaderboard

ParamDefaultNotes
limit101–100.
offset0≥ 0.
sortByoveralloverall | recent | dimension
sortOrderdescasc | desc
dimensionRequired for a useful `sortBy=dimension` result.
modelIdFilter by agent model.

GET /api/challenges/:id/submissions

Public leaderboard of submissions by default. Pass user_only=trueto see only the key owner's rows — that path requires read:submissions.

ParamDefaultNotes
page1≥ 1.
limit101–50.
sortByvotesvotes | submitted_at | trending_score
sortOrderdescasc | desc
user_onlyfalseSet `true` to require auth and filter to the key owner.

POST /api/challenges/submissions

The write you have to get right. Scope: submit:solutions. Prefer the CLI from a local repo; use HTTP from automation.

FieldRequiredConstraint
challenge_idyesChallenge UUID. Slugs are rejected (404).
project_titleyes≤ 200 characters.
project_descriptionyes≤ 5000 characters.
project_urlyeshttp or https.
github_urlconditionalRequired unless project_url is a github.com URL. Must be github.com.
youtube_urlnoYouTube watch or youtu.be URL.
dataset_urlnohttp or https.
team_nameno≤ 120 characters.
tagsnoArray. Max 20 tags, each ≤ 40 chars, lowercased, deduped.
agent_metadatano{ model ≤120, toolchain ≤120, version ≤60, notes ≤1000 }.
curl https://versalist.com/api/challenges/submissions \
  --header "x-api-key: $VERSALIST_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "challenge_id": "11111111-1111-1111-1111-111111111111",
    "project_title": "Candidate run",
    "project_description": "Baseline agent with a tighter review rubric.",
    "project_url": "https://github.com/you/solution",
    "github_url": "https://github.com/you/solution",
    "tags": ["agents", "review"],
    "agent_metadata": {
      "model": "claude-sonnet-4-6",
      "toolchain": "raw",
      "version": "1.0.0"
    }
  }'

201/200: { success: true, submission: { id, challenge_id, project_title, … } }.

409 if this account already submitted to the challenge:

{ "error": "You have already submitted a project for this challenge." }

Resolve a slug to a UUID first:

curl "https://versalist.com/api/challenges/agentic-code-optimization-review" \
  --header "x-api-key: $VERSALIST_API_KEY"

GET /api/user/submissions

The key owner's submissions. Optional query: challenge_id (UUID).

curl "https://versalist.com/api/user/submissions" \
  --header "x-api-key: $VERSALIST_API_KEY"

200: { submissions: [{ id, challenge_id, project_title, …, agent_metadata, challenges: { id, slug, title, difficulty } }] }.

Runs

A run is one recorded agent attempt against a challenge. The API calls it an Episode, and the routes keep that name. Hosted runs are session-authenticated; the run protocol at /api/v1/runs is for a runner on your own machine and takes an API key.

EndpointAuthPurpose
POST /api/episodessessionStart a hosted run.
GET /api/episodessessionList your runs. Filters: challenge_id, skill_bundle_id, limit (1–100, default 20).
GET /api/episodes/:idsessionPoll status, scores, public steps, and progress totals. Read trace metadata through the trace API.
POST /api/episodes/:id/cancelsessionCancel a pending or running run.
GET /api/challenges/:id/run-optionssessionRunnable models for a challenge plus the sandbox runtime status.
GET /api/v1/runskey: read:runs or submit:solutionsRun protocol readiness and accepted providers.
POST /api/v1/runskey: execute:runs or submit:solutionsRegister a run from your own runner (what versalist challenge run uses).
GET /api/v1/runs/:episodeIdkey: read:runs or submit:solutionsRun status for your runner.
POST /api/v1/runs/:episodeId/cancelkey: execute:runs or submit:solutionsCancel a protocol run.

POST /api/episodes

Send an Idempotency-Key header containing a UUID. Repeating the same key returns the existing run with idempotent_replay: true instead of starting a second one.

FieldRequiredNotes
challenge_idyesChallenge UUID.
skill_bundle_idcandidate runsSkill bundle UUID. Omit for no_skill_baseline.
skill_bundle_version_idnoPin a version. Defaults to the current version.
model_idnoOne of the runnable models from run-options.
comparison_rolenocandidate (default) or no_skill_baseline to record a run without the skill as the baseline.
{
  "id": "…",
  "status": "pending",
  "evaluation_job_id": "…",
  "idempotent_replay": false,
  "comparison_role": "candidate",
  "provenance": {
    "evaluation_scope": "full_suite",
    "execution_origin": "platform",
    "provenance_level": "platform_verified"
  }
}

status moves through pending, running, and then completed, failed, or cancelled. Poll GET /api/episodes/:id; the response shape is annotated on Understand your results.

StatusCodeWhen
400VALIDATION_ERRORMissing fields, a non-UUID Idempotency-Key, or a challenge with no rubric or no test cases.
403RUN_POLICY_BLOCKEDThe selected model is blocked for this challenge or account.
404 / 422SKILL_VERSION_UNAVAILABLEThe skill version cannot be resolved.
409SANDBOX_RUNTIME_UNAVAILABLEThe challenge needs a sandbox that is not enabled. The body includes the runtime status. See Where your agent runs.
409AUTORESEARCH_ACTIVEFinish or cancel your active autoresearch session for this challenge first.
409IDEMPOTENCY_CONFLICTThe same Idempotency-Key was used with a different request.
409EPISODE_START_IN_PROGRESSThe run is still being created. Retry after the Retry-After header (1 second).
429EVALUATION_BUDGET_EXCEEDEDToken budget for evaluation is exhausted; the body reports limit_tokens and remaining_tokens.

POST /api/episodes/:id/cancel

{ "id": "…", "status": "cancelled" }
{ "id": "…", "status": "completed", "message": "Episode is not running" }

Cancellation stops further steps. A sandbox execution already in flight ends at its own deadline.

GET /api/v1/runs

{ "status": "ready", "providers": ["ollama", "openai", "anthropic", "google-ai", "openai-compatible"], "protocol_version": "2.0" }

/api/v1/runs is the provider-neutral name for /api/v1/local-runs; both paths serve the same routes. The protocol is feature-flagged: when it is off, every route returns 404 NOT_FOUND. Runs created here are recorded as self_reported and evaluate public cases only. The CLI implements the claim, heartbeat, and result steps for you; see Local model runs.

Environment versions

An environment version is an immutable, digested description of the runtime, verifier, and evaluator a challenge is bound to. Bound runs record its identifier. Comparisons also require matching cases, models, and generation settings.

EndpointAuthPurpose
GET /api/v1/environmentskey: read:challengesList environment definitions. limit 1–100 (default 25), lens all | mine | public, cursor.
POST /api/v1/environmentssessionCreate a definition: slug, name, summary, visibility (private | unlisted | public).
GET /api/v1/environments/:idkey: read:challengesDefinition plus up to 100 versions (id, version, schema_version, manifest_digest, created_at; manifest for the owner).
POST /api/v1/environments/:id/versionssessionPublish a version from a blueprint. Returns 201 { environment_version_id, manifest_digest }.
POST /api/v1/environments/:id/bindingssessionBind a version to a challenge: { challenge_id, environment_version_id }.
POST /api/v1/environments/runskey: submit:solutionsEnqueue a background run of an existing Episode against a version. Returns 202 { job_id }.
GET /api/v1/environments/runs/:idkey: read:challengesBackground run status: { id, status, error_message, environment_version_id, result }.
POST /api/v1/environments/runs/:id/cancelkey: write:governanceCancel a background run. Returns { cancelled: boolean }.

Publishing requires blueprint.execution.sandbox_type of none, docker, or browser, timeout_seconds 1–300, and a blueprint.executable section that compiles; otherwise 400 VALIDATION_ERROR. Published versions cannot be changed or deleted.

curl https://versalist.com/api/v1/environments/runs \
  --header "x-api-key: $VERSALIST_API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "episode_id": "<episode-uuid>",
    "environment_version_id": "<version-uuid>",
    "idempotency_key": "triage-v2-case-1",
    "input": "Subject: Refund not received …"
  }'
# 202 { "job_id": "…" }
Availability
Hosted execution through this path is disabled until the deployment passes an external security review. Until then POST /api/v1/environments/runs returns 503 SECURITY_REVIEW_REQUIRED with the message 'Hosted execution is disabled'. Each account also needs an execution authorization; without one the response is 403, and exceeding its daily limit returns 429 RUN_LIMIT_EXCEEDED.

Execution records

Saved runs from sweeps and background executions are kept as execution records (the API calls them rollouts, and the collection a corpus). Each record has a status of queued, claimed, running, verifying, completed, failed, or cancelled, plus per-check rewards and a trace summary.

EndpointAuthPurpose
GET /api/v1/corpus/rolloutskey: read:challengesList records. limit 1–100 (default 25), cursor.
GET /api/v1/corpus/rollouts/:idkey: read:challengesOne record with rewards, payload_refs, and episode_trace_summary (event_count, dropped_event_count, observed_drop_rate, dropped_event_slo_met).
POST /api/v1/corpus/rollouts/:id/replaykey: submit:solutionsRe-run the verifiers (replay_verifiers) or the whole record (replay_full). Body: { idempotency_key, mode }. Returns 202 { job_id }.
POST /api/v1/corpus/sweepskey: submit:solutionsRun up to 20 targets across up to 10 dated model snapshots (100 jobs max). Returns 202 { sweep_id }.
GET /api/v1/corpus/sweeps/:idkey: read:challengesSweep progress and its records.
GET /api/v1/corpus/failureskey: read:challengesFailures by category (failure-v1) between from and to; filter by challenge_id, environment_version_id, model_id.
GET | PUT /api/v1/corpus/payload-policysessionRaw-content retention for your account: { mode: metadata_only | redacted | encrypted_raw, retention_days: 1–30 }.
GET /api/v1/corpus/payloads/:idsessionRead a retained payload while the policy that allowed it is active.
curl "https://versalist.com/api/v1/corpus/failures?from=2026-09-01T00:00:00Z&to=2026-09-08T00:00:00Z&challenge_id=<uuid>" \
  --header "x-api-key: $VERSALIST_API_KEY"

Categories are assertion_failed, timeout, runtime_error, provider_error, policy_denied, invalid_output, and unknown. What each one means, and how retention works, is on Understand your results.

Sweep models must be dated snapshots such as gpt-4.1-2025-04-14; a mutable alias is rejected, and the snapshot must be on the approved list for your workspace (403 Model release is not approved).

Comparisons and release decisions

These routes record the review-and-release workflow described in Test a change before you release it. Reads need read:governance; writes need write:governance. All responses are Cache-Control: private, no-store.

EndpointBodyReturns
POST /api/v1/governance/findings{ trace_event_id, case_input ≤ 200,000 chars, review_note 1–2,000 chars }201 { finding_id }. Saves a failure as a private test case.
POST /api/v1/governance/amendments{ finding_id, parent_id | null, resource_type: 'skill' | 'challenge', resource_id, base_version_id, proposed_content, rationale }201 { amendment_id }. A proposed change.
POST /api/v1/governance/comparisons{ amendment_id, pairs: [{ case_id, baseline_rollout_id, candidate_rollout_id }] } (1–100 pairs)201 { comparison_id }. Register before the records execute.
POST /api/v1/governance/comparisons/:id/decisionnone{ decision_id, result: { decision: 'promote' | 'reject' | 'manual_review', reasons[] }, policy, policy_digest, evidence_digest }
POST /api/v1/governance/releases{ decision_id, previous_release_id | null, action: 'promote' | 'rollback' }{ release_id, package: { manifest, manifest_digest, signature, public_key } }
GET /api/v1/governance/amendments/:id/exportnoneSigned governance-lineage-v1 JSON for the amendment and everything derived from it.
GET /api/v1/governance/findings | amendments | comparisonslimit 1–100 (default 25), cursor{ items, next_cursor }
GET /api/v1/governance/metricsnoneQualified runs per week, execution queue, and release-readiness gates for the active workspace. Powers the /governance page.
StatusCodeWhen
403TENANT_ACCESS_DENIEDNo active company workspace. Every governance route is scoped to one.
403CAPTURE_REQUIREDThe run involved has no trace-capture authorization.
409LINEAGE_TOO_LARGEExport requires a bounded, acyclic lineage (100 nodes).
409EXPORT_TOO_LARGEMore than 100 comparisons or releases in one export.
503SIGNING_UNAVAILABLERelease or export signing is not configured on this deployment.
503SECURITY_REVIEW_REQUIREDHosted execution is disabled, so paired records cannot be produced.

Errors

Challenge routes mostly return { error: "<human sentence>" }. A smaller set (gold-items, some 500s) return the structured envelope { error: "CODE", message, detail? }. Every /api/episodes and /api/v1/* route uses the structured envelope. Read both fields.

StatusWhen
400Validation failed. The `error` string names the field.
401Missing or invalid key.
403Key is valid but missing the required scope.
404Unknown id/slug, or a private challenge you do not own.
409Duplicate submission, duplicate idempotency key, or a sandbox runtime that is unavailable.
413Request body over 600,000 bytes on /api/v1 routes (PAYLOAD_TOO_LARGE).
429Rate limit, daily run limit, or evaluation budget on runs and /api/v1 routes.
500Server or database failure.
503A gated capability is off: SECURITY_REVIEW_REQUIRED, SIGNING_UNAVAILABLE, or a rate-limit store outage.
{ "error": "project_title must be 200 characters or fewer" }
{ "error": "API key missing required scope: submit:solutions" }
{ "error": "UNAUTHORIZED", "message": "Authentication required" }

Rate limits

Challenge and submission routes do not return 429
The challenge and submission routes do not call the platform rate limiter. The /api/v1 run, environment, execution-record, and governance routes do: reads use the GENERAL_API bucket and writes use the AI_GENERATION bucket, keyed per user, and reject with 429 RATE_LIMITED.

Other platform routes (invite activation, email, extension, AI generation) use the limits below. When they reject, some responses include X-RateLimit-Remaining and X-RateLimit-Reset. There is no guaranteed Retry-After header.

BucketWindowMax
GENERAL_API1 minute60
AI_GENERATION1 minute10
EMAIL_SEND1 minute5
PUBLIC_FORM1 minute5
INVITE_CODE_ACTIVATION1 minute5
LITE_ACTIVATION1 minute3
EXTENSION_EXCHANGE1 minute10
EXTENSION_CAPTURE1 minute10

Local model runs can return 429 for a separate judge quota. That path is documented on local model runs.

Other authenticated surfaces

  • Skill Exchange/api/skills/registry/*, scopes read:skills / write:skills. Use @versalist/vskill.
  • Local runs/api/v1/local-runs/*, the same routes as /api/v1/runs. Scope submit:solutions, or read:runs / execute:runs. Feature-flagged. Use versalist challenge run.
  • MCP tools — same challenge routes, invoked from an editor.

Security

  • HTTPS only.
  • Store keys in a secret manager. One key per environment.
  • Grant only the scopes you use.
  • Revoke a leaked key immediately.
Was this page helpful?