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:
| Call | Needs, in that project |
|---|---|
| Read its runs, workflows, activity, store values and memory | View project. Memory leaves out entries kept where the key can't view files. |
| Register a task and run it | Create tasks and Start runs |
| Write or delete a store value | Manage store |
| Pause or resume a monitor | Manage 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"
}'
| Field | Meaning |
|---|---|
project | The project's id. Leave it out for a task in no project, which takes Create tasks and Start runs at the organization. |
title | Required. |
description | Context the agent reads before working. |
workflowRef | Which 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. |
priority | Rank in your priority list, 1 being highest. |
labels | Visible on the task and usable by routing. |
url | A backlink shown on the task. |
callbackUrl | An 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
429with aRetry-Afterheader: wait, and slow the caller down. A self-hosted installation sets its own limit withPERACTOR_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.