Figa

Developer API

External integrations authenticate via x-api-key. API keys are workspace-scoped with granular permission levels per resource.

Authentication

Every request must include the x-api-key header with a valid key. Keys are generated in workspace settings and scoped to a single workspace.

curl -X GET https://app.figa.io/api/v1/expenses?year=2026&month=2 \
  -H "x-api-key: et_live_abc123..."

Generate API key in workspace settings

Capture create-key modal with read/write preset selection and one-time key reveal.

Suggested file: /help/screenshots/developers/api-key-create.png

Placeholder. Replace with real screenshot once flow is finalized.

Permission model

Each API key has per-resource permissions. Available actions per resource:

Resource Actions
expensesread, write, delete, payments
categoriesread, write, delete
recipientsread, write, delete
workspacesread

Categories

GET /api/v1/categories

List categories (workspace + global).

POST /api/v1/categories

Create category. Requires categories.write.

PUT /api/v1/categories/:id

Update category name/description/color.

DELETE /api/v1/categories/:id

Delete category. Requires categories.delete.

Recipients

GET /api/v1/recipients

List recipients (workspace + global).

POST /api/v1/recipients

Create recipient. Requires recipients.write.

PUT /api/v1/recipients/:id

Update recipient name/description.

DELETE /api/v1/recipients/:id

Delete recipient. Requires recipients.delete.

Expenses

GET /api/v1/expenses?year=YYYY&month=MM

List expenses for month. year/month query required.

POST /api/v1/expenses

Create expense. Add schedule to create recurring. amount is integer cents (14250 = 142.50).

GET /api/v1/expenses/:expenseId

Get expense by ID. Add ?occurrence=unixSeconds for recurring occurrence view.

PUT /api/v1/expenses/:expenseId

Update expense fields. Use editScope for recurring series.

DELETE /api/v1/expenses/:expenseId

Delete expense. Use ?editScope for recurring. Requires expenses.delete.

POST /api/v1/expenses/:expenseId/skip/toggle

Toggle skip for recurring occurrence (scheduledDate: unix seconds or YYYY-MM-DD).

GET /api/v1/expenses/:expenseId/history

Expense audit history.

GET /api/v1/expenses/monthly-totals

Aggregated monthly totals.

POST /api/v1/expenses/bulk

Bulk actions (e.g. markAsPaid). Returns HTTP 207.

Payments

GET /api/v1/expenses/:expenseId/payments

List payments for expense.

POST /api/v1/expenses/:expenseId/payments

Add payment. Requires expenses.payments.

DELETE /api/v1/expenses/:expenseId/payments

Remove all payments from expense.

DELETE /api/v1/expenses/:expenseId/payments/:paymentId

Remove single payment.

Insights

Deterministic analytics endpoints — aggregations, trends and forecasts computed from expense data. All require expenses.read permission. See Insights reference for full query parameters, response schemas and curl examples.

GET /api/v1/insights/summary

Spending summary for period.

GET /api/v1/insights/comparison

Period-over-period comparison.

GET /api/v1/insights/trends

Spending trends over time.

GET /api/v1/insights/anomalies

Anomaly detection in spending.

GET /api/v1/insights/forecast

Spending forecast.

Context & Notifications

GET /api/v1/context

Workspace metadata, plan tier, limits. Requires workspaces.read.

GET /api/v1/notifications

List notifications. Requires workspaces.read.

GET /api/v1/notifications/unread-count

Unread notification count.

Create expense — request body

POST /api/v1/expenses · Permission: expenses.write

Field Type Required Description
namestringYesExpense name, 1–200 characters.
amountintegerYesAmount in cents (minor units). 14250 = 142.50. Must be ≥ 1.
categoryInputstring | UUIDYesExisting category UUID or a new category name (auto-created).
recipientInputstring | UUID | nullNoExisting recipient UUID, new name (auto-created), or null.
descriptionstring | nullNoFree-text note, max 1000 characters.
expenseDateintegerNoUnix timestamp in seconds. Defaults to now.
currencystringNoISO 4217 code (PLN, USD, EUR). Defaults to workspace base currency.
scheduleobject | nullNoInclude to make expense recurring. See Schedule reference.

categoryInput / recipientInput

These fields accept either an existing UUID or a plain-text name. When a name is provided and no matching category/recipient exists, one is created automatically. This simplifies integrations that don't want to pre-create lookup data.

Minimal one-time expense

