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
| Prefix | Kind | How to get | Access |
|---|---|---|---|
sr_ | Full PAT | cp.shiprepo.com/tokens.html | Full pipeline · every paid endpoint |
sr_demo_ | Demo | POST /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
| Scope | Limit | Response when exceeded |
|---|---|---|
| Task creation (per account) | 20 POSTs / 60s | 429 rate_limited with Retry-After header |
| Demo token issuance (per IP) | 3 / 24h | 429 |
| Concurrent running tasks | Free/Demo: 1 · Paid: 3 · Scale: 5 | 429 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
| Field | Type | Required | Notes |
|---|---|---|---|
repo_url | string (URL) | for task_type=fix | Public or private GitHub URL. Omit for scaffold. |
prompt | string | yes | 3–2000 chars. Plain English, the more specific the better. |
base_branch | string | no | Default main. |
task_type | enum | no | fix (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
| Field | Type | Notes |
|---|---|---|
id | uuid | |
status | enum | pending · running · awaiting_plan · awaiting_diff · done · failed · cancelled |
mode | enum | preview (no PR) or pr |
task_type | enum | fix or scaffold |
repo_url / repo_owner / repo_name | string | |
pr_url | string | Only set when a PR was opened |
deployed_url | string | For scaffold: live URL on shiprepo.com/s/<slug>/ |
deployed_repo | string | After Ship-to-Git |
diff_summary | string | Unified diff, truncated to 4 KB |
cost_usd | number | Managed mode only; BYOK is billed by provider |
tokens_total | int | |
duration_ms | int | Queue → finished |
retry_count | int | 0–2 |
pr_merged / pr_merged_at | bool · timestamp | Filled by the merge-tracker cron |
human_edit_after | bool | True 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
| HTTP | code | Fix |
|---|---|---|
| 400 | bad_request | Malformed input — check the OpenAPI schema |
| 400 | not_a_github_url | Only github.com repos supported |
| 401 | unauthorized | Missing Bearer or invalid JWT |
| 401 | invalid_token | PAT revoked or mis-typed |
| 402 | FREE_LIMIT_REACHED | Upgrade or enable BYOK |
| 402 | PRIVATE_REPO_REQUIRES_UPGRADE | Free tier is public only |
| 402 | NO_CREDITS | Buy a top-up |
| 402 | DEMO_LIMIT_REACHED | 2-task demo cap hit — upgrade |
| 402 | DEMO_PUBLIC_REPOS_ONLY | Use a public repo or upgrade |
| 403 | forbidden | Admin / workspace-owner scope required |
| 404 | not_found | Not your resource, or deleted |
| 412 | github_not_connected | Connect GitHub on the dashboard first |
| 429 | rate_limited | Check Retry-After |
| 429 | CONCURRENT_LIMIT | Wait for the active task |
| 500 | internal_error | Open 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 →