shiftlabs Start free

API reference

Call the shiftlabs API from a script, CI or an AI agent with an API key.

API

All routes are under https://api.shiftlabs.org/v1 and act on your workspace.

The credential determines the workspace. Another workspace's trigger or run returns 404. Send an API key as Authorization: Bearer slk_…. Bodies are JSON (16 KiB at most, except a deploy's source and a run's payload), and errors are {"error": "…"} with a 4xx or 5xx status.

A re-run starts a new run with the original payload and counts against your run quotas. If the payload is older than 14 days, the re-run returns 410. When a quota is full, the API returns {"error": "quota exceeded", "limit", "max"} with 429 for runs, and 403 for counts such as workflows, triggers, secrets, connections and API keys.

To retry a run start safely, send an Idempotency-Key header: any unique string of 1 to 255 visible ASCII characters, such as a UUID. A repeat with the same key within 24 hours starts nothing and returns the first run's 202 {"runId", "versionHash"} again, with Idempotent-Replayed: true. It does not count against your run quotas, even when they are full. The same key on another workflow returns 409. The shiftlabs run command sends a key with each run and retries with it.

Routes
RouteMethodMinimum role
/v1/me principal, workspace, limitsGETviewer
/v1/workspace {"name"}, 1 to 64 characters; auditedPATCHadmin
/v1/usage runs and egress in the last 24 h, counts, limitsGETviewer
/v1/workflowsGETviewer
/v1/workflows/:workflow body: the sourcePUTdeveloper
/v1/workflows/:workflow revokes its triggers, drops its vars and connection links; runs stayDELETEdeveloper
/v1/workflows/:workflow/versionsGETviewer
/v1/workflows/:workflow/versions/:hash/source {hash, language, source}; auditedGETdeveloper
/v1/workflows/:workflow/runs body: the payload, read like a webhook's, 256 KiB at mostPOSTdeveloper
/v1/workflows/:workflow/rollback {"hash"}: new runs use that versionPOSTdeveloper
/v1/deploys · /v1/deploys/:id deploys that waited on Python packages; ?status=building|live|failedGETviewer
/v1/workflows/:workflow/connections {"connections": [...]}GET · PUTviewer · developer
/v1/workflows/:workflow/vars/:NAME {"value"}PUT · DELETEdeveloper
/v1/triggers {"workflow", "signing"?: {"scheme", "secret"?}}GET · POSTviewer · developer
/v1/triggers/:id/rotatePOSTdeveloper
/v1/triggers/:id {"signing": {"scheme", "secret"?} | null}; the URL staysPATCHdeveloper
/v1/triggers/:idDELETEdeveloper
/v1/runs?status=&workflow=&origin=&limit=&before= newest first, cursor in nextBefore; each run's origin is trigger, rerun, dashboard, api or mcp, and origin_ref names who started itGETviewer
/v1/runs/:id · /logs?limit= · /timeline · /egressGETviewer
/v1/runs/:id/rerun same workflow and version, new runPOSTdeveloper
/v1/runs/:id/payload the delivered body; 410 after 14 days; auditedGETdeveloper
/v1/secrets metadata, never valuesGETviewer
/v1/secrets/:NAME {"value", "hosts"?, "readable"?, "hmac"?}PUT · DELETEadmin
/v1/secrets/:NAME {"hosts"?, "readable"?, "hmac"?}; the value and its version stayPATCHadmin
/v1/connectionsGETviewer
/v1/connections/:name {"hosts", "template"}PUT · DELETEadmin
/v1/varsGETviewer
/v1/vars/:NAME {"value"}PUT · DELETEdeveloper
/v1/audit?limit=GETadmin
/v1/metrics/runs?hours= · /v1/metrics/egress?hours=GETviewer
/v1/api-keys {"name", "role", "capabilities"?, "scope"?, "expiresInDays"?}GET · POSTadmin
/v1/api-keys/:idDELETEadmin
/v1/membersGETadmin
/v1/members/invitations {"email", "role"}POSTadmin
/v1/members/:membershipId {"role"}PATCH · DELETEadmin
/v1/alerts {"emails", "webhookUrl", "workflows", "refusals"}; the answer that makes the webhook's secret has signingSecret onceGET · PUTadmin
/v1/alerts/testPOSTadmin
/v1/alerts/rotate-secret a new signingSecret, shown once; the old one stops workingPOSTadmin

