Push API overview
Send encrypted transactions into Budgero from any system using the Push API or the Python SDK.
In this guide
- Generate a Push API token and export your space encryption key from Settings → Integrations → Push API.
- Encrypt a transactions.add payload with AES-GCM, Base64-encode it, and POST to /api/v1/push with a bearer token; use message_id to dedupe retries.
- Use the Python SDK to handle encryption, message ids, and queue visibility (pending/processed/failed).
The Push API lets you send transactions into Budgero from other systems while keeping everything end-to-end encrypted. You generate a token, encrypt the payload with your space key, and POST it to the push endpoint over HTTPS.
Quick setup
- In Settings → Integrations → Push API, generate a Push API token.
- Export your space encryption key (Budgero never stores it server-side).
- Build a JSON payload (see format below), encrypt it with AES-256-GCM, Base64-encode it, and POST it to the push endpoint with your bearer token.
- Optionally include a message_id so retries are deduped.
- Monitor queue stats in the app or via the API to confirm processing.
Endpoint and payload spec
- Endpoint:
POST /api/v1/push(use your base URL; default ishttps://my.budgero.app) - Auth:
Authorization: Bearer <push-api-token> - Body fields:
encrypted_payload(string, Base64): AES-GCM encrypted JSON payload (salt + IV + ciphertext + auth tag; see Encryption requirements).message_id(string, optional): Client-generated id to dedupe retries.
Decrypted payload shape (before encryption)
{ "v": 2, "op": "transactions.add", "args": { "accountId": 1, "categoryId": 5, "budgetId": 1, "date": "2024-11-27", "inflow": 0, "outflow": 42500, "memo": "API push", "payee": "Vendor", "transferId": "" }, "message_id": "optional-unique-id"}Monetary values are integer milliunits (1/1000 of a currency unit), so 42.50 is sent as 42500. Always include "v": 2; legacy payloads without it are treated as format 1 (decimal amounts) and upgraded on import, but new integrations should send format 2. The Python SDK handles both the format flag and the conversion for you.
Supported operations today
transactions.add— send an income or expense. Useinflowfor income andoutflowfor expenses (only one should be non-zero). Supply your existing budget, account, and category IDs. Set"cleared": trueif the transaction has already cleared the bank; when omitted it is added as uncleared.- Matching hand-entered transactions: a pushed
transactions.addthat looks like a transaction you typed in yourself (same account and direction, amount and date within Settings → Duplicate transactions, 1% and ±7 days by default) isn't added twice. It waits on the account's to review list, where Match keeps your entry and marks it cleared, and Add as new adds exactly what was pushed. Transfers and split transactions are always added directly. transactions.updateByRef— change fields of a transaction previously created via the Push API, referenced by themessage_idof the original push:{"messageId": "...", "fields": {"outflow": 45000, "memo": "corrected"}}. Accepted fields:inflow,outflow(integer milliunits),date,memo,payee,categoryId,accountId,cleared(true/false; for example, push a pending charge and mark it cleared once it settles).transactions.deleteByRef— permanently delete a transaction previously created via the Push API:{"messageId": "..."}. Missing references are successful no-ops, including when the transaction is already gone.
Updates use account-currency milliunits and apply all fields together. Transfers cannot be patched through this operation. Split parents support memo, payee, and cleared edits; their amounts, categories, dates, and accounts cannot be patched. Reconciled transactions keep their cleared status. Unknown update fields are rejected.
Store the message_id of every add you push: under end-to-end encryption the server never sees transaction ids, so the original push's message_id is the only stable reference an integration can use to update or delete later. The app records it as provenance when it applies the add (adds pushed before this feature cannot be referenced retroactively).
With the Python SDK: result = client.add_transaction(...), then client.update_transaction(result.message_id, outflow=45.00) or client.delete_transaction(result.message_id).
Split transactions (multi-category)
Add an optional splits array to split the transaction across several categories. Each line carries its own inflow/outflow (integer milliunits in the account's currency) and categoryId (or transferAccountId for a transfer line); memo and payee are optional per line. The parent's categoryId becomes irrelevant — omit it — and the parent's inflow/outflow must equal the sum of the lines, or the item is rejected on apply with "Split amounts must sum to parent total".
{ "v": 2, "op": "transactions.add", "args": { "accountId": 1, "budgetId": 1, "date": "2024-11-27", "outflow": 42500, "payee": "Whole Foods", "splits": [ { "outflow": 35000, "categoryId": 5, "memo": "Groceries" }, { "outflow": 7500, "categoryId": 9, "memo": "Household" } ] }}With the Python SDK, pass SplitLine objects to add_transaction:
from budgero import SplitLineclient.add_transaction( account_id=1, category_id=None, budget_id=1, date="2024-11-27", outflow=42.50, payee="Whole Foods", splits=[ SplitLine(category_id=5, outflow=35.00), SplitLine(category_id=9, outflow=7.50), ],)Transfers
transactions.addTransfer— move money between two of your own accounts as a true linked transfer (same operation the app UI uses), not two loose transactions. Args:budgetId, a client-generatedtransferId(any unique string — store it, it is the handle for deletion), and two legssource/destination, each withinflow/outflow(integer milliunits),accountId,categoryId(use0— the app assigns the Transfers category automatically for on-budget transfers),date,memo,payee, and optionallycleared. Each leg uses its own account currency: the source carries theoutflow, and the destination carries the receivedinflow. Use equal amounts only for same-currency transfers. If creating the second leg fails, the app attempts to remove the first leg.transactions.deleteTransfer— delete both legs of a transfer by itstransferId.
{ "v": 2, "op": "transactions.addTransfer", "args": { "budgetId": 1, "transferId": "3f6f3f6e-0b6e-4f0a-9c2e-transfer-0001", "source": { "inflow": 0, "outflow": 480000, "accountId": 3, "categoryId": 0, "date": "2026-09-21", "memo": "", "payee": "Transfer" }, "destination": { "inflow": 480000, "outflow": 0, "accountId": 7, "categoryId": 0, "date": "2026-09-21", "memo": "", "payee": "Transfer" } }}With the Python SDK, keep the transfer handle separately from the queue message ID:
result = client.add_transfer( source_account_id=3, destination_account_id=7, budget_id=1, date="2026-09-21", amount=480, # destination_amount=520, # required when the received currency amount differs)client.delete_transfer(result.transfer_id)result.message_id identifies the push message; result.transfer_id links the two ledger entries. A transfer ID must be unique within the space.
Encryption requirements
- Algorithm: AES-256-GCM
- Salt: 32 bytes = SHA-256 of your raw 32-byte space encryption key (prepend before the IV)
- IV: 12 bytes (after the salt, before the ciphertext)
- Auth tag: 16 bytes (append after ciphertext)
- Encoding: Base64 of salt + IV + ciphertext + tag
- Key: Your space encryption key (exported from Settings). Base64-decode the exported value before hashing it or using it as the AES key; use the raw 32-byte SHA-256 digest as the salt.
The salt prefix is not optional: the client verifies it against SHA-256 of its own space key before decrypting (constant-time compare in mutation-crypto.ts). A payload without the salt is accepted by the server (HTTP 202, queued) but every apply fails client-side and the item lands in the queue as failed — the transaction silently never reaches the budget. The Python SDK already prepends the salt for you; only hand-rolled clients built from this spec are affected.
Python SDK quick start
from budgero import BudgeroClientclient = BudgeroClient( api_key="your-push-token", encryption_key="your-space-key", base_url="https://my.budgero.app",)result = client.add_transaction( account_id=1, category_id=5, budget_id=1, date="2024-11-27", outflow=42.50, memo="Example push", payee="API Demo",)print("Queued id:", result.queue_id)SDK helpers:
get_queue()— list pending itemsget_queue_stats()— pending/processed/failed countsclear_queue()— clear pending items (or all with a flag)
Operational notes and security tips
-
Deduplication: Provide a stable
message_idwhen retrying to prevent double inserts. -
Queue visibility: Check queue stats in-app or via
/push/queueand/push/statsto verify delivery. -
Token hygiene: Rotate/regenerate if exposed. Disable or revoke from Settings as needed.
-
Key hygiene: Keep the token/key out of source control.

Keep it secret, keep it safe. Use env vars (e.g.,
BUDGERO_PUSH_TOKEN,BUDGERO_SPACE_KEY) or your OS secret manager, restrict file perms, and avoid logging them. -
Transport: Always use HTTPS so the bearer token and metadata stay protected.
-
Access control: Restrict who can read the token/key. Treat them like credentials with scope to your budget.
-
Error handling: On 409 with message_id, treat it as a deduped retry. On 401/403, regenerate/enable the token. Monitor queue failures and clear or requeue as needed.