GuidesConnect and integrate

Trigger routines from your app

Create a least-privilege API key, start runs from your own code, and poll or subscribe for the result.

The dashboard is one client of the API, not a privileged one. Anything you can do to a routine, you can do from your own code with a project-scoped key.

Create a key

Dashboard → API keys → New key. The plaintext value is shown once:

wor_<prefix>_<secret>

Only a SHA-256 hash is stored. Closing the dialog removes the plaintext from the page. Keys belong to exactly one project — a key issued in one project cannot read or trigger anything in another.

Choose the least privilege the integration needs:

ScopeGrants
models:readList the model directory.
routines:readList and read routines.
routines:writeCreate, update, archive, activate, and pause.
runs:readList runs and read run detail.
runs:writeStart a manual run.
connections:readList connections and tool policies.
connections:writeCreate connections, discover tools, set policies.

New keys default to models:read, routines:read, runs:read, and runs:write — enough to trigger and observe, not to reconfigure.

Some operations are session-only

Creating organizations and projects, and managing API keys themselves, require a signed-in browser session. An API key is already bound to one project, so it cannot create the scope above itself. Those calls return 403 session_required.

Start a run

Find the routine

curl https://api.workonrepeat.com/v1/routines \
  -H "Authorization: Bearer $WORK_ON_REPEAT_API_KEY"

Trigger it

curl -X POST \
  https://api.workonrepeat.com/v1/routines/$ROUTINE_ID/run \
  -H "Authorization: Bearer $WORK_ON_REPEAT_API_KEY"

The response is 202 Accepted with the queued run. The run executes on a worker; the request does not wait for it.

{
  "data": {
    "id": "1f7b0e6a-...",
    "routineId": "9c2e...",
    "state": "queued",
    "trigger": "manual",
    "projectId": "5a41..."
  },
  "requestId": "01J..."
}

Collect the result

Prefer a webhook — it arrives once, signed, and without polling. When you must poll, read the run detail and stop on a terminal state:

wait-for-run.ts
const TERMINAL = new Set(["succeeded", "failed", "cancelled", "timed_out"]);

export async function waitForRun(runId: string, apiKey: string) {
  // Runs are bounded by their routine's timeout, so a generous ceiling here
  // still terminates.
  for (let attempt = 0; attempt < 120; attempt += 1) {
    const response = await fetch(
      `https://api.workonrepeat.com/v1/runs/${runId}`,
      { headers: { authorization: `Bearer ${apiKey}` } },
    );
    if (!response.ok) throw new Error(`run lookup failed: ${response.status}`);

    const { data } = await response.json();
    if (TERMINAL.has(data.state)) return data;

    await new Promise((resolve) => setTimeout(resolve, 10_000));
  }
  throw new Error("run did not reach a terminal state");
}

Rate limits

Authenticated /v1 traffic is limited per credential in a fixed 60-second window. Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; a 429 also carries Retry-After and the error code rate_limit_exceeded. Request bodies are capped at 1 MB.

Back off on Retry-After rather than retrying immediately — the window is per credential, so a tight loop only delays your own work.

Selecting a project

An API key is bound to one project, so nothing further is needed. A browser session can choose its active project with the X-Project-Id header, which takes precedence over the wor_project cookie. Either way the API verifies membership of the organization that owns the project.

On this page

Edit this page on GitHub