Developer API

Manage projects, retrieve recommendations and record outcomes over HTTP.

The Developer API exposes customer operations under /api/v1. It shares permissions, entitlements, recommendations, artifacts, and outcome history with the dashboard, MCP, and CLI. The endpoint catalog lives at /api/v1/openapi. It currently lists routes, scopes and effects; it is not yet a complete schema for generating a client. Use the request examples below and inspect returned fields.

Authentication

Create a developer key in Agent Access, then set the service origin explicitly. Keep the secret in your process or client credential store.

export SHIPFOUNDRY_BASE_URL=https://shipfoundry.ai
# Supply SHIPFOUNDRY_API_KEY through your secure environment configuration.

Send the key as a Bearer token:

curl "$SHIPFOUNDRY_BASE_URL/api/v1/me" \
  -H "Authorization: Bearer $SHIPFOUNDRY_API_KEY"

Developer keys reuse the sfp_mcp_ credential format, with explicit Developer API resource permission, scopes, and an all, selected, or none project grant. Historical MCP-only keys do not automatically acquire API permission. Neither generic MCP OAuth tokens nor ChatGPT OAuth tokens authenticate to /api/v1.

Key creation, listing, rotation, and revocation use /api/v1/keys with a signed-in dashboard session and the required account role. A bearer key cannot manage keys. Creating a key requires an owner or admin. Open /dashboard/settings/mcp and use Developer Keys. Choose a name, project access, permissions, and an optional expiry. Copy the secret once and store it privately. The same section lists keys and lets you confirm rotation or revocation. Rotation immediately invalidates the old secret; update your clients with the replacement.

The CLI accepts SHIPFOUNDRY_MCP_TOKEN as a legacy environment-variable name. Its value must still be a key authorized for the Developer API; the variable name does not convert an old MCP-only key.

Set up a project

Use Set Up With Your Agent for the human consent steps and activation procedure. First read GET /me, GET /usage and GET /projects. A project-create request requires project:write, an unrestricted project grant, and available plan capacity.

curl "$SHIPFOUNDRY_BASE_URL/api/v1/projects" \
  -H "Authorization: Bearer $SHIPFOUNDRY_API_KEY" \
  -H 'Content-Type: application/json' \
  -H "X-Idempotency-Key: $CREATE_REQUEST_KEY" \
  -d '{"name":"My Product","description":"The product this repository implements."}'

Choose a unique CREATE_REQUEST_KEY for this action and retain it on retry. The successful response contains the new project ID in data.id. Use an existing ID from the project list instead when that project already exists.

Set PROJECT_ID to that returned ID. Read its current context before updating:

curl "$SHIPFOUNDRY_BASE_URL/api/v1/projects/$PROJECT_ID/context" \
  -H "Authorization: Bearer $SHIPFOUNDRY_API_KEY"

curl -X PATCH "$SHIPFOUNDRY_BASE_URL/api/v1/projects/$PROJECT_ID/context" \
  -H "Authorization: Bearer $SHIPFOUNDRY_API_KEY" \
  -H 'Content-Type: application/json' \
  -H "X-Idempotency-Key: $CONTEXT_REQUEST_KEY" \
  -d '{"summary":"What the product does and for whom.","goals":["Improve checkout reliability"],"constraints":["Preserve existing payment behavior"],"importantRepoAreas":["src/checkout"]}'

Replace example facts with evidence from the actual product. Context also accepts domain, repoUrl, runtimeModel, deploymentModel, packageManager, packageName, importantDependencies, externalTools, investigationTargets, and stack entries with name, optional category, and optional versionConstraint.

After GitHub consent, GET /projects/{id}/repository lists permitted repositories. Select one with POST /projects/{id}/repository and {"repoId":"<returned-repository-UUID>","priority":0}. Read /status again. GET /projects/{id}/watchlist returns selections and suggestions. Add suggested items with POST to that path and {"action":"add","recommendationIds":["<returned-recommendation-UUID>"]}. IDs are different for repositories, suggestions and selections; never substitute names or invent IDs.

Activate and follow progress

Once setup status confirms a current repository and selected watchlist, obtain the owner's approval to start the first review. This can consume the plan's allowance.

curl "$SHIPFOUNDRY_BASE_URL/api/v1/projects/$PROJECT_ID/activation" \
  -H "Authorization: Bearer $SHIPFOUNDRY_API_KEY" \
  -H 'Content-Type: application/json' \
  -H "X-Idempotency-Key: $ACTIVATION_REQUEST_KEY" \
  -d '{"confirmed":true}'

curl "$SHIPFOUNDRY_BASE_URL/api/v1/projects/$PROJECT_ID/processing-status" \
  -H "Authorization: Bearer $SHIPFOUNDRY_API_KEY"

The activation result reports whether setup was already completed, its first-review request ID and the actual dispatch state. A pending dispatch is not accepted or completed work. Poll the project's processing-status endpoint with a deadline and report outstanding setup actions. The first-review request ID is not a brief-operation ID; do not poll it through /operations. A repeated activation of completed setup does not start another review.

Core workflow

BASE="$SHIPFOUNDRY_BASE_URL/api/v1"
AUTH=(-H "Authorization: Bearer $SHIPFOUNDRY_API_KEY")

curl "$BASE/projects" "${AUTH[@]}"
curl "$BASE/projects/$PROJECT_ID/status" "${AUTH[@]}"
curl "$BASE/projects/$PROJECT_ID/updates?workspace=active&limit=20" "${AUTH[@]}"
curl "$BASE/updates/$UPDATE_ID" "${AUTH[@]}"
curl "$BASE/updates/$UPDATE_ID/brief" "${AUTH[@]}"

Reading a stored brief never generates, spends, or queues. Generation is always explicit:

