Public API

File work into Peractor from CI, a form, Zapier, or your own backend — with a key that may do only what you give it.


The public API lets anything that can send an HTTP request file work into Peractor and have it run immediately. No app, no browser session — just an organization API key.

Create a key

In Settings → API keys, choose New key — it takes the Manage API keys permission. Give the key a name, a default workflow if you want one, and its permissions. The secret starts with prc_ and is shown once: Peractor stores only a hash, so a lost key is replaced rather than recovered. Revoke stops a key at once.

Keys are made and changed on the web, in the desktop app, on the Mac, iPhone, iPad and Android.

What a key may do

A key belongs to the organization — not to a project, and not to the person who made it — and may do only what it is given. It starts with nothing: tick its permissions in the same list as a role's. You can give a key only permissions you hold yourself; one you don't hold can't be ticked, though a key that already has it keeps it until somebody takes it away. Edit changes a key's name, default workflow and permissions, and the list shows what each key may do, who created it, and when it was last used.

A key holds no roles, so nothing given to Everyone or to a role reaches it — and what is closed to Everyone stays closed to it. A private project, or a folder or file closed to Everyone, opens to a key only through a row naming the key on that project's Access page. A project a key can't see, and everything in it, answers 404, as if it didn't exist.

A call about a project names it — project in a task's body, ?project=<id> on the others — and needs the key's permissions there:

CallNeeds, in that project
Read its runs, workflows, activity, store values and memoryView project. Memory leaves out entries kept where the key can't view files.
Register a task and run itCreate tasks and Start runs
Write or delete a store valueManage store
Pause or resume a monitorManage automations

A call without the permission it needs answers 403, naming it: "This needs the “Create tasks” permission."

What belongs to no project — the organization's own store values, and runs in no project — takes View project among the key's own permissions, as it does for a person at the organization. Without it, the organization's store values answer 403 "Reading what is in no project needs the “View project” permission.", and a run in no project is left out, as a hidden one is.

A run a key starts includes the restricted folders and files the key can open, without asking, and when that still leaves files out, Peractor pushes its changes only to files the key may edit. That is why changing a key's default workflow takes every permission the key holds: "Changing what “CI” runs needs every permission it holds."

Authenticate

Send the key either way on every call:

Authorization: Bearer prc_your_key
x-peractor-key: prc_your_key

Check a key, discover workflows

GET /api/v1/me is the "test the connection" call. A 200 proves the key works, and says what it may do and which projects it can see, with what it may do in each:

curl https://peractor.com/api/v1/me \
  -H "Authorization: Bearer prc_your_key"

{ "org": { "id": "…", "name": "Acme" },
  "key": { "name": "CI pipeline", "prefix": "prc_a1b2c3",
           "permissions": ["project.view", "runs.start", "tasks.create"] },
  "projects": [
    { "id": "…", "name": "web-app",
      "permissions": ["project.view", "runs.start", "tasks.create"] } ] }

Permissions come as ids — tasks.create is Create tasks, runs.start Start runs — and a project's id is what project takes. A task can go into any project whose list has both.

GET /api/v1/workflows?project=<id> lists the workflow ids a task there can run — the values workflowRef accepts — in the same order the app's own pickers show them. One row carries "default": true.

Register a task and run it

POST /api/v1/tasks creates a task in the project it names and starts a run from the workflow's first phase. Only title is required:

curl -X POST https://peractor.com/api/v1/tasks \
  -H "Authorization: Bearer prc_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "project": "your_project_id",
    "title": "Fix the login bug",
    "description": "Users cannot sign in after the last deploy",
    "labels": ["bug"],
    "url": "https://example.com/ticket/42"
  }'
FieldMeaning
projectThe project's id. Leave it out for a task in no project, which takes Create tasks and Start runs at the organization.
titleRequired.
descriptionContext the agent reads before working.
workflowRefWhich workflow to run. Falls back to the key's default, then the project's. An id that does not exist is refused with 400, and no task is created.
priorityRank in your priority list, 1 being highest.
labelsVisible on the task and usable by routing.
urlA backlink shown on the task.
callbackUrlAn address Peractor calls when the run finishes — see below.

The response is 202:

{ "taskId": "…", "runId": "…", "status": "backlog", "started": true }

When nothing starts, started is false, runId is absent, and reason says why: no_workflow (the project has no runnable workflow) or quota_exhausted (the monthly quota is spent — the task is still registered and waits in the backlog). The concurrency cap is not a failure: past it the run queues with started: true and starts by itself.

Hear when it finishes

Give a task a callbackUrl — a public https or http address — and Peractor posts to it once its run completes, fails or is canceled:

{ "event": "task.finished", "taskId": "…", "runId": "…",
  "status": "completed", "title": "Fix the login bug",
  "project": "…", "endedAt": "2026-07-25T10:40:00.000Z" }

