API reference
Start work in partyline from other software, and hear about it when work finishes. REST over HTTPS, JSON in and out. For the concepts behind these endpoints — keys, signing, the trigger model — read API keys & webhooks.
Two rules worth knowing before you build
- Webhooks carry ids and links, never your content.Nothing your team writes ends up in a third party's request logs. If a destination needs the detail, it fetches it here with your key.
- An inbound caller sends data, never instructions. Project, machine and task come from a trigger a human configured; the request supplies only facts about an event.
There is deliberately no write access to runs — nothing outside partyline can approve, merge, or ship.
Authentication
Every request carries a bearer token. Which kind depends on what you are doing:
- plt_org_… · plt_trg_…
- A team API key from Settings → Integrations. Scoped — a key can only do what it was granted, and a trigger key can start work but read nothing.
- plt_cli_…
- A personal token from
ptln login, for managing webhooks and triggers. Those endpoints are owner/admin only.
authorization: Bearer plt_org_…Triggers
Let other software start work here. The caller sends data about what happened; the trigger a human configured decides what runs.
/api/v1/t/{slug}Fire a trigger
Start work from an outside system. The trigger row holds the project, machine, engine and merge policy; this request supplies only facts about an event, which fill the trigger's {{placeholders}}. Send ref — your own id for the thing that happened — and the same event arriving twice will never start two runs.
Parameters
- slug *string
- the trigger's address, chosen when it was created
Body
- refstring
- your id for this event. Supplying it makes redelivery safe; omitting it means repeat calls start repeat runs.
- sourcestring
- what system this came from. Shown to the agent as the origin of the quoted input.
- *string
- any other field is available to the template as {{field}}.
Responses
- 202
- accepted — returns
run_idand the trigger'sgate - 200
- duplicate
ref— nothing started, returnsdeduplicated: true - 401
- missing or invalid key, or the key lacks
intake:write - 404
- no such trigger for this team, or it is switched off
- 409
- the trigger's project or machine is no longer usable
curl -X POST "https://partyline.sh/api/v1/t/sentry-alerts" \
-H "authorization: Bearer plt_org_…" \
-H "content-type: application/json" \
-d '{"ref":"SENTRY-4821","source":"…"}'Events
What has happened, and the detail behind it. Webhooks carry ids and links only — this is where a destination comes to get the rest.
/api/v1/eventsList events
What has happened in this team, oldest first — the opposite of most listings, because a consumer walking forward with since needs the oldest unseen event, not the newest. Returns next_since to pass back on the following call. This is also how a destination catches up after downtime.
Parameters
- sincestring (ISO 8601)
- return events after this time. Use the
next_sincefrom the previous page. - kindstring
- only this event kind. One of:
run.completed·run.failed·run.killed·run.stopped·run.stalled·run.needs_approval·run.started·work_item.accepted·trigger.fired - limitinteger
- 1–200, default 50
Responses
- 200
{ events: [...], next_since }- 400
sinceis not a valid timestamp- 401
- missing or invalid key
- 403
- the key lacks
events:read
curl "https://partyline.sh/api/v1/events" \
-H "authorization: Bearer plt_org_…"/api/v1/events/{id}Get one event, with its detail
The payload a webhook deliberately omits. Expands the event's subject — for a run that means status, preset, project, machine, task counts, and each task's branch, summary and PR link. An event belonging to another team returns the same 404 as one that does not exist, so ids cannot be probed.
Parameters
- id *uuid
- the event id, from a webhook or the list endpoint
Responses
- 200
{ event, subject }—subjectis null when there is nothing to expand- 401
- missing or invalid key
- 403
- the key lacks
events:read - 404
- no such event for this team
curl "https://partyline.sh/api/v1/events/3f9a2c14-8b7e-4d2a-9f01-6c5b8e2d7a44" \
-H "authorization: Bearer plt_org_…"Management
Create and manage the webhook endpoints and triggers themselves. Owner/admin only.
/api/v1/webhooksList webhook endpoints
Where this team's events are sent. The signing secret is never returned — it is shown once, when the endpoint is created.
Responses
- 200
{ endpoints: [...], kinds: [...] }- 401
- not signed in
curl "https://partyline.sh/api/v1/webhooks" \
-H "authorization: Bearer plt_cli_…"/api/v1/webhooksAdd a webhook endpoint
Returns the signing secret once. The URL must be https and on the public internet — private, loopback and link-local addresses are refused, because our servers make the request and an endpoint pointed inward would reach things it should not.
Body
- name *string
- what it is, e.g. "n8n"
- url *string
- https, public
- kindsstring[]
- which events. Empty means everything.
Responses
- 200
{ endpoint, secret }— the secret is not retrievable again- 400
- the URL is not https, is a private address, or is not a URL
- 401
- not signed in
- 403
- only owners and admins can add endpoints
curl -X POST "https://partyline.sh/api/v1/webhooks" \
-H "authorization: Bearer plt_cli_…" \
-H "content-type: application/json" \
-d '{"name":"…","url":"https://example.com/issues/4821","kinds":"one"}'/api/v1/webhooks/{id}Remove a webhook endpoint
Events stop being sent there immediately, and its delivery history goes with it.
Parameters
- id *uuid
- the endpoint
Responses
- 200
{ ok: true }- 401
- not signed in
- 403
- only owners and admins can remove endpoints
curl -X DELETE "https://partyline.sh/api/v1/webhooks/3f9a2c14-8b7e-4d2a-9f01-6c5b8e2d7a44" \
-H "authorization: Bearer plt_cli_…"/api/v1/triggersList triggers
The inbound entry points this team has configured, with how many times each has fired.
Responses
- 200
{ triggers: [...] }- 401
- not signed in
curl "https://partyline.sh/api/v1/triggers" \
-H "authorization: Bearer plt_cli_…"/api/v1/triggers/activityTrigger activity
What every trigger has DONE over a window, bucketed by day and counted by outcome — the series behind the dashboard's Triggers panel. Buckets are dense: every day in the window is present, including the empty ones, so a gap is a zero rather than something to interpolate across. A team with no triggers gets an empty list, not a 404.
Parameters
- daysinteger
- window length, 1–90 (default 14)
Responses
- 200
{ window_days, triggers: [{ id, name, detail, total, lastAt, inactive, buckets: [{ date, counts }] }] }- 400
daysis not a whole number between 1 and 90- 401
- not signed in
- 403
- no team
curl "https://partyline.sh/api/v1/triggers/activity" \
-H "authorization: Bearer plt_cli_…"/api/v1/triggers/{id}/eventsOne trigger's event log
Every inbound call to one trigger, newest first: what it did, the caller's own ref, why nothing ran when nothing did, and the run it started when it started one. {id} accepts the trigger's slug as readily as its uuid, so a caller never has to look up an id to use this. Read-scoped by RLS — a trigger on another team is a 404, not a 403.
Parameters
- id *string
- the trigger's uuid or slug
- limitinteger
- rows, 1–200 (default 50)
Responses
- 200
{ trigger, events: [{ id, at, outcome, ref, skipped, run_id, run_status, run_title }], truncated }- 400
limitis not a whole number between 1 and 200- 401
- not signed in
- 404
- no such trigger on your team
curl "https://partyline.sh/api/v1/triggers/3f9a2c14-8b7e-4d2a-9f01-6c5b8e2d7a44/events" \
-H "authorization: Bearer plt_cli_…"/api/v1/triggersCreate a trigger
Creating a trigger decides, in advance, what an outside system may make this team's agents do — so it is owner/admin only, and a key with intake:write is issued alongside it and shown once.
Body
- name *string
- what it is
- slug *string
- the address: lowercase letters, numbers and dashes
- project_label *string
- which project the work runs in
- daemon_id *uuid
- which machine runs it
- task_template *string
- what the agent is asked, with {{placeholders}}
- gate"review" | "auto"
- `review` (default) queues it for a human; `auto` dispatches immediately
- cron_exprstring
- ALSO fire on a clock — five-field cron, read in the org's time zone (UTC if unset). Omit for webhook-only. An expression that does not parse is refused, never stored.
Responses
- 201
{ trigger, key, placeholders }—keyis shown once- 400
- a field is missing or the slug is malformed
- 401
- not signed in
- 403
- only owners and admins can create triggers
- 404
- no such project or machine in this team
- 409
- that slug is already taken
curl -X POST "https://partyline.sh/api/v1/triggers" \
-H "authorization: Bearer plt_cli_…" \
-H "content-type: application/json" \
-d '{"name":"…","slug":"sentry-alerts","project_label":"…","daemon_id":"3f9a2c14-8b7e-4d2a-9f01-6c5b8e2d7a44","task_template":"…","gate":"…","cron_expr":"…"}'/api/v1/triggers/{id}Change a trigger
The pause button, and everything else editable in place — changing a trigger never rotates its key, which is what stops a one-word fix costing a trip back to your CI. Only the fields PRESENT are applied. Turning one off keeps its address reserved; deleting frees it and starts returning errors to anything still calling.
Parameters
- id *uuid
- the trigger
Body
- enabledboolean
- off keeps the address reserved
- cron_exprstring | null
- set, change or (null/"") remove the schedule. next_run_at is recomputed in the same patch, so a changed schedule never fires once more on the old one.
- schedule_pausedboolean
- stop the clock without closing the address. Un-pausing re-arms from now rather than firing everything missed.
Responses
- 200
{ trigger }- 403
- only owners and admins
- 404
- no such trigger for this team
curl -X PATCH "https://partyline.sh/api/v1/triggers/3f9a2c14-8b7e-4d2a-9f01-6c5b8e2d7a44" \
-H "authorization: Bearer plt_cli_…" \
-H "content-type: application/json" \
-d '{"enabled":true,"cron_expr":"…","schedule_paused":true}'/api/v1/triggers/{id}Delete a trigger
Anything still calling that address starts getting errors. Prefer turning it off if you might want the address back.
Parameters
- id *uuid
- the trigger
Responses
- 200
{ ok: true }- 403
- only owners and admins
curl -X DELETE "https://partyline.sh/api/v1/triggers/3f9a2c14-8b7e-4d2a-9f01-6c5b8e2d7a44" \
-H "authorization: Bearer plt_cli_…"/api/v1/agent-templatesList agent templates
The saved personas a trigger can wake. A template says WHO an agent is and what it is for; the tools, MCP servers and repo it can reach come from the project the trigger targets, never from the template.
Responses
- 200
{ templates: [...] }— drafts and approved, newest first- 401
- not signed in
curl "https://partyline.sh/api/v1/agent-templates" \
-H "authorization: Bearer plt_cli_…"/api/v1/agent-templatesSave an agent template
Records a persona, normally from a finished authoring conversation — pass party_id and that session's document becomes the template. Saved as a draft: a template is the agent, so nothing runs it until a human approves it. A stop_rule is required, because an unattended agent with no instruction to refuse improvises around whatever is missing instead of saying so.
Body
- party_iduuid
- the authoring conversation to take the document from
- namestring
- how someone picks it from a list. Taken from the document's heading when omitted.
- bodystring
- the persona, written as instructions to the agent. Taken from the conversation when omitted.
- stop_rule *string
- when it must refuse rather than guess
Responses
- 201
{ template }— created as a draft- 400
- no name, no body, or no stop rule
- 401
- not signed in
- 409
- that name is taken, or the conversation has no document yet
curl -X POST "https://partyline.sh/api/v1/agent-templates" \
-H "authorization: Bearer plt_cli_…" \
-H "content-type: application/json" \
-d '{"party_id":"3f9a2c14-8b7e-4d2a-9f01-6c5b8e2d7a44","name":"…","body":"…","stop_rule":"…"}'/api/v1/agent-templates/{id}Read one agent template
The whole persona, including the body the list deliberately omits — a template is a document, and a list of them is a list, not twenty documents. This is what a trigger will actually run.
Parameters
- id *uuid
- the template's id
Responses
- 200
{ template }— withbodyandstop_rule- 401
- not signed in
- 404
- no such template on your team
curl "https://partyline.sh/api/v1/agent-templates/3f9a2c14-8b7e-4d2a-9f01-6c5b8e2d7a44" \
-H "authorization: Bearer plt_cli_…"/api/v1/agent-templates/{id}Remove an agent template
Removes the persona outright. Triggers pointing at it are not deleted — they fall back to their own inline task and keep firing, and the response names them so the cost is visible rather than discovered later.
Parameters
- id *uuid
- the template's id
Responses
- 200
{ ok: true, name, detached: [slug] }— the triggers that just lost it- 401
- not signed in
- 403
- only owners and admins
- 404
- no such template on your team
curl -X DELETE "https://partyline.sh/api/v1/agent-templates/3f9a2c14-8b7e-4d2a-9f01-6c5b8e2d7a44" \
-H "authorization: Bearer plt_cli_…"16 endpoints. Anything not listed here — the daemon channel, internal endpoints — is not a public API and changes without notice.