Board Tool direct HTTP API

Build a server, script, or integration that calls Board Tool operations directly over HTTP.

The direct API is implemented and secured, but does not yet have MCP's one-URL onboarding. Obtain credentials and the exact token and API URLs for your environment from Budgee. No production URL is published here.

Authentication and consent

A signed-in Budgee user creates a narrowly scoped external credential through the authenticated boardToolAuthApi Firebase callable. Creation requires externalDataConsent: true, explicit boardIds, and explicit scopes.

const authApi = httpsCallable(
  getFunctions(firebaseApp, "us-central1"),
  "boardToolAuthApi",
);

const { data } = await authApi({
  operation: "credentials.create",
  params: {
    type: "api-key",
    externalDataConsent: true,
    name: "Monthly reporting integration",
    boardIds: ["board_123"],
    scopes: ["board:read", "transactions:read", "reports:read"],
  },
});

The API key appears only when created or rotated. Store it in a secret manager. Rotation immediately invalidates the old key and its access tokens; listing cannot recover the key. Credential operations also include credentials.list, credentials.rotate, and credentials.revoke.

curl --request POST "$BOARD_TOOL_TOKEN_URL" \
  --header "Content-Type: application/json" \
  --data '{"grant_type":"api_key","api_key":"<budgee_sk_...>"}'
{
  "access_token": "<short-lived-token>",
  "token_type": "Bearer",
  "expires_in": 900,
  "scope": "board:read transactions:read reports:read"
}

Access tokens last 60–900 seconds; 900 is the default maximum. API-key exchange returns no refresh token, so exchange the key again when needed. MCP resource-bound tokens cannot be reused against this API and fail with detail code access-token-resource-mismatch.

Boards, scopes, and roles

Every request is limited by the token's user, exact board list, and exact scopes. Board Tool then rechecks the user's current role and whether the board is writable. Removing a role, revoking a credential, or making a board read-only takes effect while an access token still has time remaining.

Start read-only. Add a write scope only for its matching preview workflow, and add preview:apply only when the integration can present and preserve explicit human approval. Call board.capabilities after authentication instead of assuming every operation is available.

Request and response

curl --request POST "$BOARD_TOOL_HTTP_URL" \
  --header "Authorization: Bearer $BOARD_TOOL_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data '{
    "boardId": "board_123",
    "operation": "board.getSummary",
    "params": {},
    "requestId": "summary-2026-08-02-01"
  }'
  • boardId is required for board-scoped operations and must be in the grant.
  • operation is an exact registered operation name.
  • params must match the strict operation schema; unknown fields fail.
  • requestId is an optional, unique, non-secret correlation id.
  • idempotencyKey is for apply operations only.
{
  "ok": true,
  "version": "1.0",
  "operation": "board.getSummary",
  "requestId": "summary-2026-08-02-01",
  "boardId": "board_123",
  "boardFingerprint": "<sha256>",
  "readBasis": {
    "kind": "board-tool-read",
    "coverage": "partial",
    "capturedAt": "<server timestamp>"
  },
  "data": {}
}

The illustration abbreviates readBasis. Accept the current response schema rather than comparing it byte-for-byte.

Pagination

For transactions.query, send pageSize from 1 to 200 with a filter and sort. When data.nextCursor is present, send it as params.cursor with the same filter and sort. Cursors are opaque and bound to that query and read-model generation. After read-model-transaction-cursor-stale, restart at page one without the cursor.

Preview, approval, apply, and readback

Create a preview with the operation matching the change. Show the human its previewId, previewHash, boardFingerprint, expiry, warnings, assumptions, proposed mutations, and apply instruction. After explicit approval, use the operation named by approvalCard.apply.operation.

{
  "boardId": "board_123",
  "operation": "previews.apply",
  "requestId": "forecast-apply-1",
  "idempotencyKey": "forecast-apply-6fdb6c88-5074-42a8-a73d-a9b6aee8c49f",
  "params": {
    "previewId": "<preview-id>",
    "expectedPreviewHash": "<preview-hash>",
    "expectedBoardFingerprint": "<preview-board-fingerprint>",
    "confirmApply": true
  }
}

Use one unique idempotency key per logical apply. If transport fails before any structured result, retry only the same request, preview, and key. Never reuse a key for another preview. Poll queued work to a verified terminal state, and reconcile outcome-unknown or applied-unverified before any further apply.

Freshness

Direct HTTP uses bounded server reads. When the read model cannot prove current evidence, a read can fail with HTTP 412 and read-model-required, or return explicit catch-up status. Do not substitute cached client data. Wait for the authorized catch-up path and retry.

Errors, limits, and retries

HTTP errors use a stable envelope. error.code is the broad machine category; when present, error.details.code gives the specific recovery reason.

{
  "ok": false,
  "error": {
    "code": "failed-precondition",
    "message": "Preview has expired.",
    "details": {
      "code": "preview-expired"
    }
  }
}
StatusMeaningSafe response
400Invalid request, schema, operation, or cursorFix it; do not retry unchanged
401Missing, invalid, expired, or rotated tokenExchange a valid credential
403Scope, board, role, rollout, or revocation denialCorrect access; do not loop
404Entity, preview, audit, or run absentRe-resolve or recreate
405Method is not POSTUse POST
412Stale preview, changed source, missing confirmation, or unavailable read modelFollow details.code
429Rate, plan, page, range, preview, or cost limitWait until resetAt or narrow
503Temporary unavailabilityRetry reads with bounded backoff
500Redacted internal failureKeep requestId; reconcile writes

Default named-operation limits are 120 calls per minute for reads and 40 for writes, keyed to user, credential, board, and operation. Token exchange separately defaults to 30 per IP per minute and 120 per IP per 15 minutes. Plan or environment configuration can change these values, so returned max and resetAt fields are authoritative.

Never log bearer tokens, API keys, client secrets, or board content as a correlation id.

Where to go next