S
ShipRepo
Sign in

API reference

Everything you need to integrate ShipRepo into a CLI, IDE extension, CI pipeline, or internal tool.

Overview

ShipRepo's API is a standard JSON + Bearer REST API. Every endpoint lives under https://api.shiprepo.com and is also aliased under /v1/ for stability. Responses are JSON unless a specific endpoint says otherwise (e.g. streams and exports).

The full machine-readable spec lives at /openapi-public.yaml and an interactive explorer at api.shiprepo.com/docs.

Authentication

All authenticated requests send a Bearer token:

Authorization: Bearer sr_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Two kinds of tokens

PrefixKindHow to getAccess
sr_Full PATcp.shiprepo.com/tokens.htmlFull pipeline · every paid endpoint
sr_demo_DemoPOST /v1/demo/token with {email}2 tasks · public repos · stops after Inspector

Tokens are hashed (SHA-256) at rest. The secret is only shown once at creation — store it in your secret manager immediately.

Base URL & versioning

https://api.shiprepo.com        # current
https://api.shiprepo.com/v1/...  # stable, recommended for integrations

Breaking changes get a new version prefix. /v1 endpoints will remain available for 12 months after any new version ships.

Rate limits

ScopeLimitResponse when exceeded
Task creation (per account)20 POSTs / 60s429 rate_limited with Retry-After header
Demo token issuance (per IP)3 / 24h429
Concurrent running tasksFree/Demo: 1 · Paid: 3 · Scale: 5429 CONCURRENT_LIMIT

Demo token

Try the API without signing up. Returns a 30-day token with 2-task lifetime and access limited to Planner → Locator → Inspector.

POST /v1/demo/token

# Request
{
  "email": "you@company.com"
}

# Response 201
{
  "token": "sr_demo_xxxxxxxxxxxxxxxxxxxxxx",
  "expires_in_days": 30,
  "scope": "demo",
  "limits": {
    "max_tasks_per_token": 2,
    "public_repos_only": true,
    "pipeline_stages": ["planner", "locator", "inspector"]
  }
}

Create task

POST /v1/tasks

Request body

FieldTypeRequiredNotes
repo_urlstring (URL)for task_type=fixPublic or private GitHub URL. Omit for scaffold.
promptstringyes3–2000 chars. Plain English, the more specific the better.
base_branchstringnoDefault main.
task_typeenumnofix (default) or scaffold.

Example

curl -X POST https://api.shiprepo.com/v1/tasks \
  -H "Authorization: Bearer $SHIPREPO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "repo_url": "https://github.com/acme/app",
    "prompt":   "fix the 500 in POST /signup when email is missing"
  }'

Response

201 Created
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "status": "pending",
  "mode": "pr",               // "preview" for Free / demo, "pr" for paid
  "task_type": "fix",
  "created_at": "2026-04-21T01:30:00Z",
  ...full task object, see #task-object
}

List tasks

GET /v1/tasks

# Response 200 — last 100 of your tasks, newest first
[{ "id": "...", "prompt": "...", "status": "done", "pr_url": "...", ... }]

Get task + logs

GET /v1/tasks/{id}

