Get Started
Guide

Host an external execution agent

Claim a slice, run it, heartbeat, and post raw output from your own runner host

Use this when a third-party host should run slices instead of thespider-agent. The host stays thin: execute the prompt, return stdout. Parsing and findings stay on the server.

You need a project-scoped token, the project id, profile, enabled model codes, a checkout at the campaign commit, and a runner that follows the runner contract.

Claim

POST /v1/campaigns/42/claim
Authorization: Bearer TOKEN
Content-Type: application/json

{
  "client_id": "runner-east-1",
  "models": ["anthropic/claude-sonnet-5"],
  "max_slices": 2,
  "lease_seconds": 3600
}

Empty leases[] means nothing claimable. Keep lease_id, slice_id, model_code, prompt, prompt_hash, marker, timeout_seconds, and lease_expires_at until you submit. Write the exact prompt bytes to the runner. Do not rebuild it.

Run

  1. SHA-256 of the prompt bytes must equal prompt_hash.
  2. Run the model in the campaign's checkout.
  3. Honor timeout_seconds (or a longer lease you requested).
  4. Capture stdout, exit code, duration, and a short stderr tail.
  5. Do not parse findings on the host.

While it runs, heartbeat:

POST /v1/leases/9001/heartbeat
Authorization: Bearer TOKEN

A missed heartbeat is not a reason to kill the model. If the lease expires, a late result can still win if nobody else finished the slice.

Submit

POST /v1/leases/9001/results
Authorization: Bearer TOKEN
Content-Type: application/json

{
  "exit_code": 0,
  "duration_ms": 182345,
  "model_code": "anthropic/claude-sonnet-5",
  "client_version": "external-host/1.0.0",
  "prompt_hash": "HASH_FROM_CLAIM",
  "stdout": "RAW_PROCESS_STDOUT",
  "stderr_tail": "LAST_4000_CHARACTERS"
}

exit_code is required. It decides whether the run counts as coverage, so it must be a JSON number — the process's real exit status — or null, which means "the runner was killed before it exited" (a timeout) and is treated as a failed run. Omitting it, or sending it as a string like "0", is a payload error and answers 400, not 422: a 422 would tell you the model failed when the problem is the request body.

StatusMeaningWhat you do
200This lease won with valid coverageRecord accepted
202Someone else won; yours is stored as a leftoverStop retrying
400missing_exit_code / invalid_exit_code — the field is absent or not a number/nullFix the payload and resubmit; retrying unchanged always fails
409prompt_hash mismatchDiscard, claim again
413Artifact too largeShrink output, claim and rerun
422Marker/JSON invalid, or nonzero exitClaim again; do not count the slice as done
429 / 5xxRate limit or server blipBack off and retry

Retries of the same (lease_id, prompt_hash) do not ingest twice.

If the host crashes: persist the lease and the result body. On restart, submit an unsent result even if the lease has expired. If there is no result, do not invent one. Let the lease expire.

Do not upload the source tree. Treat prompt and model output as tenant data. Token stays out of prompts and logs.

Copyright © 2026