curl -X POST https://app.figa.io/api/v1/expenses \
  -H "x-api-key: et_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "AWS June invoice",
    "amount": 14250,
    "categoryInput": "Cloud"
  }'

Full one-time expense

curl -X POST https://app.figa.io/api/v1/expenses \
  -H "x-api-key: et_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Office rent February",
    "amount": 420000,
    "categoryInput": "Housing",
    "recipientInput": "WeWork",
    "description": "Monthly office rent, invoice #2026-02",
    "expenseDate": 1739577600,
    "currency": "PLN"
  }'

Recurring expense (monthly rent)

curl -X POST https://app.figa.io/api/v1/expenses \
  -H "x-api-key: et_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Office rent",
    "amount": 420000,
    "categoryInput": "Housing",
    "recipientInput": "WeWork",
    "schedule": {
      "frequency": "monthly",
      "startDate": "2026-01-01",
      "dayOfMonth": 1
    }
  }'

For all schedule variants (weekly, quarterly, yearly, limited installments, etc.) see the Recurring schedule reference.

Update expense — request body

PUT /api/v1/expenses/:expenseId · Permission: expenses.write

Partial update — include only the fields you want to change.

Field Type Description
namestring1–200 characters.
amountintegerCents (minor units), ≥ 1.
categoryInputstring | UUIDExisting UUID or new name.
recipientInputstring | UUID | nullUUID, new name, or null to clear.
descriptionstring | nullMax 1000 characters. null to clear.
expenseDateintegerUnix timestamp in seconds.
currencystringISO 4217 code.
scheduleobject | nullReplace schedule. null converts recurring to one-time.
isPaidbooleanMark as paid/unpaid.
editScopestringRecurring only. See below.

editScope — recurring expense edits

When updating or deleting a recurring expense, editScope controls which occurrences are affected:

Value Effect
THIS_INSTANCEOnly the specific occurrence. Creates a materialized override.
FUTURE_INSTANCESThis occurrence and all future occurrences.
ALL_INSTANCESEntire series (past + future). Updates the template directly.

Update single occurrence of a recurring expense

curl -X PUT https://app.figa.io/api/v1/expenses/{expenseId} \
  -H "x-api-key: et_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 450000,
    "editScope": "THIS_INSTANCE"
  }'

Update all future occurrences

curl -X PUT https://app.figa.io/api/v1/expenses/{expenseId} \
  -H "x-api-key: et_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 450000,
    "editScope": "FUTURE_INSTANCES"
  }'

List expenses — query parameters

GET /api/v1/expenses · Permission: expenses.read

Param Type Required Description
yearintegerYesYear (2000–2100).
monthintegerYesMonth (1–12).
includeTemplatesbooleanNoInclude recurring templates. Default: true.
showPaidOnlybooleanNoOnly paid expenses. Default: false.
showUnpaidOnlybooleanNoOnly unpaid expenses. Default: false.
categoryIdUUIDNoFilter by category.
recipientIdUUIDNoFilter by recipient.
limitintegerNoMax results (1–50).

List unpaid expenses for February 2026

curl -X GET "https://app.figa.io/api/v1/expenses?year=2026&month=2&showUnpaidOnly=true" \
  -H "x-api-key: et_live_abc123..."

Delete expense

DELETE /api/v1/expenses/:expenseId · Permission: expenses.delete

For recurring expenses, pass editScope as a query parameter to control what gets deleted:

# Delete single occurrence
curl -X DELETE "https://app.figa.io/api/v1/expenses/{expenseId}?editScope=THIS_INSTANCE" \
  -H "x-api-key: et_live_abc123..."

# Delete entire recurring series
curl -X DELETE "https://app.figa.io/api/v1/expenses/{expenseId}?editScope=ALL_INSTANCES" \
  -H "x-api-key: et_live_abc123..."

Add payment — request body

POST /api/v1/expenses/:expenseId/payments · Permission: expenses.payments

Field Type Required Description
amountintegerNoPayment amount in cents. Defaults to full expense amount.
paymentDateintegerNoUnix timestamp (seconds). Defaults to now.
paymentMethodstringNoFree text, max 50 characters (e.g. "bank_transfer", "card").
notesstringNoFree text, max 1000 characters.

Full payment with method

curl -X POST https://app.figa.io/api/v1/expenses/{expenseId}/payments \
  -H "x-api-key: et_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 14250,
    "paymentDate": 1739577600,
    "paymentMethod": "bank_transfer",
    "notes": "Invoice #2026-02 paid"
  }'