# Response 200
{
  "task": { ...see #task-object },
  "logs": [
    { "step": "planner",   "status": "done", "output": { ... } },
    { "step": "locator",   "status": "done", "output": { ... } },
    { "step": "inspector", "status": "done", "output": { ... } },
    { "step": "fixer",     "status": "done", "output": { ... } },
    { "step": "validator", "status": "done", "output": { ... } },
    { "step": "pr",        "status": "done", "output": { "pr_url": "..." } }
  ]
}

Stream logs (SSE)

GET /v1/tasks/{id}/logs/stream
Accept: text/event-stream

# Events
event: connected
data: {"task_id":"..."}

event: log
data: {"step":"planner","status":"done","output":{...},"created_at":"..."}

event: state
data: {"status":"running","pr_url":null,"cost_usd":0.12}

event: end
data: {"status":"done"}

Best for CLIs and live terminal UIs. New log events are emitted every 1.5s; the stream closes when the task reaches done, failed, or cancelled.

Current limits / usage

GET /v1/tasks/limits

# Response
{
  "limits": { "monthly": 250, "concurrent": 3, "maxFiles": 5, "maxRetries": 2, "mode": "pr" },
  "used_this_month": 42,
  "plan": "pro",
  "use_byok": false
}

Rate a result

POST /v1/tasks/{id}/feedback

{
  "rating": 5,
  "correct": "yes",          // yes | partial | no
  "reuse": "yes",             // yes | no
  "note": "perfect"
}

Your ratings tune your future runs on the same repo (repo memory), and never leave your account.

Continue a preview

POST /v1/tasks/{id}/continue

Promote a Free / demo preview to a full PR run. Charges one credit (or enables BYOK path). Returns the new child task.

Approve a gate

POST /v1/tasks/{id}/approve

{ "stage": "plan" }    # after Planner step
{ "stage": "diff" }    # after Fixer, before Validator

Only applies when the task has approval_mode: "manual". Each gate times out after 5 min; the task is cancelled without charge.

Kill task

POST /v1/tasks/{id}/kill

Force-stops a running task. Sandbox container is killed immediately. No credit consumed.

Ship scaffold to a new GitHub repo

POST /v1/tasks/{id}/deploy-git

{
  "repo_name": "my-landing-site",
  "private":   false
}

# Response 201
{
  "ok": true,
  "repo_url":  "https://github.com/you/my-landing-site",
  "clone_url": "https://github.com/you/my-landing-site.git"
}

Only valid on scaffold tasks. Uses your connected GitHub token to create the repo under your account and push the generated files as the initial commit.

Account (/me)

GET /v1/me

{
  "id": "uuid",
  "email": "you@company.com",
  "name": "You",
  "plan": "pro",
  "credits": 203,
  "use_byok": false,
  "github": "you",                  // GitHub login (null if not connected)
  "api_keys": [ { "provider": "openai", "last4": "abcd", ... } ]
}

BYOK keys

POST /v1/me/keys

{
  "provider":      "openai",     // openai | together | fireworks
  "api_key":       "sk-xxxxxxxxxxxxxxxx",
  "base_url":      "https://api.openai.com/v1",   // optional override
  "default_model": "gpt-4o-mini"                   // optional
}

The key is AES-256-GCM encrypted and never returned. Switch on BYOK globally with POST /v1/me/byok {"enabled": true}.

Test a key before saving

POST /v1/me/keys/test
{ "provider": "openai", "api_key": "sk-..." }

# Response
{ "ok": true, "models": 42 }     # or { "ok": false, "error": "401 Unauthorized" }

Tokens

POST /v1/me/tokens   # create
GET  /v1/me/tokens   # list (no secrets)
DELETE /v1/me/tokens/{id}   # revoke

Export your data

GET /v1/me/training/summary   # counts

GET /v1/me/training/export.jsonl   # JSONL of your done tasks

Health

GET /health

{ "ok": true, "ts": 1776737344603 }

Task object

FieldTypeNotes
iduuid
statusenumpending · running · awaiting_plan · awaiting_diff · done · failed · cancelled
modeenumpreview (no PR) or pr
task_typeenumfix or scaffold
repo_url / repo_owner / repo_namestring
pr_urlstringOnly set when a PR was opened
deployed_urlstringFor scaffold: live URL on shiprepo.com/s/<slug>/
deployed_repostringAfter Ship-to-Git
diff_summarystringUnified diff, truncated to 4 KB
cost_usdnumberManaged mode only; BYOK is billed by provider
tokens_totalint
duration_msintQueue → finished
retry_countint0–2
pr_merged / pr_merged_atbool · timestampFilled by the merge-tracker cron
human_edit_afterboolTrue if a human added commits on top of the bot's

Log object

{
  "step":        "fixer",
  "status":      "done",       // running | done | failed | skipped
  "input":       { ... },
  "output":      { ... },
  "tokens":      2413,
  "cost_usd":    0.012,
  "duration_ms": 1820,
  "created_at":  "2026-04-21T01:30:12Z"
}

Error codes

HTTPcodeFix
400bad_requestMalformed input — check the OpenAPI schema
400not_a_github_urlOnly github.com repos supported
401unauthorizedMissing Bearer or invalid JWT
401invalid_tokenPAT revoked or mis-typed
402FREE_LIMIT_REACHEDUpgrade or enable BYOK
402PRIVATE_REPO_REQUIRES_UPGRADEFree tier is public only
402NO_CREDITSBuy a top-up
402DEMO_LIMIT_REACHED2-task demo cap hit — upgrade
402DEMO_PUBLIC_REPOS_ONLYUse a public repo or upgrade
403forbiddenAdmin / workspace-owner scope required
404not_foundNot your resource, or deleted
412github_not_connectedConnect GitHub on the dashboard first
429rate_limitedCheck Retry-After
429CONCURRENT_LIMITWait for the active task
500internal_errorOpen a ticket with the task id

Node.js recipe

import EventSource from "eventsource";

const API = "https://api.shiprepo.com/v1";
const TOKEN = process.env.SHIPREPO_TOKEN;

async function fixAndWait(repo, prompt) {
  const r = await fetch(`${API}/tasks`, {
    method: "POST",
    headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
    body: JSON.stringify({ repo_url: repo, prompt }),
  });
  if (!r.ok) throw new Error(await r.text());
  const task = await r.json();

  return new Promise((resolve, reject) => {
    const es = new EventSource(`${API}/tasks/${task.id}/logs/stream`, { headers: { Authorization: `Bearer ${TOKEN}` } });
    es.addEventListener("log", (e) => console.log("step:", JSON.parse(e.data).step));
    es.addEventListener("end", (e) => { es.close(); resolve(JSON.parse(e.data)); });
    es.onerror = reject;
  });
}

const r = await fixAndWait("https://github.com/acme/app", "fix login null check");
console.log("done:", r);

Python recipe

import os, requests, sseclient, json

API = "https://api.shiprepo.com/v1"
HEAD = {"Authorization": f"Bearer {os.environ['SHIPREPO_TOKEN']}"}

t = requests.post(f"{API}/tasks", headers=HEAD,
                  json={"repo_url": "https://github.com/acme/app",
                        "prompt": "fix login null check"}).json()

resp = requests.get(f"{API}/tasks/{t['id']}/logs/stream", headers=HEAD, stream=True)
for ev in sseclient.SSEClient(resp).events():
    print(ev.event, json.loads(ev.data).get("step") if ev.event == "log" else ev.data)

Bash recipe

#!/usr/bin/env bash
set -euo pipefail

TOK="$SHIPREPO_TOKEN"
REPO="$1"
PROMPT="$2"

TASK=$(curl -fsS https://api.shiprepo.com/v1/tasks \
  -H "Authorization: Bearer $TOK" -H "Content-Type: application/json" \
  -d "{\"repo_url\":\"$REPO\",\"prompt\":\"$PROMPT\"}" | jq -r .id)

while :; do
  STATE=$(curl -fsS "https://api.shiprepo.com/v1/tasks/$TASK" -H "Authorization: Bearer $TOK" | jq -r .task.status)
  echo "[$STATE]"
  [[ "$STATE" == "done" || "$STATE" == "failed" || "$STATE" == "cancelled" ]] && break
  sleep 3
done

GitHub Action

name: ShipRepo triage
on: { issues: { types: [labeled] } }
jobs:
  fix:
    if: github.event.label.name == 'shiprepo'
    runs-on: ubuntu-latest
    steps:
      - env: { TOKEN: "${{ secrets.SHIPREPO_TOKEN }}" }
        run: |
          curl -fsS https://api.shiprepo.com/v1/tasks \
            -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
            -d '{"repo_url":"https://github.com/${{ github.repository }}","prompt":"${{ github.event.issue.title }}"}'

Generate an SDK

curl https://shiprepo.com/openapi-public.yaml -o shiprepo.yaml

# TypeScript
npx @openapitools/openapi-generator-cli generate -i shiprepo.yaml -g typescript-fetch -o sdk-ts/

# Python
npx @openapitools/openapi-generator-cli generate -i shiprepo.yaml -g python -o sdk-py/

# Go
npx @openapitools/openapi-generator-cli generate -i shiprepo.yaml -g go -o sdk-go/

Something unclear?

Email api@shiprepo.com with the task id — we reply within a business day.

Grab a demo token →