Briefkin docs
Agents send one JSON event per meaningful update to POST /v1/events. Each event creates or updates a card on your dashboard. Anything that can run curl can report.
Quickstart
- Sign in with GitHub. A new workspace starts a 14-day free trial; no card needed.
- On the dashboard, under Connect your first agent, name the agent and press Create token. The token (
bk_…) is shown once; only a hash is stored. You can create more tokens and revoke them in Settings. Use one per agent or machine. - Put the token in your shell and send a first card:
export BRIEFKIN_TOKEN=bk_your_token
# Optional. Defaults to https://briefkin.dev
export BRIEFKIN_URL=https://briefkin.devcurl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
-H "authorization: Bearer $BRIEFKIN_TOKEN" \
-H "content-type: application/json" \
-d '{"v":1,"event_id":"first-card","item_key":"hello","project":"Briefkin","kind":"update","title":"Hello from my agent"}'The response is 202 with the card id, and the card appears on the dashboard within seconds:
{"accepted":true,"duplicate":false,"card_id":"5f0c…","stale":false}Claude Code
The Briefkin skill tells Claude Code when to report (finished work, PRs, blockers, things you must do) and when not to (routine steps). Install it for all projects:
mkdir -p ~/.claude/skills/briefkin
curl -sS https://briefkin.dev/skill.md -o ~/.claude/skills/briefkin/SKILL.mdFor a single repository, put it in .claude/skills/briefkin/SKILL.md inside the repo instead. The skill reads two environment variables:
BRIEFKIN_TOKENrequired- The agent token from Settings.
BRIEFKIN_URL- API base URL. Defaults to
https://briefkin.dev.
Export them in the shell profile you start Claude Code from, or add them to ~/.claude/settings.json:
{
"env": {
"BRIEFKIN_TOKEN": "bk_your_token"
}
}Read the skill itself at /skill.md. Other agents (Codex, scripts, n8n, anything with a shell or an HTTP client) can follow the same file or use the curl calls below.
Card types, with curl
Every example reads BRIEFKIN_TOKEN and optionally BRIEFKIN_URL from the environment. The same item_key always updates the same card; a new key makes a new card.
Update: finished work, with links
For results that need no action. The card goes to Activity.
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
-H "authorization: Bearer $BRIEFKIN_TOKEN" \
-H "content-type: application/json" \
-d @- <<'JSON'
{
"v": 1,
"event_id": "acme-web/pr-128/merged",
"item_key": "acme-web/checkout-redesign",
"project": "acme-web",
"kind": "update",
"title": "Checkout redesign merged and deployed",
"summary": "PR #128 merged. Production deploy finished and the smoke tests passed.",
"work_state": "done",
"links": [
{ "label": "PR #128", "url": "https://github.com/acme/web/pull/128" },
{ "label": "Deploy log", "url": "https://ci.example.com/acme/web/runs/981" }
]
}
JSONAction: something only you can do
The card goes to Needs you and stays there until you resolve it, and Telegram pings you if it is connected. instructions is shown as preformatted text, so put commands there.
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
-H "authorization: Bearer $BRIEFKIN_TOKEN" \
-H "content-type: application/json" \
-d @- <<'JSON'
{
"v": 1,
"event_id": "acme-api/migration-0042/needed",
"item_key": "acme-api/migration-0042",
"project": "acme-api",
"kind": "action",
"title": "Production migration needed before deploy",
"summary": "The release is built and waiting at the migration step.",
"work_state": "blocked",
"action": {
"request": "Run migration 0042 on production",
"why": "The new release reads orders.status_v2, which the migration adds.",
"instructions": "pnpm db:migrate:remote"
},
"links": [
{ "label": "Migration", "url": "https://github.com/acme/api/blob/main/migrations/0042_status_v2.sql" }
]
}
JSONIf the agent sees the work done before you resolve the card, it can send a newer update on the same item_key. The card leaves Needs you:
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
-H "authorization: Bearer $BRIEFKIN_TOKEN" \
-H "content-type: application/json" \
-d @- <<'JSON'
{
"v": 1,
"event_id": "acme-api/migration-0042/applied",
"item_key": "acme-api/migration-0042",
"project": "acme-api",
"kind": "update",
"title": "Migration 0042 applied, deploy finished",
"work_state": "done"
}
JSONMetric: a number for a period
The card shows the latest value, the change from the previous period and a small chart of the history. Metrics never ping.
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
-H "authorization: Bearer $BRIEFKIN_TOKEN" \
-H "content-type: application/json" \
-d @- <<'JSON'
{
"v": 1,
"event_id": "acme-shop/signups/2026-W40",
"item_key": "acme-shop/signups-weekly",
"project": "acme-shop",
"kind": "metric",
"title": "128 signups last week",
"metric": {
"label": "Signups",
"value": 128,
"period_start": "2026-09-28T00:00:00Z",
"period_end": "2026-10-05T00:00:00Z"
}
}
JSONDaily metric from cron
This script posts “example.com had N visitors yesterday” once a day. Save it as /usr/local/bin/briefkin-visitors.sh, make it executable, and replace visitors_on with your real query.
#!/usr/bin/env bash
# Posts yesterday's visitor count to Briefkin. Run it once a day from cron.
set -euo pipefail
SITE="example.com"
DAY=$(date -u -d yesterday +%F 2>/dev/null || date -u -v-1d +%F) # GNU date, then BSD/macOS
TODAY=$(date -u +%F)
# Replace with your analytics query (Plausible, GA4, SQL...). It must print one number.
visitors_on() { echo 456; }
VISITORS=$(visitors_on "$DAY")
curl -sS --fail-with-body --retry 3 -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
-H "authorization: Bearer $BRIEFKIN_TOKEN" \
-H "content-type: application/json" \
-d @- <<JSON
{
"v": 1,
"event_id": "$SITE/visitors/$DAY",
"item_key": "$SITE/visitors",
"project": "$SITE",
"kind": "metric",
"title": "$SITE had $VISITORS visitors yesterday",
"occurred_at": "${TODAY}T00:00:00Z",
"metric": {
"label": "Visitors",
"value": $VISITORS,
"period_start": "${DAY}T00:00:00Z",
"period_end": "${TODAY}T00:00:00Z"
}
}
JSONThen add it to your crontab (here, every day at 06:15):
BRIEFKIN_TOKEN=bk_your_token
15 6 * * * /usr/local/bin/briefkin-visitors.shitem_keyis the same every day, so the site has one card that updates daily and keeps the history as a chart.event_idincludes the date, so a retry or a second run for the same day changes nothing. That is what makes--retry 3safe.occurred_atis the end of the period. If you re-run an older day later, its point lands on the chart but it does not replace a newer day’s card.--fail-with-bodymakes the script exit non-zero on an error, so cron reports it.- Metrics stay quiet. If a number needs you (a site at zero visitors, a failed backup), send an action card instead.
Telegram alerts
Connect Telegram in Settings with Connect Telegram, then press Start in the bot chat. An alert has the project, the title, the request and a link to the card. Only action cards ping:
Pings
- A new action card.
- An action card you resolved, when the agent sends a newer action for it (it reopens).
- An existing update or metric card that turns into an action.
- An open action that is raised to
"urgency": "high".
Does not ping
- Update and metric cards, including
blockedandfailedupdates. Those go to Needs you quietly. - Further updates to an action that is still open, unless they raise it to urgent.
- Duplicate event ids and stale revisions.
- Anything you do in the dashboard, such as reopening a card.
Failed sends are retried with backoff and logged.
API reference (v1)
Base URL https://briefkin.dev. Every request needs authorization: Bearer <token>. A token belongs to one workspace and can only see that workspace’s cards.
POST /v1/events
The body is one JSON event with content-type: application/json.
v1required- Contract version. Always 1.
event_idstringrequired- Unique per update from one token. Sending the same event_id again changes nothing, so retries are safe. Derive it from what happened (for example site/visitors/2026-10-07) or use a UUID. max 128 characters.
item_keystringrequired- Stable key for the task or metric, for example repo/feature-name. Every update with the same item_key replaces the same card. max 200 characters.
projectstringrequired- Project or source name shown on the card. max 100 characters.
kind"update" | "action" | "metric"required- update: finished work or news. action: the user must do something. metric: a number for a period.
titlestringrequired- One short line. max 200 characters.
summarystring- Details under the title. max 2000 characters.
work_state"in_progress" | "done" | "blocked" | "failed"- blocked and failed put the card in Needs you even when it is not an action. default "done".
urgency"low" | "normal" | "high"- high makes an action card urgent (red) and pings again if it was not urgent before. default "normal".
revisioninteger- Ordering for updates to one item_key. A card only changes when the revision is higher than the stored one. Defaults to occurred_at in milliseconds. 0 or more.
occurred_atISO 8601 date-time- When it happened, ISO 8601 with a timezone, for example 2026-10-08T09:30:00Z. Defaults to when Briefkin received it.
actionobject- Required when kind is action.
action.requeststringrequired- What the user should do, as an instruction. max 500 characters.
action.whystring- Why it matters or what it unblocks. max 1000 characters.
action.instructionsstring- Commands or steps, shown as preformatted text. max 4000 characters.
metricobject- Required when kind is metric.
metric.labelstringrequired- What is measured, for example Visitors. max 100 characters.
metric.valuenumberrequired- The number for the period.
metric.unitstring- Shown after the value, for example ms or EUR. max 20 characters.
metric.period_startISO 8601 date-timerequired- Start of the period, ISO 8601.
metric.period_endISO 8601 date-timerequired- End of the period, ISO 8601. The chart keeps one point per period_end; a new event for the same period replaces it.
linksarray of objects- Evidence: PRs, deploys, reports. max 10 items, default [].
links[].labelstringrequired- Link text, for example "Review PR". max 80 characters.
links[].urlURLrequired- An http or https URL.
Responses
- 202
{"accepted": true, "duplicate": false, "card_id": "…", "stale": false} - Stored. stale is true when the card already has a higher revision: the event is kept in the history, the card is unchanged.
- 200
{"accepted": true, "duplicate": true, "card_id": "…", "stale": false} - This token already sent this event_id. Nothing changed.
- 400
{"error": "invalid_event", "issues": {…}} - The body is not valid JSON or does not match the contract. issues lists the failing fields. Fix it and send again; retrying unchanged will fail again.
- 401
{"error": "unauthorized"} - Missing, wrong or revoked token.
- 5xx
or a network error - Retry with the same event_id. If the first attempt was stored, the retry returns 200 with duplicate: true.
A 400 looks like this (here, an empty title):
{"error":"invalid_event","issues":{"errors":[],"properties":{"title":{"errors":["Too small: expected string to have >=1 characters"]}}}}Idempotency
event_id is unique per token. The first event with a given id is stored; any later event with the same id from the same token is ignored and answered with 200 and "duplicate": true, even if its body differs. Retrying a request is always safe.
Revisions and the stale rule
Each item_key has one card. An event changes the card only if its revision is higher than the card’s current one; otherwise it is stored but answered with "stale": true, so an older event can never overwrite a newer card.
revisiondefaults tooccurred_atin milliseconds, andoccurred_atdefaults to the time Briefkin received the event. Events sent in order just work.- When events can arrive out of order (parallel workers, backfills), set
occurred_ator an explicit, increasingrevision. - Pick one scheme per
item_key. A small explicit revision such as3is always lower than a default, timestamp-based one. - Metric history points are saved even when the event is stale, so backfills fill in the chart.
- A newer action, blocked or failed event reopens a card you resolved. Agents cannot resolve cards; you do that in the dashboard.
GET /v1/cards/:item_key
Returns the current state of one card in the token’s workspace, or 404 {"error": "not_found"}. URL-encode the key (/ becomes %2F). Agents use it to check whether you have handled an action: resolved_at is a timestamp in milliseconds once you resolve it, and null before.
curl -sS "${BRIEFKIN_URL:-https://briefkin.dev}/v1/cards/acme-api%2Fmigration-0042" \
-H "authorization: Bearer $BRIEFKIN_TOKEN"{
"id": "0b6e…",
"item_key": "acme-api/migration-0042",
"kind": "action",
"title": "Production migration needed before deploy",
"work_state": "blocked",
"attention": "needs_you",
"revision": 1791480000000,
"resolved_at": null,
"updated_at": 1791480000000
}attention is none, needs_you or urgent. Times are milliseconds since the Unix epoch.