briefkin

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

  1. Sign in with GitHub. A new workspace starts a 14-day free trial; no card needed.
  2. 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.
  3. 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.dev
curl -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.md

For a single repository, put it in .claude/skills/briefkin/SKILL.md inside the repo instead. The skill reads two environment variables:

BRIEFKIN_TOKEN required
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" }
  ]
}
JSON

Action: 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" }
  ]
}
JSON

If 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"
}
JSON

Metric: 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"
  }
}
JSON

Daily 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"
  }
}
JSON

Then add it to your crontab (here, every day at 06:15):

BRIEFKIN_TOKEN=bk_your_token
15 6 * * * /usr/local/bin/briefkin-visitors.sh

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

Does not ping

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.

v 1required
Contract version. Always 1.
event_id stringrequired
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_key stringrequired
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.
project stringrequired
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.
title stringrequired
One short line. max 200 characters.
summary string
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".
revision integer
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_at ISO 8601 date-time
When it happened, ISO 8601 with a timezone, for example 2026-10-08T09:30:00Z. Defaults to when Briefkin received it.
action object
Required when kind is action.
action.request stringrequired
What the user should do, as an instruction. max 500 characters.
action.why string
Why it matters or what it unblocks. max 1000 characters.
action.instructions string
Commands or steps, shown as preformatted text. max 4000 characters.
metric object
Required when kind is metric.
metric.label stringrequired
What is measured, for example Visitors. max 100 characters.
metric.value numberrequired
The number for the period.
metric.unit string
Shown after the value, for example ms or EUR. max 20 characters.
metric.period_start ISO 8601 date-timerequired
Start of the period, ISO 8601.
metric.period_end ISO 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.
links array of objects
Evidence: PRs, deploys, reports. max 10 items, default [].
links[].label stringrequired
Link text, for example "Review PR". max 80 characters.
links[].url URLrequired
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.

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.