API · 7 operations

Events and webhooks

The change feed, live stream and webhook endpoints.

get /v1/events

What changed on this account: after a cursor, or filtered and newest first

scope budget:read · listEvents

Parameters

  • after query · string

  • before query · string

  • limit query · integer · ≥ 1 · ≤ 100

  • order query · string

  • type query · string

  • since query · string

  • until query · string

  • kind query · string

  • actor_kind query · string

  • actor query · string

  • q query · string

  • budget_id query · string

Returns · 200

{ events: [{ id, event, kind, budget_id, actor: { kind, principal, name }, data, created_at }], next_after, next_before, more }. One row per domain event, with the same event names and data the webhooks deliver (transaction.created, budget.updated with a reason, connection.linked, connection.reauth_required with status saying why a relink is needed and, on the reminder a week before a blocked bank is removed, reminder: true and disconnects_at). kind says what kind of change it is (the kinds the kind filter takes). An edit's data.changes is { field: { from, to } } for each field it really moved (money in cents). actor.kind is agent (an API key, name is the key's name), person (the app) or system (a bank sync, an auto-entered bill). Rows are kept ninety days. Without after, before or since, a read covers the last 24 hours. Pass after = the last id you saw to read only what is new; next_after is the id to pass next time. order=desc reads the newest first (what the app's Activity screen shows); page back with before = next_before while more is true. Ids are ULIDs and sort by time. Filters, all optional and combined: since and until (ISO instants), kind (comma separated: money, transactions, bills, envelopes, bank, accounts, goals, payees, settings, security), actor_kind (agent, person, system), actor (one principal), q (words, an agent's name, or the name of an envelope, category, account, payee, bill, asset, loan, goal, scenario or transaction the row touched) and budget_id (that budget's rows and the rows that name no budget). Use this to learn what your human changed while you were away, or to wait for a bank sync to land, instead of re-reading the whole budget. type= filters by name prefix: type=security. reads the security trail: refusals (security.auth_failed with status, reason, path, a salted ip_hash, and suppressed for a burst folded into one row per minute), keys minted and revoked, banks unlinked, exports, webhook endpoints, household membership changes.

Example

curl 'https://api.mintro.money/v1/events' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

get /v1/events/summary

How much changed, who changed it, and what needs a look

scope budget:read · getEventsSummary

Parameters

  • since query · string

  • until query · string

  • kind query · string

  • actor_kind query · string

  • actor query · string

  • q query · string

  • budget_id query · string

  • tz query · string

Returns · 200

{ total, by_day: [{ day, agent, person, system }], by_kind: { money, transactions, bills, envelopes, bank, accounts, goals, payees, settings, security }, actors: [{ kind, principal, name, count, last_at }], last_24h: { total, agent, person, system }, last_7d: { total, agent, person, system }, oldest_at, needs_look: { bank_rows_waiting, banks_need_relink, refused_7d } }. Takes the same filters as GET /v1/events, plus tz (an IANA zone, UTC by default). total counts the rows every filter matches, and by_day the same rows a calendar day at a time in tz, by who made them; each by_kind count keeps every filter but the kind, and actors every filter but who, so a screen can show what each choice would hold; each actor's last_at is when its newest matching change was made, which tells apart two keys given the same name. last_24h and last_7d count the budget's changes (security rows left out) whatever the other filters say. oldest_at is the oldest row kept: how far back a read can go. needs_look counts bank rows waiting for the person to decide, bank connections that need signing in again, and requests refused in the last week. Counts only; no money.

Example

curl 'https://api.mintro.money/v1/events/summary' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

get /v1/events/stream

The same rows as they happen, as Server-Sent Events

scope budget:read · streamEvents

Parameters

  • after query · string

Returns · 200

text/event-stream. Each message is id: <event id>, event: <event name>, data: <the same JSON row GET /v1/events returns>; a : ping comment every 25s keeps the connection warm. Send Last-Event-ID (or ?after=) to resume after what you last saw; without it the stream starts from now and replays nothing. Served from the live host, not the API host: the same bearer, one connection per open screen. An MCP client cannot hold a stream; poll GET /v1/events with after instead. 503 with Retry-After when the machine is full or the API cannot be reached.

Example

curl 'https://api.mintro.money/v1/events/stream' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

post /v1/webhook-endpoints

Register an endpoint (secret returned once)

scope webhooks:manage · plan feature webhooks · createWebhookEndpoint

Body · JSON

  • url string · required · URL · ≤ 2000 chars

  • events string[] · required · ≤ 20 items

Returns · 201

The endpoint plus secret, which appears in this response and never again. Store it; it signs every delivery.

Example

curl -X POST 'https://api.mintro.money/v1/webhook-endpoints' \ -H "Authorization: Bearer $MINTRO_KEY" \ -H 'Content-Type: application/json' \ -d '{ "url": "https://example.com/mintro", "events": [ "<events>" ] }'

Errors · besides 401, 429, 500

  • 400

    validation_failed: The request body or query is malformed. The message names the offending field; fix it and retry.

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

get /v1/webhook-endpoints

List endpoints with their delivery health

scope webhooks:manage · plan feature webhooks · listWebhookEndpoints

Returns · 200

{ endpoints[] }, each with active, consecutive_failures, last_delivery_at, last_status (the receiver's HTTP status, or null when the attempt never got one), last_latency_ms and last_error. This is how you check whether a test or a real event actually landed.

Example

curl 'https://api.mintro.money/v1/webhook-endpoints' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

delete /v1/webhook-endpoints/{id}

Delete an endpoint

scope webhooks:manage · plan feature webhooks · deleteWebhookEndpoint

Parameters

  • id path · string · required

Returns · 200

{ deleted: true }.

Example

curl -X DELETE 'https://api.mintro.money/v1/webhook-endpoints/<id>' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.

post /v1/webhook-endpoints/{id}/test

Queue a ping event and show exactly what will be delivered

scope webhooks:manage · plan feature webhooks · testWebhookEndpoint

Parameters

  • id path · string · required

Returns · 200

{ queued: true, event_id, payload, signature: { header, format, signed_string } , verify: { ... }, check_delivery }. Delivery is asynchronous (Inngest, with retries), so the response cannot carry the receiver's answer: poll GET /v1/webhook-endpoints and read last_status, last_latency_ms and last_error for that outcome.

Example

curl -X POST 'https://api.mintro.money/v1/webhook-endpoints/<id>/test' \ -H "Authorization: Bearer $MINTRO_KEY"

Errors · besides 401, 429, 500

  • 402

    plan_limit: The tenant's plan does not allow this. Read entitlements on GET /v1/me to see what the plan unlocks and allows, report the limit to the human, and let them choose to upgrade at https://mintro.money/pricing; do not retry.

  • 403

    forbidden: The key is valid but lacks the scope this call needs. Ask the human to mint a key with the scope named in the message.

  • 404

    not_found: No such record on this budget. Re-read GET /v1/budgets/{id} (or the relevant list) for current ids rather than reusing a remembered one.

  • 409

    idempotency_conflict: This Idempotency-Key was used for a different request, or the first request is still in flight. Retry the in-flight case after a second; use a fresh key for a genuinely different request.