Roles

Roles rank viewer < developer < admin. Each role can do everything the roles below it can. Roles work the same for API keys and for members signed in to the dashboard.

Members · ridgeline-ops

3
MemberRole
dana@ridgeline.exampleadmin
li@ridgeline.exampledeveloper
sam@ridgeline.exampleviewer

The last active admin cannot be demoted or removed.

Each route needs one capability, such as workflow.deploy or run.view, and each role holds the capabilities at or below it. A caller without the route's capability gets 403 {"error": "forbidden", "required": "<minimum role>", "capability": "<name>"}. GET /v1/me lists the caller's capabilities. The last active admin cannot be demoted or removed (409). A suspended workspace can still read and revoke triggers, API keys and members. Any other write returns 403 {"error": "tenant suspended"}.

API keys

An API key is shown only once, when an admin creates it. We store only its SHA-256 hash.

Key created · deploy-bot

developer

slk_7Qh2mZ0vYp4cN8xW1sR6tL3kJ9bF5dG0aE2uI7oP4yT

Id
key_3f9a0c7e21b4d856
Prefix
slk_7Qh2mZ0v
Expires
2027-01-18, in 90 days

Copy the key now. It's shown only once.

Rate limits

Over a limit, a request gets 429 and a Retry-After header with the seconds to wait. Request rates are the same on every plan.

Request rates
RequestsLimitCountedRetry-After
Any request to the API, webhooks or dashboard600 a minuteper IP60 (webhooks: 10)
Failed authentication: a bad API key or a failed sign-in10 a minuteper IP60
/v1 calls, from API keys, MCP tool calls and the dashboard1,200 a minuteper workspace60
Run logs, timelines and egress, and /v1/metrics a read repeated within 10 seconds is served from cache and not counted120 a minuteper workspace60
Deploys, PUT /v1/workflows/:workflow20 a minuteper workspace60
Member invitations5 a minuteper workspace60
Test alerts, POST /v1/alerts/test1 a minuteper workspace60
Webhook deliveries to one trigger100 per 10 secondsper trigger10
Webhook deliveries to all of a workspace's triggers300 per 10 secondsper workspace10
Any request to shiftlabs.org or its subdomains the edge blocks the IP for 10 seconds and answers for us, so the body is not our JSON150 per 10 secondsper IPwait 10 seconds

MCP

AI agents work with your workspace through the Model Context Protocol at https://api.shiftlabs.org/mcp, with an API key.

json · shellMCP
// In your MCP client's server list (streamable HTTP).
{ "shiftlabs": {
    "type": "http",
    "url": "https://api.shiftlabs.org/mcp",
    "headers": { "Authorization": "Bearer slk_…" } } }

# The tools a key can call.
curl -X POST https://api.shiftlabs.org/mcp \
  -H "authorization: Bearer $SHIFTLABS_API_KEY" -H "content-type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'

Alert webhooks

Failure alerts post JSON to the webhook you set with PUT /v1/alerts. Each post is signed with your workspace's secret, so your endpoint can check that it came from shiftlabs.

javascriptNode.js
// secret: the signingSecret. header: the Shiftlabs-Signature header.
// body: the raw request body as a string, before any JSON parsing.
import { createHmac, timingSafeEqual } from "node:crypto";

function verify(secret, header, body) {
  const parts = Object.fromEntries(header.split(",").map((part) => part.trim().split("=")));
  const t = Number(parts.t);
  if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${t}.${body}`).digest();
  const given = Buffer.from(parts.v1 ?? "", "hex");
  return given.length === expected.length && timingSafeEqual(given, expected);
}

Versioning

Every route is under /v1. Within v1, changes only add.