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
Permission model
Each API key has per-resource permissions. Available actions per resource:
| Resource | Actions |
|---|---|
expenses | read, write, delete, payments |
categories | read, write, delete |
recipients | read, write, delete |
workspaces | read |
- Workspace settings expose
readandwritekey presets. writepermission impliesreadaccess.writepreset covers mutation routes, including delete and payment operations.- Non-allowlisted routes return
403 FORBIDDEN.
Categories
/api/v1/categories List categories (workspace + global).
/api/v1/categories Create category. Requires categories.write.
/api/v1/categories/:id Update category name/description/color.
/api/v1/categories/:id Delete category. Requires categories.delete.
Recipients
/api/v1/recipients List recipients (workspace + global).
/api/v1/recipients Create recipient. Requires recipients.write.
/api/v1/recipients/:id Update recipient name/description.
/api/v1/recipients/:id Delete recipient. Requires recipients.delete.
Expenses
/api/v1/expenses?year=YYYY&month=MM List expenses for month. year/month query required.
/api/v1/expenses Create expense. Add schedule to create recurring. amount is integer cents (14250 = 142.50).
/api/v1/expenses/:expenseId Get expense by ID. Add ?occurrence=unixSeconds for recurring occurrence view.
/api/v1/expenses/:expenseId Update expense fields. Use editScope for recurring series.
/api/v1/expenses/:expenseId Delete expense. Use ?editScope for recurring. Requires expenses.delete.
/api/v1/expenses/:expenseId/skip/toggle Toggle skip for recurring occurrence (scheduledDate: unix seconds or YYYY-MM-DD).
/api/v1/expenses/:expenseId/history Expense audit history.
/api/v1/expenses/monthly-totals Aggregated monthly totals.
/api/v1/expenses/bulk Bulk actions (e.g. markAsPaid). Returns HTTP 207.
Payments
/api/v1/expenses/:expenseId/payments List payments for expense.
/api/v1/expenses/:expenseId/payments Add payment. Requires expenses.payments.
/api/v1/expenses/:expenseId/payments Remove all payments from expense.
/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.
/api/v1/insights/summary Spending summary for period.
/api/v1/insights/comparison Period-over-period comparison.
/api/v1/insights/trends Spending trends over time.
/api/v1/insights/anomalies Anomaly detection in spending.
/api/v1/insights/forecast Spending forecast.
Context & Notifications
/api/v1/context Workspace metadata, plan tier, limits. Requires workspaces.read.
/api/v1/notifications List notifications. Requires workspaces.read.
/api/v1/notifications/unread-count Unread notification count.
Create expense — request body
POST /api/v1/expenses · Permission: expenses.write
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Expense name, 1–200 characters. |
amount | integer | Yes | Amount in cents (minor units). 14250 = 142.50. Must be ≥ 1. |
categoryInput | string | UUID | Yes | Existing category UUID or a new category name (auto-created). |
recipientInput | string | UUID | null | No | Existing recipient UUID, new name (auto-created), or null. |
description | string | null | No | Free-text note, max 1000 characters. |
expenseDate | integer | No | Unix timestamp in seconds. Defaults to now. |
currency | string | No | ISO 4217 code (PLN, USD, EUR). Defaults to workspace base currency. |
schedule | object | null | No | Include to make expense recurring. See Schedule reference. |
categoryInput / recipientInput
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 |
|---|---|---|
name | string | 1–200 characters. |
amount | integer | Cents (minor units), ≥ 1. |
categoryInput | string | UUID | Existing UUID or new name. |
recipientInput | string | UUID | null | UUID, new name, or null to clear. |
description | string | null | Max 1000 characters. null to clear. |
expenseDate | integer | Unix timestamp in seconds. |
currency | string | ISO 4217 code. |
schedule | object | null | Replace schedule. null converts recurring to one-time. |
isPaid | boolean | Mark as paid/unpaid. |
editScope | string | Recurring only. See below. |
editScope — recurring expense edits
When updating or deleting a recurring expense, editScope controls which occurrences are affected:
| Value | Effect |
|---|---|
THIS_INSTANCE | Only the specific occurrence. Creates a materialized override. |
FUTURE_INSTANCES | This occurrence and all future occurrences. |
ALL_INSTANCES | Entire 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 |
|---|---|---|---|
year | integer | Yes | Year (2000–2100). |
month | integer | Yes | Month (1–12). |
includeTemplates | boolean | No | Include recurring templates. Default: true. |
showPaidOnly | boolean | No | Only paid expenses. Default: false. |
showUnpaidOnly | boolean | No | Only unpaid expenses. Default: false. |
categoryId | UUID | No | Filter by category. |
recipientId | UUID | No | Filter by recipient. |
limit | integer | No | Max 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 |
|---|---|---|---|
amount | integer | No | Payment amount in cents. Defaults to full expense amount. |
paymentDate | integer | No | Unix timestamp (seconds). Defaults to now. |
paymentMethod | string | No | Free text, max 50 characters (e.g. "bank_transfer", "card"). |
notes | string | No | Free 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 |
|---|---|---|---|
startYear | integer | Yes | Starting year (2000–2100). |
startMonth | integer | Yes | Starting month (1–12). |
months | integer | No | Number 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 |
|---|---|---|
action | string | One of: markAsPaid, markAsUnpaid, delete, updateCategory, updateRecipient. |
ids | string[] | Array of expense IDs (1–100 items). |
payload | object | Additional data depending on action (see below). |
Payload by action
| Action | Payload fields |
|---|---|
markAsPaid | paymentDate (optional, unix seconds), paymentMethod (optional, string) |
markAsUnpaid | No payload needed. |
delete | No payload needed. |
updateCategory | categoryId (UUID, required). |
updateRecipient | recipientId (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 |
|---|---|---|
scheduledDate | integer | string | Occurrence 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
Error handling
| Status | Meaning | Action |
|---|---|---|
400 | Validation error | Fix request payload or query params. Response body contains field-level errors. |
401 | Unauthorized | Check API key is valid and not expired. |
403 | Forbidden | Key lacks required permission or route not in allowlist. |
404 | Not found | Resource ID does not exist or is not accessible. |
409 | Conflict | Duplicate name, plan limit reached, or concurrent idempotent request. |
429 | Rate limited | Back off and retry after delay. |
5xx | Server error | Retry with same Idempotency-Key if applicable. |
Troubleshooting 403 / Cloudflare blocks
403 from Cloudflare (e.g. “Error 1010”), try a browser-like
User-Agent header or use the provided OpenClaw skill scripts.
About timestamps
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