Invoke an agent
There are five ways to start the same run: wait for the answer, stream it, fire and poll, put it on a cron, or let a signed webhook start it. They differ only in how the caller waits. The run itself, its journal, budget checks and receipts are identical. Pick the mode from the table, then copy the request from its tab.
1. Invoke and wait
The caller posts to the A2A invoke endpoint, which needs the runs:execute scope.
- Callers and outputs
- Control plane
| You want | Use | Caller waits for |
|---|---|---|
| The answer in the response, under a minute | Sync | the output (up to 60 s) |
| Tokens or steps as they happen | Stream | an SSE stream that ends with the run |
| To hand off long work and check later | Async | a run id, immediately |
| The agent on a timetable | Cron | nothing; the scheduler starts it |
| GitHub, Sentry, PagerDuty or any HMAC sender to start it | Webhook | 202 with the run id |
| Many inputs in one call, with a completion callback | Batch | a batch id; per-run results, and a callback when all finish |
The A2A invoke endpoint creates the run, waits for it and returns the output. It polls for up to 60 s. A run that has not succeeded by then answers 502 with its runId and status, so you can keep following it asynchronously.
curl -s -X POST http://localhost:8080/v1/agents/my-agent/a2a/invoke \ -H "Authorization: Bearer $PAL_API_KEY" -H "Content-Type: application/json" -d '{"input":{"q":"..."}}'# 200 {"runId":"...","status":"succeeded","output":{...}}Needs the runs:execute scope. Another tenant can call it only when the agent is public; see Marketplace.
Create the run, then subscribe to its events over SSE. The stream replays the journal from the first event, so a late subscriber misses nothing. It closes when the run reaches succeeded, failed or cancelled.
RUN_ID=$(curl -s -X POST http://localhost:8080/v1/runs -H "Authorization: Bearer $PAL_API_KEY" -H "Content-Type: application/json" \ -d '{"agentName":"my-agent","input":{}}' | jq -r .id)curl -N http://localhost:8080/v1/runs/$RUN_ID/events \ -H "Authorization: Bearer $PAL_API_KEY" -H "Accept: text/event-stream"For a chat turn, sessions stream token by token instead; see Streaming.
POST /v1/runs returns as soon as the run is queued. Poll GET /v1/runs/{id} or subscribe to events later. A process restart does not lose the run: the recovery sweep re-drives it every 30 s from its journal and skips steps that already completed (Durable execution).
import { PalClient, type Run } from "@pal-runtime/sdk";
const client = new PalClient({ apiKey: process.env.PAL_API_KEY, baseUrl: "http://localhost:8080" });const run = (await client.runs.create({ agent: "my-agent", input: {} })) as Run;// ...laterconst { status, output } = await client.runs.get(run.id);# inside an async function, with client = PalClient(api_key=..., base_url="http://localhost:8080")run = await client.runs.create(agent="my-agent", input={})# ...laterrun = await client.runs.get(run.id)// c := pal.New(pal.WithAPIKey(key), pal.WithBaseURL("http://localhost:8080"))r, _ := c.CreateRun(ctx, pal.RunOptions{AgentName: "my-agent"})// ...laterr, _ = c.GetRun(ctx, r.ID)The SDKs are not published to npm or PyPI yet; install them from the repo path (SDKs). Their default base URL does not resolve, so always pass the base URL as above.
A schedule starts a run on a cron expression in an IANA timezone. Unset, the timezone falls back to PAL_DEFAULT_TIMEZONE, then UTC.
curl -s -X POST http://localhost:8080/v1/schedules -H "Authorization: Bearer $PAL_API_KEY" -H "Content-Type: application/json" -d '{ "agentName": "research-agent", "cronExpr": "0 9 * * 1-5", "timezone": "America/New_York", "inputTemplate": { "mode": "daily-digest" }, "enabled": true}'More: Scheduling.
A trigger gives an agent a public URL. The sender’s HMAC signature is the only credential, and a delivery sent twice starts one run.
pal hooks create my-agent --provider github# prints the URL path (/v1/hooks/{id}) and the signing secret, onceMore: Triggers.
Submit many inputs to one agent in one request. Each item becomes its own run, with its own journal, budget check and retries; the batch tracks them and can call you back when the last one finishes.
curl -s -X POST http://localhost:8080/v1/batches \ -H "Authorization: Bearer $PAL_API_KEY" -H "Content-Type: application/json" \ -H "Idempotency-Key: nightly-2026-10-30" \ -d '{"agentName":"summarizer","concurrency":10, "callbackUrl":"https://example.com/hooks/batch", "items":[{"input":{"url":"https://a.example"}},{"input":{"url":"https://b.example"}}]}'| Call | What it does |
|---|---|
POST /v1/batches |
Start the batch. concurrency defaults to 20; at most PAL_BATCH_MAX_ITEMS items (default 1000). Re-sending the same Idempotency-Key returns the same batch |
GET /v1/batches/{id} |
Counts by status (pending, running, succeeded, failed, cancelled) and total cost |
GET /v1/batches/{id}/results |
Each run’s output, 100 per page |
POST /v1/batches/{id}/cancel |
Cancel every child that has not finished |
A single run takes the same callbackUrl on POST /v1/runs. The callback is a POST of {runId, status, output, error, costUsd, tokensIn, tokensOut}, signed X-Pal-Signature: v1=<HMAC-SHA256 of the body> with a per-tenant key. It is tried up to 4 times (5xx and network errors only) and delivered at most once: if the control plane crashes between attempts the callback is lost, so poll the run or batch to reconcile. The SDKs do not wrap batches yet; call the REST API.
What stays the same
Section titled “What stays the same”Whatever starts the run, it goes through the same path: the budget check (a hard-fail cap returns 402 before anything executes), the journal, the isolation tier from the agent’s manifest, and a receipt you can verify afterwards.
Limits
Section titled “Limits”- Sync waits at most 60 s. Longer work belongs on stream or async.
- Batch does not exist yet.