Every endpoint is one HTTP call. Nothing here needs an SDK, though there is one.
Authenticate with an API key from your dashboard. Address a person by their recipient key.
curl -X POST https://api.agentsignal.net/v1/messages \
-H "Authorization: Bearer as_live_…" \
-H "Content-Type: application/json" \
-d '{
"to": "u_8fk2…",
"title": "Deploy finished",
"body": "prod is green",
"priority": 1
}'{
"ok": true,
"id": "b715…",
"deliveries": { "total": 3, "sent": 3, "failed": 0, "queued": 0 }
}deliveries.total is how many devices that person has registered. Each one counts against your plan.
| to | Recipient key or id. One of `to`, `group`, `broadcast`. |
| group | Group slug or id — sends to everyone in it. |
| broadcast | true — everyone in the label. |
| title | Up to 250 characters. Defaults to the label name. |
| body | Required. Up to 4096 characters. |
| priority | -2 to 2. See below. |
| sound | Sound name, or `default`. |
| url | A link shown under the message. |
| url_title | Label for that link. |
| ttl_seconds | Drop the message if undelivered by then. |
| tags | Up to 16 strings, for your own filtering. |
| data | Arbitrary JSON, delivered untouched. |
| collapse_key | Replaces an earlier undelivered message. |
| idempotency_key | Replay-safe. Also accepted as a header. |
| -2 | No alert. Badge only. |
| -1 | Alert with no sound. |
| 0 | Default. |
| 1 | Bypasses quiet hours. |
| 2 | Repeats until a human acknowledges. |
Priority 2 takes retry_seconds (minimum 30) and expire_seconds (maximum 10800), returns a receipt, and will POST to your callback_url when someone acknowledges.
curl -X POST https://api.agentsignal.net/v1/messages \
-H "Authorization: Bearer as_live_…" \
-d '{
"to": "u_8fk2…",
"body": "Disk at 98% on db-1",
"priority": 2,
"retry_seconds": 60,
"expire_seconds": 3600,
"callback_url": "https://example.com/acked"
}'An agent that retries a failed request must not notify a human twice. Send an Idempotency-Key header and a replay returns the original message without dispatching anything.
curl -X POST https://api.agentsignal.net/v1/messages \
-H "Authorization: Bearer as_live_…" \
-H "Idempotency-Key: run-4821-step-7" \
-d '{"to":"u_8fk2…","body":"done"}'| 400 | The body failed validation. `details` says which field. |
| 401 | Missing, malformed, revoked, or expired key. |
| 402 | Monthly quota exhausted. Upgrade, or pay per send. |
| 403 | The key lacks the required scope. |
| 404 | No such recipient or group. |
| 429 | Rate limited. `Retry-After` says how long. |
{
"ok": false,
"error": { "code": "recipient_not_found", "message": "Could not send: recipient not found." }
}A sandbox label’s messages are swept clean on a schedule, so an agent can be pointed at one and the whole thing deleted afterwards with nothing retained. Sandbox deliveries count against your monthly quota like any other. Its keys are visibly different — as_test_… instead of as_live_… — so a test key cannot quietly end up in production.
A schedule sends the same message on a rule instead of when you call us. Describe it in plain language — every weekday at 09:00, the 1st of every month, every two hours — or give a cron expression if you already think that way. A timezone is required, so 09:00 stays 09:00 through a daylight saving change rather than drifting by an hour.
curl -X POST https://api.agentsignal.net/v1/schedules \
-H "Authorization: Bearer $AGENTSIGNAL_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Standup nudge",
"kind": "recurring",
"timezone": "America/New_York",
"freq": "weekly",
"days_of_week": [1, 2, 3, 4, 5],
"at_time": "09:00",
"payload": { "to": "u_...", "body": "standup in 5" }
}'GET /v1/schedules/:id/preview reads the rule back as actual dates, which is the fastest way to notice you picked the wrong day. A rule that would never fire — a one-off already past, the 30th of February — is refused when you write it rather than silently never arriving. GET /v1/schedules/:id/runs shows what each run actually did, so a schedule that has been failing quietly is visible.
Each run carries an idempotency key derived from its slot, so a schedule cannot notify anyone twice for the same due time.
An agent with a wallet and no AgentSignal account can send by paying for it, in USDC, over x402. No key, no signup. The label owner turns this on per label; while it is off, the endpoint answers 404 for their recipient keys.
Send without payment and you get a 402 describing exactly what it costs.
curl -X POST https://api.agentsignal.net/v1/x402/messages \
-H "Content-Type: application/json" \
-d '{"to":"u_8fk2…","body":"build finished"}'HTTP/1.1 402 Payment Required
{
"x402Version": 1,
"error": "This send costs $0.0010.",
"accepts": [{
"scheme": "exact",
"network": "base",
"maxAmountRequired": "1000",
"asset": "0x8335…2913",
"payTo": "0x…"
}]
}Pay it and repeat the request with an X-PAYMENT header. Any x402 client does this for you — with x402-fetch the whole thing is one call:
import { wrapFetchWithPayment } from 'x402-fetch';
import { privateKeyToAccount } from 'viem/accounts';
const pay = wrapFetchWithPayment(fetch, privateKeyToAccount(KEY));
await pay('https://api.agentsignal.net/v1/x402/messages', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ to: 'u_8fk2…', body: 'build finished' }),
});Notifications cost $0.0002 per device the person has registered. Payments below $0.001 cost more in gas than they collect, so that is the minimum a 402 will ask for — and the difference is not kept. It stays as credit on the label and pays for the next sends, which go through with no payment round trip at all.
Ask what a send costs, without committing to one:
curl https://api.agentsignal.net/v1/x402/quote/u_8fk2…{ "ok": true, "devices": 3, "cost": "$0.0006", "credit_micros": 800 }Paid sends address exactly one person. Groups and broadcasts are not payable, so being given one recipient key never buys the ability to reach a whole label. Idempotency-Key works here too, and a replay is free — a retrying agent is never charged twice for one notification.
People do not need an account. Create a person in the dashboard, hit Pair a device, and they scan the QR or type the eight characters into the app. Codes are single-use and expire in fifteen minutes.
The QR encodes https://agentsignal.net/enroll/CODE. The system camera opens it straight into the app, and on a phone without the app it opens a page showing the code instead of failing.