03 · GUIDES

Build your own dashboard

Everything the Layrcake console shows is available to you as JSON. Read what your agents did, restrict them when you need to, and preview any action before it happens. Three rules: read with any key, restrict with a control:manage key, loosen only as a signed-in owner in the console.

1

Read: who am I, what happened, who replied

GET /v1/me tells a key what it is allowed to do and how much room it has left today: monthly spend cap and spend to date, the workspace's daily send cap and what launches have used, reply ceilings, autonomy. Free, any key. Agents should call it first.

curl
curl https://api.layrcake.dev/v1/me \
  -H "Authorization: Bearer cake_sk_live_…"

GET /v1/trail is your workspace's outcome record: one event per moment a machine touched a human on your behalf — admitted, removed (with the reason), launch_requested / launched, reply, bounce, stop, approval_requested / approved / declined, pause_requested / paused and more. Newest first, typed per kind, never a provider's raw payload. Requires the trail:read scope, which only a workspace owner can grant to a key (it is never issued through OAuth).

curl
curl "https://api.layrcake.dev/v1/trail?kind=reply&kind=stop&since=2026-09-01T00:00:00Z&limit=100" \
  -H "Authorization: Bearer cake_sk_live_…"

{ "result": {
    "events": [
      { "id": "…", "kind": "reply", "observed_at": "2026-09-15T09:12:41.020Z", "source": "webhook",
        "campaign_id": "…", "campaign_lead_id": "…", "reply_id": "…", "request_id": null, "operation_id": null,
        "email": "ada@example.com", "detail": { "classification": "interested" } },
      …
    ],
    "next_cursor": "eyJ3Ijoi…",
    "evaluated_at": "2026-09-16T08:00:00.000Z" } }
  • Filters: campaign_id, email (resolved to your own subject; unknown addresses match nothing), kind (repeat or comma-separate), since, until, limit (≤ 200).
  • Pass next_cursor back as cursor to page. The cursor is opaque and bound to your workspace and your filters; reusing it with different filters is a 400.
  • email is present while the person's address is retained (24 months after last seen, or until erased) and omitted afterwards; the event itself stays.
  • Reads are recorded as read-mode requests: they show in your audit but never as billable calls.

For the full text of a reply use get_replies / get_reply (see the reference) — the trail carries only the fact and its classification.

2

Restrict: a key that can only tighten

Create a key with the control:manage scope in the console (owners only). Such a key can make other keys safer and can stop sending, but it can never loosen anything and can never edit itself.

curl
# Lower another key's monthly cap, drop launch rights, turn autonomy off
curl -X PATCH https://api.layrcake.dev/v1/keys/<key_id> \
  -H "Authorization: Bearer cake_sk_live_…control…" \
  -H "Idempotency-Key: 7c1e…" \
  -H "Content-Type: application/json" \
  -d '{ "monthly_spend_cap": "50", "scopes": ["data:read","usage:read"], "autonomous_external_actions": false }'

# Stop all sending for the workspace (provider-verified pause)
curl -X POST https://api.layrcake.dev/v1/kill \
  -H "Authorization: Bearer cake_sk_live_…control…" \
  -H "Idempotency-Key: 9a2f…" \
  -d '{ "reason": "runaway loop detected" }'
ChangeFrom a keyFrom the console
Lower a cap or limit, drop a scope, turn autonomy offallowed (any member too)allowed
Raise a cap, add a scope, turn autonomy on403 owner_requiredsigned-in owner only
Grant control:manage or trail:read403signed-in owner only
Edit the key's own policy403 self_modification
Kill sendingallowedowner
Clear a suspensionnot available to keyssigned-in owner only

Every change is one row in the control log with who did it and which way it went; the same Idempotency-Key with the same body replays the stored result, a different body is a 409. Killing does not revoke keys and cannot recall an email the provider already accepted; paused campaigns do not restart when the suspension is cleared — rebuild them as new drafts.

3

Preview: dry_run

Add "dry_run": true to setup_sending, create_campaign, add_leads, launch_campaign, reply_to_thread or set_lead_status and you get the plan instead of the action: what would happen, who would be removed and why, the credits estimate, and every limit the real call would trip. Nothing is reserved, nothing is sent, no confirmation is created; the only trace is a zero-credit audit row. It is a point-in-time estimate — call again right before you act.

json
{ "result": { "dry_run": true, "evaluated_at": "2026-09-16T08:00:00.000Z",
    "plan": { "verb": "launch_campaign", "plan_version": 1, "input_version": "…",
              "would": { "mailboxes_attached": 3, "leads_active": 480, "leads_suppressed_at_launch": 20,
                         "removed": { "opted_out": 12, "platform_blocked": 3, "invalid": 5, "capped": 0, "other": 0, "total": 20 },
                         "daily_send_limit": 120, "needs_confirmation": true },
              "credits_estimate": "0", "would_exceed": [], "warnings": [] } } }

dry_run together with a confirmation_token is refused — preview first, then approve, then act.

4

Loosen: only in the console

Raising a cap, adding a scope, turning autonomy on, granting control:manage or trail:read, and clearing a suspension all require a signed-in owner in the console. There is deliberately no API for any of them: a compromised or over-eager agent can only ever make your workspace safer.