Transactions API
List, create, categorize, update, attach, and remove eligible transactions through public API v1.
Summary
Transaction operations use the transactions.read scope or the transactions.write scope. Transaction reads and target-row mutations are scoped to the authenticated team. Use account, category, tag, and assignee identifiers that belong to that team.
Capabilities
Endpoints
| Method | Endpoint | Scope | Purpose |
|---|---|---|---|
| GET | /transactions | transactions.read | List and filter transactions |
| POST | /transactions | transactions.write | Create one transaction |
| GET | /transactions/{id} | transactions.read | Retrieve one transaction |
| PATCH | /transactions/{id} | transactions.write | Update the fields that you send |
| DELETE | /transactions/{id} | transactions.write | Delete a manually created transaction |
| POST | /transactions/bulk | transactions.write | Create up to 100 transactions |
| PATCH | /transactions/bulk | transactions.write | Update many transactions |
| DELETE | /transactions/bulk | transactions.write | Delete up to 100 manual transactions |
| POST | /transactions/{transactionId}/attachments/{attachmentId}/presigned-url | transactions.read | Create a time-limited attachment URL |
Prerequisites
Use a bearer token with the scope for each operation; write access does not replace read access. Obtain a real account ID from Bank Accounts API. Start with disposable records and inspect the response limitations below before enabling automated writes.
Concepts
Pagination
The list response contains:
- data
- meta.cursor
- meta.hasNextPage
- meta.hasPreviousPage
The response does not contain a total count. Keep the same filters and sort when you request the next cursor, and treat the cursor as opaque. A full final page can report hasNextPage: true; the next page can be empty.
Amounts are signed: positive values are income and negative values are expenses. manual: true identifies API-created, form-created, and CSV-imported records, not just records entered by hand. A transaction is fulfilled when it has an attachment or its status is completed; fulfillment does not prove that someone reviewed the receipt.
Workflow
List Transactions
The GET /transactions operation accepts cursor pagination. It accepts pageSize from 1 to 10,000, with a default of 40. Use whole numbers.
The filter schema includes q, start, end, categories, tags, accounts, assignees, statuses, recurring, attachments, manual, amountRange, amount, type, needsReview, syncStates, glCodes, and sort. manual and attachments use include or exclude. type uses income or expense. Send list-valued filters with the serialization supported by your client and confirm the returned rows before relying on a filter.
needsReview selects rows with a pending receipt-match suggestion, or rows without an attachment whose status is not completed, excluded, or archived. Current limitation: this query field uses a strict boolean schema without URL-string conversion. A request containing needsReview=true can fail validation; the example below omits it.
curl --request GET \
--url "https://api.eigenn.io/v1/transactions?pageSize=100" \
--header "Authorization: Bearer $EIGENN_API_TOKEN"Create a Transaction
The POST /transactions operation needs these fields:
- name
- amount
- currency
- date
- bankAccountId
curl --request POST \
--url https://api.eigenn.io/v1/transactions \
--header "Authorization: Bearer $EIGENN_API_TOKEN" \
--header "Content-Type: application/json" \
--header "Idempotency-Key: transaction-office-supplies-2026-07-11" \
--data '{
"name": "Office Supplies Purchase",
"amount": -150.75,
"currency": "USD",
"date": "2026-07-11T12:00:00.000Z",
"bankAccountId": "00000000-0000-0000-0000-000000000000",
"categorySlug": "office-supplies",
"internal": false
}'Replace the example account ID and category slug with values from your team. Single and bulk creation check every referenced bank account before writing. A missing or foreign-team account returns 404; a failing account check prevents the batch from starting. Required strings must be non-empty. Use a valid date and currency; non-empty text alone does not establish that the database accepts a value.
Optional fields are assignedId, categorySlug, note, internal, and attachments. Each attachment describes an already-uploaded file using path (path segments), name, size, and type. This request does not upload file bytes. New transactions are marked manual and start with status posted.
A transaction in another currency than the team's base currency also stores its amount in the base currency, converted with Eigenn's stored exchange rates as a synced transaction is. Without a rate for that currency it has no base amount, and base-currency reports leave it out. A PATCH that changes amount or currency converts it again.
The single-create response is a transaction object with HTTP 200, matching the current OpenAPI contract. Check the response limitations below: a failed response can follow a saved transaction.
Update
PATCH /transactions/{id} accepts name, amount, currency, date, bankAccountId, categorySlug, glCode, accountingDate, departmentId, locationId, classId, vendorName, status, internal, recurring, frequency, note, assignedId, taxRate, and taxAmount. Omitted fields normally retain their values. Use the schema's nullable fields when clearing a value; not every field accepts null.
Statuses are pending, archived, completed, posted, and excluded. Frequencies are weekly, monthly, annually, and irregular. Setting recurring is a classification change, not a command to create future payments.
Category changes reset tax overrides. Sending categorySlug, including null or the existing slug, clears stored tax rate, amount, and type. This also overrides tax values sent in that same request. Returned tax values can then come from the category. Set the category first, then submit any tax override separately and inspect the result.
Delete and Exclude
Delete operations remove only records marked manual: true, including CSV imports. They do not remove bank-synced records. To retain a synced row with an excluded classification, PATCH its status to excluded.
Single delete returns { "id": "..." } on success. A missing, foreign-team, or bank-synced ID returns 404, and nothing is deleted.
Bulk delete accepts a JSON array of 1–100 UUIDs. It returns an array of { "id": "..." } objects for rows actually deleted. Ineligible or missing IDs are omitted; an empty array can be a successful response. Compare the returned IDs with the requested IDs.
Bulk Operations
| Operation | Request and response | Limitations |
|---|---|---|
| Create | JSON array of 1–100 create objects; returns a transaction array | Success status is 200, matching OpenAPI. Attachment metadata is accepted by the schema but is discarded by bulk creation. |
| Update | Object containing ids and shared changes; returns data and pagination meta | Supported changes are categorySlug, glCode, status, frequency, internal, note, assignedId, recurring, and tagId. Amount, date, name, and other single-update fields are not bulk fields. |
| Delete | JSON array of 1–100 UUIDs; returns deleted ID objects | Only matching manual rows in the team are deleted. |
Bulk update has no explicit 100-ID schema limit. Keep batches small enough to inspect and reconcile. Setting tagId adds that tag; it does not replace all existing tags. A null tag ID does not remove assignments. Category updates clear tax overrides just as single updates do.
Bulk update does not provide the same coding-constraint check as single update. Treat the two operations as separate contracts. Bulk changes are not an all-or-nothing workflow: tag, provenance, data, or follow-up work can persist before a later failure. Inspect the saved rows after an error.
Use one idempotency key for one complete write request. Do not reuse it for a different body or ID set. For current cached responses, the guard compares a hash of the exact request body and returns 422 if it differs; even JSON whitespace or field order can matter. Older cache entries without a body hash can still replay their original response. Use one key for one unchanged request. Failed responses are not cached as successful writes. See Errors and Retry Safety.
Attachments
Transaction responses can include attachment IDs, paths, filenames, sizes, and MIME types.
The presigned-URL operation returns url, expiresAt, and nullable fileName; the URL lasts 60 seconds. It checks that the attachment belongs to the transaction and team and that its stored path belongs to the team. Stored paths are not public URLs.
The handler defaults to download mode. The download query uses boolean coercion, so the non-empty URL string false can still become true. Do not depend on download=false for inline viewing until that behavior is verified for your deployment.
Behavior specification
Response Limitations and Recovery
The current response schema requires a non-null account and bank connection, although manual accounts can have no bank connection. A single read, list, create, or update can therefore fail with 500 when a returned transaction has no connection. Null account currency can also fail this schema. A missing or foreign-team transaction on GET or PATCH returns 404 Transaction not found. This is separate from a malformed response for an existing row.
Response validation runs after mutations. Creation, updates, attachments, or deletion can already have persisted when the caller receives an error. Check the application ledger and saved record before retrying; repeating a failed create can create another transaction. Do not treat an unsuccessful HTTP response as proof of rollback.
The public response schema strips fields it does not declare, including GL code, accounting dimensions, sync state, and assigned-user details. Their use in a request does not guarantee they appear in the returned object. Confirm those changes in the application where needed.
Common Errors
| Status | Cause |
|---|---|
400 / 422 | Invalid request data; attachment URL requests also return 400 for a missing stored path |
401 | Missing or invalid bearer token |
403 | Required scope or current-role permission is missing |
404 | Transaction lookup/update failed; create referenced an unavailable bank account; delete found no eligible manual row; or attachment ownership failed |
409 | Another request with the same idempotency key is still in progress |
429 | Request-rate limit exceeded |
500 | Response-schema mismatch, storage failure, or an unmapped database/coding error |
Diagrams
Rendering diagram…
Screenshots
Not applicable: this page describes an API or a non-visual workflow rather than an application screen.
Verification
- List transactions with a documented filter and confirm the returned rows match it.
- Create a manual transaction using the documented required fields, then retrieve it and compare the saved values.
- Update one editable field and retrieve the transaction again to confirm persistence.
- Compare manual and synced deletion, including omitted bulk-delete IDs and the single-delete
404. - Check category tax resets, tag addition, bulk attachment omission, and response fields with disposable records.
- Test manual-account response failures and inspect persisted state before retrying. Check attachment ownership, expiry, and download behavior separately.
These are acceptance checks to perform in an isolated environment. Source review has not established runtime completion; verification metadata remains pending.