Quick payment (full amount, today)

curl -X POST https://app.figa.io/api/v1/expenses/{expenseId}/payments \
  -H "x-api-key: et_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{}'

Monthly totals — query parameters

GET /api/v1/expenses/monthly-totals · Permission: expenses.read

Param Type Required Description
startYearintegerYesStarting year (2000–2100).
startMonthintegerYesStarting month (1–12).
monthsintegerNoNumber of months to return (1–12). Default: 7.
curl -X GET "https://app.figa.io/api/v1/expenses/monthly-totals?startYear=2026&startMonth=1&months=6" \
  -H "x-api-key: et_live_abc123..."

Bulk actions — request body

POST /api/v1/expenses/bulk · Permission: expenses.write · Returns HTTP 207

Field Type Description
actionstringOne of: markAsPaid, markAsUnpaid, delete, updateCategory, updateRecipient.
idsstring[]Array of expense IDs (1–100 items).
payloadobjectAdditional data depending on action (see below).

Payload by action

Action Payload fields
markAsPaidpaymentDate (optional, unix seconds), paymentMethod (optional, string)
markAsUnpaidNo payload needed.
deleteNo payload needed.
updateCategorycategoryId (UUID, required).
updateRecipientrecipientId (UUID or null to clear).

Bulk mark as paid

curl -X POST https://app.figa.io/api/v1/expenses/bulk \
  -H "x-api-key: et_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "markAsPaid",
    "ids": ["550e8400-...", "6ba7b810-..."],
    "payload": {
      "paymentDate": 1739577600,
      "paymentMethod": "bank_transfer"
    }
  }'

Bulk change category

curl -X POST https://app.figa.io/api/v1/expenses/bulk \
  -H "x-api-key: et_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "action": "updateCategory",
    "ids": ["550e8400-...", "6ba7b810-..."],
    "payload": {
      "categoryId": "a1b2c3d4-..."
    }
  }'

Skip / restore occurrence

POST /api/v1/expenses/:expenseId/skip/toggle · Permission: expenses.write

Toggle the skip status of a single recurring occurrence. Only unpaid instances can be skipped. Skipped instances remain visible but are excluded from totals.

Field Type Description
scheduledDateinteger | stringOccurrence identifier: Unix timestamp (seconds) or ISO date YYYY-MM-DD.

Skip using Unix timestamp

curl -X POST https://app.figa.io/api/v1/expenses/{expenseId}/skip/toggle \
  -H "x-api-key: et_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"scheduledDate": 1740787200}'

Skip using ISO date (recommended for automations)

curl -X POST https://app.figa.io/api/v1/expenses/{expenseId}/skip/toggle \
  -H "x-api-key: et_live_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"scheduledDate": "2026-03-30"}'

If no matching occurrence exists, API returns 404 (for both timestamp and ISO date).

Idempotency

POST mutations support the Idempotency-Key header (UUID format). If the same key is sent again within 24 hours, the server returns the cached response instead of processing the request again. This prevents duplicate creates from chatbot retries.

curl -X POST https://app.figa.io/api/v1/expenses \
  -H "x-api-key: et_live_abc123..." \
  -H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
  -H "Content-Type: application/json" \
  -d '{"name":"AWS","amount":14250,"categoryInput":"Cloud"}'

Idempotency is optional

Without the header, requests are processed normally. The header is recommended for automation and chatbot integrations where retries are common.

Error handling

Status Meaning Action
400Validation errorFix request payload or query params. Response body contains field-level errors.
401UnauthorizedCheck API key is valid and not expired.
403ForbiddenKey lacks required permission or route not in allowlist.
404Not foundResource ID does not exist or is not accessible.
409ConflictDuplicate name, plan limit reached, or concurrent idempotent request.
429Rate limitedBack off and retry after delay.
5xxServer errorRetry with same Idempotency-Key if applicable.

Troubleshooting 403 / Cloudflare blocks

If you receive a 403 from Cloudflare (e.g. “Error 1010”), try a browser-like User-Agent header or use the provided OpenClaw skill scripts.

About timestamps

All date fields (expenseDate, paymentDate, scheduledDate, startDate as integer) are Unix timestamps in seconds (not milliseconds). String dates accept ISO 8601 format (YYYY-MM-DD).

API request verification

Capture a successful request/response in your API client with x-api-key header.

Suggested file: /help/screenshots/developers/api-request-success.png

Placeholder. Replace with real screenshot once flow is finalized.