A private or loopback address is refused with 400 when the task is registered. The call is tried at most twice and isn't signed, so treat it as a nudge: read the run before acting on it.

Poll a run

curl https://peractor.com/api/v1/runs/<runId> \
  -H "Authorization: Bearer prc_your_key"

{ "runId": "…", "taskId": "…", "status": "running", "phase": "implement",
  "title": "Fix the login bug", "prUrl": "https://github.com/…/pull/12",
  "previewUrl": null, "createdAt": "2026-07-25T10:12:00.000Z" }

status is the run lifecycle state. prUrl and previewUrl fill in once a pull request or preview exists. Ids from another organization, or from a project the key can't see, return 404. A run that worked with folders or files the key can't open answers its status alone, with "withheld": true and no links.

Activity — figures already counted

GET /api/v1/activity?from=&to=&project=<id> answers what a project's runs and backlog did over a window: finished, succeeded, and failed runs with a per-day series, tasks created and closed by origin, and per-member closes. Leave project out for every project the key can see.

Every figure is counted by Peractor, so whatever writes your weekly summary — usually an agent — never has to tally records itself. Closed work counts tasks, not runs, so retries do not inflate it, and quiet days appear as zeroes so charts line up. Dates are ISO; omitted bounds mean the last seven days; a window spans at most 366 days.

GET /api/v1/activity/runs returns the rows behind those figures — quote the totals, name the rows. Only settled runs appear here.

Every run carries a key that only reads. Inside a run, PERACTOR_API_KEY and PERACTOR_API_URL hold a token that reads the run's own project — private or not, though a folder closed to Everyone stays closed to it — and what belongs to no project, like the organization's memory and store values. It changes nothing: every write answers 403. So a workflow step can ask what the project has been doing and write a report, but a prompt that went wrong cannot start work. It reads its project without naming it, dies with the run, and never appears in your key list.

Memory — read it, propose to it

Your memory is readable the way the app reads it. GET /api/v1/memory?scope=project&project=<id> lists a project's entries — what each is and how it applies — and scope=org the organization's, which takes View project among the key's own permissions. A key reads a project's memory with its own permissions, so an entry kept in a folder closed to it is left out. GET /api/v1/memory/entry takes the same two, plus an id such as billing/money-in-cents, and reads one entry whole: its fields, its body and its file.

POST /api/v1/memory/proposals proposes one entry — the whole markdown file as text, with scope, project, path (named from its title when absent) and a note for the person who decides:

curl -X POST https://peractor.com/api/v1/memory/proposals \
  -H "Authorization: Bearer prc_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "scope": "project", "project": "your_project_id",
        "note": "From the incident review",
        "text": "---\nkind: lesson\ntitle: Port 3000 is taken\n---\n\n## Lesson\n…" }'

It waits for a person who decides there — someone with Manage workflows for a project's memory, Manage organization for the organization's — and they are told in the Inbox. A key is not a person, so it can propose and never approve; a run's own token cannot propose at all.

Triggers — list, save, test, remove

A project's triggers are the same through the API as in the app. GET /api/v1/triggers?project=<id> lists them as the runtime sees them — each with its one-sentence summary, any problems, and its recent deliveries. PUT /api/v1/triggers/<id> saves one: trigger with its fields as the file holds them, or content with the file itself, and an optional version so a change made meanwhile isn't overwritten.

curl -X PUT "https://peractor.com/api/v1/triggers/deploy-check?project=your_project_id"   -H "Authorization: Bearer prc_your_key"   -H "Content-Type: application/json"   -d '{ "trigger": { "name": "Check each deploy", "on": "webhook",
        "secret": "${DEPLOY_HOOK}", "filter": "service == \"api*\"",
        "debounce": "5m", "perDay": 20, "workflow": "smoke-test" } }'

POST /api/v1/triggers/<id>/test takes an optional payload and answers what would happen, guard by guard, without starting anything; DELETE /api/v1/triggers/<id> removes one. Saving, testing and removing take Manage automations, and what the file names takes what it would in the app — Manage secrets for a secret, Create tasks for file:, every permission of a trigger that has its own. A key is not a person, so it can't turn Acts as me on; a personal trigger it saves keeps the owner it has.

Limits

  • API keys work only under /api/v1/*. They can't call the rest of Peractor's API, or make, change or revoke keys — people do that, in Settings.
  • A key may make 120 calls a minute. Past that, calls answer 429 with a Retry-After header: wait, and slow the caller down. A self-hosted installation sets its own limit with PERACTOR_API_RATE_LIMIT.

The calendar is a signed-in person's view. Its authenticated /calendar/account endpoints use the first-party session client, rather than API keys or run tokens. Personal events remain private, and exports contain only records the person can currently access. See Calendar for events, task blocks, reminders and deliberately scheduled execution.

Want a person driving Peractor from a chat window rather than a system filing work? That is Peractor in your chat apps.