curl "$BASE/updates/$UPDATE_ID/brief-requests" "${AUTH[@]}" \
  -H 'Content-Type: application/json' \
  -d '{"confirmed":true,"mode":"if_missing_or_stale","idempotencyKey":"<unique-key>"}'

An accepted request returns HTTP 202 with an operation ID. Poll GET /operations/$OPERATION_ID until terminal, then read the stored brief and the execution packet:

curl "$BASE/operations/$OPERATION_ID" "${AUTH[@]}"
curl "$BASE/updates/$UPDATE_ID/packet?mode=full" "${AUTH[@]}"

Record what actually happened, with evidence:

curl "$BASE/updates/$UPDATE_ID/outcomes" "${AUTH[@]}" \
  -H 'Content-Type: application/json' \
  -d '{"outcome":"investigated","summary":"Investigated checkout compatibility; implementation remains pending.","filesInspected":["src/checkout/index.ts"],"validationNotes":["Existing checkout tests passed."],"idempotencyKey":"<unique-key>"}'

Other customer operations

Paths below are relative to /api/v1. Use returned UUIDs in {id} and send JSON for bodies. Mutations accept X-Idempotency-Key; preserve it across retries. Access also depends on the project's grant and the operation's scopes.

TaskRequestBody Or Parameters
Update projectPATCH /projects/{id}name and/or summary
Archive or restorePOST /projects/{id}/archiveaction: archive or restore; confirmed: true
Remove watchlist itemsDELETE /projects/{id}/watchlistselectionIds: array of returned selection UUIDs
Check processingGET /projects/{id}/processing-statusNo body. Does not start processing.
Check connectionsGET /connectionsNo body. Consent remains in the browser.
Read an operationGET /operations/{id}No body. Use the operation ID returned by a write.
Cancel supported workPOST /operations/{id}/cancelconfirmed: true. Running or completed work may not be cancellable.
Answer a questionPOST /updates/{id}/answersCurrent questionHash from the update plus answer as a string or array of strings. Stale questions are rejected.
Record decisionPOST /updates/{id}/decisionsconfirmed: true; decision: keep_open, useful, not_useful, dismiss, or reopen. Dismiss requires dismissReason; other also requires reason. useful and not_useful record explicit feedback with an optional reason note and never change the recommendation state.
Mark brief usedPOST /updates/{id}/brief-usesbriefId: UUID of the stored brief. This does not mark adoption.
Preview Linear issuePOST /linear/previewmatchId: update UUID. Preview does not export.
Export Linear issuePOST /linear/exportmatchId, confirmed: true. Read the preview first; reuse an existing export instead of forcing a duplicate.
Read account settingsGET /settingsNo body. Billing and credential changes remain session-controlled.

Decision dismissal reasons are already_handled, does_not_apply, no_next_step, not_now, other, or too_small. Outcome values are useful, ignored, false_positive, adopted, investigated, waiting, needs_review, or deferred. Evidence may include summary, reason, filesInspected, filesChanged, validationNotes, prUrl and commitUrl. Only claim adoption when the implementation evidence supports it. Recommendations can impose additional readiness requirements.

Errors and retries

Failures use stable machine codes with a request ID and recovery hint: unauthorized, wrong_audience, forbidden, not_found, validation_error, conflict, idempotency_conflict, billing_required, quota_exceeded, provider_unavailable, service_unavailable.

For a write you may retry, choose one X-Idempotency-Key (or idempotencyKey) and keep it with the request.

  • Same key and request: returns the original saved result.
  • Same key with a changed request: returns HTTP 409.
  • No client key: each HTTP request receives a new key, so the server cannot recognize a retry of an earlier request.

When retrying across API and MCP, use the same explicit key and equivalent arguments. MCP calls without a key keep their operation-specific behavior and do not use this shared retry record.

If a write returns reconciliation_required, its external result is uncertain. Keep the request ID and resolve that attempt before starting another. Changing the key can duplicate the action.

Cancelling a local wait never cancels server work: use POST /operations/$ID/cancel explicitly.

Pagination

List responses carry an opaque cursor scoped to the account, project, and filters. Cursors from another listing are rejected. The queue exposes the 50 most relevant items; refine filters to page further.

Billing and limits

Reads never spend. First-review activation, brief generation, regeneration with a reason, and Linear export are explicit, allowance-checked writes. No interface bypasses trial, billing, project, or watchlist limits, and nothing purchases automatically. GET /usage reports plan and quotas.

Request Missing Coverage

Send POST /projects/{id}/tracking-requests with project:write permission and an X-Idempotency-Key header:

{
  "name": "<missing-tool>",
  "note": "How this repository uses it",
  "confirmed": true
}

The response includes requestId, coverageStatus, status and trackingActivated: false. Reuse the same key and payload for retries. Read GET /projects/{id}/watchlist and its requests list for saved coverage requests. This records demand; it does not enable monitoring or generate work. A tool already in the catalog returns status: "catalog_match" for a separate tracking decision.

Retry a failed first review

After fixing a reported failure, send POST /projects/{id}/activation with {"confirmed":true,"retryFailed":true} and a new X-Idempotency-Key. This is a new approved attempt, not a replay of the original activation. Keep that new key for retries of the same request. Running reviews and unresolved dispatches are not restarted. Read /projects/{id}/processing-status for the review outcome.

Discover MCP schemas and Markdown guides

For MCP, initialize /mcp and call tools/list for the current tool names and input schemas. Inspect tool errors even when the HTTP response is 200. See the MCP guide for authentication and recovery.

Documentation is also available as Markdown at /docs/<slug>.md, with a resource map at llms.txt and the full documentation export.

Was This Page Useful?

On this page