Skip to main content
Use Create export to generate an income statement, balance sheet or cash flow file. The request returns an export_ ID immediately; Retrieve export tells you when it is ready and returns a signed download URL. Both operations require API access: Manage and Reports: View. The key selects the company. Any currently authorized teammate may retrieve an API export in that company. Generating an export does not edit your books.

Choose the report

All four examples below use POST /api/v1/reports/exports. Send a different Idempotency-Key for each new job. Income statement:
Balance sheet:
GAAP department P&L:
Cash flow:

Fields and options

group_by is an object; omitted members take their defaults. Quarter/year columns follow the company’s existing calendar/fiscal report settings. First and last columns can be partial periods. There is no date_type override. Balance sheets need a range, too. Each displayed period shows cumulative balances through its cutoff, retaining activity before start_date. Department rows work only for income_statement and require the departments feature. Without it, the request returns 403 feature_disabled. Ranges are limited to 120 calendar months, counting both boundary months, regardless of format or grouping. PDF exports additionally support 13 displayed periods, using the company’s calendar/fiscal report settings. For more columns, use a shorter range, coarser grouping, CSV or XLSX. Oversized ranges return 400 invalid_date_range with param: "end_date" before creating or queuing a job. Unknown fields are rejected, including email_on_completion, grouped_by, a single date, or a company selector. Company, actor and filename are inferred by Finta.

Submit an export

Creation and same-key replay return 201 Created, with Location pointing to the retrieval path and Cache-Control: no-store.
A fast worker or a replay can return a later state immediately. Inspect status; HTTP 201 does not guarantee that the job is new or still pending.

Poll for completion

Retrieval returns 200 OK for every recognized job state: Stop polling at success or failure. Every poll consumes the ordinary API allowance, so use bounded backoff rather than a tight loop. There is no guaranteed generation time, completion webhook, email notification or MCP event subscription in this flow. The worker reads the books when it generates the file; POST does not freeze a financial snapshot. Completed files are retained results. To regenerate against updated books, submit a new job with a new key.

Download the file

Only succeeded responses expose a file object:
The URL is illustrative. In a real response, size is bytes and expires_at is Unix seconds. CSV uses text/csv; XLSX uses application/vnd.openxmlformats-officedocument.spreadsheetml.sheet. The client or agent fetches the returned URL with its own HTTP/file tools:
The signed storage URL does not need a Finta bearer token. The API supplies the file; the caller decides where to save it on their computer. Fresh links are available for 30 days after completed_at (2,592,000 seconds). At or after that cutoff, GET and idempotent POST replays still return status: "succeeded", but file and error are null. The export record and stored file are retained. To generate another report from the current books, submit a new export with a new key. Within that window, successful retrieval requests a fresh link with expiry seven days from retrieval, not from job creation or when the link is opened. Signing credentials or storage policies can invalidate a link sooner. If it expires, retrieve the export again before its 30-day cutoff. Already-issued links keep their original expiry and can remain valid beyond the regeneration cutoff. A download started before expiry can finish afterward; restarting or requesting additional byte ranges afterward needs a fresh link. Links are bearer credentials: anyone with one can download until it expires, even if API permissions change in the meantime. Retrieval itself checks current access. Link expiry does not delete the file; export records and files have no automatic age-based cleanup policy in the application.

Idempotency and retries

POST requires a nonblank, case-sensitive Idempotency-Key of 1–255 characters. It is scoped to the company, submitting user and operation, across that user’s API keys. Identical normalized inputs replay the same export and current state, including failures. Different inputs with that key return 409 idempotency_key_conflict. Keys remain reserved for the lifetime of the record.
  • After a timeout or 503 report_export_failed, retry the same body/key. A job may already be queued; same-key retry repairs an interrupted dispatch without creating another record. The retained job stays pending rather than becoming a terminal enqueue failure.
  • After a terminal failed job, fix the cause and use a new key to retry. The old key continues to replay that failure.
  • For intentionally new work, including refreshing the same dates after the books change, use a new key.

Export capacity

A company may have at most five unfinished API exports, across all submitters. New submissions above that limit return 429 report_export_limit_exceeded without creating or enqueueing a job. Existing-key replays and GET polling remain available, subject to ordinary API limits. A terminal success or failure releases its slot. This capacity error has no Retry-After: the finish time is unknown. Poll existing jobs with backoff and retry the rejected submission after one finishes. It is separate from burst/monthly quotas. For ordinary rate_limit_exceeded, honor Retry-After; for monthly_limit_exceeded, stop short-term retries.

Headers

Standard X-RateLimit-* headers describe API requests, not export slots. Their availability and reset behavior are documented under Usage Limits.

Failures and metrics

An HTTP 200 with status: "failed" contains a job error with code/message, not the HTTP error envelope’s type/doc_url fields:
  • access_revoked: the submitter lost required access before generation finished.
  • generation_failed: generation could not finish after retries.
  • enqueue_failed: a compatibility value in the declared job-error schema. New dispatch uncertainty returns HTTP 503 with a pending job, rather than recording this terminal code.
started_at is the first generation-attempt timestamp and can remain null when access is revoked before generation. completed_at is terminal completion, for success or failure. Both are Unix seconds and nullable before their events. generation_duration_ms is nullable until an attempt finishes, then accumulates generation/render/upload attempt time, including failed attempts but excluding queue and retry waits. It is not total time since submission. Finta records export ID, status and whether a download link was issued in its API request logs. Pending/failed responses and HEAD probes do not count as link issuance. Issuing a link does not prove its bytes were downloaded; actual S3 downloads and download duration are not tracked. Unknown or other-company IDs, browser-created exports and raw database IDs return 404 resource_missing. Department exports also require the feature on retrieval. There is no export list, cancel or delete operation. Use recognized schema values and inspect status before file/error. Treat an unknown future state as unrecognized, rather than assuming success. File exports use the export_ resource; they are separate from transaction job_ resources. The JSON income-statement endpoint keeps its scalar group_by=category|department; this new export body uses group_by: {period, rows}.