# Introduction Free, paperless invoicing for everyone. Create invoices, get paid and automate the busy work from the web, your AI assistant or your own code. Free Invoice Automation is free, paperless invoicing. Create invoices, send them online and get paid straight to your own Stripe account, bank or wallet. ## Get started ### Build directly with the API Every feature is one API call. Create an [API key](/api-reference/authentication) in **Settings > API keys**, then create a draft invoice: ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/documents" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 6f1c2d4e-first-invoice" \ -d '{ "type": "invoice", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "currency": "USD", "lines": [ { "description": "Website design", "quantity": "1", "unit_price_minor": "120000" } ] }' ``` Money is always sent as a string of integer minor units, so `"120000"` is 1,200.00 USD. See the [API reference](/api-reference/introduction) for every endpoint, or [Build with AI](/ai/overview) to work through an assistant. ## Other ways to get started ### Make one invoice without an account The [free invoice generator](/generator) makes a PDF invoice in your browser with no sign-up. Your invoices stay on your device until you choose to create an account, and then they move in with you. ### Install the CLI The `freeinvoiceautomation` command line tool runs every API call from your terminal and scripts. It ships in the week of 25 January 2027. ```bash npm install -g freeinvoiceautomation freeinvoiceautomation business list ``` The CLI and the stdio MCP client are the only parts we publish to npm. They are thin clients of the same API, so they can do exactly what your API key allows and nothing more. ### Sign in and send your first invoice Create your free account Email and password, Google or a magic link. No card needed. Follow the quickstart Business, client, invoice and pay page in about five minutes. --- ## What can Free Invoice Automation do? Invoice Invoices, estimates, credit notes and receipts with taxes, discounts and any currency Get paid Card payments through your own Stripe, bank transfer with payment proofs, local QR rails Automate An AI agent, recurring invoices, reminders, late fees and webhooks do the follow-up ### Why Free Invoice Automation? - **Free forever**: every invoicing feature is on the Free plan. Team ($10 a month) adds members, roles, an audit log, more emails and more AI credits; it never unlocks invoicing features. - **No cut of your money**: we never take a percentage of an invoice payment and never hold your funds. - **Paperless by design**: documents are created, sent, signed and paid online, so nothing needs printing or posting. - **One capability, every surface**: the web app, REST API, MCP server, CLI, Shopify app, WordPress plugin and AI agent all call the same actions, with the same permissions. - **Clean for your clients**: no ads, affiliates or sponsors on your PDFs, pay pages, portal or client emails. - **Safe by default**: integrations and AI create drafts, and a person confirms every send unless you choose otherwise. --- ## Invoices Create an invoice in the app, through the API or by asking your assistant. Each business keeps its own gap-free numbering, and a sent invoice is never edited in place: changes become a new revision, so what your client received stays on record. See [Invoices](/invoicing/invoices). ```json Draft invoice (response) { "data": { "id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "type": "invoice", "status": "draft", "number": null, "currency": "USD", "subtotal_minor": "120000", "tax_minor": "0", "total_minor": "120000", "amount_paid_minor": "0", "balance_minor": "120000" } } ``` ## Payments Connect your own Stripe account through Stripe Connect, or show your bank details for transfers. A pay page link goes with every invoice. An invoice is marked paid only when Stripe confirms the payment, or when a person records it, never because a browser was redirected. See [How payments work](/payments/overview). ## Automation The [AI invoice agent](/ai/agent/overview) turns plain language, pasted text or a receipt into draft invoices and clients. Reminders, late fees and recurring invoices arrive in January 2027, and [webhooks](/integrations/webhooks) tell your own systems when anything changes. --- ## Resources API Reference Every endpoint with parameters, examples and errors Build with AI MCP server for Claude, ChatGPT, Grok and Gemini, plus the AI agent Integrations TypeScript SDK, CLI, webhooks, Shopify, WooCommerce, QuickBooks and Xero Plans and billing What Free includes, what Team adds, and how AI credits work Source: https://docs.freeinvoiceautomation.com/introduction --- # Quickstart Create your account, add a business and a client, send your first invoice and get paid, in the web app or through the API. This guide takes you from nothing to a paid invoice. You create a free account, add the business that issues the invoice, add a client, make and send the invoice, and then see how your client pays and how the payment is recorded. Every step works in the web app and through the API, because both call the same actions with the same permissions. You need about five minutes and an email address. No card is needed for the Free plan. If you only need one PDF invoice right now and do not want an account, use the [free invoice generator](/generator). Anything you make there moves into your account later if you sign up. ## Before you start - **Web app:** open [app.freeinvoiceautomation.com](https://app.freeinvoiceautomation.com). Everything below happens there. - **API:** you still create the account in the web app first. Then create an API key in **Settings > API keys** and keep it somewhere safe; it is shown once. Send it as `Authorization: Bearer ` to `https://api.freeinvoiceautomation.com/v1`. See [Authentication](/api-reference/authentication). Money in the API is always a string of integer minor units with a currency, so `"120000"` with `"currency": "USD"` is 1,200.00 USD. Read [Money and currencies](/api-reference/money) if that is new to you. ## Send your first invoice Create your account Go to [app.freeinvoiceautomation.com/sign-up](https://app.freeinvoiceautomation.com/sign-up) and sign up with email and password, Google or a magic link. A new account starts on the Free plan with you as its Owner. Verify your email address from the link we send you. You can use the app straight away, but sending email through our platform mailer needs a verified address. See [Create your account](/account/sign-up). Add your first business Right after sign-up, onboarding asks for your first business: the person or company whose name goes on the invoice. Enter the legal name, country and base currency. The country fills in sensible defaults such as currency and tax presets, and you can change them later. Each business has its own numbering, tax rates, payment methods and templates. You can add as many businesses as you like on every plan. See [Businesses](/account/businesses). Choose how you get paid Open the business's payment settings and set up at least one way to be paid: - **Stripe:** connect your own Stripe account in one click. Card payments go straight to you. This needs a fresh sign-in, because it is a sensitive action. See [Stripe](/payments/stripe). - **Bank transfer:** add your bank details (free text plus structured fields such as IBAN and SWIFT). Your client sees them on the pay page. See [Bank transfer](/payments/bank-transfer). You can skip this step and add a method later, but your pay page will have nothing to offer until you do. Add a client Add the person or company you are billing: name, email and, if you like, address, tax id, default currency and language. Clients never get a password; they open what you send them from a link. See [Clients and contacts](/clients/clients). Create the invoice Create an invoice for that client and add lines with a description, quantity and unit price. Add taxes and discounts if you need them. Totals are worked out for you in integer minor units, so they always add up to the cent. The invoice starts as a **draft**. A draft has no number yet and you can change it freely. See [Invoices](/invoicing/invoices) and [Line items](/invoicing/line-items). Preview and send Click send. You always see a preview of the email before anything goes out. When you confirm, the invoice gets its number from the business's gap-free sequence, a snapshot of it is stored, and your client receives an email with a link to the pay page. You can also click **Copy link** to share the pay link through any channel you like, or **Download PDF**. See [Send a document](/invoicing/sending). Your client pays The link opens the [pay page](/invoicing/pay-page): your invoice, the amount due and every payment method you enabled. - **Card:** the client clicks **Pay by card** and pays on a hosted Stripe Checkout page. The invoice is marked paid when Stripe confirms the payment to us, not when the client's browser comes back. - **Bank transfer:** the client pays from their bank, then clicks **I have paid** and can upload proof. You are notified, but the invoice is not marked paid until you record the payment. Record a bank payment When the money arrives in your account, record the payment on the invoice: amount, date, reference and the proof if you have one. The balance goes down and the invoice moves to paid (or partly paid). Every record is written to the audit trail. See [Record a payment](/payments/recording). That is the whole loop. From here you can add saved items, estimates, credit notes and more businesses, all on the Free plan. ## The same flow through the API Each step above is one API call. The examples use cURL; the [TypeScript SDK](/integrations/sdk) offers the same calls as typed methods. 1. Business Create the business that issues the invoice. See [Create a business](/api-reference/businesses/create-business). ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/businesses" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: quickstart-business-1" \ -d '{ "legal_name": "Studio North", "country": "GB", "base_currency": "USD", "timezone": "Europe/London", "locale": "en-GB", "email": "billing@studionorth.example" }' ``` 2. Client Add a client to that business. The same route updates a client when you send an existing `id`. See [Create or update a client](/api-reference/clients/upsert-client). ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/clients" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: quickstart-client-1" \ -d '{ "business_id": "0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51", "name": "Acme Ltd", "email": "accounts@acme.example" }' ``` 3. Invoice Create a draft invoice. See [Create an invoice](/api-reference/documents/create-invoice). ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/documents" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: quickstart-invoice-1" \ -d '{ "type": "invoice", "business_id": "0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "currency": "USD", "due_date": "2027-01-31", "lines": [ { "description": "Website design", "quantity": "1", "unit_price_minor": "120000" } ] }' ``` The response is the draft with `"status": "draft"`, `"number": null` and its computed totals. 4. Send First ask for a preview, then send with `"confirm": true`. Sends need that confirmation unless your key is in `auto_send` mode, so nothing reaches your client by accident. See [Preview a send](/api-reference/documents/preview-send), [Send a document](/api-reference/documents/send-document) and [Drafts and confirmations](/api-reference/confirmations). ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/send-preview" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Idempotency-Key: quickstart-preview-1" curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/send" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: quickstart-send-1" \ -d '{ "confirm": true }' ``` To share the link yourself instead, create a share link with [Create a share link](/api-reference/documents/create-share-link). The document then carries a `pay_url`. 5. Payment Record a bank transfer you received. Card payments through Stripe are recorded for you from Stripe's confirmation, so you never call this for them. See [Record a payment](/api-reference/payments/record-payment). ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/payments" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: quickstart-payment-1" \ -d '{ "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "amount_minor": "120000", "currency": "USD", "received_at": "2027-01-20T09:30:00Z", "reference": "BANK-REF-55120" }' ``` Check the result at any time with [Get payment status](/api-reference/payments/get-payment-status). Send an `Idempotency-Key` with every write. If your network drops and you retry with the same key, you get the first result back and nothing is created twice. See [Idempotency](/api-reference/idempotency). ## Prefer your AI assistant? Connect Claude, ChatGPT, Grok or Gemini to our MCP server at `https://mcp.freeinvoiceautomation.com/mcp`, sign in, and ask in plain words: "Create an invoice for Acme Ltd for website design, 1,200 USD, due 31 January." The assistant creates a draft and shows you a preview; it sends only when you confirm. See the [MCP overview](/ai/mcp/overview). ## What to do next Core concepts Accounts, businesses, clients, documents and how statuses work Saved items Save your products and services so lines fill themselves Estimates Let clients accept online, then convert to an invoice in one step Plans and billing What Free includes and what Team adds Source: https://docs.freeinvoiceautomation.com/quickstart --- # Free invoice generator Make a professional PDF invoice in your browser with no account. Your data stays on your device until you choose to sign up. The free invoice generator at [freeinvoiceautomation.com/generator](https://freeinvoiceautomation.com/generator) makes a professional PDF invoice with no sign-up. It runs entirely in your browser: your business details, your client's details and every line you type stay on your device. It is built for the moment you need one invoice now, and the goal is a finished PDF in under 90 seconds. If you later create an account, the invoices you made in the generator move into it once, so you never type them again. ## What you can do In the generator you can: - enter your business details and your client's details; - add lines with quantity and unit price, plus taxes and discounts; - pick the currency; - watch a live preview update as you type; - download the finished invoice as a PDF. Totals are calculated in your browser by the same money engine the signed-in app uses, in integer minor units, so the numbers on your PDF are exactly what the app would produce. The PDF is made with the same templates as the app, so it looks the same as an invoice sent from an account. ## How your data is stored The generator is **local-first**. Everything you enter is saved in your browser's own storage (IndexedDB) on this device. - **Nothing goes to our servers.** No network request carries your invoice data while you create, preview or download. We test this on every release. - **Your work is kept between visits.** Close the tab or the browser and come back to `/generator` on the same device and browser: your documents are still there. - **It stays on this device.** Another browser, another computer or a private window does not see it. Clearing your browser's site data for freeinvoiceautomation.com removes it. Because nothing is stored on our side, we cannot recover generator documents for you. If you clear your browser data before signing up, they are gone. Download the PDF of anything you need to keep. ## Make an invoice Open the generator Go to [freeinvoiceautomation.com/generator](https://freeinvoiceautomation.com/generator). No sign-in or email is asked for. Fill in who and what Enter your business details and your client's details, then add your lines: description, quantity and unit price. Add tax and a discount if you need them and choose the currency. Check the preview The preview on the page updates with every change, so you can see the invoice exactly as it will look. Download the PDF Download the PDF and send it to your client in any way you like. ## Clean by design The generator and the PDFs it makes carry no ads, affiliate links or sponsors. A generator PDF shows a small "Powered by Free Invoice Automation" line, as every Free plan document does. Generator PDFs have no verification QR code, because there is no stored document on our side for a client to check against. Invoices sent from an account carry a verification QR that opens a [verification page](/invoicing/verification). ## Move your invoices into an account When you create an account, you can bring your generator documents with you. Sign up or sign in Start the sign-up from the generator. Use any sign-in method. See [Create your account](/account/sign-up). Your documents upload once Back in the generator, your browser reads the documents it saved and uploads them **once** into your account's first business. Totals are worked out again on our side by the same engine, so nothing changes. Your browser copy is cleared When the upload is done, the generator marks the documents as synced and clears them from the browser. From now on they live in your account and you can open them from any device. The upload is safe to repeat. Each document carries an id your browser created, so if you sign in again on the same browser, nothing is uploaded a second time. Invoice numbers You may already have sent a generator PDF with a number you chose yourself. As designed, imported documents keep that number as an external number and do not use up your business's gap-free sequence. New invoices you create in the account then start the business's own sequence. See [Document numbering](/invoicing/numbering). ## Generator or account? | | Generator | Free account | | --- | --- | --- | | Price | Free | Free | | Sign-up | None | Email, Google or magic link | | Where data lives | Your browser, this device only | Your account, any device | | PDF download | Yes | Yes | | Email the invoice, pay page, card and bank payments | No | Yes | | Verification QR on the PDF | No | Yes | | Saved clients, saved items, estimates, credit notes | No | Yes | | API, MCP and AI agent | `generate_pdf` only | Yes | Use the generator for a quick one-off. Create a free account as soon as you want to send by email, get paid by card or bank transfer from a pay page, or keep a record you can reach anywhere. ## Generate a PDF from code or an AI assistant The generator has a matching public action, `generate_pdf`, that needs no account and no API key. Send it the same invoice data plus business and client details and a PDF template key, and it returns the PDF. Nothing is stored. - **REST:** `POST https://api.freeinvoiceautomation.com/v1/public/generate-pdf` returns the PDF as `application/pdf`. See [Generate a PDF](/api-reference/public/generate-pdf). - **MCP:** the `generate_pdf` tool works without signing in, so any assistant connected to our MCP server can make a PDF invoice for you. It returns the PDF as base64. See the [MCP tool reference](/ai/mcp/tools). ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/public/generate-pdf" \ -H "Content-Type: application/json" \ -o invoice.pdf \ -d '{ "business": { "legal_name": "Studio North", "country": "GB" }, "client": { "name": "Acme Ltd", "email": "accounts@acme.example" }, "currency": "USD", "lines": [ { "description": "Website design", "quantity": "1", "unit_price_minor": "120000" } ] }' ``` Public calls are limited per IP address and in request size to stop abuse. If you hit the limit you get `429 rate_limited` with a `Retry-After` header. PDFs from `generate_pdf` always carry the "Powered by Free Invoice Automation" line and have no verification QR. ## Related Quickstart Create an account and send your first invoice with a pay page Create your account Sign-up methods and what happens to your generator documents PDFs and templates How PDFs are made and how to change the template Paperless mission Why invoicing is free and paperless Source: https://docs.freeinvoiceautomation.com/generator --- # Core concepts How accounts, businesses, clients, documents, statuses, revisions, capabilities, permissions and money fit together in Free Invoice Automation. A few ideas run through the whole product: who owns what, how a document moves from draft to paid, why sent documents never change in place, and how the same action works in the web app, the API, the MCP server and the AI agent. Reading this page once makes every other page easier. ## The hierarchy ```text Account (Free or Team; members with roles; billing) └── Business (1 to many: the entity that issues documents) └── Client (receives documents; never has a password) ``` ### Account An **account** is your workspace. It holds your businesses, your members and your billing, and it is on one plan: Free or Team. When you sign up, a new account on the Free plan is created with you as its **Owner**. One login can belong to many accounts. You can own your own account and be a member of someone else's at the same time, each with its own role. The account and business switcher at the top of the app lists everything you can reach. Permissions, plan limits, billing, branding and the audit log always follow the account you are looking at, never the person. ### Business A **business** is the person or company whose name is on the invoice. Each business has its own profile and logo, its own gap-free numbering, tax rates, payment methods and templates. You can have as many businesses as you like on every plan, which suits people who run more than one company or trade under more than one name. See [Businesses](/account/businesses). ### Client A **client** is who you bill. Clients belong to a business and can have several contacts. Clients **never** have a password: they open documents from a secure link, and from January 2027 they can open a magic-link [client portal](/clients/portal) with all their documents for that business. See [Clients and contacts](/clients/clients). ### Members People who work in an account are **members**. Each member has a role and, below Owner, a business scope (all businesses or a list). On Free an account has the Owner plus one free view-only Accountant. Team adds more members and editable roles. See [Members and invitations](/team/members). ## Documents Everything you send is a **document**. They share one line model, one money engine and one set of rules. | Type | What it is for | | --- | --- | | **Invoice** | A request for payment. Has a pay page. | | **Estimate** | A price you offer first. The client accepts it or asks for changes online, and you convert it to an invoice in one step. | | **Credit note** | Reduces what an invoice asks for, fully or in part, with its own number. | | **Receipt** | Confirms a payment, with its own number. | Each type has its own gap-free number sequence per business, for example `INV-2027-0001` and `CN-2027-0001`. A number is given on first send and never reused or skipped. See [Document numbering](/invoicing/numbering). ### Statuses A document's status tells you what needs doing. The main ones: | Type | Statuses | | --- | --- | | Invoice | `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void` | | Estimate | `draft`, `sent`, `changes_requested`, `accepted`, `expired`, `converted`, `void` | | Credit note | `draft`, `issued`, `applied`, `refunded` | | Receipt | `issued`, `void` | See [Document statuses](/invoicing/statuses) for what moves a document from one status to another. ### Drafts and sent documents A **draft** is private and flexible. It has no number, you can change anything, and you can delete it. When you **send** a document for the first time, three things happen: it gets its number, a **snapshot** of exactly what you sent is stored, and its status becomes `sent`. From then on the document is a record your client has seen, so it is protected: - A sent document is never edited in place. A change creates a new **revision** with its own snapshot, and the earlier versions stay readable. What your client received is always on record. See [Revisions](/invoicing/revisions). - A sent document is never deleted. A sent invoice you no longer want is **voided** with a reason: it keeps its number and shows a VOID stamp. An invoice that already has payments cannot be voided; you issue a **credit note** instead. See [Void and delete](/invoicing/void) and [Credit notes](/invoicing/credit-notes). ### Paid means paid An invoice is marked paid only from a trusted event: - a verified payment confirmation from the payment provider (for example Stripe); - a reconciliation run that checks the provider directly; - a person in your account recording the payment, which is written to the audit trail. A client being sent back to the pay page after checkout is never enough on its own, and neither is a client clicking "I have paid" for a bank transfer. This is why you can trust the word "paid". See [How payments work](/payments/overview). ## Capabilities and surfaces Every action in the product, such as "create an invoice" or "void a document", is defined **once** as a **capability**: its inputs and outputs, the permission it needs, whether it is read-only or destructive, and a preview of what it would do. From that one definition come: - the web app screens; - the REST API route under `https://api.freeinvoiceautomation.com/v1`; - the MCP tool your AI assistant calls (for the actions on the MCP tool list); - the CLI command (from late January 2027); - the tools the in-app [AI agent](/ai/agent/overview) uses; - the actions the Shopify app and WordPress plugin call. So no feature exists only in the web UI, and every surface follows the same rules and permissions. If you can do it in the app, you can do it through the API. ### Drafts first, confirmation for anything risky Integrations, MCP clients and the AI agent create **drafts**. Sending needs a person to confirm, unless you deliberately created an API key or automation in `auto_send` mode. Destructive or money-moving actions, such as voiding an invoice or issuing a credit note, always need confirmation: a dialog in the app, or `"confirm": true` in the API and MCP. Without it you get a preview and the error `confirmation_required`. See [Drafts and confirmations](/api-reference/confirmations). ## Permissions Each capability needs one **permission key** named `module.action`, for example `documents.send`, `documents.void` or `payments.record`. A role is a list of keys. - Every account has an **Owner**, who holds every key and cannot be edited or removed. - Preset roles **Admin**, **Editor**, **Viewer** and **Accountant** come with sensible defaults. On Team you can edit them or create custom roles. - Nobody can give a permission they do not hold themselves. - API keys and MCP tokens carry scopes from the same list, and at each call they can do only what both their scopes and their creator's current permissions allow. The same check runs in the app, API, MCP, CLI and AI agent, and the app hides what your role cannot do. See [Roles and permissions](/team/roles) and [Permission keys](/api-reference/permissions). ## Money as minor units Money is always stored and sent as a **whole number of minor units** plus a **currency**. Minor units are the smallest unit of the currency, such as cents for USD. ```json { "total_minor": "120000", "currency": "USD" } ``` This means 1,200.00 USD. In the API the number is a string, so no language or tool can turn it into a rounded floating-point value; floating-point amounts are rejected. All calculations (line totals, taxes, discounts, exchange rates) happen in one engine, so totals match to the cent everywhere: in the generator, the app, the PDF and the API. When an invoice is in a different currency from the business's base currency, the exchange rate is frozen on the document. See [Money and currencies](/api-reference/money) and [Currencies and exchange rates](/invoicing/currencies). ## Links your clients open Pay pages, estimate pages and portal links use secure tokens: random, stored only in hashed form, made for one purpose, expiring and revocable. Voiding an invoice cancels its pay link. Pay, portal, pay-me and verification pages carry no ads and no analytics. See [Share links](/invoicing/share-links). ## Related Quickstart Put these ideas to work with your first invoice Invoices Create, send and track invoices Roles and permissions What each role can do by default Glossary Every term used in the product, A to Z Source: https://docs.freeinvoiceautomation.com/concepts --- # Create your account Sign up for free with email and password, Google, Apple or a magic link, verify your email and set up your first business. A free account gives you everything the generator does plus email sending, pay pages, card and bank payments, saved clients and items, estimates, credit notes, the API and the MCP server. It costs nothing, needs no card, and every invoicing feature is included on the Free plan. Sign up at [app.freeinvoiceautomation.com/sign-up](https://app.freeinvoiceautomation.com/sign-up). ## Ways to sign up | Method | How it works | | --- | --- | | **Email and password** | Choose a password. Very short passwords and passwords known from data breaches are refused with a clear message. | | **Google** | One click with your Google account. | | **Apple** | Sign in with Apple. If Apple gives us a private relay email address instead of your real one, we accept it as your account email. Sign in with Apple is available at launch if Apple approves our developer account in time, otherwise in the week of 11 January 2027. | | **Magic link** | Enter your email and we send a sign-in link. Each link works once and expires after a short time. | After you are in, you can add a **passkey** for fast sign-in with your device's fingerprint, face or PIN, and turn on **two-factor authentication** with an authenticator app. Both are optional. See [Sign-in methods](/security/sign-in) and [Two-factor and passkeys](/security/two-factor). A sign-in button only appears when that provider is set up on our side. If you do not see one, use another method; you can sign in the same way next time. ## Sign up step by step Open the sign-up page Go to [app.freeinvoiceautomation.com/sign-up](https://app.freeinvoiceautomation.com/sign-up), or start from the generator if you already made invoices there. Choose a method Pick email and password, Google, Apple or a magic link and follow the prompts. Your account is created We create a new account on the **Free plan** with you as its **Owner**. The Owner holds every permission and is the only person who can delete the account or transfer ownership. Add your first business Onboarding asks for your first business: the legal name that goes on invoices, the country and the base currency. For businesses in Pakistan, India, the UK, the EU, the US and the UAE, the country pre-fills the currency and tax presets. You can change everything later in the business settings. See [Businesses](/account/businesses). Verify your email Open the verification link we email you. You can use the app before you verify, but sending documents through our platform mailer needs a verified address. ## Verify your email We send a verification link to your email address when you sign up. Until you click it: - you can create businesses, clients and documents, download PDFs and copy pay links; - sending email through the **platform mailer** is refused with "verify your email first" (error `email_not_verified`). Sending through your own SMTP mailbox is not affected. If the email does not arrive, check your spam folder and ask for a new link from the app. See [Platform email](/messaging/platform-email). ## If you used the generator first If you made invoices in the [free invoice generator](/generator) on this browser, they move into your account after you sign up. Your browser uploads them **once** into your first business, then clears its own copy. Signing in again on the same browser does not upload them a second time. Generator documents on another device or browser stay there until you sign in from that browser too. ## One login, many accounts Your login is personal; accounts are shared spaces. - If someone invites you to their account, you join it as a member with the role they chose, using the same login. You do not need a second sign-up. - You can be the Owner of your own account and a member of other accounts at the same time. - The switcher at the top of the app lists every account and business you can reach. What you can do always depends on the account you are viewing. - Being deactivated in one account does not affect your other accounts or your own. See [Members and invitations](/team/members). ## Staying safe - **Sessions** are kept on our servers and use secure cookies. - **Wrong passwords:** after too many wrong attempts the account is locked for a while and further attempts are slowed down. - **Forgotten password:** ask for a reset and we email a single-use reset link. - **Fresh sign-in:** sensitive actions ask you to sign in again if your session is not recent. These include connecting a payment method such as Stripe, deleting a business, changing billing, adding a billing card, buying a credit pack, creating an `auto_send` API key and deleting the account. If an API call needs this, it returns `fresh_signin_required`. See [How we protect data](/security/overview). ## What the Free plan includes Every invoicing feature, unlimited businesses, the API, MCP server and connectors, and 20 platform emails a month (unlimited with your own SMTP). The account has the Owner plus one free view-only Accountant. In your first month after sign-up you can unlock 250 AI credits by adding a card that is verified for $0 and never charged. When you need more people, editable roles, an audit log you can view, 100 platform emails a month, 1,000 AI credits a month and no "Powered by" line, you can [upgrade to Team](/billing/team). There is no trial; Team comes with a 30-day money-back guarantee on the first payment. ## Related Quickstart Send your first invoice in about five minutes Free and Team plans Compare the plans side by side Sign-in methods Add or change how you sign in Delete your account What happens if you leave Source: https://docs.freeinvoiceautomation.com/account/sign-up --- # Free and Team plans Free has every invoicing feature, forever. Team adds members, roles, an audit log, more emails and AI credits, and removes our branding. Free Invoice Automation has two plans you can choose today: **Free** and **Team**. Free is not a trial or a cut-down version. It has every invoicing feature and always will. Team is for working with other people and for a few comforts, and it never unlocks invoicing features. We never take a percentage of the money your clients pay you, on any plan. ## Compare the plans | | **Free** | **Team** | | --- | --- | --- | | Price | $0, forever | $10 a month, or $96 a year (shown as $8 a month billed yearly). Regional price $5 a month or $48 a year | | Invoicing features | Everything | Everything | | Businesses | Unlimited | Unlimited | | API, MCP server, CLI, connectors | Yes | Yes | | Members | Owner plus 1 free view-only Accountant | 5 included, then $2 a month ($20 a year) per extra member; regional $1 a month ($10 a year) | | Roles | Owner and Accountant, fixed | Edit every role except Owner, create custom roles | | Audit log screen | Locked (entries are still recorded) | Yes | | Platform emails (without your own SMTP) | 20 a month | 100 a month | | Your own SMTP | Unlimited | Unlimited | | AI invoice agent | 250 welcome credits in your first month, unlocked by adding a card; then credit packs or your own AI key | 1,000 credits a month (2,000 in your first paid month); credit packs; your own AI key | | AI credit packs | 1,000 credits for $5 | 1,000 credits for $5 | | "Powered by Free Invoice Automation" on PDFs, pay pages, portal and emails | Shown | Removed | | Priority support | No | Email, first reply within 1 business day | | Trial | Not needed | No trial; 30-day money-back guarantee on the first payment | "Everything" means invoices, estimates, credit notes, receipts, saved items, recurring invoices, reminders and late fees, deposits and payment schedules, reports, contracts, time and expenses, accounting sync and the ledger, as each of them ships. Features that arrive after launch arrive on Free too. ## What is free, and why Invoicing should never be the reason someone prints paper or pays a fee to get paid. So the Free plan includes: - every document type and every invoicing feature; - unlimited businesses, clients and documents; - card payments through your own Stripe and bank transfers, with no cut taken by us; - the REST API, the MCP server for AI assistants, the CLI and the store connectors. Free has fair-use limits only to stop abuse, such as a cap on API calls per month, storage and sends per minute. Real businesses rarely meet them. See [Fair-use limits](/billing/fair-use). ## What Team adds Team is for when more than two people work in the account, or when you want the extras. - **Members:** 5 people included, then extra seats. See [Member seats](/billing/members). - **Roles:** edit Admin, Editor, Viewer and Accountant, or build custom roles from scratch. See [Custom roles](/team/custom-roles). - **Audit log screen:** see who did what and when. See [Audit log](/team/audit-log). - **No "Powered by" line** on new PDFs, pay pages, portal pages and client emails. - **More platform emails:** 100 a month instead of 20. - **AI credits:** 1,000 a month for the in-app agent, and 2,000 in your first paid month. See [AI credits](/billing/ai-credits). - **Priority support** by email, with a target first reply within 1 business day. ## Regional price Team costs **$5 a month or $48 a year** (extra members $1 a month or $10 a year) when your **billing country** is on our regional list. At launch the list is Pakistan, India, Bangladesh, Nigeria, Kenya, Egypt, Sri Lanka, Nepal, Ghana, the Philippines, Indonesia and Vietnam. Checkout shows the price for your card's country. If the card's country and your billing address country do not match, the standard price applies. Everything in Team is the same at both prices. The list may change over time. ## Who you pay Free Invoice Automation is owned by **Genyune (SMC-Private) Limited**. Team subscriptions and AI credit packs are sold by **Genyune Inc.**, which collects payment through Stripe. That is the name you see at checkout and on your receipts. ## Enterprise An Enterprise plan is designed but not available at launch. It is planned from $49 a month or $490 a year, with 10 members included (then $3 a month each), custom domains, white-label and single sign-on, and AI credits by contract. If you need it, email [info@freeinvoiceautomation.com](mailto:info@freeinvoiceautomation.com). ## Free Team for non-profits, education and students Verified non-profits, schools and students can apply for Team at no charge, with proof. Applications are reviewed by hand. The application process opens after launch. ## How prices and limits change Every price and allowance on this page is a setting on our side, not something built into the code, so we can adjust them without downtime. Checkout always shows the price you are about to pay. Monthly allowances (platform emails and Team AI credits) reset on the first of each month and do not roll over. ## Check your plan through the API Your plan, seats and billing state are returned by [Get billing](/api-reference/billing/get-billing). Your current emails, AI credits, API calls and storage against your limits are returned by [Get usage](/api-reference/messaging/get-usage), and shown in **Settings > Usage**. ```bash cURL curl "https://api.freeinvoiceautomation.com/v1/usage" \ -H "Authorization: Bearer $FIA_API_KEY" ``` Viewing billing needs the `billing.manage` permission, which only the Owner has by default. ## Related Upgrade to Team Buy Team, change interval, cancel AI credits Allowances, expiry and the order credits are used 30-day money-back A full refund on your first Team payment Failed renewal and downgrade What happens if a renewal payment fails Source: https://docs.freeinvoiceautomation.com/billing/plans --- # Upgrade to Team Buy Team monthly or yearly through Stripe Checkout, manage your subscription in the Stripe Customer Portal, and cancel whenever you like. Team adds members, editable roles, the audit log screen, 100 platform emails and 1,000 AI credits a month, priority support, and removes the "Powered by" line from what your clients see. You buy it inside the app in a couple of minutes. There is no trial: instead, your first Team payment is covered by a [30-day money-back guarantee](/billing/money-back). ## Prices | | Standard | Regional | | --- | --- | --- | | Monthly | $10 a month | $5 a month | | Yearly | $96 a year ($8 a month billed yearly) | $48 a year | | Each member above 5 | $2 a month or $20 a year | $1 a month or $10 a year | The regional price applies when your billing country is on the regional list. See [Free and Team plans](/billing/plans#regional-price). ## Who can buy Buying and managing Team needs the `billing.manage` permission. By default only the **Owner** has it. Because billing is sensitive, the app asks you to sign in again if your session is not recent. ## Buy Team Billing"> In the app, go to **Settings > Billing** and click **Upgrade to Team**. Choose monthly or yearly Pick the interval. If you already know you need more than 5 members, you can add extra seats now; you can also add them later. See [Member seats](/billing/members). Pay on Stripe Checkout You are taken to Stripe Checkout, run by Genyune Inc., the company that sells Team. Pay by card or Link, or by Apple Pay or Google Pay where your device supports them. Checkout shows the price for your card's country. Team switches on When Stripe confirms the payment to us, your account moves to Team. Coming back from Checkout in your browser does not switch the plan by itself; the confirmation from Stripe does, which usually takes a moment. Free Invoice Automation is owned by Genyune (SMC-Private) Limited. Team subscriptions are sold by Genyune Inc. on its Stripe account, so Genyune Inc. is the name on your receipt. ## What changes when Team starts - **Members:** you can have 5 members, then add paid seats. See [Members and invitations](/team/members). - **Roles:** Admin, Editor, Viewer and Accountant become editable, and you can create custom roles. See [Roles and permissions](/team/roles). - **Audit log:** the screen unlocks. Entries were already being recorded on Free, so you can see earlier activity too. - **Branding:** new PDFs, pay pages, portal pages and emails no longer show "Powered by Free Invoice Automation". Documents you already sent are not changed. - **Platform emails:** your monthly quota becomes 100. - **AI credits:** your first paid month gives you 2,000 credits (1,000 monthly plus a one-time welcome bonus of 1,000, once per account). After that you get 1,000 a month. See [AI credits](/billing/ai-credits). - **Support:** email support with a target first reply within 1 business day. ## Manage your subscription Open **Settings > Billing** to see your plan, interval, seats and next renewal. From there you can open the **Stripe Customer Portal** to: - change the card you pay with; - download your invoices and receipts; - cancel the subscription. ### Cancel Cancelling is your choice at any time. Team stays on until the end of the period you already paid for. At the end of that period the account moves to Free at once, with no grace period. Nothing is deleted: your documents, clients, payments and settings stay, and pay pages keep working. What changes on Free (members, branding, credits, email quota) is the same as after a failed renewal; see [What changes on downgrade](/billing/failed-renewal#what-changes-on-downgrade). ### If a renewal payment fails You get 5 days of grace with everything still working, a red banner in the app and emails telling you what to do. See [Failed renewal and downgrade](/billing/failed-renewal). ### Upgrade again later You can return to Team at any time through Checkout. Your roles and branding settings come back. Members who were deactivated when the account went to Free are not restored automatically: the Owner reactivates each one in **Settings > Team**. You get 1,000 monthly AI credits; the first-month bonus is given only once per account. ## Through the API Billing actions are part of the API like everything else, but they need a recent sign-in, so they run from a signed-in session rather than an API key. | Action | Endpoint | | --- | --- | | See your plan, seats and billing state | [Get billing](/api-reference/billing/get-billing) `GET /v1/billing` | | Start a Team checkout | [Start Team checkout](/api-reference/billing/start-team-checkout) `POST /v1/billing/checkout` | | Open the Stripe Customer Portal | [Open billing portal](/api-reference/billing/open-billing-portal) `POST /v1/billing/portal` | ```json Start Team checkout (request body) { "interval": "year", "extra_members": 2 } ``` The response gives you the Stripe Checkout URL to open. The standard or regional price is chosen for you from the billing country. All three actions need `billing.manage`. ## Errors you may see | Code | What it means | | --- | --- | | `fresh_signin_required` | Sign in again, then retry. | | `permission_denied` | Your role does not have `billing.manage`. Ask the Owner. | | `upstream_error` | Stripe did not answer as expected. Try again in a moment. | ## Related Member seats How extra members are billed 30-day money-back A full refund on your first payment Failed renewal and downgrade The 5-day grace period and what happens next Free and Team plans Everything each plan includes Source: https://docs.freeinvoiceautomation.com/billing/team --- # Member seats How many people each plan includes, what extra Team seats cost, and how seats work when you invite, deactivate or remove members. A **member** is a person who can sign in to your account and work in it. Each plan includes a number of members, and on Team you can add more for a small monthly or yearly price. Your clients are never members and never count as seats: they open documents from links and have no password. ## Seats by plan | Plan | Included | Extra members | | --- | --- | --- | | **Free** | The Owner plus 1 free view-only Accountant | Not available. Upgrade to Team for more people. | | **Team** | 5 members | $2 a month or $20 a year each; regional price $1 a month or $10 a year each | | **Enterprise** (after launch) | 10 members | $3 a month each | ### On Free A Free account has two fixed seats: - the **Owner**, who has every permission; - one **Accountant**, who can view documents, clients, payments and reports and export them, but cannot change anything. The Accountant seat is free on every plan, which suits a bookkeeper or accountant who needs to see your numbers. If you try to invite a second member, or a member with any role other than Accountant, the app shows the Team upgrade instead. ### On Team Team includes 5 members of any role. Roles are fully yours to shape: you can edit Admin, Editor, Viewer and Accountant, or create custom roles. See [Roles and permissions](/team/roles). ## Add a sixth member and beyond Invite the member Go to **Settings > Team**, invite the person by email and choose their role and the businesses they can reach. See the price first When the invite would go above the members included in your plan, the app shows the extra-member price before the invitation is sent, so there are no surprises. Confirm The extra seat is added to your Team subscription. Stripe adjusts the charge for the part of the billing period that is left (proration), and your next invoice shows the seats you have. You need the `team.manage` permission to invite members. The extra seat changes your subscription, so it is billed to the account's Team subscription managed by the Owner. ## Seats and member status Members can be active, deactivated or removed. Here is how each affects your account: - **Active:** the member can sign in and work, within their role and business scope. - **Deactivated:** access ends at once. The member's sessions in this account are signed out and their API keys and MCP tokens for it are revoked. Their work stays. The Owner or a member with `team.manage` can reactivate them later; they then create new API keys. - **Removed:** the membership ends. Their work stays and is still shown as theirs in documents and the audit log. The Owner can never be deactivated or removed. Every deactivate, reactivate and remove is audited, and the member gets an email. Deactivating someone in your account does not affect their own account or any other account they belong to. See [Deactivate and remove](/team/deactivate). ## When Team ends If Team ends because a renewal payment failed after the grace period, because you cancelled, or because you used the money-back guarantee, the account moves to Free, which allows only the Owner and one view-only Accountant. - Every member except the Owner is **deactivated** at once: signed out of the account, API keys and MCP tokens for it revoked, work kept. - The Owner can reactivate **one** member, who continues as the view-only Accountant. - Custom and edited roles are kept but inactive until you upgrade again. When you upgrade again, roles come back but deactivated members are **not** restored automatically. The Owner reactivates each one in **Settings > Team**, or removes them, and each reactivated member creates new API keys and MCP tokens. See [Failed renewal and downgrade](/billing/failed-renewal). ## One person, several accounts Seats are counted per account, not per person. The same email can be the Owner of their own account and a member of other accounts, and they take a seat in each account they belong to. Their permissions, and the plan rules that apply, always follow the account they are working in. ## Through the API | Action | Endpoint | Permission | | --- | --- | --- | | List members | [List members](/api-reference/team/list-members) | `settings.view` | | Invite a member | [Invite a member](/api-reference/team/invite-member) | `team.manage` | | Change role or business scope | [Update a member](/api-reference/team/update-member) | `team.manage` | | Deactivate | [Deactivate a member](/api-reference/team/deactivate-member) | `team.manage` | | Reactivate | [Reactivate a member](/api-reference/team/reactivate-member) | `team.manage` | | Remove | [Remove a member](/api-reference/team/remove-member) | `team.manage` | ## Errors you may see | Code | What it means | | --- | --- | | `plan_limit_reached` | Your plan does not include another member of this kind. On Free, upgrade to Team. | | `feature_locked` | Roles other than Owner and Accountant are a Team feature. | | `member_deactivated` | The member is deactivated in this account. Old API keys and MCP tokens from that member are revoked and return `unauthenticated`. | | `last_owner` | The change would leave the account without an Owner. | ## Related Members and invitations Invite people and set their business scope Roles and permissions What each preset role can do Upgrade to Team Buy Team and manage your subscription Deactivate and remove End someone's access at once Source: https://docs.freeinvoiceautomation.com/billing/members --- # AI credits How credits for the AI invoice agent work: what each plan gets, what an action costs, when credits expire and which credits are used first. The [AI invoice agent](/ai/agent/overview) in **Automate > AI agent** turns plain language into draft invoices, estimates and clients. It runs on our own AI provider key, so you do not need one. Each time it works for you it uses **credits**, a simple unit that stands for the AI cost behind the scenes. This page explains how many credits you get, what things cost, and when credits expire. If you connect your own AI key (Anthropic, OpenAI, Google or an OpenAI-compatible provider), the agent uses your key and **no credits at all**, on any plan. See [Use your own AI key](/ai/agent/own-key). ## How many credits you get | | Free | Team | | --- | --- | --- | | Monthly credits | None | 1,000 a month | | Welcome credits | 250, in your first month after sign-up, unlocked by adding a card | 1,000 bonus in your first paid month (so 2,000 that month), once per account | | Credit packs | 1,000 for $5 | 1,000 for $5 | | Your own AI key | Unlimited, no credits used | Unlimited, no credits used | ### Free A Free account gets **250 welcome credits** in its first month after sign-up, once you add a card. The card is verified for $0 at Stripe and is **never charged**. Prepaid cards cannot unlock the welcome credits. Each card and each account can receive the welcome grant only once. See [Billing card](/billing/billing-card). After that first month, a Free account can buy [credit packs](/billing/credit-packs) or connect its own AI key. Without either, the agent stays visible but locked, with a message offering a credit pack, Team or your own key. ### Team A Team account gets **1,000 credits on the first of each month**. In your first paid month you get **2,000**: the usual 1,000 plus a one-time welcome bonus of 1,000. The bonus is given once per account, so upgrading again later does not repeat it. There is no Team trial, so there are no trial credits. ## What things cost | Action | Credits | | --- | --- | | An agent request that creates or updates up to one document | 1 | | Reading a receipt or a PDF (from February 2027) | 2 | | One run of an automation | 1 | | A run that fails before the agent calls any tool | 0 | These are the starting costs. They are settings on our side and may be adjusted, and new runs always use the current costs. Other AI features, such as AI-written reminders and voice input, use the same credits when they ship. ## Expiry Credits are either **free** (given to you) or **paid** (bought in a pack). - **Free credits expire within a month.** Free welcome credits expire at the end of your first month after sign-up. Team monthly credits and the first-month bonus expire at the end of the month they were given. Nothing rolls over. - **Paid credits expire 12 months after purchase.** ## Which credits are used first The agent always spends **free credits first**, then your **oldest paid credits**. This way the credits that expire soonest are used first, and pack credits you paid for last as long as possible. For example, a Team account on 20 March with 300 March credits left and two packs (one bought in January, one in March) spends the 300 March credits, then the January pack, then the March pack. ## Watch your balance **Settings > Usage** shows your balance as a bar, split into monthly or welcome credits and pack credits, with their expiry dates. - At **80%** used, the agent shows a warning. - At **0**, the agent stops before doing any work and offers you a credit pack (any plan), an upgrade to Team (on Free), or connecting your own AI key (any plan). You can read the same numbers through [Get usage](/api-reference/messaging/get-usage), which returns AI credits split into monthly or welcome and pack credits with their expiry dates, plus an 80% warning flag. ```bash cURL curl "https://api.freeinvoiceautomation.com/v1/usage" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ## Limits behind the credits To keep the service fair, each account also has a per-minute limit on agent requests and a monthly ceiling on the AI work behind the credits. If either is reached, the agent pauses for that account even if credits remain. Normal use does not come near these limits. See [Fair-use limits](/billing/fair-use). ## When your plan changes - **Free to Team:** you get 1,000 monthly credits plus the one-time 1,000 bonus in your first paid month. - **Team to Free** (cancel, failed renewal or money-back refund): Team monthly credits stop, and any left this month expire. **Paid pack credits are kept** until their 12-month expiry. The Free welcome credits are not given again. - **Free back to Team:** 1,000 monthly credits; no second first-month bonus. ## Credits and MCP When you use Free Invoice Automation from Claude, ChatGPT, Grok or Gemini through our [MCP server](/ai/mcp/overview), your own assistant does the AI work. MCP calls never use credits. Credits are only for the agent inside our app. ## Errors you may see | Code | What it means | | --- | --- | | `ai_credits_exhausted` | No credits left. Buy a pack, upgrade to Team, or connect your own AI key. | | `feature_locked` | The agent is locked on Free with no welcome, pack or own-key access. | | `billing_card_required` | The Free welcome credits need a verified card first. | | `welcome_credits_not_eligible` | This card or account already received welcome credits, or the first month has passed. | | `rate_limited` | Too many agent requests in a minute. Wait for the time in `Retry-After`. | ## Related Credit packs Buy 1,000 credits for $5, only when you click Billing card Unlock the Free welcome credits Credits and costs Credit use from the agent's side Use your own AI key Unlimited agent use with no credits Source: https://docs.freeinvoiceautomation.com/billing/ai-credits --- # Billing card Add a card on the Free plan to unlock 250 welcome AI credits. It is verified for $0, stays at Stripe and is never charged. On the Free plan you can add a **billing card** to unlock **250 welcome AI credits** for the [AI invoice agent](/ai/agent/overview). The card is checked with a **$0 verification** at Stripe and is **never charged**: not for credit packs, not automatically, not ever. Its only job is to show that a real person with a real card is behind the account, which keeps free AI credits from being claimed again and again with new accounts. You do not need a card for anything else on Free. Invoicing, payments, the API and MCP all work without one. ## What the card is for, and what it is not | The billing card | Yes or no | | --- | --- | | Unlocks the 250 Free welcome credits | Yes | | Charged when you add it | No, it is a $0 verification | | Charged for credit packs | No. Packs are paid on their own Stripe Checkout page | | Charged automatically for anything | No. We never charge automatically | | Used to pay for Team | No. Team is paid on Stripe Checkout | | Stored on our servers | No. The card stays at Stripe | ## When you can unlock the welcome credits - Your account is on the **Free** plan. - You are in your **first month after sign-up**. The welcome credits are for that first month and unused ones expire at its end. - Neither this **card** nor this **account** has received welcome credits before. We recognise a card by a fingerprint Stripe gives us, so the same card cannot unlock credits on a second account. - The card is a **debit or credit card**, not a prepaid card. ## Add a card You need the `billing.manage` permission (the Owner has it by default), and the app asks you to sign in again if your session is not recent. Open the prompt Go to **Settings > Usage**, or open the AI agent in **Automate > AI agent**, and choose to add a card to unlock the welcome credits. Enter the card on Stripe's form The card form is provided by Stripe and is on the Genyune Inc. Stripe account, the company that sells Team and credit packs. Your card number goes to Stripe, not to us. Stripe verifies it for $0 Stripe checks the card without charging it. Some banks show a $0 or temporary authorisation; nothing is taken. Credits arrive When Stripe confirms the verification to us, 250 welcome credits are added to your balance. You can see them in **Settings > Usage**. If the verification fails, no credits are given and the app tells you why. ## Prepaid cards Prepaid cards, including virtual prepaid cards, **cannot unlock the welcome credits**. Stripe tells us each card's funding type, and a prepaid card is refused with this message: > Prepaid cards cannot unlock the welcome credits. Use a debit or credit card, or buy a credit pack. The error code is `card_not_eligible`. No card is saved and no credits are given. A prepaid card **can** still pay for [credit packs](/billing/credit-packs) on Stripe Checkout like any other card. ## What we keep Card data stays at Stripe. We store only: - the Stripe customer and payment-method ids; - the card brand and the last 4 digits, so you can recognise it; - the card fingerprint, so one card unlocks credits only once; - the funding type (debit, credit or prepaid). We never see or store your full card number, expiry date or security code. See [How we protect data](/security/overview). ## Remove the card You can remove the card at any time from **Settings > Usage**. It is detached at Stripe. Credits you already received stay until they expire at the end of your first month. Removing and adding the card again does not give you a second grant. ## Through the API | Action | Endpoint | | --- | --- | | Add a billing card | [Add a billing card](/api-reference/billing/add-billing-card) `POST /v1/billing/card` | | Remove the billing card | [Remove the billing card](/api-reference/billing/remove-billing-card) `DELETE /v1/billing/card` | `POST /v1/billing/card` returns a Stripe SetupIntent client secret. Your page uses it with Stripe's own card form to run the $0 verification; the card never passes through our API. Both actions need `billing.manage`, and adding a card needs a recent sign-in, so it runs from a signed-in session. ## Errors you may see | Code | What it means | | --- | --- | | `card_not_eligible` | The card is prepaid. Use a debit or credit card, or buy a credit pack. | | `welcome_credits_not_eligible` | This card or account already received welcome credits, or your first month has passed. | | `billing_card_required` | You tried to use the welcome credits before adding a verified card. | | `fresh_signin_required` | Sign in again, then retry. | | `rate_limited` | Too many card attempts in a short time. Wait and try later. | ## Related AI credits Allowances, expiry and the order credits are used Credit packs Buy more credits, any card accepted Agent overview What the AI invoice agent does Use your own AI key No credits and no card needed Source: https://docs.freeinvoiceautomation.com/billing/billing-card --- # Credit packs Buy 1,000 AI credits for $5 on Free or Team. Packs are bought only when you click, never charged automatically, and last 12 months. A **credit pack** adds **1,000 AI credits for $5** to your account. Packs work the same on Free and Team. You buy one only when you choose to, by clicking a button; we never top up your credits or charge a card automatically. Pack credits last **12 months** from purchase and are kept even if your plan changes. Credits are used by the [AI invoice agent](/ai/agent/overview). If you connect your own AI key instead, the agent uses no credits and you never need a pack. See [AI credits](/billing/ai-credits) for the full picture. ## What you get | | | | --- | --- | | Credits per pack | 1,000 | | Price | $5 | | Plans | Free and Team | | Expiry | 12 months after purchase | | How you pay | Stripe Checkout on the Genyune Inc. account, any card including prepaid | | When you are charged | Only when you click and complete Checkout | ## Bought only by your click We designed packs so you are never surprised: - **No auto top-up.** Running out of credits stops the agent; it does not buy anything. - **No charge to a saved card.** Your Free [billing card](/billing/billing-card) is only for the $0 verification and is never charged. Packs always go through their own Checkout page. - **A fresh sign-in first.** Buying a pack is a billing action, so the app asks you to sign in again if your session is not recent. - **Credits after confirmation.** Credits are added only when Stripe confirms the payment to us, never because your browser came back from Checkout. ## Buy a pack You need the `billing.manage` permission. By default only the Owner has it. Click Buy 1,000 credits In **Settings > Usage**, click **Buy 1,000 credits**. The same button appears in the agent when your credits run out. Sign in again if asked If your session is not recent, sign in again to continue. Pay on Stripe Checkout Stripe Checkout opens on the Genyune Inc. account for $5. Any card works here, including prepaid cards. Credits are added When Stripe confirms the payment, 1,000 credits appear in **Settings > Usage**, with their expiry date. ## How pack credits are spent The agent spends **free credits first** (Free welcome credits, or Team monthly and bonus credits), then **the oldest pack credits**. So a pack bought today is used only after your free credits for the month and any older packs are gone. That makes the most of credits that expire soon and keeps your paid credits for as long as possible. ## Pack credits and plan changes - **Free to Team:** your pack credits stay and are used after your Team monthly credits. - **Team to Free** (cancel, failed renewal or money-back refund): Team monthly credits stop, but **pack credits are kept** until their 12-month expiry. - **Removing your billing card** does not affect pack credits. ## Refunds Credit packs can be refunded within 14 days of purchase if the credits are unused. To ask for one, email [info@freeinvoiceautomation.com](mailto:info@freeinvoiceautomation.com). The full rules are in the Terms of Service on [freeinvoiceautomation.com](https://freeinvoiceautomation.com). ## Through the API | Action | Endpoint | | --- | --- | | Buy a pack | [Buy a credit pack](/api-reference/billing/buy-credit-pack) `POST /v1/billing/credit-packs` | | List your packs | [List credit packs](/api-reference/billing/list-credit-packs) `GET /v1/billing/credit-packs` | | See your balance | [Get usage](/api-reference/messaging/get-usage) `GET /v1/usage` | `POST /v1/billing/credit-packs` creates a Stripe Checkout Session for one pack and returns its URL. The person still pays on Stripe's page; the API never charges a card by itself. Call it only when the user has asked to buy. It needs `billing.manage` and a recent sign-in, so it runs from a signed-in session. From late January 2027 the CLI command `credits buy` prints the Checkout URL for you to open. ## Errors you may see | Code | What it means | | --- | --- | | `fresh_signin_required` | Sign in again, then retry. | | `permission_denied` | Your role does not have `billing.manage`. Ask the Owner. | | `upstream_error` | Stripe did not answer as expected. Try again in a moment. | ## Related AI credits Allowances, expiry and order of use Billing card The $0 card that unlocks Free welcome credits Upgrade to Team 1,000 credits every month Use your own AI key Skip credits entirely Source: https://docs.freeinvoiceautomation.com/billing/credit-packs --- # Failed renewal and downgrade If a Team renewal payment fails you get 5 days of grace with everything working. If it stays unpaid, the account moves to Free and nothing is deleted. Cards expire and banks decline payments. If your Team renewal does not go through, nothing breaks straight away. You get a **5-day grace period** in which everything in Team keeps working, while Stripe retries the card and we remind you. If the payment is still not made at the end of the grace period, the account moves to the **Free** plan. Nothing is ever deleted, and your clients can still pay you. This applies to monthly and yearly Team subscriptions alike. ## The timeline | Day | What happens | | --- | --- | | **0** (renewal date) | Stripe tries your card. If it fails, a red banner appears in the app for the Owner and anyone with `billing.manage`: "Payment failed. Update your card by `{date}` to keep Team." The Owner gets an email with a link to the Stripe Customer Portal. **Everything in Team keeps working.** | | **1 to 4** | Stripe retries the card on days 1, 3 and 5. A reminder email goes out on day 3. Paying at any moment ends the grace period and nothing changes for you. | | **5** (end of grace) | The last retry, then a "last day" email. If the payment is still not made, the subscription is cancelled and the account moves to Free. The Owner gets a "Your account is now on Free" email that explains what changed, with a one-click way back to Team. | During the grace period your PDFs, pay pages, portal and emails keep Team branding (no "Powered by" line), your members keep working, and your Team limits stay in place. ## Fix it during the grace period Open the Stripe Customer Portal Click the link in the banner or in the email, or go to **Settings > Billing** and open the Stripe Customer Portal. Update your card Add a working card, or fix the problem with your bank. Pay the open invoice As soon as Stripe confirms the payment, the grace period ends, the banner goes away and nothing changes for your account. Updating the card before day 5 is all it takes. You do not lose credits, members or settings by paying late within the grace period. ## What changes on downgrade If day 5 passes unpaid, the account moves to Free. Here is exactly what changes, and what does not. ### Keeps working - **Getting paid:** pay pages, the client portal, card payments through your Stripe and bank transfers. - **Automation:** recurring invoices, reminders and late fees keep running. - **Developers:** the API, MCP server and connectors keep working. Only keys and tokens of members who are deactivated stop. - **Your data:** every document, client, payment, item and setting stays. Nothing is deleted. ### Changes - **Branding:** "Powered by Free Invoice Automation" returns on **new** PDFs, pay pages, portal pages and emails. Documents you already sent are not changed. - **Members:** Free allows the Owner plus one view-only Accountant. Every other member is **deactivated** at once: their sessions in this account are signed out, they cannot open it, and their API keys and MCP tokens for it are revoked. Their work (documents, comments, audit history) stays, and their own login still works for any other account they belong to. - **The Accountant seat:** the Owner chooses which member, if any, to reactivate as the one view-only Accountant. - **Roles:** custom and edited roles are kept but inactive. - **AI credits:** Team monthly credits stop and any left this month expire. **Paid credit pack credits are kept** until their 12-month expiry. The Free welcome credits are not given again. - **Email:** the platform email quota becomes 20 a month from the moment of the downgrade. If the account has already sent 20 or more platform emails this month, the platform mailer pauses until next month. Reminder and recurring emails sent through the platform mailer count against this quota too. Your own SMTP is not affected. - **Audit log:** the screen is hidden. Entries are still recorded, and you can see them again after upgrading. If you rely on platform email for reminders or recurring invoices, connect your own SMTP mailbox before the grace period ends. It has no monthly limit and is not affected by the downgrade. See [Your own SMTP](/messaging/smtp). ## Other ways Team ends - **You cancel:** Team stays until the end of the period you already paid for, then the account moves to Free at once. There is no grace period, because nothing failed. - **Money-back refund:** if you use the [30-day money-back guarantee](/billing/money-back), the account moves to Free at once. In both cases the changes listed above apply in the same way. ## Come back to Team You can upgrade again at any time through Checkout from **Settings > Billing**, or from the link in the "Your account is now on Free" email. - Your roles and branding settings come back. - You get 1,000 monthly AI credits. The first-month bonus is given only once per account, so it does not repeat. - Deactivated members are **not** restored automatically. The Owner reactivates each one with one click in **Settings > Team**, or removes them. Reactivated members get new sessions and must create new API keys and MCP tokens. ## Errors you may see after a downgrade | Code | What it means | | --- | --- | | `member_deactivated` | The member was deactivated in this account by the downgrade. The Owner can reactivate them. | | `unauthenticated` | An API key or MCP token of a deactivated member was revoked. Create a new one after reactivation. | | `platform_mail_paused` | The account already sent 20 or more platform emails this month. Connect your own SMTP or wait for next month. | | `feature_locked` | A Team feature such as role editing or the audit log screen. | ## Behind the scenes The plan changes only from Stripe's confirmed billing events, never from a page in your browser. A daily check compares every account with Stripe, so even if one message from Stripe is lost, your account ends up in the right state. Every change of plan is written to the audit log. ## Related Upgrade to Team Manage your subscription and card Member seats How seats work on each plan Deactivate and remove Reactivate members after re-upgrading Platform email Quotas and what happens when they run out Source: https://docs.freeinvoiceautomation.com/billing/failed-renewal --- # 30-day money-back Team has no trial. Instead, your first Team payment is fully refundable for 30 days, once per account. Team has **no free trial**. You buy it directly, and your **first Team payment** comes with a **30-day money-back guarantee**: if Team is not right for you, ask within 30 days of that payment and you get a full refund. Each account can use the guarantee once. We chose a guarantee over a trial because a trial ends with a surprise charge or a sudden loss of features. With the guarantee you get the real Team from day one, with your real members and credits, and you can still change your mind. ## What is covered | | Covered | | --- | --- | | Your account's first Team payment, monthly or yearly | Yes, in full | | Within 30 days of that payment | Yes | | Later renewals | No | | A second Team purchase after an earlier refund | No, the guarantee is once per account | | Requests after 30 days | No | | Credit packs | No; packs have their own refund rule (see below) | If you bought extra member seats with your first payment, they are part of that payment. ## How to ask for a refund Email us within 30 days The Owner emails [info@freeinvoiceautomation.com](mailto:info@freeinvoiceautomation.com) from the account's email address within 30 days of the first Team payment and asks for the money-back refund. A reason is welcome but not required. We check eligibility We confirm it is the account's first Team payment, that it is within 30 days and that the guarantee has not been used before. We refund through Stripe The full amount goes back to the card or wallet you paid with, through Stripe. Every refund is recorded in our own audit log. How long the money takes to show on your statement depends on your bank. Your account moves to Free The account moves to the Free plan at once. ## What happens to your account Moving to Free after a money-back refund works like any other downgrade, except it happens straight away: - Nothing is deleted. Documents, clients, payments and settings stay, and pay pages keep working. - "Powered by Free Invoice Automation" returns on new PDFs, pay pages, portal pages and emails. - Every member except the Owner is deactivated. The Owner can reactivate one as the view-only Accountant. - Custom and edited roles are kept but inactive. The audit log screen is hidden; entries are still recorded. - Team monthly credits stop and any left this month expire. Credit pack credits you bought are kept until they expire. - The platform email quota becomes 20 a month. The full list is in [What changes on downgrade](/billing/failed-renewal#what-changes-on-downgrade). ## Buying Team again You can upgrade to Team again whenever you like, but the guarantee does not apply a second time, and the first-month bonus of 1,000 AI credits is not given again. ## Credit pack refunds Credit packs are not part of the money-back guarantee. A credit pack can be refunded within 14 days of purchase if its credits are unused. Email [info@freeinvoiceautomation.com](mailto:info@freeinvoiceautomation.com) to ask. See [Credit packs](/billing/credit-packs). ## Who sells Team Team is sold by **Genyune Inc.** through its Stripe account, and the refund comes from that account. Free Invoice Automation is owned by **Genyune (SMC-Private) Limited**. The full refund and cancellation rules are in the Terms of Service on [freeinvoiceautomation.com](https://freeinvoiceautomation.com). ## If the request is not eligible If the payment is not the account's first, is more than 30 days old, or the guarantee was already used, we tell you so. Internally this is the `money_back_not_eligible` state. You can still cancel Team at any time in the Stripe Customer Portal; it then stays active until the end of the period you paid for. See [Upgrade to Team](/billing/team#cancel). ## Related Upgrade to Team Buy, manage and cancel Team Free and Team plans What each plan includes Failed renewal and downgrade Everything that changes when Team ends Contact support How to reach us Source: https://docs.freeinvoiceautomation.com/billing/money-back --- # Fair-use limits The limits that protect the free service from abuse, such as API calls per month, storage and sends per minute, and what happens if you reach one. Free Invoice Automation is free for everyone, so we protect it from abuse with a small set of **fair-use limits**. They exist to stop spam, scraping and runaway scripts, not to push real businesses onto a paid plan. Every invoicing feature stays available on Free, and normal use does not come near these limits. ## What is limited | Limit | What it counts | Applies to | | --- | --- | --- | | **API calls per month** | Requests to the REST API and MCP server made with your API keys and tokens | Each account | | **Storage** | Files you keep with us, such as logos, payment proofs and PDFs | Each account | | **Sends per minute** | Documents and messages sent in one minute | Each account | | **Requests per minute per key** | Requests from one API key or MCP token | Each key or token | | **Platform emails per month** | Emails sent through our platform mailer: 20 on Free, 100 on Team | Each account | | **AI agent** | Agent requests per minute and a monthly ceiling on the AI work behind credits | Each account | | **Public actions** | Calls with no account, such as `generate_pdf`, per IP address | Each IP address | Sign-in is also protected: too many wrong passwords lock the account for a while. ## Starting values These are the starting values planned for launch. They are settings on our side, so we can adjust them without downtime, and they may change as we learn how the service is used. Your current limits and usage are always shown in **Settings > Usage**. | Limit | Free | Team | | --- | --- | --- | | API calls per month | 10,000 | 100,000 | | Storage | 1 GB | 10 GB | | Sends per minute | 10 | 10 | | Requests per minute per API key | 600 | 600 | | Platform emails per month | 20 | 100 | | AI agent requests per minute | 10 | 10 | | `generate_pdf` per IP address per hour | 30 | 30 | Platform email and AI credit allowances are plan features rather than fair-use limits; they are listed here so you can see every limit in one place. Your own SMTP mailbox has no monthly email limit, and your own AI key uses no credits. ## What happens when you reach a limit Nothing is lost. Your documents, payments and data are never touched by a limit. - **Per-minute limits** pause you briefly. The API answers `429 rate_limited` with a `Retry-After` header that tells you how many seconds to wait. - **Monthly and storage limits** stop that one action until the limit resets or you make room. The API answers `403 plan_limit_reached` with a clear message. - **Platform email quota** blocks sends through the platform mailer at 100% and offers to connect your own SMTP, which has no monthly limit. Your draft is kept. You see a warning at 80%. See [Platform email](/messaging/platform-email). - **AI limits** pause the agent for your account even if credits remain, until the minute or month resets. Monthly counters reset on the first of each month and do not roll over. ### See it coming - **Settings > Usage** shows emails, AI credits, API calls and storage against your limits. - [Get usage](/api-reference/messaging/get-usage) returns the same numbers with an 80% warning flag, so a script can slow down before it hits a limit. - Every API response carries rate-limit headers with your limit, what is left and when it resets. ```http RateLimit-Limit: 600 RateLimit-Remaining: 12 RateLimit-Reset: 41 ``` ## Handle limits in your code Respect Retry-After On `429 rate_limited`, wait for the number of seconds in `Retry-After`, then retry. Retry safely Send the same `Idempotency-Key` when you retry a write, so a request that did go through is not done twice. See [Idempotency](/api-reference/idempotency). Use lists and filters Ask for many records in one call with cursor pagination and filters, instead of one call per record. See [Pagination and filters](/api-reference/pagination). Use webhooks instead of polling Let [webhooks](/integrations/webhooks) tell you when a document is paid or sent, rather than checking every few seconds. See [Rate limits](/api-reference/rate-limits) for the full details. ## If you need more If your real business needs more than the limits allow, email [info@freeinvoiceautomation.com](mailto:info@freeinvoiceautomation.com) and tell us what you are doing. Team has higher API and storage limits. Limits can also be raised for a single account. ## Why limits exist A free service attracts people who would misuse it: sending spam through the platform mailer, scraping, or scripts stuck in a loop. Fair-use limits keep the service fast and reliable for everyone and keep our sending reputation good, which helps your invoices reach your clients' inboxes. They are the only reason Free has limits at all. ## Related Rate limits Headers, errors and retry advice for developers Platform email Monthly email quota and your own SMTP AI credits Credits for the AI invoice agent Free and Team plans Everything each plan includes Source: https://docs.freeinvoiceautomation.com/billing/fair-use --- # Paperless mission Why Free Invoice Automation exists, what \"paperless\" means in the product, and how we talk about it honestly. We built Free Invoice Automation to save trees. Paper invoices, printed copies and posted reminders use paper for work that can be done online. Keeping invoicing free removes the last reason to print: no business should need paper to get paid. Our footer says it in one line: "Get paid today. Keep the trees for tomorrow." ## What paperless means in the product Paperless is not a slogan on top of the product; it is how the product works. Every step of getting paid happens online, so nothing needs to be printed or posted. | Step | How it happens without paper | | --- | --- | | **Create** | Invoices, estimates, credit notes and receipts are made in the browser, the app, the API or your AI assistant. | | **Send** | Documents go out by email or a secure link, with a PDF for anyone who wants a file. No postage. | | **Agree** | Clients accept estimates online. From March 2027 they can sign contracts online too. | | **Pay** | Clients pay from the pay page by card, or see your bank details and tell you when they have paid. | | **Follow up** | Reminders go by email (and later by other messaging channels), not by letter. | | **Keep records** | Every sent document is stored with its history, and clients can open their documents from a link at any time. | | **Check** | A verification code on each PDF lets anyone confirm online that an invoice is real, so there is no need for stamped paper copies. | ## Why free matters for paperless Many people print because the digital tools cost money or limit how many invoices they can send. So we made every invoicing feature free: - **Free forever:** invoices, estimates, credit notes, reminders, payments, the API and the MCP server are all on the Free plan. - **No cut of your payments:** we never take a percentage of what your clients pay you. - **No account needed for one invoice:** the [free invoice generator](/generator) makes a PDF in your browser with no sign-up. The Team plan pays for running the service. It adds members and comforts, never invoicing features. See [Free and Team plans](/billing/plans). ## How we talk about it We want every claim we make to be true and checkable. Rules for consumer claims about the environment are getting stricter, including in the European Union, and we agree with the spirit of them. So: - We tell the mission as **our purpose**: we built this to save trees. - We keep **product claims factual**: "paperless", "nothing to print or post", "never printed", "no postage". These describe what the product does. - We do **not** use broad environmental labels about ourselves or the product, and we do not publish numbers of trees or emissions saved. We have no documented, checked method that would make such numbers true, so we do not print them. - If we ever show a counter, it counts something we can measure from real data, such as **invoices sent digitally**. It does not convert them into trees. If you write about Free Invoice Automation, we ask you to follow the same rules. ## What you will see on our site - The site title "Free, paperless invoicing" and the announcement "Free and paperless, forever." - A "Zero paper. Zero printing." stamp seal and an illustrated forest in the footer. They tell the story; they are not a measurement. - Later, a counter of invoices sent digitally, taken from real data only. ## Clean pages for your clients Being paperless only helps if the online version is pleasant to use. Your clients see your business, not ours: - No ads, affiliate links or sponsors on the generator, your PDFs, the pay page, the client portal or the emails your clients receive. - No analytics on the pay, portal, pay-me or verification pages. - On Team, no "Powered by" line at all. ## What you can do - Send every invoice by email or link instead of post. See [Send a document](/invoicing/sending). - Turn on reminders so you never need to post a reminder letter. See [Reminders](/collections/reminders). - Let clients pay online from the [pay page](/invoicing/pay-page) by card or bank transfer. - Point clients to the [client portal](/clients/portal) for copies of past documents, instead of printing them. ## Related Introduction What Free Invoice Automation does Free invoice generator One invoice, no sign-up, nothing printed The pay page How clients pay online Verification codes Prove an invoice is real without paper Source: https://docs.freeinvoiceautomation.com/mission --- # Invoices Create, send and track invoices with lines, taxes and discounts. Each business numbers its own invoices with no gaps, and sent invoices change only by revision. An invoice asks your client to pay for work you did or goods you sold. You build it from lines, taxes and discounts, send it by email or link, and your client pays on the [pay page](/invoicing/pay-page). Invoices are on every plan, including Free, with no limit on how many you create. ## How invoices work Every invoice belongs to one [business](/account/businesses) and one [client](/clients/clients). It moves through a simple life: 1. **Draft.** You create and edit the invoice freely. A draft has no number yet, so you can delete it without leaving a gap. 2. **Sent.** The first time you send or share it, the invoice gets the next number from the business's sequence and a snapshot is stored. From now on it is fixed. 3. **Paid, partially paid or overdue.** Payments move the balance down. An invoice turns paid only when a payment rail confirms it or a person records the payment, never because a browser was redirected. 4. **Void**, if it was sent by mistake and nothing has been paid. A voided invoice keeps its number. The full list is on [Document statuses](/invoicing/statuses). ### What is on an invoice | Part | What it holds | | --- | --- | | Header | business, client, currency, issue date, due date, terms and notes | | Lines | description, quantity, unit, unit price, discount and tax for each line ([Line items](/invoicing/line-items)) | | Totals | subtotal, discounts, tax, total, amount paid, amount credited and balance | | Accepted methods | which of the business's payment methods the client may use on this invoice | | Template | the PDF template used to render it ([PDFs and templates](/invoicing/pdf)) | All totals are calculated by the same engine on every surface: the web app, the API, MCP, the CLI, the AI agent and the free generator. Amounts are stored as whole numbers of the currency's smallest unit (cents, paisa, fils), so rounding never creates or loses money between lines and totals. The balance is always total minus amount paid minus amount credited. ### Fixed once sent A sent invoice is a record your client already holds. To keep that record trustworthy: - The business details, client details, lines, tax rates and exchange rate are frozen in a snapshot when it is first sent. - Changing a sent invoice creates a [revision](/invoicing/revisions) with its own snapshot. Earlier versions stay readable. - A sent invoice can never be deleted. If it was a mistake, [void it](/invoicing/void). If it was paid, issue a [credit note](/invoicing/credit-notes). ## Create an invoice in the web app Start a new invoice Click **New invoice** in the top bar (on a phone, the centre button of the bottom tab bar). If you can reach more than one business, check the business switcher first: the invoice is issued by the business you are working in. Pick the client Choose an existing client or add a new one. The client's defaults, such as currency, payment terms and default tax rate, are applied to the invoice. Add lines Type a description, or start typing the name of a [saved item](/invoicing/saved-items) and pick it to fill the line. Set quantity, unit price, discount and tax on each line. The live preview on the right updates as you type. Check dates, terms and notes The due date comes from your payment terms ([Due dates and payment terms](/invoicing/due-dates)). Add any terms and notes the client should see. Save or send Use **Save draft** to keep working later, or **Send** to number it and deliver it ([Send a document](/invoicing/sending)). Open **Invoices** to see every invoice. Filter by status, client and date, and click a row to open a side panel with the PDF preview, the timeline of what happened, and the actions you are allowed to take. ## Create an invoice through the API or MCP Every action in the app is also an API call with the same rules and permissions. A new invoice is always a draft. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/documents" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 4b1d7f2e-invoice-website" \ -d '{ "type": "invoice", "business_id": "0193f2a0-1b2c-7d3e-8f40-5a6b7c8d9e01", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "currency": "USD", "issue_date": "2027-01-04", "due_date": "2027-01-18", "lines": [ { "description": "Website design", "quantity": "1", "unit_price_minor": "120000" }, { "description": "Hosting setup", "quantity": "2", "unit": "hour", "unit_price_minor": "4500" } ] }' ``` ```json MCP tool call { "name": "create_invoice", "arguments": { "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "currency": "USD", "lines": [ { "description": "Website design", "quantity": "1", "unit_price_minor": "120000" } ] } } ``` ```json Response (201) { "data": { "id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "type": "invoice", "status": "draft", "number": null, "currency": "USD", "subtotal_minor": "129000", "discount_minor": "0", "tax_minor": "0", "total_minor": "129000", "amount_paid_minor": "0", "amount_credited_minor": "0", "balance_minor": "129000" } } ``` Money is a string of integer minor units, so `"129000"` is 1,290.00 USD. Floating-point amounts are refused. | Task | Endpoint | MCP tool | | --- | --- | --- | | Create a draft | [Create an invoice](/api-reference/documents/create-invoice) | `create_invoice` | | Change it | [Update an invoice](/api-reference/documents/update-invoice) | `update_invoice` | | Read one or many | [Get a document](/api-reference/documents/get-document), [List documents](/api-reference/documents/list-documents) | `get_document`, `list_documents` | | See what is late | [List overdue documents](/api-reference/documents/list-overdue) | `list_overdue` | | Send it | [Send a document](/api-reference/documents/send-document) | `preview_send`, `send_document` | Integrations, MCP clients and the AI agent only ever create drafts. A person confirms each send unless the API key or MCP token was created with auto-send. See [Drafts and confirmations](/api-reference/confirmations). ## Rules and limits - **Unlimited.** There is no cap on invoices, clients or businesses on any plan. - **Numbers are per business and per type.** Invoices have their own gap-free sequence, separate from estimates, credit notes and receipts. See [Document numbering](/invoicing/numbering). - **Number on first send.** A draft has no number. The number is assigned in the same step that sends it. - **Currency and exchange rate.** When the invoice currency differs from the business's base currency, the rate is frozen on the invoice ([Currencies](/invoicing/currencies)). - **Paid state comes only from trusted events**: a verified webhook from the payment rail, a reconciliation run, or a payment a person records. A client saying "I have paid" does not mark it paid. - **Clean for clients.** No ads, affiliates or sponsors appear on your PDFs, pay pages or client emails. ## Permissions | Action | Permission key | | --- | --- | | View invoices | `documents.view` | | Create drafts | `documents.create` | | Edit drafts and make revisions | `documents.edit` | | Send and share | `documents.send` | | Void | `documents.void` | | Issue credit notes | `documents.credit` | Owners and Admins hold all of these by default; Editors can create, edit and send but not void or credit. See [Roles and permissions](/team/roles). ## Errors you may see | Code | Meaning | | --- | --- | | `validation_failed` | A field is missing or wrong, for example a quantity of 0. `details.issues` lists each field. | | `document_not_editable` | The invoice was sent. Change it by revision. | | `document_voided` | The action is not allowed on a void invoice. | | `business_out_of_scope` | Your role cannot reach this business. | | `permission_denied` | Your role lacks the permission key. | The full list is on [Errors](/api-reference/errors). ## Related pages Line items Quantities, units, prices and per-line tax and discounts Send a document Email, share link, PDF download and copy link Credit notes Correct or refund an invoice that was sent or paid Record a payment Mark money you received outside the system Source: https://docs.freeinvoiceautomation.com/invoicing/invoices --- # Estimates Send a quote before you invoice. Your client accepts it or asks for changes online, and you convert an accepted estimate into an invoice in one step. An estimate (also called a quote) tells a client what the work will cost before you start. The client opens it online, accepts it or asks for changes, and once it is accepted you turn it into an invoice without typing the lines again. Estimates are on every plan. ## How estimates work An estimate uses exactly the same building blocks as an invoice: the same [line items](/invoicing/line-items), [taxes](/invoicing/taxes), [discounts](/invoicing/discounts-and-shipping), [currencies](/invoicing/currencies) and totals engine. What is different: - **Its own type and sequence.** Estimates are numbered separately from invoices, by default `EST-2027-0001`, `EST-2027-0002` and so on. See [Document numbering](/invoicing/numbering). - **No payments.** A client does not pay an estimate. Money is collected on the invoice you create from it. - **An estimate page instead of a pay page.** The client's link opens `/estimate/[token]`, where they can **Accept** or **Request changes**. - **An expiry date.** Each business sets how long its estimates stay valid; the starting value is 30 days. ### Estimate statuses | Status | Meaning | | --- | --- | | `draft` | Being prepared. No number yet. | | `sent` | Numbered and delivered to the client. | | `changes_requested` | The client asked for changes and left a note. | | `accepted` | The client accepted it. | | `expired` | The validity period ran out before it was accepted. | | `converted` | An invoice was created from it. | | `void` | Cancelled. It keeps its number. | The status list is a registry, so the exact names may still change before launch. See [Document statuses](/invoicing/statuses). ## Create and send an estimate in the web app Start a new estimate Create a new document and choose the estimate type. Pick the client. Add lines and terms Add lines by hand or from your [saved items](/invoicing/saved-items). Write any terms or conditions of the offer in the terms field. Send it Click **Send**. The estimate gets its number, a snapshot is stored, and the client receives an email with a link to the estimate page. You can also copy the link and share it any way you like ([Share links](/invoicing/share-links)). Wait for the client's answer When the client clicks **Accept**, the estimate is marked accepted, an audit entry is written and you are notified. When they click **Request changes**, their note appears on the estimate so you can revise it. Convert it to an invoice Open the accepted estimate and convert it. A new draft invoice is created with the same lines, linked back to the estimate. Review it and send it as usual. ## What the client sees The estimate page is a public page on `freeinvoiceautomation.com`, themed with your business's logo and colour. It shows the estimate and two choices: - **Accept**: records the acceptance, with the time, in the estimate's history. - **Request changes**: the client writes a note, which you see on the estimate. The page has no analytics, no ads and no app chrome. The link is a private token that can be revoked, and it expires. For estimates, the token lifetime follows the estimate validity (30 days to start with). ## Through the API and MCP Create the estimate as a document with `"type": "estimate"`: ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/documents" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 9c2e5a10-estimate-redesign" \ -d '{ "type": "estimate", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "currency": "EUR", "lines": [ { "description": "Brand redesign, phase 1", "quantity": "1", "unit_price_minor": "250000" } ] }' ``` ```json MCP tool call { "name": "create_estimate", "arguments": { "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "currency": "EUR", "lines": [ { "description": "Brand redesign, phase 1", "quantity": "1", "unit_price_minor": "250000" } ] } } ``` Then convert an accepted estimate into an invoice: ```bash Convert to invoice curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2c0-7a1b-7c2d-8e3f-4a5b6c7d8e90/convert" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Idempotency-Key: 9c2e5a10-estimate-convert" ``` The response is the new draft invoice. Its `links` show the estimate it came from. | Task | Endpoint | MCP tool | | --- | --- | --- | | Create a draft estimate | [Create an estimate](/api-reference/documents/create-estimate) | `create_estimate` | | Change it | [Update an invoice](/api-reference/documents/update-invoice) (same route for every type) | `update_invoice` | | Send it | [Send a document](/api-reference/documents/send-document) | `send_document` | | Convert to an invoice | [Convert an estimate](/api-reference/documents/convert-estimate) | none | | What the client calls | [Get the estimate page](/api-reference/public/get-estimate-page), [Accept an estimate](/api-reference/public/accept-estimate), [Request changes](/api-reference/public/request-estimate-changes) | none | [Webhooks](/integrations/webhooks) tell your own systems what happened: `estimate.accepted`, `estimate.changes_requested` and `estimate.converted`. ## Deposits and contracts Two features build on estimates after launch: - **Deposits** (week of 25 January 2027): an estimate can carry a deposit, a percentage or a fixed amount due on acceptance or by a date. When the client accepts, the deposit invoice is created automatically. See [Deposits and schedules](/payments/schedules). - **Contracts with e-signature** (March 2027): a contract can be attached to an estimate so the client accepts and signs in one step. See [Contracts and e-signature](/features/contracts). ## Rules and edge cases - **Converting copies, it does not move.** The estimate stays on record as it was sent; the invoice is a new document with its own invoice number, assigned when you send it. - **Sent estimates change only by revision**, just like invoices. If the client requests changes, your edit creates a new [revision](/invoicing/revisions) and the earlier version stays readable. - **Drafts can be deleted**, because they have no number. A sent estimate can be voided but never deleted. - **Client acceptance is audited.** The acceptance is written to the audit log with the time it happened. ## Permissions | Action | Permission key | | --- | --- | | View estimates | `documents.view` | | Create estimates and convert them | `documents.create` | | Edit and revise | `documents.edit` | | Send and share | `documents.send` | | Void | `documents.void` | Your client needs no account and no password: the private link is their access. ## Errors you may see | Code | Meaning | | --- | --- | | `validation_failed` | A field is missing or invalid. | | `document_not_editable` | The estimate was sent; edit it by revision. | | `document_voided` | The estimate is void. | | `token_expired` | The client opened an expired estimate link. Share a new link with them. | | `token_revoked` | The link was revoked. | ## Related pages Invoices What an estimate becomes once accepted Share links How the private estimate link works Deposits and schedules Ask for a deposit when an estimate is accepted Document statuses Every status an estimate can have Source: https://docs.freeinvoiceautomation.com/invoicing/estimates --- # Credit notes Reduce or cancel what a client owes on a sent invoice. Credit all of it or part of it, then refund the money or keep it as client credit. A credit note is the proper way to correct an invoice after it was sent. It is its own document, with its own number, linked to the original invoice, and it lowers that invoice's balance. Use it when you overcharged, when work was cancelled after the invoice went out, or when a client was paid back. If an invoice has recorded payments, a credit note is the only way to correct it, because an invoice with payments cannot be voided. ## When to use a credit note or a void | Situation | Use | | --- | --- | | Invoice sent by mistake, nothing paid | [Void](/invoicing/void) the invoice | | Invoice sent, nothing paid, but the amount was too high | Credit note for the difference, or void and issue a new invoice | | Invoice paid in full or in part, and you need to give money back | Credit note with **refund** | | Invoice paid, and the client will buy from you again | Credit note kept as **client credit** | | An order was refunded in your Shopify or WooCommerce store | A credit note is created for you automatically when the store app is installed | ## How credit notes work - **Own type, own sequence.** Credit notes have a gap-free sequence of their own in each business, by default `CN-2027-0001`, `CN-2027-0002` and so on. The number is allocated when the credit note is created. See [Document numbering](/invoicing/numbering). - **Linked to one invoice.** Every credit note points to the invoice it credits, and the invoice lists its credit notes. The credit note's PDF uses the "CREDIT NOTE" variant and shows the original invoice number. - **Full or partial.** Credit the whole invoice, pick the lines to credit, or enter an amount. - **Lowers the balance.** The invoice's `amount_credited_minor` goes up and its balance goes down by the same amount. A full credit note brings the balance to exactly 0. - **Never more than the invoice.** The sum of all credit notes on an invoice can never exceed the invoice total. ### When the invoice was already paid If money has been received, you choose what happens to the credited amount: Refund The money goes back to the client. When the payment rail supports refunds, such as Stripe, the refund is made through it on your own connected account and recorded when Stripe confirms it. For rails without refunds, such as bank transfer, you pay the client back yourself and record the refund manually; it is written to the audit log. See [Refunds](/payments/refunds). Keep as client credit The amount stays with you as a credit for that client, in that currency. It is applied to the client's next invoice. You can see the balance on the client's page. See [Client credit](/clients/client-credit). ## Issue a credit note in the web app Open the invoice Find the invoice in **Invoices** and open it. Credit notes can be issued only for an invoice that was sent. Choose what to credit Credit the full invoice, select the lines to credit, or type an amount. The preview shows the new balance. Choose refund or client credit This choice appears only when the invoice has payments. Confirm A confirmation dialog shows exactly what will happen. Once confirmed, the credit note is numbered, linked to the invoice and audited. Your client sees the credit note clearly: the invoice's [pay page](/invoicing/pay-page) shows it with the reduced balance, and from the week of 11 January 2027 it also appears in the [client portal](/clients/portal). ## Through the API and MCP Creating a credit note is a destructive, money-moving action. Over the API and MCP you must send `"confirm": true`; without it you get the preview and the `confirmation_required` error, so nothing changes by accident. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/credit-notes" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 2f8a6c41-credit-inv-0007" \ -d '{ "amount_minor": "30000", "settlement": "client_credit", "confirm": true }' ``` ```json MCP tool call { "name": "create_credit_note", "arguments": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "amount_minor": "30000", "settlement": "refund", "confirm": true } } ``` - Send `settlement` only when the invoice has payments: `"refund"` or `"client_credit"`. - Credit specific lines instead of an amount by listing them; each credit note line can point to the invoice line it credits. - An `Idempotency-Key` is required, so a retried request never creates two credit notes. | Task | Endpoint | MCP tool | | --- | --- | --- | | Create a credit note | [Create a credit note](/api-reference/documents/create-credit-note) | `create_credit_note` | | Apply client credit to an invoice | [Apply client credit](/api-reference/documents/apply-client-credit) | none | | Check a client's credit | [Get client credit](/api-reference/clients/get-client-credit) | none | | Refund a payment directly | [Refund a payment](/api-reference/payments/refund-payment) | none | Your systems are told through the `credit_note.created` [webhook](/integrations/webhooks) event, and `payment.refunded` when a refund completes. ## Credit note statuses | Status | Meaning | | --- | --- | | `draft` | Being prepared. | | `issued` | Numbered and in effect. | | `applied` | The credit was used against an invoice. | | `refunded` | The credited amount was paid back to the client. | These are the proposed values in the status registry. See [Document statuses](/invoicing/statuses). ## Rules and edge cases - **Credit notes are permanent records.** Like every document they are soft-deleted only, and once issued they keep their number forever. - **A credit note does not void the invoice.** The invoice keeps its number and history; its balance changes. - **Reminders stop** when credit notes cover the whole balance (reminders arrive in the week of 11 January 2027). - **Store refunds mirror automatically.** When the [Shopify](/integrations/shopify) or [WooCommerce](/integrations/woocommerce) app receives a verified refund from your store, it issues the credit note without asking, because it records something that already happened. It is audited with the store connector as the actor. - **Credit notes travel everywhere.** They appear in reports, accounting sync and the ledger as those features ship. - **AI and integrations ask first.** An MCP client or the AI agent can prepare a credit note, but a person confirms it. ## Permissions | Action | Permission key | | --- | --- | | Issue a credit note | `documents.credit` | | Apply client credit to an invoice | `documents.edit` | | Refund through the rail | `payments.refund` | | View | `documents.view` | By default only Owners and Admins can issue credit notes. ## Errors you may see | Code | Meaning | | --- | --- | | `confirmation_required` | Send `"confirm": true`. The response includes the preview. | | `credit_exceeds_balance` | The credit is larger than what can still be credited on this invoice. | | `rail_capability_missing` | The rail cannot refund. Record the refund manually instead. | | `rail_not_connected` | The rail used for the payment has no active connection. | | `document_voided` | The invoice is void, so there is nothing to credit. | | `permission_denied` | Your role lacks `documents.credit`. | ## Related pages Void and delete Cancel an invoice that has no payments Client credit How kept credit is applied to the next invoice Refunds Refund through Stripe or record a manual refund Document numbering The separate `CN-` sequence Source: https://docs.freeinvoiceautomation.com/invoicing/credit-notes --- # Receipts Issue a receipt for a sale that was paid on the spot, such as a store order paid at checkout. Receipts have their own gap-free numbers and PDF. A receipt confirms that a client has already paid. Where an invoice asks for money, a receipt records money that was received, for example an order paid at checkout in your online store. Receipts are their own document type, with their own number sequence and PDF, and they are on every plan. ## Receipts compared with invoices | | Invoice | Receipt | | --- | --- | --- | | Purpose | asks the client to pay | confirms the client paid | | Default number | `INV-2027-0001` | `RCT-2027-0001` | | Typical source | you, the API, MCP or the AI agent | a store order paid at checkout, or a payment you received | | Statuses | draft, sent, partially paid, paid, overdue, void | issued, void | Receipts use the same [line items](/invoicing/line-items), [taxes](/invoicing/taxes), [discounts](/invoicing/discounts-and-shipping) and [currencies](/invoicing/currencies) as every other document, and the same engine computes their totals in integer minor units. ## How receipts work - **Own type, own sequence.** Each business has a gap-free receipt sequence, separate from invoices, estimates and credit notes. The default pattern is `RCT-{YYYY}-{SEQ:4}`, and you can change it in **Settings > Templates and numbering**. See [Document numbering](/invoicing/numbering). - **Issued, not drafted.** The proposed status list for receipts has two states: `issued` and `void`. A receipt records a payment that already happened, so there is no draft stage waiting for a send. - **A snapshot is kept.** Like any issued document, the business and client details, lines, taxes and totals are frozen, and changes are made by [revision](/invoicing/revisions). - **Verification QR.** The receipt's PDF carries the same verification QR as every PDF from a signed-in account, so the client can check it is genuine at `/v/[code]`. See [Verification codes](/invoicing/verification). - **Deliver it like any document.** Email it, download the PDF or copy its link. See [Send a document](/invoicing/sending). ## Where receipts come from ### From your online store The main use of receipts is store orders that were paid at checkout: - The [Shopify app](/integrations/shopify) (February 2027) and the [WordPress and WooCommerce plugin](/integrations/woocommerce) (week of 25 January 2027) let you choose, per install, whether an order creates an **invoice** or a **receipt**. The default is invoice. - Numbers always come from your business's own receipt sequence, never from the store's order numbers. - Sending the receipt to the store's customer stays a draft unless you turn on "send automatically" for that install, which needs a fresh sign-in. ### From the AI agent You can set up an [automation](/ai/agent/automations) such as "when a Stripe payment arrives, make the receipt". The agent creates the receipt through the same action as the API, under your permissions, and each automation run uses 1 AI credit. As with everything the agent does, sending it to the client waits for your confirmation unless you enabled auto-send for that automation. ### By hand or through the API Create a receipt yourself in the web app, or with the API: ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/documents" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 7d3a9b02-receipt-order-1042" \ -d '{ "type": "receipt", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "currency": "GBP", "issue_date": "2027-02-03", "lines": [ { "description": "Ceramic mug, blue", "quantity": "2", "unit_price_minor": "1800" } ] }' ``` The endpoint is [Create a receipt](/api-reference/documents/create-receipt). There is no dedicated MCP tool for receipts at launch; MCP clients can list and read them with `list_documents` and `get_document`, and the capability is available in the REST API, the CLI (from the week of 25 January 2027) and the AI agent. Connectors send a deterministic `Idempotency-Key` built from the store and order id, so the same order never creates two receipts. ## Create a receipt in the web app Start a new document Create a new document, choose the receipt type and pick the client. Add what was paid for Add the lines, taxes and any discount, exactly as the client paid. Issue and deliver it Issuing gives the receipt the next number in the sequence. Then email it, download the PDF or copy its link. ## Rules and edge cases - **Voiding a receipt.** A receipt can be voided, with a reason, if it was issued by mistake. It keeps its number and shows a "VOID" stamp. See [Void and delete](/invoicing/void). - **No deletion once numbered.** Like every numbered document, a receipt is never deleted, so your sequence stays complete. - **Platform email quota.** Emailing a receipt through the platform mailer counts against your monthly quota (Free 20, Team 100). Your own SMTP is unlimited. See [Platform email](/messaging/platform-email). - **Clean for clients.** No ads, affiliates or sponsors on receipts. The "Powered by Free Invoice Automation" line appears on Free and is removed on Team. ## Permissions | Action | Permission key | | --- | --- | | View receipts | `documents.view` | | Create receipts | `documents.create` | | Revise receipts | `documents.edit` | | Email and share | `documents.send` | | Void | `documents.void` | ## Errors you may see | Code | Meaning | | --- | --- | | `validation_failed` | A field is missing or invalid. | | `idempotency_key_reused` | The same `Idempotency-Key` was sent with a different body. | | `document_voided` | The receipt is void. | | `quota_exceeded` | Your platform emails for this month are used up. Connect your own SMTP to keep sending. | ## Related pages Invoices When the client has not paid yet Shopify Turn store orders into invoices or receipts Automations Let the agent make receipts when payments arrive Document numbering The `RCT-` sequence and how to change it Source: https://docs.freeinvoiceautomation.com/invoicing/receipts --- # Line items Each line on a document has a description, quantity, unit, unit price, optional discount and tax. Totals are computed exactly in integer minor units. Lines are what you charge for. Every document type (invoices, estimates, credit notes and receipts) uses the same line model, so a line behaves the same everywhere and the same engine adds it up on every surface: the web app, the API, MCP, the CLI, the AI agent and the free generator. ## What a line holds | Field | What it means | | --- | --- | | `description` | What you are charging for. Required. | | `quantity` | How many. A decimal written as a string, for example `"1"`, `"2.5"` or `"0.75"`. Must be greater than 0. | | `unit` | Optional label such as hour, day or piece. | | `unit_price_minor` | Price of one unit in integer minor units, as a string. `"4500"` in USD is 45.00. | | `discount_type` and `discount_value` | Optional discount on this line: `percent` or `amount`. See [Discounts](/invoicing/discounts-and-shipping). | | `tax_rate_id` | Optional tax rate from the business's list. See [Taxes](/invoicing/taxes). | | `item_id` | Optional reference to the [saved item](/invoicing/saved-items) that filled the line. | | `position` | The line's order on the document. | The engine then computes three amounts for every line: `subtotal_minor`, `tax_minor` and `total_minor`. You never send these; they are always calculated for you, so a client of the API cannot store a total that does not add up. ### Lines added by the system Most lines are ones you type. Some features add lines of their own, marked with a kind so they can be told apart: - **Late fee** lines, added as a revision when a late fee applies (week of 11 January 2027). - **Time** and **expense** lines, when you invoice unbilled time or billable expenses (April 2027). - **Deposit** lines, for deposit invoices (week of 25 January 2027). ## How totals are calculated For each line, the engine works from quantity and unit price, the line's discount and its tax (exclusive, inclusive or compound, as the tax rate says), and stores the line's subtotal, tax and total. The document totals then follow one rule that is tested on thousands of random documents: the total equals the sum of the line totals plus taxes minus discounts. The document shows: - **Subtotal** (`subtotal_minor`) - **Discount** (`discount_minor`) - **Tax** (`tax_minor`) - **Total** (`total_minor`) - **Balance**: total minus amount paid minus amount credited. Rounding happens only at defined points, per line or per document according to a business setting, and is tested so that rounding never creates or loses a single minor unit between the lines and the total. Currencies with 0 or 3 decimal places, such as JPY or KWD, work the same way as two-decimal currencies. Money is never a floating-point number. Send amounts as strings of integer minor units with a `currency` on the document. A body such as `"unit_price_minor": 45.5` is refused with `validation_failed`. ## Add lines in the web app Open the editor Open a draft, or click **New invoice**. The form is on the left and a live PDF preview is on the right. Type or pick Start typing in a line. Matching saved items are suggested; pick one to fill description, unit, price and default tax. Or keep typing to write a one-off line. Set quantity, price, discount and tax Adjust the values for this document. Changing a line never changes the saved item it came from. Order and tidy Put the lines in the order you want and remove the ones you do not need, while the document is still a draft. ## Lines through the API and MCP Send lines in the `lines` array when you create or update a document: ```json Request body { "type": "invoice", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "currency": "USD", "lines": [ { "item_id": "0193f2a5-3e4f-7a6b-9c1d-2e3f4a5b6c7d", "description": "Consulting", "quantity": "6.5", "unit": "hour", "unit_price_minor": "9000", "tax_rate_id": "0193f2a3-2d4e-7f60-8a1b-2c3d4e5f6a70" }, { "description": "Travel", "quantity": "1", "unit_price_minor": "12000", "discount_type": "percent", "discount_value": "10" } ] } ``` Use [Create an invoice](/api-reference/documents/create-invoice) for a new draft and [Update an invoice](/api-reference/documents/update-invoice) to change the lines of a draft. Over MCP, `create_invoice`, `create_estimate` and `update_invoice` take the same `lines` array. When a line fails validation, the error points at it, for example `"path": ["lines", 0, "quantity"]` with the message "Must be greater than 0". ## Rules and edge cases - **Lines are copies.** When a saved item fills a line, the line keeps its own copy of the values. Editing or archiving the item later never changes documents that already use it. - **Tax is frozen on the line.** The tax name, rate and calculation method are copied onto the line, so changing a tax rate in your settings never changes old documents. - **Locked after send.** Once a document has a number, its lines can change only through a [revision](/invoicing/revisions), which stores a new snapshot and keeps the old one readable. The database itself refuses any other change. - **Credit note lines** can point at the invoice line they credit, so a partial credit note shows exactly what was credited. - **No stock.** Lines do not track stock or inventory; that is out of scope. ## Permissions | Action | Permission key | | --- | --- | | Add or change lines on a draft | `documents.create` or `documents.edit` | | Change lines on a sent document (revision) | `documents.edit` | | See lines | `documents.view` | ## Errors you may see | Code | Meaning | | --- | --- | | `validation_failed` | A line is invalid; `details.issues` gives its index and field. | | `document_not_editable` | The document was sent. Change its lines by revision. | | `not_found` | A `tax_rate_id` or `item_id` does not exist or is not visible to you. | ## Related pages Saved items Keep your products and services ready to pick Taxes Exclusive, inclusive and compound tax rates Discounts and shipping Percent or amount discounts on each line Money and currencies How amounts are sent and returned in the API Source: https://docs.freeinvoiceautomation.com/invoicing/line-items --- # Saved items Keep a list of the products and services each business sells, then pick them while typing a line. Import and export as CSV, and archive instead of deleting. Saved items are your price list. Save each product or service once, with its price, unit and default tax, and pick it while you type a line instead of writing it again. Every business has its own list, and saved items are on every plan with no limit. ## What an item holds | Field | What it means | | --- | --- | | `name` | What the item is called. Required. Used when you search in the editor. | | `description` | Optional longer text copied onto the line. | | `sku` | Optional stock-keeping code. | | `unit` | Optional unit: hour, day, piece and so on. | | `unit_price_minor` | Price of one unit in integer minor units, as a string. Required. | | `currency` | Currency of the price, for example `USD`. Required. | | `default_tax_rate_id` | Optional tax rate applied when the item fills a line. | Items track prices, not stock: there are no inventory counts, by design. ## How saved items work - **Search and fill.** In the document editor, typing in a line suggests matching items by name. Picking one fills the line's description, unit, price and tax. - **Lines keep a copy.** The line stores its own copy of the item's values. When you change an item's price later, documents that already used it do not change. This keeps sent invoices exactly as your client received them. - **Archive instead of delete.** An archived item no longer appears in editor search, and old documents stay unchanged. You can restore an archived item later. - **Per business.** Each [business](/account/businesses) has its own list, so two businesses can sell the same service at different prices. ## Manage items in the web app Open your items list Open the items list for the business you are working in. Add an item Enter the name, price and currency, and optionally a description, SKU, unit and default tax rate. Save it. Use it on a document In any invoice, estimate, credit note or receipt, start typing the item's name in a line and pick it from the suggestions. Archive what you no longer sell Archive the item. It disappears from the editor search but stays on every document that already uses it. ### Import and export as CSV If you are moving from another tool, import your list as a CSV file. Each valid row becomes an item; rows that cannot be imported are listed with the reason, so you can fix them and import again. Export downloads every item field as a CSV, in the same columns the import accepts. Products from your Shopify or WooCommerce store can be imported later through the store connectors. ## Through the API and MCP Creating and updating use one call: send an `id` to update an existing item, leave it out to create a new one. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/items" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 5e7c1a3b-item-consulting" \ -d '{ "business_id": "0193f2a0-1b2c-7d3e-8f40-5a6b7c8d9e01", "name": "Consulting", "description": "Senior consulting, billed per hour", "unit": "hour", "unit_price_minor": "9000", "currency": "USD", "default_tax_rate_id": "0193f2a3-2d4e-7f60-8a1b-2c3d4e5f6a70" }' ``` ```json MCP tool call { "name": "upsert_item", "arguments": { "name": "Consulting", "unit": "hour", "unit_price_minor": "9000", "currency": "USD" } } ``` | Task | Endpoint | MCP tool | | --- | --- | --- | | List items | [List items](/api-reference/items/list-items) | `list_items` | | Read one item | [Get an item](/api-reference/items/get-item) | none | | Create or update | [Create or update an item](/api-reference/items/upsert-item) | `upsert_item` | | Archive | [Archive an item](/api-reference/items/archive-item) | none | | Import a CSV | [Import items](/api-reference/items/import-items) | none | | Export a CSV | [Export items](/api-reference/items/export-items) | none | To use an item on a document through the API, copy its values into a line and, if you like, send its id as `item_id` on the line. See [Line items](/invoicing/line-items). Your systems are told about changes with the `item.created` and `item.updated` [webhook](/integrations/webhooks) events. ## Rules and edge cases - **Editing never rewrites history.** A price change applies to new lines only. - **Archived items keep working on old documents.** Archiving only hides the item from search and pickers. - **SKUs are unique per business** among active items, as proposed in the data design, so two live items cannot share a code. - **Currency is part of the price.** An item priced in USD fills a line with that price; if the document is in another currency, check the line before sending. Exchange rates are frozen per document, not per item. See [Currencies](/invoicing/currencies). - **AI help.** The [AI agent](/ai/agent/overview) and MCP clients can list and create items for you with the same permissions you have. ## Permissions | Action | Permission key | | --- | --- | | View, search and export items | `items.view` | | Create, update, archive and import | `items.manage` | By default Owners, Admins and Editors can manage items; Viewers and Accountants can view them. ## Errors you may see | Code | Meaning | | --- | --- | | `validation_failed` | A required field is missing or a price is not a whole number of minor units. | | `idempotency_key_reused` | The same key was sent with a different body. | | `business_out_of_scope` | Your role cannot reach this business. | | `permission_denied` | Your role lacks `items.manage`. | ## Related pages Line items How a picked item becomes a line Taxes Set a default tax rate on an item Import and export Move clients and data in and out Shopify Bring store orders into your invoices Source: https://docs.freeinvoiceautomation.com/invoicing/saved-items --- # Taxes Set up the tax rates each business charges, as exclusive, inclusive or compound taxes, and apply them per line. Rates are frozen on each line. Taxes such as VAT, GST or sales tax are set up once per business as a list of tax rates, then applied to each line of a document. The engine works out the tax for every line and the document's tax total, in integer minor units, and copies the rate onto the line so later changes never alter documents you already issued. ## Tax rates Each business keeps its own list of tax rates in **Settings > Taxes**. A tax rate has: | Field | What it means | | --- | --- | | `name` | The label printed on the document, for example "VAT" or "GST". | | `rate` | The percentage, for example `20` or `17.5`. Up to four decimal places. | | `calculation` | `exclusive`: tax is added on top of the price. `inclusive`: the price already contains the tax. | | `compound` | When true, this tax is calculated on top of the amount including other taxes. | | `preset_key` | Set when the rate was created from a built-in preset. | Rates you no longer use can be **archived**. An archived rate is not offered for new lines, and documents that already used it are not changed. ### Exclusive, inclusive and compound Exclusive The tax is added to the line. A line of 100.00 with a 20% exclusive tax comes to 120.00, with 20.00 of tax. Inclusive The price already includes the tax. A line of 120.00 with a 20% inclusive tax still comes to 120.00, of which 20.00 is tax. Use this when your prices are quoted tax included. Compound A compound tax is charged on an amount that already includes another tax, for places where one tax applies on top of another. Set the `compound` flag on such a rate. The engine is tested so that exclusive, inclusive and compound taxes give the same result whether they are computed line by line or recomputed from a stored snapshot, and so that rounding never creates or loses a minor unit. ### Built-in presets To save typing, tax presets ship for the first markets: Pakistan, India, the United Kingdom, the European Union, the United States and the United Arab Emirates. Preset names and rates are taken from official sources when they are built, so check them against your own obligations. The United States has no national rate: you add state rates yourself. Free Invoice Automation calculates the tax you choose; it does not decide which tax applies to you or file returns. Check with your accountant or tax office which rates you must charge. ## Set up and use taxes in the web app Taxes"> Make sure the business switcher shows the business you are setting up. Add a rate Start from a preset or enter the name, percentage, calculation (exclusive or inclusive) and whether it is compound. Set defaults Optionally choose a default tax rate for the business, override it for particular clients, or give a [saved item](/invoicing/saved-items) its own default rate. Apply on lines In the document editor, each line has a tax field filled from those defaults. Change it on any line if needed. Your business's own tax registration numbers live on the business profile, and a client's tax number on the client, so both can be printed on the document. ## Through the API Create or update a tax rate with one call, then reference it on lines with `tax_rate_id`: ```bash Create a tax rate curl -X POST "https://api.freeinvoiceautomation.com/v1/tax-rates" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 1a2b3c4d-vat-standard" \ -d '{ "business_id": "0193f2a0-1b2c-7d3e-8f40-5a6b7c8d9e01", "name": "VAT", "rate": "20", "calculation": "exclusive", "compound": false }' ``` ```json Line using it { "description": "Website design", "quantity": "1", "unit_price_minor": "100000", "tax_rate_id": "0193f2a3-2d4e-7f60-8a1b-2c3d4e5f6a70" } ``` With a 20% exclusive rate, that line has `tax_minor` of `"20000"` and `total_minor` of `"120000"`. | Task | Endpoint | | --- | --- | | List tax rates | [List tax rates](/api-reference/businesses/list-tax-rates) | | Create or update a rate | [Create or update a tax rate](/api-reference/businesses/upsert-tax-rate) | | Archive a rate | [Archive a tax rate](/api-reference/businesses/archive-tax-rate) | Tax rates have no MCP tool of their own. MCP clients and the AI agent apply existing rates by id when they create documents with `create_invoice` or `create_estimate`. ## Rules and edge cases - **Frozen on the line.** When a line uses a tax rate, the tax name, rate and calculation are copied onto the line. Editing the rate in your settings later changes new lines only. - **Frozen in the snapshot.** When a document is first sent, its line tax rates are part of the snapshot. Changes after that are revisions. - **Totals per rate.** The document's `tax_minor` is the sum of its lines' tax. From the week of 25 January 2027 the [Tax collected report](/features/reports) groups tax by rate and period. - **Store taxes.** The Shopify and WooCommerce connectors let you map store taxes to your tax rates in their settings. ## Permissions | Action | Permission key | | --- | --- | | See tax rates | `businesses.view` | | Create, change or archive tax rates | `settings.manage` | | Choose a tax rate on a line | `documents.create` or `documents.edit` | By default only Owners and Admins can change tax rates. ## Errors you may see | Code | Meaning | | --- | --- | | `validation_failed` | For example a calculation other than `exclusive` or `inclusive`. | | `not_found` | The `tax_rate_id` on a line does not exist in this business. | | `permission_denied` | Your role lacks `settings.manage`. | | `document_not_editable` | The document was sent; change a line's tax by revision. | ## Related pages Line items Where tax is applied Discounts and shipping Discounts on each line Businesses Tax registration numbers and country defaults Reports Tax collected per rate and period Source: https://docs.freeinvoiceautomation.com/invoicing/taxes --- # Discounts and shipping Give a percentage or fixed discount on any line. The engine totals every discount on the document, and delivery charges are added as ordinary lines. A discount lowers what the client pays for a line, either by a percentage or by a fixed amount. Discounts are part of the same totals engine as taxes and prices, so they are calculated exactly, in integer minor units, the same way in the web app, the API, MCP, the AI agent and the free generator. ## Line discounts Every [line](/invoicing/line-items) can carry one discount, set with two fields: | Field | Values | Example | | --- | --- | --- | | `discount_type` | `percent` or `amount` | `"percent"` | | `discount_value` | the percentage, or the amount for the line | `"10"` | - **Percent** takes that percentage off the line. A line of 500.00 with a 10% discount is reduced by 50.00. - **Amount** takes a fixed sum off the line, in the document's currency. Leave both fields empty for a line with no discount. ### The document's discount total The document shows the sum of its discounts as `discount_minor`, next to the subtotal, tax and total. The totals always follow one rule, tested on thousands of random documents: the total equals the sum of the line totals plus taxes minus discounts, after rounding, and rounding never creates or loses a minor unit. ## Add a discount in the web app Open the draft Open the invoice, estimate or receipt in the editor. Set the discount on a line In the line's discount field, choose percent or amount and type the value. Check the preview The live PDF preview on the right shows the discount and the new totals before you save or send. ## Discounts through the API and MCP Send the discount on each line when you create or update a document: ```json Request body (excerpt) { "currency": "USD", "lines": [ { "description": "Annual maintenance", "quantity": "1", "unit_price_minor": "50000", "discount_type": "percent", "discount_value": "10" }, { "description": "Logo files", "quantity": "1", "unit_price_minor": "8000" } ] } ``` The response includes `discount_minor` for the document and the computed `subtotal_minor`, `tax_minor` and `total_minor` for every line. Use [Create an invoice](/api-reference/documents/create-invoice) or [Update an invoice](/api-reference/documents/update-invoice); over MCP, `create_invoice`, `create_estimate` and `update_invoice` accept the same fields. Prices and totals are always strings of integer minor units, such as `"50000"` for 500.00 USD. The discount value is stored as a decimal number, because it can be a percentage. The [Update an invoice](/api-reference/documents/update-invoice) reference shows the exact format for amount discounts. ## Shipping and delivery charges Documents have no separate shipping or delivery field. If you charge for delivery, postage of goods, or a similar fee, add it as an ordinary line, for example "Delivery" with a quantity of 1 and the price. As a line, it can carry its own tax rate and discount like any other charge, and it is copied into the snapshot when the document is sent. To use the same delivery charge often, save it as a [saved item](/invoicing/saved-items) and pick it from the editor. When the [Shopify app](/integrations/shopify) or [WooCommerce plugin](/integrations/woocommerce) creates documents from store orders, the connector builds the lines from the order through the same API. ## Rules and edge cases - **Discounts are frozen with the document.** When a document is first sent, its lines, including their discounts, are stored in the snapshot. Changing a discount afterwards is a [revision](/invoicing/revisions), and the earlier version stays readable. - **Credit notes, not negative discounts.** To reduce what a client owes on an invoice that was already sent, issue a [credit note](/invoicing/credit-notes) rather than revising the price down. The credit note is a separate numbered record that both of you keep. - **Same maths everywhere.** The PDF, the pay page, reports and the API all show amounts computed by the same engine, so totals never disagree between surfaces. ## Permissions | Action | Permission key | | --- | --- | | Set discounts on a draft | `documents.create` or `documents.edit` | | Change discounts on a sent document (revision) | `documents.edit` | ## Errors you may see | Code | Meaning | | --- | --- | | `validation_failed` | For example a `discount_type` other than `percent` or `amount`. | | `document_not_editable` | The document was sent; change it by revision or issue a credit note. | ## Related pages Line items Every field a line can carry Taxes Exclusive, inclusive and compound tax Credit notes Lower what is owed after sending Saved items Keep a delivery charge ready to pick Source: https://docs.freeinvoiceautomation.com/invoicing/discounts-and-shipping --- # Currencies and exchange rates Invoice in any currency. Amounts are exact integer minor units, and when a document's currency differs from your business currency, the exchange rate is frozen on it. You can bill each client in the currency they pay in. Every document has one currency, every amount on it is stored exactly in that currency's smallest unit, and when the currency differs from your business's base currency, the exchange rate of the day is frozen on the document so your reports never shift later. ## Money is exact Amounts are never floating-point numbers. Each one is a whole number of the currency's minor unit plus a three-letter currency code: | Amount | Stored as | | --- | --- | | 1,200.00 USD | `"total_minor": "120000"`, `"currency": "USD"` | | 1,250.00 PKR | `"total_minor": "125000"`, `"currency": "PKR"` | | 5,000 JPY (no decimals) | `"total_minor": "5000"`, `"currency": "JPY"` | | 12.500 KWD (three decimals) | `"total_minor": "12500"`, `"currency": "KWD"` | Currencies with 0 or 3 decimal places work exactly like two-decimal currencies; the number of decimals comes from the currency data. In the API, money is always a string, so very large amounts are never cut by number limits in JavaScript or other languages. See [Money and currencies](/api-reference/money). ## Which currency a document uses The currency is chosen when the document is created. If you do not pick one, it is resolved in this order, the first that has a value wins: 1. the currency you set on the document; 2. the client's default currency; 3. the business setting; 4. the account setting; 5. the business's base currency. Your business's **base currency** is set on the business profile. Reports total amounts in it. ## Exchange rates When a document's currency is different from the business's base currency, an exchange rate to the base currency is **frozen on the document**. It never changes after that, so: - the document's stored totals never move when markets do; - reports show amounts per currency, plus a total in your base currency at each document's own frozen rate (reports arrive in the week of 25 January 2027); - the PDF, pay page and API always agree. Rates are fetched once a day from the exchange-rate provider. The default provider is the European Central Bank reference rates. Each business may also enter a rate by hand when it needs a specific one, for example the rate its bank used. The exchange rate is for your records and reports. Your client always pays the amount in the document's own currency. ## Set the currency in the web app Set the business base currency In the business profile, choose the currency your books are kept in. Give clients a default On a client who always pays in another currency, set their default currency. New documents for that client use it. Change it on one document In the editor, choose the currency for this document while it is a draft. If it differs from the base currency, the exchange rate is shown and frozen with the document. ## Through the API and MCP Send `currency` with every document, and every money field as a string of minor units: ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/documents" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 8e1f3a5c-eur-invoice" \ -d '{ "type": "invoice", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "currency": "EUR", "lines": [ { "description": "Translation, 4,000 words", "quantity": "1", "unit_price_minor": "48000" } ] }' ``` ```json MCP tool call { "name": "create_invoice", "arguments": { "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "currency": "EUR", "lines": [ { "description": "Translation, 4,000 words", "quantity": "1", "unit_price_minor": "48000" } ] } } ``` The returned document includes `fx_rate`, the frozen rate to your base currency, when the currencies differ. Filter lists by currency with `?currency=EUR` on [List documents](/api-reference/documents/list-documents). ## Rules and edge cases - **One currency per document.** All lines, payments and credit notes of a document are in its currency. - **Payment methods follow currency.** Local QR payments are shown only when the currency matches the rail: UPI for INR, and Raast, JazzCash and Easypaisa for PKR (from the week of 25 January 2027). A mismatch is refused with `currency_mismatch`. See [Local QR payments](/payments/qr-rails). - **Client credit is per currency.** Credit kept from a credit note is held per client and per currency, and is applied to a later invoice in the same currency. See [Client credit](/clients/client-credit). - **Saved items carry a currency.** An item's price is in its own currency. If you use it on a document in another currency, check the line price before sending. - **The rate is frozen, not the currency list.** You can still create new documents in any currency at any time. - **Changing the currency after sending** is part of a [revision](/invoicing/revisions), like any change to a sent document. ## Permissions | Action | Permission key | | --- | --- | | Choose a currency on a document | `documents.create` or `documents.edit` | | Change a client's default currency | `clients.edit` | | Change business and account settings | `settings.manage` | | Change the business profile | `businesses.manage` | ## Errors you may see | Code | Meaning | | --- | --- | | `validation_failed` | The currency is not a three-letter code, or an amount is not a whole number of minor units. | | `currency_mismatch` | A payment method or credit does not match the document's currency. | ## Related pages Money and currencies (API) Minor units and strings in requests and responses Businesses Base currency and country defaults Local QR payments Rails that work only in PKR or INR Reports Totals in your base currency at frozen rates Source: https://docs.freeinvoiceautomation.com/invoicing/currencies --- # Document numbering Every business numbers each document type in its own gap-free sequence, with a pattern you choose. A number is assigned on first send and never reused. Many tax rules expect invoice numbers to run in order with nothing missing. Free Invoice Automation numbers documents per business and per document type, with no gaps and no duplicates, even when two people or two integrations send at the same moment. You choose the pattern; the system makes sure the sequence stays complete. ## How numbering works - **Per business, per type.** Each [business](/account/businesses) has a separate sequence for invoices, estimates, credit notes and receipts (and contracts from March 2027). Sending an invoice never uses up an estimate number, and two businesses in the same account never share a sequence. - **Assigned on first send.** A draft has no number. The number is given in the same step that sends or shares the document for the first time. A credit note gets its number when it is created. - **Gap-free.** Numbers are handed out inside the same database transaction as the send. If anything fails, the whole step is undone, including the number, so the next document gets the same number instead of skipping one. - **Never reused.** A voided document keeps its number. Drafts can be deleted precisely because they never had one. - **No duplicates.** The same number can never be stored twice for one business and type. ## Patterns A pattern is the text around the counter. The defaults are: | Document type | Default pattern | Example | | --- | --- | --- | | Invoice | `INV-{YYYY}-{SEQ:4}` | `INV-2027-0001` | | Estimate | `EST-{YYYY}-{SEQ:4}` | `EST-2027-0001` | | Credit note | `CN-{YYYY}-{SEQ:4}` | `CN-2027-0001` | | Receipt | `RCT-{YYYY}-{SEQ:4}` | `RCT-2027-0001` | `{YYYY}` is the year and `{SEQ:4}` is the counter, padded to four digits. Change the prefix and layout to match what you used before, for example `ACME-{SEQ:4}`. ### When the counter resets A business setting controls when the counter starts again at 1: never, every year or every month. The default is yearly, which matches the `{YYYY}` in the default patterns, so the first invoice of 2028 is `INV-2028-0001`. Moving from another tool? Set your pattern to continue your old numbers, so your records read as one unbroken series. ## Change a pattern in the web app Templates and numbering"> Check the business switcher first: patterns belong to one business. Choose the document type Each type has its own pattern. Edit the pattern and save New numbers follow the new pattern; numbers already given never change. ## Through the API Read the sequences of a business and change a pattern: ```bash Update a pattern curl -X PATCH "https://api.freeinvoiceautomation.com/v1/numbering-sequences/0193f2a6-4f5a-7b6c-8d7e-9f0a1b2c3d4e" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 3c5e7a9b-inv-pattern" \ -d '{ "pattern": "ACME-{YYYY}-{SEQ:5}" }' ``` | Task | Endpoint | | --- | --- | | List the sequences of a business | [List numbering sequences](/api-reference/businesses/list-numbering-sequences) | | Change a pattern | [Update a numbering pattern](/api-reference/businesses/update-numbering-pattern) | You never send a number when you create a document; the `number` field stays `null` until the document is sent. Over MCP, `send_document` returns the document with its new number. ## Rules and edge cases Where does a number come from? Always from the business's sequence for that document type, which is what keeps it gap-free. To change how numbers look, change the pattern. What happens when two people send at the same time? The sends wait in line for the business's counter, one after the other, so each gets the next number and none is skipped or repeated. I deleted a draft. Did I lose a number? No. Drafts have no number, so deleting one leaves no gap. I sent an invoice by mistake. Can I get its number back? No. Void it instead: it keeps its number with a "VOID" stamp, so your series stays complete and anyone checking can see what happened. See [Void and delete](/invoicing/void). Do store orders use the store's order number? No. Invoices and receipts from the Shopify app and the WooCommerce plugin are numbered from your business's own sequence, never from store order numbers. The order id is kept as a reference on the document so the same order never creates two documents. What about invoices I made in the free generator before signing up? When your browser documents move into your new account, they keep the numbers you gave them as an outside reference and do not use up your business's sequence (proposed). Your first invoice in the app starts the sequence. ## Permissions | Action | Permission key | | --- | --- | | See sequences and patterns | `businesses.view` | | Change a pattern or reset policy | `settings.manage` | | Send a document (which assigns its number) | `documents.send` | ## Errors you may see | Code | Meaning | | --- | --- | | `validation_failed` | The pattern is not valid. | | `permission_denied` | Your role lacks `settings.manage`. | | `idempotency_in_progress` | A send with the same `Idempotency-Key` is still running; wait for it rather than retrying with a new key. | ## Related pages Send a document The step that assigns the number Void and delete Why voided documents keep their number Credit notes The separate credit note sequence Free invoice generator Documents made before you sign up Source: https://docs.freeinvoiceautomation.com/invoicing/numbering --- # Due dates and payment terms Set when an invoice must be paid with payment terms per business or client. The due date drives overdue status, reminders and late fees. The due date is the day your client should pay by. It is printed on the invoice and the pay page, and it is what reminders, late fees and the overdue list count from. You can type a due date on each invoice, or let payment terms work it out for you. ## Issue date, due date and terms | Field | What it is | | --- | --- | | `issue_date` | The date of the document. | | `due_date` | The date payment is due. | | `terms` | Free text printed on the document, for example "Payment within 14 days by bank transfer." | **Payment terms** are the number of days between the issue date and the due date. They are a setting you can set for a business and override for a client, so a client on 30-day terms gets a due date 30 days after the issue date while everyone else stays on your default. The starting value is 14 days. The terms text is a separate setting at the business, client and document level, so you can write your standard terms once and adjust them where needed. ## Set terms and due dates in the web app Set your default In the business settings, set the payment terms in days and your standard terms text. Override for a client On a client with different terms, set their payment terms or terms text. New documents for that client use them. Check each invoice In the editor, the due date is filled from the terms. Change it on the invoice if this one is different. ## Through the API and MCP Send `issue_date` and `due_date` as `YYYY-MM-DD`: ```bash cURL curl -X PATCH "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 6a8c0e2f-due-date" \ -d '{ "issue_date": "2027-01-04", "due_date": "2027-02-03", "terms": "Payment within 30 days." }' ``` ```json MCP tool call { "name": "update_invoice", "arguments": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "due_date": "2027-02-03" } } ``` Find invoices by due date with the `due_before` and `due_after` filters, or sort with `sort=due_date`, on [List documents](/api-reference/documents/list-documents). [List overdue documents](/api-reference/documents/list-overdue) (MCP `list_overdue`) returns invoices past their due date. ## What the due date drives ### Overdue status An unpaid invoice past its due date becomes **overdue**. A scheduled background check sets the status, the invoice appears in the overdue list, and a `document.overdue` [webhook](/integrations/webhooks) event tells your systems. Paying the invoice moves it to paid as usual. Automatic overdue marking ships with reminders in the week of 11 January 2027. ### Reminders From the week of 11 January 2027, [reminder sequences](/collections/reminders) send their steps on days counted from the due date, for example 3 days before, on the day, and 3, 7, 14 and 30 days after. Reminders go out in the business's time zone. ### Late fees Also from the week of 11 January 2027, a [late fee](/collections/late-fees) policy can charge a fee once grace days after the due date have passed. Late fees are off by default and must be shown in the invoice terms before the invoice is sent, because in many countries a fee that was not in the terms cannot be enforced. ### Instalments From the week of 25 January 2027, an invoice can be split into a deposit and instalments, each with its own due date. Reminders, late fees and auto-charge then work per instalment. See [Deposits and schedules](/payments/schedules). ### Promise to pay If a client promises to pay by a certain date, you can record it, and the reminder sequence stops. See [Promise to pay](/collections/promise-to-pay). ## Estimates have an expiry date instead An estimate is not paid, so it has no due date in the same sense. It has an expiry: each business sets how many days its estimates stay valid, starting at 30. An estimate that is not accepted in time becomes `expired`. See [Estimates](/invoicing/estimates). ## Rules and edge cases - **Dates are frozen after sending.** Changing the issue or due date of a sent invoice is a [revision](/invoicing/revisions), and the earlier version stays readable.- **Voided invoices** stop reminders and late fees at once. - **Show your late-fee terms.** If you plan to charge late fees, put them in the terms text before you send. ## Permissions | Action | Permission key | | --- | --- | | Set dates and terms on a draft | `documents.create` or `documents.edit` | | Change dates on a sent invoice (revision) | `documents.edit` | | Change business defaults | `settings.manage` | | Change a client's terms | `clients.edit` | | Record a promise to pay | `documents.edit` | ## Errors you may see | Code | Meaning | | --- | --- | | `validation_failed` | A date is not in `YYYY-MM-DD` format. | | `document_not_editable` | The invoice was sent; change dates by revision. | | `document_voided` | The invoice is void. | ## Related pages Reminders Steps counted from the due date Late fees Fees after the grace days, stated in your terms Deposits and schedules A due date for every instalment Document statuses When an invoice becomes overdue Source: https://docs.freeinvoiceautomation.com/invoicing/due-dates --- # Send a document Deliver invoices, estimates, credit notes and receipts by email, share link, PDF download or copied link. Sending numbers the document and freezes it. Sending is the moment a draft becomes a real document. The first send gives it the next number in its sequence, stores a snapshot of exactly what your client receives, and delivers it. You can send by email from the app, or share a link or PDF through any channel you like, with nothing to print or post. ## Ways to deliver a document | Method | What your client gets | | --- | --- | | **Email** | An email with a link to the document's page (the pay page for an invoice) and, by default, the PDF attached. | | **Share link** | A private link you copy and paste anywhere: chat, your own email, a message. See [Share links](/invoicing/share-links). | | **Download PDF** | The PDF file, to attach or upload yourself. See [PDFs and templates](/invoicing/pdf). | | **Copy link** | Copies the pay link `/pay/[token]` of a sent invoice. | WhatsApp, Telegram and SMS arrive in April 2027 as further channels. See [WhatsApp, Telegram and SMS](/messaging/other-channels). ## What happens on the first send 1. The document gets its **number** from the business's gap-free sequence ([Document numbering](/invoicing/numbering)). 2. A **snapshot** freezes the business details, client details, lines, taxes, totals, terms and exchange rate. From now on, changes are [revisions](/invoicing/revisions). 3. The PDF is rendered from the snapshot, with its [verification QR](/invoicing/verification). 4. The email is sent, and the send is logged with its status. 5. The status becomes **sent** and the `document.sent` [webhook](/integrations/webhooks) event fires. All of the numbering and snapshot work happens in one step: if anything fails, nothing is half done and no number is lost. ## Send by email in the web app Click Send In the editor's footer, or from the document's actions in the list. Check the preview You always see a preview of the email first: recipient, subject, message and attachment. Nothing is sent until you confirm. Confirm The document is numbered, frozen and emailed. Clicking twice sends only one email. ### Who the email comes from Each send uses one sender, chosen in this order: 1. a sender set on the document; 2. the client's preference; 3. the business default, usually your own SMTP mailbox if you connected one; 4. the **platform mailer**, only if your account has quota left. Your own SMTP Emails go from your own mailbox, with no monthly limit from us. See [Your own SMTP](/messaging/smtp). Platform mailer Emails come from `email@freeinvoiceautomation.com` as "Your Business via Free Invoice Automation", with replies going to your business email. Free includes 20 platform emails a month and Team 100; the counter resets on the first of each month. You see a warning at 80%; at 100% the send dialog offers **Connect your own SMTP (unlimited)** and your draft is kept. The platform mailer works only after you verify your email address. See [Platform email](/messaging/platform-email). The words of the email come from your [message templates](/messaging/templates), per business, event and language. ## Through the API and MCP Sending is a two-step pattern: preview, then send. Integrations and AI clients create drafts, and a send needs `"confirm": true` unless the API key or MCP token was created with `mode: auto_send`. ```bash Preview curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/send-preview" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ```bash Send curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/send" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 0f2d4b6a-send-inv-0007" \ -d '{ "confirm": true }' ``` ```json MCP tool calls [ { "name": "preview_send", "arguments": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f" } }, { "name": "send_document", "arguments": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "confirm": true } } ] ``` | Task | Endpoint | MCP tool | | --- | --- | --- | | Preview the send | [Preview a send](/api-reference/documents/preview-send) | `preview_send` | | Send | [Send a document](/api-reference/documents/send-document) | `send_document` | | Create a share link | [Create a share link](/api-reference/documents/create-share-link) | none | | Download the PDF | [Render a PDF](/api-reference/documents/render-pdf) | `render_pdf` | | See what was sent | [List messages](/api-reference/messaging/list-messages) | none | Creating an `auto_send` API key needs a fresh sign-in, because a key with auto-send can email your clients without a person confirming each one. Use `draft_only` (the default) unless you really need it. ## Rules and edge cases - **Duplicate protection.** A document is sent once per event and channel. A retried or double-clicked send never emails twice, and the `Idempotency-Key` makes retries safe. - **The number and snapshot are set once.** Later deliveries, such as copying the link or downloading the PDF, never change the number; only a [revision](/invoicing/revisions) adds a new snapshot. - **Void documents cannot be sent.** The API answers `document_voided`. - **Quota is checked safely.** Two sends at the same moment cannot both take your last platform email. - **Sending limits.** There is a per-minute limit on sends to protect against abuse. Over it, you get `rate_limited` with a time to retry. - **No ads in client emails.** Your client emails never carry ads, affiliates or sponsors. The "Powered by Free Invoice Automation" footer is shown on Free and removed on Team. ## Permissions | Action | Permission key | | --- | --- | | Preview a send | `documents.view` | | Send, share and copy links | `documents.send` | | Change sender connections | `senders.manage` | | Edit message templates | `templates.manage` | ## Errors you may see | Code | Meaning | | --- | --- | | `confirmation_required` | Send `"confirm": true`, or use a key with `auto_send`. The response includes the preview. | | `email_not_verified` | Verify your email address before using the platform mailer. | | `quota_exceeded` | Your platform emails for this month are used up; `details.suggestion` is `connect_smtp`. | | `platform_mail_paused` | Your account moved to Free after already sending 20 or more platform emails this month. | | `document_voided` | The document is void. | | `rate_limited` | Too many sends in a minute; retry after the time in `Retry-After`. | | `upstream_error` | Your SMTP server failed; check the connection. | ## Related pages Share links Private links that expire and can be revoked Platform email Quotas, sender name and abuse controls Your own SMTP Unlimited email from your own mailbox Drafts and confirmations How draft-first works for the API and MCP Source: https://docs.freeinvoiceautomation.com/invoicing/sending --- # Share links Private links let your client open a document, pay it or accept it without an account. Links cannot be guessed, expire, and can be revoked at any time. A share link is a private web address for one document. Your client opens it in any browser, with no account and no password, to see the document and act on it: pay an invoice on the [pay page](/invoicing/pay-page) or accept an estimate on the estimate page. Copy it into any channel you like, or let the email send it for you. ## Kinds of links | Link | Opens | Used for | | --- | --- | --- | | `/pay/[token]` | the [pay page](/invoicing/pay-page) | invoices: view, pay by card, see bank details, upload payment proof | | `/estimate/[token]` | the estimate page | estimates: accept or request changes | | `/v/[code]` | the [verification page](/invoicing/verification) | anyone holding the PDF checks it is genuine | | `/portal` | the [client portal](/clients/portal) (week of 11 January 2027) | all of one client's documents with one business, through a single-use sign-in link | All of these pages live on `freeinvoiceautomation.com`, are themed with your business's logo and colour, and carry no analytics, no ads and no app chrome. ## How links are kept safe Every link token follows the same rules: - **Cannot be guessed.** Each token is 256 bits of randomness. - **Stored hashed.** We keep only a hash of the token, so even someone reading our database could not rebuild your links. - **Single-purpose.** A pay link only opens the pay page for its own invoice; it cannot be used for anything else. - **Expiring.** Pay links last 90 days to start with, and are renewed when the document is sent again or a reminder goes out (proposed). Estimate links follow the estimate's validity, 30 days to start with. Expired tokens are deleted 30 days after they expire. - **Revocable.** You can cancel a link at any moment. Voiding an invoice cancels its pay links automatically. Token pages also tell browsers not to pass the link on to other sites, cannot be embedded in other sites' frames, and are rate limited per visitor, so links cannot be leaked or flooded easily. Anyone who has the link can open the document, so share it only with your client. If a link was sent to the wrong person, revoke it and create a new one. ## Share and revoke in the web app Send the document first A share link is for a sent document. Sending gives it its number and freezes it ([Send a document](/invoicing/sending)). Copy the link Click **Copy link** on the document. The pay link is copied, ready to paste into chat, your own email or a message. Revoke when needed Revoke the link from the document. Anyone who opens it afterwards sees "link no longer valid" and no document data. ## Through the API Create a link for a document, then revoke it by its id: ```bash Create a share link curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/share-links" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Idempotency-Key: 4d6f8a0c-share-inv-0007" ``` ```bash Revoke it curl -X DELETE "https://api.freeinvoiceautomation.com/v1/share-links/0193f2d1-8b2c-7d3e-9f4a-5b6c7d8e9f01" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Idempotency-Key: 4d6f8a0c-unshare-inv-0007" ``` Once a share link exists, the document returned by the API includes its `pay_url`. | Task | Endpoint | | --- | --- | | Create a share link | [Create a share link](/api-reference/documents/create-share-link) | | Revoke a share link | [Revoke a share link](/api-reference/documents/revoke-share-link) | | What the pay page reads | [Get the pay page](/api-reference/public/get-pay-page) | | What the estimate page reads | [Get the estimate page](/api-reference/public/get-estimate-page) | Over MCP, `send_document` delivers the document with its link. The share link capabilities themselves are in the REST API and the CLI, not on the MCP tool list. ## What your client sees when a link stops working | Situation | Client sees | API error | | --- | --- | --- | | The token does not exist | link not valid | `token_invalid` (404) | | The link expired | link no longer valid | `token_expired` (410) | | You revoked it, or the invoice was voided | link no longer valid | `token_revoked` (410) | In every case the page shows no document data. ## Rules and edge cases - **Links never mark anything paid.** Opening a pay link, or returning from checkout, changes nothing. An invoice is paid only when the payment rail confirms it or you record the payment. - **Your client never needs a password.** Clients have no accounts. The link, or a single-use portal sign-in link, is their access. - **Uploads through a link are private.** Payment proofs a client uploads are stored privately and limited in size and type; images are re-encoded. ## Permissions | Action | Permission key | | --- | --- | | Create, copy and revoke share links | `documents.send` | | See a document's links | `documents.view` | ## Related pages The pay page What your client sees on a pay link Verification codes Check that a PDF is genuine Client portal Every document for one client in one place How we protect data Tokens, encryption and privacy Source: https://docs.freeinvoiceautomation.com/invoicing/share-links --- # The pay page Every sent invoice has a pay page where your client sees the invoice, the amount due and every payment method you accept, with no ads and no analytics. The pay page is what your client opens from the link in your email or the link you shared. It shows the invoice in your branding, the amount due, and the ways they can pay you. Money goes straight to your own Stripe account or bank, never through us, and we never take a cut. ## What your client sees The pay page at `freeinvoiceautomation.com/pay/[token]` shows: - the invoice as it was sent, themed with your logo and business colour; - the **amount due**, which is the invoice balance after payments and credit notes; - **every payment method you accept on this invoice**; - the void or credit note state, when there is one. The page has no app chrome, no analytics, no ads, no affiliates and no sponsors. On Free it carries a small "Powered by Free Invoice Automation" line; on Team it does not. ## Payment methods Which methods appear depends on what your business has set up in **Settings > Payment methods** and which of them are accepted on this invoice. | Method | How the client pays | When it is marked paid | | --- | --- | --- | | **Card by [Stripe](/payments/stripe)** | **Pay by card** opens Stripe's hosted checkout on your own connected Stripe account. Card details never touch our servers. | When Stripe's verified webhook confirms the payment. | | **[Bank transfer](/payments/bank-transfer)** | The page shows your bank details. The client pays from their bank, then clicks **I have paid** and may upload proof. | When you record the payment. | | **[Local QR payments](/payments/qr-rails)** (week of 25 January 2027) | UPI for INR invoices; Raast, JazzCash and Easypaisa for PKR invoices. The client scans the QR or uses the account details shown next to it, then clicks **I have paid**. | When you record the payment. | | **[Whop](/payments/whop)** (February 2027) | A Whop checkout link on your own Whop account. | When Whop's verified webhook confirms it. | QR rails appear only when the invoice currency matches them, so a USD invoice never shows UPI or Raast. ### Partial payments Partial payments are allowed by default. The client may pay part of the amount, and the balance updates once the payment is confirmed. A business can turn partial payments off for itself, a client or one invoice. ### "I have paid" and payment proof For bank transfer and QR payments, the client clicks **I have paid**, optionally enters the amount and date and adds a note, and may upload a proof such as a bank screenshot or PDF. You are notified and the proof is stored privately. The invoice is **not** marked paid until you accept the proof or [record the payment](/payments/recording). See [Payment proofs](/payments/payment-proofs). ## What changes after launch - **Payment schedules** (week of 25 January 2027): when the invoice has a deposit or instalments, the page shows the schedule and asks for the next amount due; the client may also pay more. See [Deposits and schedules](/payments/schedules). - **Save card** (March 2027): on the first Stripe checkout, the client may tick "Save this card for future invoices from" your business, so later invoices can be charged automatically with their consent. See [Saved cards and auto-charge](/payments/auto-charge). ## Void and credited invoices | Invoice state | Pay page shows | | --- | --- | | Voided | "VOID" and **no payment button**. Voiding also revokes the pay link, so new visits get "link no longer valid". | | Credited | The credit note and the reduced balance. | | Fully credited | A balance of 0. | ## How payment is confirmed The client pays By card through Stripe Checkout, or outside the system by bank or QR. The page waits for confirmation After checkout, the client returns to a page that says the payment is being confirmed. Returning from checkout never marks the invoice paid by itself. A trusted event confirms it The invoice changes only on a verified webhook from the rail, a reconciliation run that catches a missed webhook, or a payment a person on your team records. Each is written to the audit log. ## Through the API The pay page is built on public routes that need only the pay token. You normally do not call them yourself, but they are documented so you can build your own page if you want: ```bash Read the pay page data curl "https://api.freeinvoiceautomation.com/v1/public/pay/$PAY_TOKEN" ``` ```bash Start a card checkout curl -X POST "https://api.freeinvoiceautomation.com/v1/public/pay/$PAY_TOKEN/checkout" \ -H "Content-Type: application/json" \ -d '{ "connection_id": "0193f2a8-6a7b-7c8d-9e0f-1a2b3c4d5e70", "amount_minor": "60000" }' ``` The checkout call returns the URL of the hosted checkout. It never marks anything paid. | Task | Endpoint | | --- | --- | | Read the pay page | [Get the pay page](/api-reference/public/get-pay-page) | | Start a checkout | [Start a checkout](/api-reference/public/start-checkout) | | Upload a proof file | [Upload a payment proof file](/api-reference/public/upload-payment-proof-file) | | Submit "I have paid" | [Submit a payment proof](/api-reference/public/submit-payment-proof) | | Check status as the business | [Get payment status](/api-reference/payments/get-payment-status) (MCP `get_payment_status`) | ## Rules and limits - **We never hold your money.** Stripe and Whop pay into your own accounts; bank and QR payments go straight to you. - **Rate limits** protect the page: token pages allow 60 requests a minute per visitor, and "I have paid" with upload is limited to 5 an hour per link. - **Uploads** are limited in size and type, and images are re-encoded before they are stored. - **The link expires and can be revoked.** See [Share links](/invoicing/share-links). ## Permissions Your client needs no account; the pay link is their access. On your side: | Action | Permission key | | --- | --- | | Share a pay link | `documents.send` | | Choose payment methods | `rails.manage` | | Accept or reject a payment proof, record a payment | `payments.record` | | See payment status | `payments.view` | ## Errors your client may see | Code | Meaning | | --- | --- | | `token_expired` | The link expired. | | `token_revoked` | The link was revoked or the invoice was voided. | | `rail_not_connected` | The chosen method has no active connection. | | `rate_limited` | Too many requests; wait and try again. | ## Related pages How payments work Rails, confirmations and the paid state Stripe Connect your own Stripe account Bank transfer Show bank details and record payments Share links How pay links expire and are revoked Source: https://docs.freeinvoiceautomation.com/invoicing/pay-page --- # Verification codes Every PDF from a signed-in account carries a QR code. Scanning it opens a page that confirms who issued the document, its amount and its current status. Fake invoices are a common trick: someone copies a supplier's invoice and changes the bank details. Verification codes let your client check that a PDF really came from you. Every PDF issued from a signed-in account carries a verification QR code that opens a page on `freeinvoiceautomation.com` showing the real document's key facts and current status. ## How it works 1. When a document is sent and its PDF is rendered, a short random **verification code** is created for it and printed as a QR code on the PDF. 2. Anyone holding the PDF scans the QR, or opens `freeinvoiceautomation.com/v/[code]`. 3. The verification page shows what we have on record for that document. If the facts on the page do not match the PDF, the PDF was changed after it was issued, or did not come from that business. ## What the verification page shows | Shown | Example | | --- | --- | | Issuing business | Acme Design Studio | | Document type and number | Invoice `INV-2027-0042` | | Issue date | 2027-01-04 | | Amount and currency | 1,200.00 USD | | Current status | paid, void, and so on | The page shows the minimum needed to verify a document. It is designed not to show the client's address or the line items (proposed), and nothing beyond what is printed on the PDF. Like every client-facing page, it carries no analytics and no ads. Tell your clients about it. A line in your terms such as "Scan the QR code to check this invoice is genuine" helps them spot a fake before they pay one. ## The status is live The page shows the document's **current** status, not the status when the PDF was printed. So a client holding an old PDF of an invoice you later voided will see "void" when they scan it, and a paid invoice shows as paid. This makes the QR useful for anyone the PDF is passed to, such as a client's accounts team. Each code is tied to the document and to the snapshot it was printed from. ## Where the QR appears - **Invoices, estimates, credit notes and receipts** issued from a signed-in account: on every PDF. - **Voided documents**: the PDF shows the "VOID" stamp, and the verification page shows the void status. - **The free generator**: PDFs made without an account have no verification QR, because no document is stored on our side to check against. The same applies to the no-sign-in `generate_pdf` MCP tool (proposed). ## Check a document in the web app Scan or open the code Point a phone camera at the QR on the PDF. It opens the verification page in the browser. Compare the facts Check the business, number, date, amount and currency against the PDF. Check the status Make sure the document is not void before you pay it. No account or sign-in is needed to verify a document. ## Through the API The verification page reads one public route that needs no authentication: ```bash Verify a code curl "https://api.freeinvoiceautomation.com/v1/public/verify/K7Q2M9XD" ``` ```json Response { "data": { "issuer": "Acme Design Studio", "type": "invoice", "number": "INV-2027-0042", "issue_date": "2027-01-04", "total_minor": "120000", "currency": "USD", "status": "paid" } } ``` See [Verify a document](/api-reference/public/verify-document). The response shape above is illustrative; the endpoint page lists the exact fields. ## Rules and edge cases - **Codes are random and rate limited.** Codes are short and URL-safe but random, and the route is limited per visitor, so codes cannot be found by trying them one after another. - **A code can be revoked.** A revoked code no longer shows the document. - **Verification is on for every PDF.** It is a platform rule, not a per-business option, so every client of every business gets the same protection. - **The verification QR is separate from payment QR codes.** Local QR payment codes (UPI, Raast, JazzCash, Easypaisa, from the week of 25 January 2027) are printed for paying; the verification QR is only for checking. ## Permissions Verification needs no permission: anyone with the code can check the document. Creating the code happens automatically when a document with a PDF is sent, which needs `documents.send`. ## Errors you may see | Code | Meaning | | --- | --- | | `rate_limited` | Too many checks in a minute; wait and try again. | A code that does not exist, or was revoked, shows no document data. Treat a PDF whose code does not verify as suspicious and ask the business directly, using contact details you already trust. ## Related pages PDFs and templates Where the QR is printed Share links The other public pages and how they are protected Void and delete How a void shows on the PDF and verification page How we protect data Security of public pages Source: https://docs.freeinvoiceautomation.com/invoicing/verification --- # PDFs and templates Every document renders to a professional PDF from a template you can style. The same template is used in the browser and on the server, so PDFs always match. Every invoice, estimate, credit note and receipt can be downloaded as a PDF, attached to emails and shared anywhere. PDFs are built from templates that are data, not code, so you can change layout, colours, fonts, labels and footer without anyone writing software, and the PDF your client downloads looks exactly like the preview you saw. ## How PDFs are made - **One template, everywhere.** The same template engine renders the PDF in your browser (in the free generator and the editor preview) and on our server. Both give the same layout. - **From the snapshot.** A sent document's PDF is rendered from its snapshot, so the PDF shows exactly what was sent. The file's fingerprint is stored with the snapshot. - **Totals come from the engine.** Templates only display numbers; every total is calculated by the same engine as the rest of the product, so the PDF never disagrees with the pay page or the API. ## What is on every PDF | Element | Notes | | --- | --- | | Your logo and business details | from the business profile, frozen at send | | Client details | frozen at send | | Lines, taxes, discounts and totals | in the document's currency | | Terms and notes | from the document, client or business settings | | **Verification QR** | on every PDF from a signed-in account; opens `/v/[code]` ([Verification codes](/invoicing/verification)) | | "Powered by Free Invoice Automation" | on Free; removed on Team | | Payment QR | for UPI, Raast, JazzCash and Easypaisa invoices, from the week of 25 January 2027, with the plain account details beside it | There are never ads, affiliates or sponsors on your PDFs. ### Variants Every template has three variants, chosen automatically: | Variant | Used for | | --- | --- | | Standard | invoices, estimates and receipts | | **VOID** | a voided document: the PDF carries a "VOID" stamp so nobody pays it by mistake | | **CREDIT NOTE** | credit notes: clearly marked, with the original invoice number | ## Templates Templates come from a built-in template list, and each business can save its own versions with its own options: | Option | Notes | | --- | --- | | Layout | layout options offered by the built-in template | | Colours | for example your brand colour | | Fonts | the typeface used | | Labels | the words printed for each field | | Footer | your own footer text | | QR | QR code options | | Paper size | A4 or Letter; the default follows your business's country | Each business has a default template. The template for a document is resolved in this order: the template chosen on the document, then the client's, then the business default. ## Choose and style a template in the web app Templates and numbering"> Check the business switcher first: templates belong to one business. Create or edit a template Start from a built-in template, set colours, fonts, labels, footer and paper size, and give it a name. Make it the default Mark it as the business default, or pick it for particular clients. Download a PDF Open any document and click **Download PDF**. The editor's live preview shows the same layout before you send. Your logo and brand colour are set once per business in [Branding](/account/branding). ## Through the API and MCP Download a document's PDF: ```bash cURL curl "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/pdf" \ -H "Authorization: Bearer $FIA_API_KEY" \ -o INV-2027-0042.pdf ``` ```json MCP tool call { "name": "render_pdf", "arguments": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f" } } ``` Over MCP the PDF comes back encoded in base64. | Task | Endpoint | MCP tool | | --- | --- | --- | | Download a document's PDF | [Render a PDF](/api-reference/documents/render-pdf) | `render_pdf` | | List templates | [List PDF templates](/api-reference/messaging/list-pdf-templates) | none | | Create or change a template | [Create or update a PDF template](/api-reference/messaging/upsert-pdf-template) | none | | Make a PDF with no account | [Generate a PDF](/api-reference/public/generate-pdf) | `generate_pdf` (no sign-in) | ### PDFs without an account `generate_pdf` takes the same document fields as `create_invoice`, plus business and client details and a template, and returns a PDF. Nothing is stored. It is rate limited per visitor, always shows the "Powered by" footer, and has no verification QR because there is no stored document to verify (proposed). It is what the [free generator](/generator) uses. ## Rules and edge cases - **Sent PDFs do not change.** A PDF rendered from a snapshot stays as it was. When a document is revised, its new snapshot gets its own PDF, and the old one stays readable. - **Changing a template** affects PDFs rendered after the change. Documents already sent keep the PDF of their snapshot. - **Email attachments.** The PDF is attached to document emails by default; this is a setting for the business, client or document. - **Branding follows the plan.** On a downgrade from Team to Free, "Powered by" returns on new PDFs only; PDFs already sent are not changed. ## Permissions | Action | Permission key | | --- | --- | | Download PDFs, list templates | `documents.view` | | Create and change templates | `templates.manage` | | Upload a business logo | `businesses.manage` | By default Owners, Admins and Editors can manage templates. ## Errors you may see | Code | Meaning | | --- | --- | | `not_found` | The document or template does not exist or is not visible to you. | | `validation_failed` | A template option is invalid. | | `rate_limited` | Too many `generate_pdf` calls from one address. | ## Related pages Verification codes The QR printed on every PDF Branding Logo and colours for your documents Free invoice generator Make a PDF with no account Send a document Email the PDF to your client Source: https://docs.freeinvoiceautomation.com/invoicing/pdf --- # Document statuses Every status an invoice, estimate, credit note or receipt can have, what moves a document from one to the next, and how to filter by status. A status tells you at a glance where a document stands: still a draft, waiting for payment, paid, overdue or void. Statuses change because of things that happen, such as a send, a confirmed payment or a void, never because someone types a new status in. This page lists them all and explains what moves a document between them. Statuses come from a statuses registry, so new ones can be added without changing the API. The lists below are the proposed values for launch; treat unknown values in API responses as possible and handle them gracefully. ## Invoice statuses | Status | Meaning | How it gets there | | --- | --- | --- | | `draft` | Being prepared. No number. Can be deleted. | Created in the app, API, MCP, AI agent or an integration. | | `sent` | Numbered, frozen and delivered. | First send or share. | | `partially_paid` | Some money received, balance left. | A confirmed payment smaller than the balance. | | `paid` | Balance is zero through payments. | A confirmed payment that covers the balance. | | `overdue` | Unpaid after the due date. | A scheduled check after the due date (from the week of 11 January 2027). | | `void` | Cancelled. Keeps its number. | [Void](/invoicing/void), only when no payments are recorded. | ### How an invoice moves ```text Invoice life draft ──send──> sent ──payment──> partially_paid ──payment──> paid │ │ ▲ delete ├──due date passes──> overdue ──payment──────┘ (soft) │ └──void (no payments)──> void ``` - A **draft** can be edited freely or deleted. - A **sent**, **overdue** or **partially paid** invoice changes only by [revision](/invoicing/revisions). - A payment moves the status only when it is confirmed by a verified webhook from the rail, a reconciliation run, or a person recording it. A client clicking "I have paid" does not change the status. - A **paid** invoice can still receive a [credit note](/invoicing/credit-notes), refunded or kept as client credit. ## Estimate statuses | Status | Meaning | | --- | --- | | `draft` | Being prepared. | | `sent` | Delivered to the client. | | `changes_requested` | The client asked for changes on the estimate page. | | `accepted` | The client accepted it. | | `expired` | The validity period ran out. | | `converted` | An invoice was created from it. | | `void` | Cancelled. | See [Estimates](/invoicing/estimates). ## Credit note statuses | Status | Meaning | | --- | --- | | `draft` | Being prepared. | | `issued` | Numbered and in effect. | | `applied` | Used against an invoice as client credit. | | `refunded` | Paid back to the client. | ## Receipt statuses | Status | Meaning | | --- | --- | | `issued` | Numbered and in effect. | | `void` | Cancelled. Keeps its number. | ## Later releases - **Instalments** (week of 25 January 2027) have their own statuses: `pending`, `due`, `partially_paid`, `paid`, `overdue` and `void`. See [Deposits and schedules](/payments/schedules). - **Contracts** (March 2027): `draft`, `sent`, `partially_signed`, `signed`, `sealed` and `superseded`. See [Contracts and e-signature](/features/contracts). ## Find documents by status in the web app Open the list Open **Invoices** (or your list of estimates, credit notes or receipts). Filter Use the status filter chips, together with client and date filters. Each status shows as a coloured pill, and void is shown with a strike-through. Open the side panel Click a row to see the PDF preview, the timeline of what happened and the actions you can take. From March 2027 you can save a filtered list as a [saved view](/features/saved-views). ## Through the API and MCP Filter with the `status` query parameter. Several values are comma-separated: ```bash cURL curl "https://api.freeinvoiceautomation.com/v1/documents?type=invoice&status=sent,overdue,partially_paid&sort=due_date" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ```json MCP tool call { "name": "list_documents", "arguments": { "type": "invoice", "status": "sent,overdue,partially_paid" } } ``` | Task | Endpoint | MCP tool | | --- | --- | --- | | List by status | [List documents](/api-reference/documents/list-documents) | `list_documents` | | Overdue invoices | [List overdue documents](/api-reference/documents/list-overdue) | `list_overdue` | | One document's history | [List document activity](/api-reference/documents/list-document-activity) | none | | Payment status | [Get payment status](/api-reference/payments/get-payment-status) | `get_payment_status` | ### Webhook events for status changes | Event | When | | --- | --- | | `document.sent` | first send | | `document.partially_paid`, `document.paid` | a confirmed payment | | `document.overdue` | the due date passed unpaid | | `document.voided` | void | | `estimate.accepted`, `estimate.changes_requested`, `estimate.converted` | estimate answers and conversion | | `credit_note.created` | a credit note was issued | See [Webhook events](/api-reference/webhook-events). ## Rules and edge cases - **Void stays on record.** A voided document keeps its number and history, and is never deleted. If the work still needs billing, create a new invoice. - **Void and paid do not mix.** An invoice with recorded payments cannot be voided; use a credit note. - **Overdue stops when paid.** Paying an overdue invoice moves it to paid. - **Reminders and late fees stop** on paid, void, or when credit notes cover the balance. ## Permissions | Action | Permission key | | --- | --- | | See statuses and filter | `documents.view` | | Send (draft to sent) | `documents.send` | | Record a payment | `payments.record` | | Void | `documents.void` | ## Related pages Revisions How sent documents change Void and delete Leave a document on record but cancelled How payments work What confirms a payment Due dates and payment terms When an invoice becomes overdue Source: https://docs.freeinvoiceautomation.com/invoicing/statuses --- # Revisions A sent document is never edited in place. Each change creates a revision with its own snapshot, and every earlier version stays readable. Once your client has a document, it is a record you both rely on. So a sent document is never changed in place: when you edit it, Free Invoice Automation creates a **revision**, a new version with its own snapshot, and keeps every earlier version readable. You can always show exactly what was sent, when, and what changed after. ## Snapshots and versions | Moment | What is stored | | --- | --- | | First send | Version 1: a snapshot of the business details, client details, header, lines, totals, terms and exchange rate, and the PDF rendered from it. | | Each revision | The next version, with a new snapshot and PDF, and a record of why it changed. | | A draft | Nothing yet. A draft is version 0 and can be edited freely. | Snapshots and revision records are **append-only**: they can never be edited or deleted, by anyone, including our own systems. The database itself refuses changes to a sent document's money, lines, client, currency or dates unless they come through the revision process, which writes the new snapshot in the same step. ### Why a revision was made Each revision records a reason. The proposed reasons are: | Reason | When | | --- | --- | | `edit` | You changed the document. | | `late_fee` | A late fee was added (from the week of 11 January 2027). | | `late_fee_waived` | A late fee was waived. | | `payment_schedule` | A deposit or instalments were set or changed (from the week of 25 January 2027). | A summary of what changed is stored with it. ## What a revision changes and what it keeps | Changes | Stays the same | | --- | --- | | Lines, prices, discounts, taxes and totals | The document's number | | Dates, terms and notes | Every earlier version and its PDF | | | The payments already recorded | | | The audit trail of who did what | Status, payment and timestamp fields are not part of a revision: they change as payments arrive or the document is voided. ## Revise a sent document in the web app Open the sent document Find it in the list and open it. Edit Make your change in the editor, as you would on a draft. The editor knows the document was sent, so saving creates a revision instead of overwriting. Save the revision The new version is stored with its snapshot and PDF. The side panel's timeline shows each version, and earlier versions stay available to open. If the change lowers what the client owes on an invoice that was already sent, consider a [credit note](/invoicing/credit-notes) instead. It leaves a separate, numbered record of the correction that you and your client both keep, and it is the only way to correct an invoice that has payments. ## Through the API and MCP Update a sent document with the same call as a draft. The API creates the revision for you: ```bash cURL curl -X PATCH "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 7b9d1f3a-revise-inv-0042" \ -d '{ "notes": "Updated purchase order number: PO-5521" }' ``` ```json MCP tool call { "name": "update_invoice", "arguments": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "notes": "Updated purchase order number: PO-5521" } } ``` Then list the versions: ```bash List versions curl "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/versions" \ -H "Authorization: Bearer $FIA_API_KEY" ``` | Task | Endpoint | MCP tool | | --- | --- | --- | | Change a document (draft or revision) | [Update an invoice](/api-reference/documents/update-invoice) | `update_invoice` | | List versions | [List document versions](/api-reference/documents/list-document-versions) | none | | See the timeline | [List document activity](/api-reference/documents/list-document-activity) | none | A revision fires the `document.revised` [webhook](/integrations/webhooks) event. Each document carries a `current_version` number, which is 0 for a draft and goes up by one with every snapshot. ## Rules and edge cases - **Drafts are not revised.** Editing a draft changes it directly; there is nothing to protect until it is sent. - **Void documents are not revised.** A void document cannot be changed; the API answers `document_voided`. - **The number never changes.** A revision is the same document, so it keeps its number. - **Payments stay valid.** Payments recorded against the document stay recorded against it. - **Late fees use revisions.** When the late-fee feature adds a fee, it does so as a revision, adds a line called "Late fee", writes an audit entry and tells the client in the next reminder. Waiving the fee is also a revision. - **Contracts are different.** A signed contract (March 2027) never changes at all: a change is a new contract version that must be signed again. ## Permissions | Action | Permission key | | --- | --- | | Revise a sent document | `documents.edit` | | See versions and activity | `documents.view` | Every revision writes an audit entry with who made it. On Team you can review them in the [audit log](/team/audit-log). ## Errors you may see | Code | Meaning | | --- | --- | | `document_not_editable` | The document was sent, so this change cannot be made in place; it must be made as a revision. | | `document_voided` | The document is void. | | `validation_failed` | The change itself is invalid. | | `permission_denied` | Your role lacks `documents.edit`. | ## Related pages Document statuses Which statuses can be revised Credit notes The right tool for reducing a sent invoice Late fees Fees added as revisions Audit log Who changed what and when Source: https://docs.freeinvoiceautomation.com/invoicing/revisions --- # Void and delete Delete a draft you do not need, or void a sent document with a reason. A voided document keeps its number, and an invoice with payments needs a credit note instead. Mistakes happen: an invoice goes to the wrong client, or the work is cancelled. What you can do depends on whether the document was sent. A **draft** has no number, so you can simply delete it. A **sent** document is part of your numbered records, so it is never deleted: you **void** it, with a reason, and it stays on record marked "VOID". If an invoice already has payments, you cannot void it at all; you issue a [credit note](/invoicing/credit-notes). ## Which one to use | Document | Payments recorded? | What you can do | | --- | --- | --- | | Draft | no (drafts cannot be paid) | **Delete** | | Sent invoice, estimate or receipt | no | **Void** with a reason | | Sent invoice | yes, any amount | **Credit note** (void is refused) | ## Delete a draft Deleting a draft hides it from your lists. Like every document, it is kept as a soft-deleted record rather than erased, but because it never had a number, your sequence has no gap. Open the draft Find it in your list. Delete it Choose delete from the document's actions and confirm. If you try to delete a sent document, the delete is refused and void or a credit note is offered instead. ## Void a sent document ### What voiding does When you void a document, all of this happens in one step: - It **keeps its number**, so your sequence stays complete. - It is marked **void**, and a **"VOID" stamp** appears on its PDF, its pay page and in the client portal. - **Reminders and late fees stop.** - **Open payment links are cancelled.** The pay link is revoked, so a client who opens it sees that the link is no longer valid and cannot pay. - The **reason** you gave is stored, with who voided it and when. - An **audit entry** is written and the `document.voided` [webhook](/integrations/webhooks) event fires. Anyone who scans the PDF's [verification code](/invoicing/verification) sees the void status. ### Void in the web app Open the sent document Open it from **Invoices** or your other document lists. Choose void and give a reason A reason is required, for example "Sent to the wrong client". It is kept on the record. Confirm A confirmation dialog shows what will happen. Once confirmed, the void cannot be undone. Voiding is permanent. The document stays on record as void and keeps its number. If you still need to bill the work, create a new invoice, which gets the next number. ## Through the API and MCP Voiding is a destructive action. The API and MCP require `"confirm": true`; without it you receive a preview of what would happen and the `confirmation_required` error, and nothing changes. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/void" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 9a1c3e5f-void-inv-0042" \ -d '{ "reason": "Sent to the wrong client", "confirm": true }' ``` ```json MCP tool call { "name": "void_document", "arguments": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "reason": "Sent to the wrong client", "confirm": true } } ``` ```bash Delete a draft curl -X DELETE "https://api.freeinvoiceautomation.com/v1/documents/0193f2b9-1c2d-7e3f-8a4b-5c6d7e8f9a01" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Idempotency-Key: 9a1c3e5f-delete-draft" ``` A successful delete returns `204` with no body. | Task | Endpoint | MCP tool | | --- | --- | --- | | Void a document | [Void a document](/api-reference/documents/void-document) | `void_document` (destructive) | | Delete a draft | [Delete a draft](/api-reference/documents/delete-draft) | none | | Correct an invoice with payments | [Create a credit note](/api-reference/documents/create-credit-note) | `create_credit_note` (destructive) | An MCP client or the AI agent can prepare a void, but a person confirms it. An `Idempotency-Key` is required on void calls, so a retry never runs it twice. ## Store cancellations When the [Shopify app](/integrations/shopify) (February 2027) or the [WooCommerce plugin](/integrations/woocommerce) (week of 25 January 2027) receives a verified order cancellation from your store, it voids the matching invoice automatically, or issues a credit note if payments exist. This records something that already happened in your store, so it does not wait for confirmation. It is audited with the store connector as the actor and shows in the document's activity. ## Rules and edge cases - **Never deleted once numbered.** The database refuses to delete any document that has a number. Documents and payments are only ever soft-deleted. - **Payments block a void.** If any payment is recorded, even a partial one, void is refused with `document_has_payments`. Issue a credit note for the amount instead. - **Void needs a reason.** A void without a reason is refused. - **Nothing more on a void document.** Sending, revising or paying a void document is refused with `document_voided`. - **Estimates and receipts** can be voided the same way, and keep their numbers too. ## Permissions | Action | Permission key | | --- | --- | | Void | `documents.void` | | Delete a draft | `documents.edit` | | Issue a credit note instead | `documents.credit` | By default only Owners and Admins can void. Every void writes an audit entry. ## Errors you may see | Code | Meaning | | --- | --- | | `confirmation_required` | Send `"confirm": true`. The response includes the preview. | | `document_has_payments` | The invoice has recorded payments and cannot be voided. Create a credit note instead. | | `document_voided` | It is already void. | | `validation_failed` | The reason is missing. | | `permission_denied` | Your role lacks `documents.void`. | ## Related pages Credit notes Correct an invoice that has payments Document numbering Why numbers are never reused Revisions Change a sent document without cancelling it Drafts and confirmations How destructive actions are confirmed Source: https://docs.freeinvoiceautomation.com/invoicing/void --- # Clients and contacts Save the people and companies you bill, with contacts and defaults that fill every new document for them. A client is the person or company that receives your documents. Each client belongs to one [business](/account/businesses), holds its own contacts and defaults, and never has a password: clients open what you send them through secure links and, from January 2027, the [client portal](/clients/portal). Saving a client once means every invoice, estimate and credit note for them starts with the right details. ## How clients work Your account can hold any number of businesses, and each business keeps its own client list. A client saved under one business is not visible from another business, even in the same account. Members only see the clients of the businesses their role can reach. A client record has these parts: | Part | What it holds | | --- | --- | | Identity | Name (required), company name, email, phone, address, country and tax id | | Defaults | Default currency, language and other preferences that fill new documents | | Notes | Private notes for your team; clients never see them | | Contacts | Any number of people at the client, each with their own email, phone and role | | Reminder options | A per-client reminder sequence and a reminder opt-out (January 2027) | | Credit | A [client credit](/clients/client-credit) balance per currency, if you kept a credit note as credit | ### Defaults that fill documents When you create a document for a client, their defaults fill it in: for example the currency, the document language, the email sender, and payment terms. You can still change any of these on a single document. Settings are resolved in a fixed order, and the first one that has a value wins: 1. The document itself 2. The client 3. The business 4. The account 5. Plan and platform defaults So a client who always pays in EUR gets EUR invoices even if your business works in USD, while one special invoice can still use another currency. Accepted payment methods, the PDF template and terms text can also be set per client. See [Settings](/account/settings) for how this resolution works everywhere in the product. ### Contacts A client can have several contacts, for example a project lead and an accounts payable team. Each contact has a name, email, phone, a free-text role, and two switches: - **Primary**: the main person at the client. - **Receives documents**: whether this contact should get documents you send. Contacts are managed with the client. Deleting a contact does not affect documents already sent, because every sent document keeps a frozen copy of the client details from the moment it was sent. ### Sent documents keep their copy When a document is sent for the first time, a snapshot of the business and client details is stored with it. Editing a client later (a new address, a new tax id) never changes documents you already sent. New documents use the new details. This keeps your records trustworthy, which matters for tax. ## Add and edit clients in the app Open Clients In the sidebar, open **Clients** under **Get paid**. Make sure the business switcher in the top bar shows the business you want the client under. Create the client Choose to add a client and enter at least a name. Add the company name, email, address, country and tax id if you want them printed on documents. Set defaults Pick the default currency and language, and any other defaults you want new documents to start with. Leave them empty to use the business defaults. Add contacts Add the people you deal with. Mark one as primary and switch on **Receives documents** for everyone who should get invoices. Save The client is saved under the current business and is ready to pick in the document editor. The [AI invoice agent](/ai/agent/overview) can create clients too when you describe work for someone new. Like everything the agent does, the result is a draft you review before anything is sent. The client list has a search box that matches client names, so you can find a client quickly even in a long list. Clicking a row opens a side panel with the client's details. ## Through the API and MCP Every client action is one capability, available in the REST API, the CLI (from the week of 25 January 2027) and, where listed, as an MCP tool. | Action | REST | MCP tool | Permission | | --- | --- | --- | --- | | [List clients](/api-reference/clients/list-clients) | `GET /v1/clients` | `list_clients` | `clients.view` | | [Get a client](/api-reference/clients/get-client) | `GET /v1/clients/{id}` | none | `clients.view` | | [Create or update a client](/api-reference/clients/upsert-client) | `POST /v1/clients` or `PATCH /v1/clients/{id}` | `upsert_client` | `clients.create` / `clients.edit` | | [Delete a client](/api-reference/clients/delete-client) | `DELETE /v1/clients/{id}` | none | `clients.delete` | | [List contacts](/api-reference/clients/list-client-contacts) | `GET /v1/clients/{id}/contacts` | none | `clients.view` | | [Create or update a contact](/api-reference/clients/upsert-client-contact) | `POST /v1/clients/{id}/contacts` or `PATCH /v1/client-contacts/{id}` | none | `clients.edit` | | [Delete a contact](/api-reference/clients/delete-client-contact) | `DELETE /v1/client-contacts/{id}` | none | `clients.edit` | `POST /v1/clients` with an existing `id` updates that client, which is why the MCP tool is called `upsert_client`. If your key can reach exactly one business, you may leave out `business_id`. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/clients" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: client-acme-001" \ -d '{ "business_id": "0193f2a1-1b2c-7d3e-8f40-5a6b7c8d9e01", "name": "Acme Studio", "company_name": "Acme Studio GmbH", "email": "billing@acme.example", "country": "DE", "tax_id": "DE123456789", "default_currency": "EUR", "language": "de" }' ``` ```json MCP tool call { "name": "upsert_client", "arguments": { "name": "Acme Studio", "email": "billing@acme.example", "default_currency": "EUR" } } ``` ```json Response (201) { "data": { "id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "business_id": "0193f2a1-1b2c-7d3e-8f40-5a6b7c8d9e01", "name": "Acme Studio", "company_name": "Acme Studio GmbH", "email": "billing@acme.example", "country": "DE", "tax_id": "DE123456789", "default_currency": "EUR", "language": "de", "reminders_opt_out": false, "created_at": "2027-01-04T10:15:00Z" } } ``` Lists use cursor [pagination](/api-reference/pagination) and accept the text search parameter `q`. Creating and updating clients sends the `client.created` and `client.updated` [webhook events](/api-reference/webhook-events). ## Rules and edge cases - **Delete is a soft delete.** Deleting a client is marked destructive, so over the API and MCP it needs `"confirm": true`, and in the app it asks you to confirm. Clients are never hard-deleted while documents or payments refer to them; sent documents keep their copy of the client. - **One business per client.** To bill the same company from two businesses, add it as a client in each. - **No client passwords.** Clients reach documents through single-purpose links and the magic-link portal. There is nothing for them to sign up for. - **Reminder opt-out.** From January 2027 a client can opt out of reminder emails, and you can set it on the client. Opted-out clients are skipped and the skip is logged. See [Reminders](/collections/reminders). - **Store customers.** The [WordPress plugin](/integrations/woocommerce) and [Shopify app](/integrations/shopify) create clients from store customers through the same API. ## Permissions | Key | Lets a member | Default roles | | --- | --- | --- | | `clients.view` | See clients, contacts and client credit | Owner, Admin, Editor, Viewer, Accountant | | `clients.create` | Add clients | Owner, Admin, Editor | | `clients.edit` | Change clients, contacts and client-level defaults | Owner, Admin, Editor | | `clients.delete` | Delete clients | Owner, Admin | See [Roles and permissions](/team/roles) for how roles and business scope work. ## Errors | Code | When | | --- | --- | | `validation_failed` | A field is missing or invalid, for example no name or a currency that is not three letters | | `permission_denied` / `scope_missing` | Your role or API key lacks the client permission | | `business_out_of_scope` | Your role cannot reach the business the client belongs to | | `not_found` | The client does not exist or belongs to another account | | `confirmation_required` | A delete was sent without `"confirm": true`; the response includes a preview | ## Related Client credit Keep a credit note as credit and apply it to the next invoice Client portal One place for a client to see, pay and download their documents Import and export Get your client data in and out Invoices Create your first invoice for a client Source: https://docs.freeinvoiceautomation.com/clients/clients --- # Client credit Keep money from a credit note on a paid invoice as credit for the client, and apply it to their next invoice. Client credit is money you owe a client that you keep on their account instead of paying it back. It comes from a [credit note](/invoicing/credit-notes) on an invoice that was already paid: rather than refunding, you choose to keep the amount as credit and use it on the client's next invoice. This suits regular clients who would rather have the amount taken off their next bill than wait for a refund. ## When client credit is created A credit note reduces what a client owes on an invoice. What happens next depends on whether the invoice was already paid: | Invoice state | What the credit note does | | --- | --- | | Not paid, or partly paid | Reduces the invoice balance. Nothing is owed back, so no credit is created. | | Paid | You choose a **settlement**: **refund** the money, or **keep as client credit** | If you choose **refund**, the money goes back through the payment rail when it supports refunds (Stripe), or you record the refund manually (bank transfer and other rails). See [Refunds](/payments/refunds). If you choose **keep as client credit**, the credited amount is added to the client's credit balance in the credit note's currency. The credit note itself stays on record with its own number (for example `CN-2027-0001`) and is linked to the original invoice. ## How the balance works - Each client has **one credit balance per currency**. Credit in EUR can only be applied to EUR invoices; credit in USD only to USD invoices. There is no automatic currency conversion. - The balance can never go below zero. - Every change is written as an entry in an append-only history with a reason: `credit_note` (credit added), `applied` (used on an invoice) or `refunded` (paid back later). Entries are never edited or deleted, so you can always see how a balance was built. - The balance and its entries are updated together in one step, so they always agree. Client credit is tracked per business. A client in one business has no access to credit kept by another business in your account. ## Apply credit to an invoice Credit does not apply itself silently. You apply it to an invoice, which reduces that invoice's balance by the credited amount. Open the invoice Open the client's next invoice in the same currency as the credit. Apply client credit Choose to apply the client's available credit in that currency. Check the new balance The invoice balance goes down by the amount applied. The credit balance goes down by the same amount, and an `applied` entry links the credit to this invoice. An invoice's balance is always its total minus what has been paid minus what has been credited. Applying client credit counts toward the credited part, so an invoice covered fully by credit and payments shows a zero balance. ## Through the API | Action | REST | Permission | | --- | --- | --- | | [Create a credit note](/api-reference/documents/create-credit-note) with `settlement: "client_credit"` | `POST /v1/documents/{id}/credit-notes` | `documents.credit` | | [Get a client's credit](/api-reference/clients/get-client-credit) | `GET /v1/clients/{id}/credit` | `clients.view` | | [Apply client credit](/api-reference/documents/apply-client-credit) | `POST /v1/documents/{id}/apply-credit` | `documents.edit` | Creating a credit note is destructive (it changes what a client owes), so the call needs `"confirm": true`. Without it you get `confirmation_required` and a preview of what would happen. Send an `Idempotency-Key` so a retry never creates a second credit note. ```bash Keep a credit as client credit curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/credit-notes" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: cn-inv-0042-credit" \ -d '{ "amount_minor": "20000", "settlement": "client_credit", "confirm": true }' ``` ```bash Check the client's credit curl "https://api.freeinvoiceautomation.com/v1/clients/0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60/credit" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ```json Client credit (response) { "data": [ { "currency": "EUR", "balance_minor": "20000" } ] } ``` Then apply it to the next invoice: ```bash Apply credit curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b6-1a2b-7c3d-8e4f-5a6b7c8d9e0f/apply-credit" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Idempotency-Key: apply-credit-inv-0051" ``` Over MCP, `create_credit_note` is a destructive tool: your assistant shows you what it is about to do and needs your confirmation. Applying credit is not on the MCP tool list; use the app, the API or the CLI (`freeinvoiceautomation doc apply-credit`, from the week of 25 January 2027; command names are proposed). ## What the client sees - The credit note is a document the client can open and download, marked as a credit note on the PDF, with its own number and a link to the invoice it credits. - When credit is applied, the next invoice's pay page shows the reduced amount due. - From January 2027, the [client portal](/clients/portal) shows the client's documents, credit notes and a statement, so they can follow how credit was added and used. ## Rules and edge cases - **Only paid invoices create credit.** On an unpaid invoice, a credit note simply lowers the balance. - **A credit note cannot exceed what can be credited.** If you try, you get `credit_exceeds_balance`. - **Voided invoices.** An invoice with recorded payments cannot be voided; you issue a credit note instead. A voided invoice accepts no further actions, including applied credit (`document_voided`). - **Same currency only.** Credit is held per currency; apply it to an invoice in that currency. - **Credit can be refunded later.** A `refunded` entry records credit that you later pay back to the client. - **Store refunds.** Refunds from the Shopify app and WordPress plugin create credit notes automatically and are recorded as refunded outside our system, not as client credit. ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `documents.credit` | Creating a credit note and choosing the settlement | Owner, Admin | | `documents.edit` | Applying client credit to an invoice | Owner, Admin, Editor | | `clients.view` | Seeing a client's credit balance | Owner, Admin, Editor, Viewer, Accountant | | `payments.refund` | Refunding instead of keeping credit | Owner, Admin | ## Errors | Code | When | | --- | --- | | `confirmation_required` | Creating a credit note without `"confirm": true` | | `credit_exceeds_balance` | The credit note is larger than what can be credited | | `document_voided` | The invoice is void | | `permission_denied` | Your role lacks `documents.credit` or `documents.edit` | | `idempotency_key_reused` | The same `Idempotency-Key` was sent with a different body | ## Related Credit notes Full or partial credit notes and how they link to invoices Refunds Send money back through Stripe or record a manual refund Clients and contacts Client records, contacts and defaults Client portal Where clients see their credit notes and statement Source: https://docs.freeinvoiceautomation.com/clients/client-credit --- # Client portal A passwordless portal where a client sees, pays and downloads every document one business has sent them. The client portal gives each client one place for everything a business has sent them: their documents, credit notes and a statement. Clients open it with a magic link sent to their email, so they never create an account or remember a password. It saves them from searching their inbox, and saves you from resending old invoices. ## How the portal works The portal lives at `https://freeinvoiceautomation.com/portal` and is themed with the business's branding. A portal session is always for **one business and one client**: 1. The client enters their email address on the portal sign-in page for your business. 2. If the address belongs to a client of that business, they receive a **single-use magic link** by email. 3. Opening the link starts a portal session limited to that one client of that one business. 4. The client browses, pays and downloads. They can sign out at any time, and the session also ends on its own after a while. The sign-in page always gives the same answer, whether or not the email matches a client. This stops anyone from using the portal to find out who your clients are. If the same person is a client of two businesses, they get two separate portals. One session never shows documents from another business or another client, even inside the same account. This is enforced by the database itself, not just by the page. ### Links and sessions | Item | Behaviour | | --- | --- | | Magic link | 256-bit random, stored only as a hash, single-use, short-lived (15 minutes is the proposed default) | | Portal session | Client-scoped, expires (24 hours is the proposed default) and ends on sign-out | | Requests for a link | Rate limited per email address and per IP address | Exact link and session lifetimes are platform settings and may change; nothing about them is hardcoded. ## What the client sees Documents Every document the business has sent the client, with its status (for example sent, partly paid, paid, overdue or void). Each document can be opened, paid through its pay page and downloaded as a PDF. Voided invoices show a clear "VOID" stamp and cannot be paid. Credit notes Credit notes issued to the client, each linked to the invoice it credits. Clients can see when a balance was reduced and why. Statement A statement of the client's account with this business: what was invoiced, paid and credited. Saved card From March 2027, if the client saved a card for [auto-charge](/payments/auto-charge), the portal shows it by brand and last 4 digits only, with a **Remove** button. Removing it detaches the card at Stripe and future automatic charges fall back to a normal pay link. Contracts From March 2027, contracts the client signed can be downloaded from the portal as sealed PDFs. See [Contracts and e-signature](/features/contracts). The portal is clean by design: - No analytics, ads, affiliates or sponsors, ever. - "Powered by Free Invoice Automation" is shown only for businesses on the Free plan. Team businesses show only their own brand. - Paying from the portal uses the same pay page as a pay link, so the same payment methods, partial payments and rules apply. An invoice is marked paid only when the payment is confirmed (a verified Stripe webhook or a payment you record), never because the client was redirected back. ## Give a client access You do not need to invite clients or switch anything on per client. Any client with an email address on their record can request a magic link. Check the client's email Open the client under **Clients** and make sure the email address is the one they use. Send them to the portal Share the portal sign-in page for your business with the client. They enter their email and receive the link. The client opens the link The link opens their portal straight away. It works once; a new visit later needs a new link. Pay links in your invoice emails keep working on their own. The portal is an extra place for clients who want to see everything together, not a replacement for pay links. ## Through the API The portal is built on public and client-scoped API routes, the same API the pages use. You would call these only if you build your own client-facing pages. | Action | REST | Release | | --- | --- | --- | | [Request a magic link](/api-reference/public/request-portal-link) | `POST /v1/public/portal/magic-link` | January 2027 | | [Open a portal session](/api-reference/public/open-portal-session) | `POST /v1/public/portal/session` | January 2027 | | [List documents](/api-reference/portal/portal-list-documents) | `GET /v1/portal/documents` | January 2027 | | [Get a document or its PDF](/api-reference/portal/portal-get-document) | `GET /v1/portal/documents/{id}` and `/pdf` | January 2027 | | [Get the statement](/api-reference/portal/portal-get-statement) | `GET /v1/portal/statement` | January 2027 | | [List saved cards](/api-reference/portal/portal-list-saved-payment-methods) | `GET /v1/portal/saved-payment-methods` | March 2027 | | [Remove a saved card](/api-reference/portal/portal-remove-saved-payment-method) | `DELETE /v1/portal/saved-payment-methods/{id}` | March 2027 | | [List signed contracts](/api-reference/portal/portal-list-contracts) | `GET /v1/portal/contracts` and `/{id}/pdf` | March 2027 | | [Sign out](/api-reference/portal/portal-end-session) | `POST /v1/portal/logout` | January 2027 | ```bash Request a magic link curl -X POST "https://api.freeinvoiceautomation.com/v1/public/portal/magic-link" \ -H "Content-Type: application/json" \ -d '{ "business_slug": "acme-studio", "email": "billing@client.example" }' ``` The magic-link route always answers `202 Accepted` with no body details, whether or not the email matches. The session route exchanges the token from the email for a client-scoped session cookie; the `/v1/portal/*` routes then read only that client's data. Portal routes are not on the MCP tool list. They authenticate with the client's portal session rather than an API key, because they act as the client, not as you. ## Rules and edge cases - **No passwords anywhere.** Clients never have a password. Losing a link only means asking for a new one. - **Used or expired links** show that the link is no longer valid and reveal no document data. - **Changed email.** Magic links go to the email address on the client record, so keep it current. - **Voided and credited invoices** show their state clearly, the same way the pay page does: "VOID" with no payment button, or the credit note and the reduced balance. - **Reminder opt-out** is separate from the portal. Clients opt out of reminders through the link in reminder emails. See [Reminders](/collections/reminders). ## Permissions Clients do not use permission keys; their access comes from the portal session. On your side: | Key | Why it matters | | --- | --- | | `clients.edit` | Needed to change the client's email address that the magic link is sent to | | `documents.send` | Needed to send the documents that later appear in the portal | ## Errors | Code | When | | --- | --- | | `token_invalid` | The magic link token is unknown | | `token_expired` | The magic link or session has expired; request a new link | | `token_revoked` | The token was cancelled | | `rate_limited` | Too many link requests; wait and try again (`Retry-After` says how long) | | `unauthenticated` | A `/v1/portal/*` call without a valid portal session | ## Related The pay page What clients see when they pay an invoice Clients and contacts Client records and the email used for the portal Saved cards and auto-charge Cards saved at Stripe, removable from the portal How we protect data Tokens, client scope and other safeguards Source: https://docs.freeinvoiceautomation.com/clients/portal --- # Import and export What you can bring in and take out today: the full account export, saved items as CSV, generator invoices, and client data through the API. You should never be locked in. This page explains every way to get data into Free Invoice Automation and out again, and is clear about what exists. In short: you can export **everything** in your account at any time, import and export your **saved items** as CSV, bring your **generator invoices** with you when you sign up, and read or write **clients** in bulk through the API. A dedicated CSV import or export screen for clients is not part of the product. ## What exists | Data | Import | Export | | --- | --- | --- | | Whole account (all businesses, clients, documents, payments, files) | No | Yes: **Export everything** (JSON, PDFs and CSV) | | Saved items (products and services) | Yes: CSV | Yes: CSV | | Invoices made in the free generator before sign-up | Yes: moved in automatically, once, on sign-up | Included in the account export | | Clients | Through the API (`upsert_client`), one request per client | In the account export, or through the API (`list_clients`) | | Reports (from the week of 25 January 2027) | No | CSV or PDF per report | | Store customers and orders (WordPress from January 2027, Shopify from February 2027) | Created automatically by the store connector | Included in the account export | ## Export everything **Export everything** produces a complete copy of your account: JSON data, the PDFs of your documents, and CSV files. It covers every business in the account. Use it before you delete anything, to keep your own tax records, or simply to have a copy. Data export and delete"> Only the account Owner can start an export. A request from any other member, including an Admin, is refused. Start the export Choose **Export everything**. The export is prepared in the background, so you can keep working. Download it When the export is ready, download it from the same page. Deleting your account deletes your documents and payments with it. Your clients and tax authorities may expect you to keep invoices for years, so export first. See [Delete your account](/account/delete). Through the API, the Owner (or an API key created by the Owner with the `account.export` scope) starts an export with `POST /v1/account/exports`, which returns `202` and an export id, then checks it with `GET /v1/account/exports/{id}` until it is ready. See [Export your account](/api-reference/account/export-account) and [Export your data](/account/export). ```bash Start an account export curl -X POST "https://api.freeinvoiceautomation.com/v1/account/exports" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Idempotency-Key: export-2027-01-31" ``` ## Saved items as CSV Your list of products and services can be moved in and out as CSV, which is the quickest way to bring a price list from another tool. - **Import:** each valid row becomes an item. Rows that cannot be imported are listed with the reason, so you can fix and re-import them. CSV files up to 5 MB are accepted (proposed limit). - **Export:** downloads a CSV with every item field: name, description, SKU, unit, price, currency and default tax rate. - Items are never deleted, only archived, so old documents always keep their lines. Each document line also keeps its own copy of the item values. | Action | REST | Permission | | --- | --- | --- | | [Import items](/api-reference/items/import-items) | `POST /v1/items/import` | `items.manage` | | [Export items](/api-reference/items/export-items) | `GET /v1/items/export` | `items.view` | See [Saved items](/invoicing/saved-items) for the full item model. When you open an exported CSV in a spreadsheet, cells that start with `=`, `+`, `-` or `@` are prefixed so the spreadsheet does not run them as formulas (proposed safeguard). ## Invoices from the free generator If you made invoices in the [free invoice generator](/generator) before you had an account, they live in your browser only. When you create an account, they move into it once: - The import happens one time, right after sign-up, and is safe to retry: the same invoice is never imported twice. - Imported invoices keep the number you already gave them, so a PDF you sent earlier still matches. They do not use up numbers from your business's own sequence; new invoices start your sequence as normal. ## Clients through the API There is no CSV upload screen for clients. To move a client list in from another tool, use the API: send one [create or update a client](/api-reference/clients/upsert-client) request per row. Because `POST /v1/clients` with an existing `id` updates that client, a script can be re-run safely, and an `Idempotency-Key` per row protects you against duplicates when a request is retried. ```bash Create a client curl -X POST "https://api.freeinvoiceautomation.com/v1/clients" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: import-clients-row-17" \ -d '{ "name": "Northwind Traders", "email": "ap@northwind.example", "default_currency": "GBP" }' ``` ```bash List clients (one page) curl "https://api.freeinvoiceautomation.com/v1/clients?limit=200" \ -H "Authorization: Bearer $FIA_API_KEY" ``` To read all clients, follow `next_cursor` until `has_more` is `false`; see [Pagination and filters](/api-reference/pagination). From the week of 25 January 2027 the CLI wraps the same calls (`freeinvoiceautomation client upsert` and `client list`; command names are proposed). Your AI assistant can also add clients one by one with the MCP tool `upsert_client`, for example from a list you paste into the chat. Bulk scripts count toward your API rate limit and, on the Free plan, the monthly fair-use limits. See [Rate limits](/api-reference/rate-limits) and [Fair-use limits](/billing/fair-use). ## Store connectors The [WordPress and WooCommerce plugin](/integrations/woocommerce) and the [Shopify app](/integrations/shopify) create invoices, clients and payments from store orders through the same API. When Shopify asks for a customer's data or for it to be removed, the app passes that request on: the customer's client data is exported to the merchant, or redacted except what tax law requires on documents already issued. ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `account.export` | Export everything | Owner only | | `items.manage` | Importing items | Owner, Admin, Editor | | `items.view` | Exporting items | Owner, Admin, Editor, Viewer, Accountant | | `clients.create` / `clients.edit` | Creating or updating clients through the API | Owner, Admin, Editor | | `reports.export` | Exporting reports as CSV or PDF | Owner, Admin, Editor, Accountant | ## Errors | Code | When | | --- | --- | | `permission_denied` | You are not the Owner (account export) or your role lacks the items or clients key | | `validation_failed` | A client or item field is invalid; `details.issues` lists each field | | `idempotency_key_reused` | The same key was sent with a different body; use one key per row | | `rate_limited` | Too many requests in a minute; wait for `Retry-After` | | `plan_limit_reached` | A Free fair-use limit was reached | ## Related Export your data Everything about the full account export Saved items Products and services, CSV import and archive Free invoice generator Make invoices without an account; they move in when you sign up Clients and contacts The client record and its API Source: https://docs.freeinvoiceautomation.com/clients/import-export --- # How payments work Payment methods, how money reaches you, and the one rule that decides when an invoice is marked paid. Free Invoice Automation helps your clients pay you, but the money never passes through us. Card payments go straight to your own Stripe account, bank transfers go to your own bank, and local payments go to your own wallet. We never hold your funds and never take a percentage of an invoice payment. This page explains the payment methods, what a client sees, and how an invoice moves to paid. ## Payment methods (rails) Each way of getting paid is called a **rail**. You connect the rails you want for each business, then choose which ones each invoice accepts. There are three kinds: | Kind | How it works | Examples | | --- | --- | --- | | **Gateway** | The client pays on the provider's hosted checkout page; the provider tells us when the payment succeeds | [Stripe](/payments/stripe), [Whop](/payments/whop) | | **Instruction** | The pay page shows your details; the client pays outside our system and you record the payment | [Bank transfer](/payments/bank-transfer), [local QR payments](/payments/qr-rails) | | **Link** | A payment link to another service | Reserved for future rails | ### What is available when | Rail | Available | | --- | --- | | Stripe (card payments through Stripe Connect) | Launch, 1 January 2027 | | Bank transfer with manual payment record | Launch, 1 January 2027 | | Local QR payments: UPI, Raast, JazzCash, Easypaisa | Week of 25 January 2027 | | Whop | February 2027 | | Auto-charge of a saved card (part of Stripe) | March 2027 | Other rails, such as PayPal, Razorpay, Paystack, Safepay, Wise, Payoneer, bKash, M-Pesa and PIX, are added when real users ask for them. Adding one changes nothing in the app, the pay page or the API: it simply appears as a new rail you can connect. ## How a client pays 1. You send an invoice by email, or share its pay link. Every sent invoice has a pay page at `freeinvoiceautomation.com/pay/...`. 2. The client opens the [pay page](/invoicing/pay-page). It shows the invoice in your branding, the amount due, and every payment method you accept for that invoice. 3. The client picks a method: - **Card (Stripe)**: they click **Pay by card** and pay on Stripe's hosted Checkout page. - **Bank transfer or a QR code**: they pay from their bank or wallet, then click **I have paid** and can upload a [proof of payment](/payments/payment-proofs). 4. The invoice moves to **paid**, or **partially paid** if only part was paid, once the payment is confirmed. Clients can pay part of an invoice when partial payments are allowed (the default). From the week of 25 January 2027, invoices can also carry a [deposit and instalments](/payments/schedules), and the pay page asks for the next amount due. ## When is an invoice paid? The paid rule An invoice's paid state changes only from one of three trusted sources: 1. A **verified webhook** from the payment provider (for example Stripe). 2. A **reconciliation** run that checks the provider directly. 3. A **payment recorded by a person** on your team, which is written to the audit log. Nothing else can mark an invoice paid. A client returning from checkout to a "thank you" page, or clicking **I have paid**, does not change the paid state. This protects you from losing money by mistake. If a client closes the browser after paying, the webhook still arrives and the invoice is marked paid. If someone visits a success URL without paying, nothing happens: the success page only says the payment is being confirmed. If a webhook is ever lost, a reconciliation job fetches the true state from the provider and corrects it. Each payment records where its paid state came from: | Source | Meaning | | --- | --- | | `webhook` | A verified event from the provider, such as Stripe | | `reconcile` | Found by a reconciliation run | | `manual` | Recorded by a team member; the person is always stored | | `connector` | A paid order from a verified Shopify or WooCommerce store channel | ## Balances Every invoice keeps three amounts, all in integer minor units of the invoice currency: - `amount_paid_minor`: the sum of payments received. - `amount_credited_minor`: the sum of [credit notes](/invoicing/credit-notes) and applied [client credit](/clients/client-credit). - `balance_minor`: `total_minor - amount_paid_minor - amount_credited_minor`. ```json Payment status (response) { "data": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "status": "partially_paid", "currency": "USD", "total_minor": "120000", "amount_paid_minor": "50000", "amount_credited_minor": "0", "balance_minor": "70000" } } ``` ## Set up payment methods Payment methods"> Payment methods are set per business. Pick the business in the top bar first. Connect a rail Connect Stripe in one click, or add your bank details. Connecting a rail asks you to sign in again, because it decides where money goes. Choose order and defaults Turn methods on or off and set the order they appear in on the pay page. New invoices accept every enabled method that fits them. Adjust per client or invoice A client or a single invoice can accept a narrower set of methods. QR rails appear only on invoices in their own currency (PKR or INR). ## Through the API and MCP | Action | REST | MCP tool | Permission | | --- | --- | --- | --- | | [Get payment status](/api-reference/payments/get-payment-status) | `GET /v1/documents/{id}/payment-status` | `get_payment_status` | `payments.view` | | [Record a payment](/api-reference/payments/record-payment) | `POST /v1/payments` | `record_payment` | `payments.record` | | [List payments](/api-reference/payments/list-payments) | `GET /v1/payments` | none | `payments.view` | | [List rails](/api-reference/connections/list-rails) | `GET /v1/rails` | none | `rails.manage` | | [Create a connection](/api-reference/connections/create-connection) | `POST /v1/connections` | none | `rails.manage` | | [Set payment methods](/api-reference/connections/set-payment-methods) | `PUT /v1/businesses/{id}/payment-methods` | none | `rails.manage` | Your systems can follow payments with the `payment.succeeded`, `payment.recorded`, `payment.refunded`, `payment_proof.submitted`, `document.paid` and `document.partially_paid` [webhook events](/api-reference/webhook-events). ## Rules - **No cut, no holding.** We never take a percentage of an invoice payment and never hold money for you. Provider fees (for example Stripe's) are between you and the provider. - **No card data on our servers.** Card details are entered only on Stripe's hosted pages. - **Voided invoices cannot be paid.** Voiding an invoice cancels its open payment links and the pay page shows "VOID". - **No ads.** Pay pages never show ads, affiliates, sponsors or analytics. ## Permissions | Key | Lets a member | Default roles | | --- | --- | --- | | `payments.view` | See payments, proofs and payment status | Owner, Admin, Editor, Viewer, Accountant | | `payments.record` | Record payments, accept or reject proofs | Owner, Admin, Editor | | `payments.refund` | Refund payments | Owner, Admin | | `payments.auto_charge` | Turn on auto-charge (March 2027) | Owner, Admin | | `rails.manage` | Connect rails and choose payment methods | Owner, Admin | ## Errors | Code | When | | --- | --- | | `rail_not_connected` | No active connection for the chosen method | | `rail_capability_missing` | The rail cannot do this, for example a refund on bank transfer | | `currency_mismatch` | A QR rail on an invoice that is not in PKR or INR | | `fresh_signin_required` | Connecting a rail needs a fresh sign-in | | `upstream_error` | The payment provider failed; try again | ## Related Stripe Card payments on your own Stripe account Bank transfer Show your bank details and record transfers Record a payment Mark money received outside the system The pay page What your client sees Source: https://docs.freeinvoiceautomation.com/payments/overview --- # Stripe Take card payments on your own Stripe account through Stripe Connect, with hosted Checkout and refunds from the app. Stripe lets your clients pay invoices by card. You connect **your own** Stripe account to a business through Stripe Connect (Standard accounts), and each invoice gets a hosted Stripe Checkout page. Money goes straight from your client to your Stripe account and then to your bank on Stripe's normal schedule. We never hold the money and never take a percentage of it. ## How it works - **Your account, your money.** Stripe Connect with Standard accounts means the Stripe account is yours: you sign in to Stripe directly, see your payments there, and Stripe pays out to you. Free Invoice Automation is connected to it so it can create checkouts and read payment events. - **Hosted Checkout only.** Card details are typed only on Stripe's own Checkout page. They never reach our servers, which keeps your business within the simplest card-security scope (PCI SAQ-A). - **Webhooks mark invoices paid.** When a payment succeeds, Stripe sends a signed event. We verify it, store it, process it, and then mark the invoice paid or partially paid. A client being redirected back to us does not mark anything paid. - **Reconciliation as a safety net.** If an event is ever lost, a reconciliation run asks Stripe for the true state and corrects the invoice. - **Fees.** Stripe charges its own fees on your Stripe account. We record the fee on the payment where Stripe reports it, so you can see it, but we add nothing. ## Connect Stripe Payment methods"> Pick the business that should take card payments. Each business connects its own Stripe account. Connect Stripe Choose to connect Stripe. You are asked to sign in again first, because this decides where money is paid. Finish on Stripe Stripe opens its own onboarding. Sign in to your existing Stripe account or create one, then return to the app. Check the connection The connection shows as active once it is verified. Stripe now appears as a payment method on new invoices for this business. Connections are checked every night. If a connection breaks (for example you disconnected the app inside Stripe), the business shows a warning and the connection status becomes `failing` or `disconnected` until you fix it. | Connection status | Meaning | | --- | --- | | `pending` | Onboarding started but not finished | | `active` | Ready to take payments | | `failing` | The last check failed; see the error shown on the connection | | `disconnected` | The connection was removed or Stripe revoked access | ## What the client sees 1. On the [pay page](/invoicing/pay-page), the client clicks **Pay by card**. 2. They are sent to Stripe Checkout for the amount due (or, when partial payments are allowed, the amount they chose). 3. After paying they return to a page that says the payment is being confirmed. 4. As soon as Stripe's webhook is verified, the invoice is marked paid and the pay page shows the new balance. From March 2027, Checkout can also offer **Save this card for future invoices from** your business name. See [Saved cards and auto-charge](/payments/auto-charge). ## Through the API | Action | REST | Permission | | --- | --- | --- | | [Start Stripe Connect onboarding](/api-reference/connections/start-stripe-connect) | `POST /v1/connections/stripe/onboarding` | `rails.manage` (fresh sign-in) | | [List connections](/api-reference/connections/list-connections) | `GET /v1/connections` | `rails.manage` | | [Verify a connection](/api-reference/connections/verify-connection) | `POST /v1/connections/{id}/verify` | `rails.manage` | | [Delete a connection](/api-reference/connections/delete-connection) | `DELETE /v1/connections/{id}` | `rails.manage` | | [Start a checkout](/api-reference/public/start-checkout) (pay page) | `POST /v1/public/pay/{token}/checkout` | pay token | | [Refund a payment](/api-reference/payments/refund-payment) | `POST /v1/payments/{id}/refunds` | `payments.refund` | Starting onboarding needs a fresh sign-in, so it is done from a web session rather than with an API key. The pay page calls the checkout route with the pay token; it returns a hosted Stripe Checkout URL and never marks anything paid. ```json Start a checkout (pay page request) { "connection_id": "0193f2a9-3c4d-7e5f-8a6b-7c8d9e0f1a2b", "amount_minor": "50000" } ``` To check whether an invoice is paid, read its payment status or listen for webhooks: ```bash Payment status curl "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/payment-status" \ -H "Authorization: Bearer $FIA_API_KEY" ``` Over MCP, ask your assistant with `get_payment_status`. Outgoing [webhook events](/api-reference/webhook-events) include `payment.succeeded`, `document.paid`, `document.partially_paid` and `payment.refunded`. ### Stripe events we act on Stripe sends events for all connected accounts to one endpoint, and each event is matched to your connection by its Stripe account id. Each event is processed once, even if Stripe sends it again. The planned list covers completed, expired and failed Checkout sessions, succeeded and failed payment intents, refunds (`charge.refunded`), disputes (`charge.dispute.created`), account updates and deauthorisation, and, from March 2027, saved-card events. ## Refunds You can refund a Stripe payment from the app, without opening Stripe. Refunds usually start from a [credit note](/invoicing/credit-notes) with the **refund** settlement, or directly on the payment. The refund is created on your connected account and recorded when Stripe confirms it. See [Refunds](/payments/refunds). ## Rules and edge cases - **One Stripe account per business connection.** Different businesses can connect different Stripe accounts. - **Currency.** Stripe takes payments in the currencies Stripe supports for your account. - **Partial payments** are allowed by default; the pay page lets the client pay less than the balance. - **Voided invoices** have their open payment links cancelled; a client cannot start a new checkout. - **Disconnecting.** Deleting the connection is destructive and asks for confirmation. Invoices already paid keep their payments. - **Not our billing.** This Stripe connection is for your clients paying you. Your own Team plan is billed separately. ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `rails.manage` | Connecting, verifying and removing Stripe | Owner, Admin | | `payments.view` | Seeing Stripe payments | Owner, Admin, Editor, Viewer, Accountant | | `payments.refund` | Refunding Stripe payments | Owner, Admin | ## Errors | Code | When | | --- | --- | | `fresh_signin_required` | Connecting Stripe without a recent sign-in | | `rail_not_connected` | The invoice offers Stripe but the connection is not active | | `connection_failed` | Verifying the connection failed | | `upstream_error` | Stripe returned an error; try again | | `not_configured` | Card payments are temporarily unavailable on the platform | | `token_revoked` | The pay link was cancelled, for example because the invoice was voided | ## Related How payments work Rails, balances and the paid rule Refunds Refund through Stripe from the app Saved cards and auto-charge Charge a saved card on the due date The pay page What your client sees before Checkout Source: https://docs.freeinvoiceautomation.com/payments/stripe --- # Bank transfer Show your bank details on every invoice, let clients tell you when they have paid, and record the transfer yourself. Bank transfer works in every country and every currency. You add your bank details to a business once, and the pay page of every invoice shows them. The client pays from their own bank, clicks **I have paid**, and can upload a proof. You check your bank, record the payment, and the invoice moves to paid. No payment provider sits in between, so there is nothing to connect and no fee from us. ## How it works Bank transfer is an **instruction** rail: we show your details, but the money moves outside our system, from your client's bank to yours. Because we cannot see that money arrive, a person on your team confirms it: 1. The pay page shows your bank details and the amount due. 2. The client pays by bank transfer. 3. The client clicks **I have paid** and may upload a proof, such as a transfer receipt. 4. You are notified. You check your bank account. 5. You [record the payment](/payments/recording) with the amount, date and reference. The invoice moves to **paid** or **partially paid**, and an audit entry records who confirmed it. **I have paid** never marks an invoice paid on its own. A client's claim, even with a proof attached, is only a notice to you. The invoice changes only when you record the payment. This protects you from forged receipts. ## What you can enter Bank details are flexible because banking differs between countries: | Field type | Examples | | --- | --- | | Structured fields | IBAN, SWIFT/BIC, and local fields for your country | | Free text | Anything else your clients need: account name, bank address, a reference to quote, instructions in your own words | These details are not secret: they are shown on pay pages to anyone who has the pay link, just as they would be printed on a paper invoice. Do not put passwords or private notes in them. ## Set up bank transfer Payment methods"> Choose the business that should receive transfers. Each business has its own bank details. Add bank transfer Add a bank transfer method and fill in the structured fields and free text. Give it a clear label, for example "EUR account" or "Local bank". Confirm with a fresh sign-in Adding or changing a payment method asks you to sign in again, because it decides where clients send money. Turn it on Enable the method and set its position on the pay page. New invoices for this business now show your bank details. You can add more than one bank transfer method, for example one account per currency. Use per-client or per-invoice accepted methods so each client sees only the account that fits them. ## What the client sees On the [pay page](/invoicing/pay-page), the client sees your bank details as you entered them, with the amount due and the invoice number. Below them is an **I have paid** button. When they click it they can: - enter the amount they paid and the date they paid it, - add a short note, - upload a proof of payment (PDF, PNG or JPEG). We then notify the business. The invoice stays unpaid until you record the payment. See [Payment proofs](/payments/payment-proofs) for what happens to the proof. ## Through the API Create the bank transfer connection with the rail's connection schema, then enable it as a payment method: | Action | REST | Permission | | --- | --- | --- | | [List rails](/api-reference/connections/list-rails) (see the bank transfer fields) | `GET /v1/rails` | `rails.manage` | | [Create a connection](/api-reference/connections/create-connection) | `POST /v1/connections` | `rails.manage` (fresh sign-in) | | [Update a connection](/api-reference/connections/update-connection) | `PATCH /v1/connections/{id}` | `rails.manage` | | [Set payment methods](/api-reference/connections/set-payment-methods) | `PUT /v1/businesses/{id}/payment-methods` | `rails.manage` | | [Record a payment](/api-reference/payments/record-payment) | `POST /v1/payments` | `payments.record` | | [Accept a payment proof](/api-reference/payments/accept-payment-proof) | `POST /v1/payment-proofs/{id}/accept` | `payments.record` | Because creating a rail connection needs a fresh sign-in, set up bank transfer in the app. Recording payments works with an API key or over MCP: ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/payments" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pay-inv-0042-transfer" \ -d '{ "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "amount_minor": "120000", "currency": "EUR", "received_at": "2027-01-18T00:00:00Z", "reference": "SEPA 7781-2201" }' ``` ```json MCP tool call { "name": "record_payment", "arguments": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "amount_minor": "120000", "currency": "EUR", "reference": "SEPA 7781-2201" } } ``` ## Rules and edge cases - **Any country, any currency.** Bank transfer has no currency restriction. - **Partial transfers.** Record exactly what arrived. The invoice shows partially paid until the balance reaches zero. - **Refunds.** Bank transfer cannot send money back for you. If you refund a client, pay them from your bank and record the refund manually. See [Refunds](/payments/refunds). - **Voided invoices.** Voiding cancels the pay link, so the bank details are no longer shown for that invoice. - **Reminders.** From January 2027, reminder sequences stop when the client reports payment with a proof, so they are not chased while you check your bank. See [Reminders](/collections/reminders). ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `rails.manage` | Adding or changing bank details | Owner, Admin | | `payments.record` | Recording payments, accepting or rejecting proofs | Owner, Admin, Editor | | `payments.view` | Seeing payments and proofs | Owner, Admin, Editor, Viewer, Accountant | ## Errors | Code | When | | --- | --- | | `fresh_signin_required` | Adding or changing bank details without a recent sign-in | | `validation_failed` | A bank field is invalid | | `rail_capability_missing` | Asking the rail to refund; record the refund manually instead | | `document_voided` | Recording a payment on a void invoice | ## Related Payment proofs Review "I have paid" notices and proofs Record a payment Confirm money you received Local QR payments UPI, Raast, JazzCash and Easypaisa How payments work Rails and the paid rule Source: https://docs.freeinvoiceautomation.com/payments/bank-transfer --- # Payment proofs How clients report a payment with \"I have paid\", and how you accept or reject their proof. When a client pays by bank transfer or a local QR code, the money moves outside our system. A payment proof is how the client tells you they have paid: they click **I have paid** on the pay page, enter what they paid, and can upload a receipt or screenshot. You review it, check your account, and then accept it (which records the payment) or reject it. A proof never marks an invoice paid on its own. ## How proofs work 1. On the [pay page](/invoicing/pay-page) of an invoice that accepts an instruction rail ([bank transfer](/payments/bank-transfer), or from the week of 25 January 2027 a [local QR rail](/payments/qr-rails)), the client clicks **I have paid**. 2. They can enter: - the amount they paid, - the date they paid, - a short note, - a proof file (PDF, PNG or JPEG). 3. A proof is created with the status `pending`, and the business is notified. 4. A member with `payments.record` reviews it: - **Accept**: a payment is recorded from the proof, the invoice balance goes down, and the invoice moves to paid or partially paid. The proof becomes `accepted` and is linked to the payment. - **Reject**: nothing is recorded. The proof becomes `rejected`. 5. Who reviewed the proof, and when, is stored with it and in the audit log. "I have paid" is a claim, not a payment. Anyone with the pay link could submit a proof, and a screenshot can be edited. Always check that the money arrived in your account before you accept. ### Proof statuses | Status | Meaning | | --- | --- | | `pending` | Submitted by the client, waiting for your review | | `accepted` | You confirmed it; a payment was recorded and linked | | `rejected` | You declined it; no payment was recorded | ## Review proofs in the app Open Payments In the sidebar, open **Payments** under **Get paid**. Pending proofs are listed with the invoice, client, claimed amount and date. Open the proof Look at the client's note and open the uploaded file. Files are stored privately and opened through short-lived links. Check your account Find the transfer in your bank or wallet. Compare the amount, date and reference. Accept or reject Accept to record the payment. Reject if you cannot find the money. If a different amount arrived than the client claimed, [record the payment](/payments/recording) for the amount that actually arrived. ## Through the API | Action | REST | Permission | | --- | --- | --- | | [List payment proofs](/api-reference/payments/list-payment-proofs) | `GET /v1/payment-proofs` | `payments.view` | | [Accept a payment proof](/api-reference/payments/accept-payment-proof) | `POST /v1/payment-proofs/{id}/accept` | `payments.record` | | [Reject a payment proof](/api-reference/payments/reject-payment-proof) | `POST /v1/payment-proofs/{id}/reject` | `payments.record` | | [Submit a proof](/api-reference/public/submit-payment-proof) (pay page) | `POST /v1/public/pay/{token}/proofs` | pay token | | [Upload a proof file](/api-reference/public/upload-payment-proof-file) (pay page) | `POST /v1/public/pay/{token}/uploads` | pay token | ```bash List pending proofs curl "https://api.freeinvoiceautomation.com/v1/payment-proofs?status=pending" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ```json Response { "data": [ { "id": "0193f2d0-7a8b-7c9d-8e0f-1a2b3c4d5e70", "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "claimed_amount_minor": "120000", "currency": "EUR", "claimed_paid_on": "2027-01-17", "client_note": "Paid from our main account, ref 7781-2201", "status": "pending", "payment_id": null } ], "next_cursor": null, "has_more": false } ``` ```bash Accept a proof curl -X POST "https://api.freeinvoiceautomation.com/v1/payment-proofs/0193f2d0-7a8b-7c9d-8e0f-1a2b3c4d5e70/accept" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Idempotency-Key: accept-proof-0193f2d0" ``` On the pay page, the client side works in two steps: the upload route returns a signed upload URL for the file, then the proof route creates the proof with `claimed_amount_minor`, `claimed_paid_on`, `note` and the uploaded `attachment_id`, all optional. Proofs are not on the MCP tool list; your assistant can still record the payment itself with `record_payment` after you have checked your account. Your systems can react to new proofs with the `payment_proof.submitted` [webhook event](/api-reference/webhook-events), and to the accepted payment with `payment.recorded`. ## What the client sees - An **I have paid** button under the payment details on the pay page. - A short form for amount, date, note and file. - The invoice stays unpaid on the pay page until you accept the proof or record the payment. Once you do, the pay page shows the new balance. ## Rules and limits | Rule | Value | | --- | --- | | File types | PDF, PNG, JPEG | | Maximum file size | 10 MB (proposed) | | Submissions | 5 per hour per pay link (proposed) | | File handling | Images are re-encoded and PDFs checked by their content; files are private and never public URLs | - **Paid state.** A proof changes nothing about paid state until a person accepts it. Accepting is the audited human confirmation that the paid rule requires. - **Reminders stop.** From January 2027, a reminder sequence stops when the client reports payment with a proof, so you do not chase a client who says they paid. If you reject the proof, follow up yourself or [send a reminder](/collections/reminders). - **Stripe payments do not need proofs.** Card payments are confirmed by Stripe directly. - **Voided invoices** cannot receive new proofs: the pay link is cancelled when you void. ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `payments.view` | Seeing proofs | Owner, Admin, Editor, Viewer, Accountant | | `payments.record` | Accepting or rejecting proofs | Owner, Admin, Editor | ## Errors | Code | When | | --- | --- | | `permission_denied` | Your role lacks `payments.record` | | `conflict` | The proof was already accepted or rejected | | `rate_limited` | Too many proofs submitted on one pay link | | `token_revoked` / `token_expired` | The pay link is no longer valid | | `validation_failed` | The file type or size is not allowed | ## Related Bank transfer Where most proofs come from Record a payment Record money that arrived without a proof Local QR payments Scan-to-pay rails that use the same flow The pay page Where clients click "I have paid" Source: https://docs.freeinvoiceautomation.com/payments/payment-proofs --- # Record a payment Mark money you received outside the system, by transfer, cash, cheque or a QR payment, so the invoice shows the right balance. Recording a payment tells Free Invoice Automation that money arrived for an invoice by a route we cannot see: a bank transfer, a local QR payment, cash, a cheque, or anything else. You enter the amount, date and reference, and the invoice balance goes down. It is the "audited human confirmation" in our paid rule: a named person confirms the money arrived, and the record is kept in the audit log. ## When to record a payment | Situation | Do this | | --- | --- | | A client paid by bank transfer and sent a proof | [Accept the proof](/payments/payment-proofs), which records the payment for you | | A client paid by bank transfer, QR code, cash or cheque with no proof | Record the payment yourself | | A client paid by card through Stripe | Nothing: Stripe's verified webhook records it automatically | | A client paid on Whop (from February 2027) | Nothing: Whop's verified webhook records it automatically | | A store order was paid in Shopify or WooCommerce | Nothing: the store connector records it through its verified channel | ## What a payment record holds | Field | Notes | | --- | --- | | Invoice | The document the money is for | | Amount | Integer minor units in the invoice currency; must be greater than zero | | Date received | When the money arrived | | Reference | For example the bank reference, so you can find it later | | Proof | Optional: a proof file, or the client's proof if one was submitted | | Notes | Optional internal notes | | Recorded by | Set automatically to you; every manual payment has a named person | The payment's source is `manual`. Its rail is the instruction rail it came through, or `manual` when it was not through a connected rail. ## Record a payment in the app Open the invoice Open the invoice from **Invoices** or **Payments**. Only invoices that are not void can take payments. Record a payment Choose to record a payment. Enter the amount that actually arrived, the date, and a reference. Attach a proof (optional) Attach a proof file if you have one, for example a bank statement extract. Save The balance goes down at once. The invoice moves to **partially paid**, or to **paid** when the balance reaches zero. The invoice timeline and the audit log show who recorded it. Record what really arrived, not what the invoice says. If a client paid 1,000.00 of a 1,200.00 invoice, record 1,000.00. The remaining 200.00 stays open and reminders keep working on it. ## Through the API and MCP | Action | REST | MCP tool | Permission | | --- | --- | --- | --- | | [Record a payment](/api-reference/payments/record-payment) | `POST /v1/payments` | `record_payment` | `payments.record` | | [Get payment status](/api-reference/payments/get-payment-status) | `GET /v1/documents/{id}/payment-status` | `get_payment_status` | `payments.view` | | [List payments](/api-reference/payments/list-payments) | `GET /v1/payments` | none | `payments.view` | | [Get a payment](/api-reference/payments/get-payment) | `GET /v1/payments/{id}` | none | `payments.view` | | [Delete a payment](/api-reference/payments/delete-payment) | `DELETE /v1/payments/{id}` | none | `payments.record` | An `Idempotency-Key` is required when recording a payment with an API key or over MCP, so a retried request never records the same money twice. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/payments" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: pay-inv-0042-cash" \ -d '{ "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "amount_minor": "100000", "currency": "USD", "received_at": "2027-01-20T00:00:00Z", "reference": "Cheque 004512", "notes": "Handed over at the client meeting" }' ``` ```json MCP tool call { "name": "record_payment", "arguments": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "amount_minor": "100000", "currency": "USD", "reference": "Cheque 004512" } } ``` ```json Response (201) { "data": { "id": "0193f2c7-2b3c-7d4e-9f50-6a7b8c9d0e1f", "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "rail": "manual", "source": "manual", "amount_minor": "100000", "currency": "USD", "received_at": "2027-01-20T00:00:00Z", "reference": "Cheque 004512", "status": "succeeded", "refunded_amount_minor": "0" } } ``` From the week of 25 January 2027 the CLI has the same action (`freeinvoiceautomation payment record`; command names are proposed). When you ask an AI assistant to record a payment, it calls `record_payment` under your permissions; the record still names you as the person who confirmed it. A recorded payment sends the `payment.recorded` [webhook event](/api-reference/webhook-events), plus `document.paid` or `document.partially_paid`. ## Correcting a mistake Payments are never hard-deleted. If you recorded a payment by mistake (wrong invoice, wrong amount), delete it: this is a soft delete, allowed only for manual records, and it is marked destructive, so the app asks you to confirm and the API needs `"confirm": true`. The invoice balance goes back up, and the record is kept in the database rather than erased, with the deletion in the audit log. Payments that came from Stripe, Whop or a store cannot be deleted this way, because they reflect money the provider confirmed. To give that money back, use a [refund](/payments/refunds). ## Rules and edge cases - **Partial payments** are recorded as they arrive; several payments can add up to the total. - **From the week of 25 January 2027**, payments on invoices with a [payment schedule](/payments/schedules) are allocated to instalments, oldest first. - **Void invoices** cannot take payments. An invoice that already has payments cannot be voided; issue a [credit note](/invoicing/credit-notes) instead. - **Currency.** A payment is recorded in the invoice's currency. - **Reminders stop** when the invoice is paid (from January 2027). Late fees are worked out on the overdue balance, so recording what arrived keeps them fair. ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `payments.record` | Recording and deleting manual payments | Owner, Admin, Editor | | `payments.view` | Seeing payments and payment status | Owner, Admin, Editor, Viewer, Accountant | ## Errors | Code | When | | --- | --- | | `validation_failed` | The amount is zero or negative, or a field is missing | | `document_voided` | The invoice is void | | `idempotency_key_reused` | The same key was used with a different body | | `idempotency_in_progress` | The same request is still running | | `confirmation_required` | Deleting a payment without `"confirm": true` | | `permission_denied` | Your role lacks `payments.record` | ## Related Payment proofs Accept a client's proof to record the payment Refunds Give money back How payments work The paid rule and balances Audit log See who recorded what Source: https://docs.freeinvoiceautomation.com/payments/recording --- # Refunds Give money back to a client through Stripe from the app, or record a refund you made yourself, with a credit note on record. A refund sends money back to a client for a payment they made. If the payment came through Stripe, you can refund it from the app and Stripe returns the money to the client's card. If the payment came by bank transfer or another route we cannot reach, you pay the client back yourself and record the refund, so your records stay correct. Either way, a [credit note](/invoicing/credit-notes) usually explains why, and every refund is audited. ## Two ways to refund | The payment came through | How the refund works | | --- | --- | | **Stripe** | Refunded through the Stripe rail, on your connected Stripe account. It is recorded when Stripe confirms it. | | **Bank transfer, QR rails, cash or anything recorded manually** | You return the money yourself (for example by bank transfer), then record the refund manually with an audit entry. | The difference comes from what each rail can do. Stripe supports refunds; instruction rails such as bank transfer only show your details and cannot move money. If you ask a rail without refunds to refund, the API answers `rail_capability_missing` so you know to record it manually. ## Refund with a credit note The usual path is a credit note, because it keeps your documents and numbering clean: the invoice stays as it was, and the credit note (with its own number, for example `CN-2027-0004`) shows what was given back. Open the paid invoice Open the invoice you want to refund, fully or in part. Create a credit note Create a full credit note, or a partial one by picking lines or entering an amount. Choose Refund Because the invoice was paid, you choose what happens to the money: **refund** or **keep as client credit**. Choose **refund**. Confirm Refunds move money, so you confirm before anything happens. For a Stripe payment the refund is sent to Stripe; for other payments you record the refund you made. If you would rather not pay the money back, choose **keep as client credit** instead. See [Client credit](/clients/client-credit). ## Refund a payment directly You can also refund a single payment from the payment itself, for example after a duplicate card payment. The same rules apply: through Stripe when the payment came from Stripe, recorded manually otherwise. ## What happens after a Stripe refund 1. The refund is created on your connected Stripe account. 2. Stripe processes it and sends a refund event. 3. When that event is verified, the refund is recorded against the original payment, and the [webhook event](/api-reference/webhook-events) `payment.refunded` is sent to your endpoints. 4. The client sees the money back on their card on Stripe's normal timeline. A refund is never recorded just because a request was sent: as with payments, we wait for Stripe's verified confirmation. We charge nothing for refunds; any fees are those of your own Stripe account. ### Payment states | Payment status | Meaning | | --- | --- | | `succeeded` | Paid, nothing refunded | | `partially_refunded` | Part of the payment was refunded | | `refunded` | The whole payment was refunded | Each payment keeps the total refunded so far, so you can refund in several steps up to the original amount. ## Through the API | Action | REST | MCP tool | Permission | | --- | --- | --- | --- | | [Create a credit note](/api-reference/documents/create-credit-note) with `settlement: "refund"` | `POST /v1/documents/{id}/credit-notes` | `create_credit_note` | `documents.credit` | | [Refund a payment](/api-reference/payments/refund-payment) | `POST /v1/payments/{id}/refunds` | none | `payments.refund` | | [Get a payment](/api-reference/payments/get-payment) | `GET /v1/payments/{id}` | none | `payments.view` | Both actions are destructive: they need `"confirm": true`, or they return `confirmation_required` with a preview. They also need an `Idempotency-Key`, so a retry never refunds twice. ```bash Credit note with refund curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/credit-notes" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: cn-inv-0042-refund" \ -d '{ "amount_minor": "30000", "settlement": "refund", "confirm": true }' ``` ```bash Refund a payment curl -X POST "https://api.freeinvoiceautomation.com/v1/payments/0193f2c7-2b3c-7d4e-9f50-6a7b8c9d0e1f/refunds" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: refund-0193f2c7-1" \ -d '{ "amount_minor": "30000", "confirm": true }' ``` Over MCP, `create_credit_note` is a destructive tool: your assistant shows a preview and waits for your confirmation, even if its key is allowed to send documents automatically. ## What the client sees - The credit note, as a document with its own number and a "CREDIT NOTE" PDF, linked to the original invoice. - The original invoice's pay page shows the credit note and the reduced balance. - From January 2027, both appear in the [client portal](/clients/portal). - For a Stripe refund, the money back on their card. ## Store refunds When a refund happens in your Shopify store (from February 2027) or WooCommerce store (from the week of 25 January 2027), the connector creates the credit note automatically, because the refund already happened in the store. It is recorded as refunded outside our system and audited with the store connection as the actor. ## Rules and edge cases - **You cannot refund more than was paid.** Refunds and credit notes are limited to what can be credited (`credit_exceeds_balance`). - **Voiding is not refunding.** An invoice with payments cannot be voided; issue a credit note. - **Disputes** (chargebacks) are handled in Stripe. Stripe tells us about them, but you respond to a dispute in your Stripe account. - **Manual refunds** do not move money. Record them only after you paid the client back. ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `payments.refund` | Refunding a payment | Owner, Admin | | `documents.credit` | Creating a credit note with a refund | Owner, Admin | | `payments.view` | Seeing refunds | Owner, Admin, Editor, Viewer, Accountant | ## Errors | Code | When | | --- | --- | | `confirmation_required` | The call did not include `"confirm": true`; the response has a preview | | `rail_capability_missing` | The rail cannot refund; record the refund manually | | `credit_exceeds_balance` | The amount is more than can be refunded or credited | | `upstream_error` | Stripe returned an error; nothing was recorded | | `permission_denied` | Your role lacks `payments.refund` or `documents.credit` | | `idempotency_key_reused` | The same key was sent with a different body | ## Related Credit notes Full and partial credit notes Client credit Keep the money as credit instead Stripe The rail that refunds from the app Record a payment Manual payment records Source: https://docs.freeinvoiceautomation.com/payments/refunds --- # Whop Let clients pay invoices through your own Whop account, with a Whop checkout link on each invoice and webhooks that mark it paid. Whop is a payment rail for creators and businesses that already sell on Whop. You connect your own Whop account to a business with an API key, and each invoice gets a Whop checkout link on its pay page. When Whop confirms the payment through a verified webhook, the invoice is marked paid. As with every rail, the money goes to your own account: we never hold it and never take a cut. ## How it works Whop is a **gateway** rail, like Stripe: 1. You connect your Whop account by entering its API key. The key is checked before the connection is saved. 2. On the [pay page](/invoicing/pay-page) of an invoice that accepts Whop, the client chooses Whop. 3. The pay page creates a Whop checkout link for the invoice and sends the client to it. 4. The client pays on Whop. 5. Whop sends a signed webhook to your connection's own webhook address. We verify the signature, store the event, process it, and mark the invoice paid. A client coming back from Whop's page does not mark anything paid. Only the verified webhook does (or a payment you record yourself). Whop here is a way for **your clients to pay you**. It is not used to pay for our Team plan, which is billed separately. ### Webhooks Whop events arrive at a webhook address that belongs to your connection: `https://api.freeinvoiceautomation.com/webhooks/whop/` followed by the connection id. Your Whop account must send its events there: either you register the address in your Whop account, or it is registered for you through Whop's API when you connect. We listen for Whop's payment succeeded, payment failed and payment refunded events, with the exact names Whop's API uses. A succeeded payment for the invoice's checkout records the payment and marks the invoice paid, or partially paid if it covered only part of the balance. A failed payment marks nothing paid. Every event is verified with your connection's webhook secret before anything else, and each event is processed only once even if Whop sends it again. ## Connect Whop Get your Whop API key In your Whop account, create an API key for the company you want to be paid into. Payment methods"> Choose the business that should take Whop payments and add Whop. You are asked to sign in again, because this decides where money goes. Enter the key Paste the API key and, if asked, the webhook secret. Both are stored sealed and are write-only: after saving, you only ever see the last four characters. Verify and enable The connection is verified with a test call. Once it is active, turn Whop on as a payment method and set where it appears on the pay page. Connections are checked every night. If your key stops working, the business shows a warning and the connection is marked as failing until you update it. ## Through the API Whop uses the same connection capabilities as every rail, with Whop's connection schema from the rails registry: | Action | REST | Permission | | --- | --- | --- | | [List rails](/api-reference/connections/list-rails) | `GET /v1/rails` | `rails.manage` | | [Create a connection](/api-reference/connections/create-connection) | `POST /v1/connections` | `rails.manage` (fresh sign-in) | | [Update a connection](/api-reference/connections/update-connection) | `PATCH /v1/connections/{id}` | `rails.manage` | | [Verify a connection](/api-reference/connections/verify-connection) | `POST /v1/connections/{id}/verify` | `rails.manage` | | [Delete a connection](/api-reference/connections/delete-connection) | `DELETE /v1/connections/{id}` | `rails.manage` | | [Start a checkout](/api-reference/public/start-checkout) (pay page) | `POST /v1/public/pay/{token}/checkout` | pay token | | [Get payment status](/api-reference/payments/get-payment-status) | `GET /v1/documents/{id}/payment-status` | `payments.view` | The checkout route takes the Whop connection id and the amount, returns the Whop checkout link, and never marks the invoice paid: ```json Start a checkout (pay page request) { "connection_id": "0193f2aa-4d5e-7f6a-8b7c-8d9e0f1a2b3c", "amount_minor": "4900" } ``` Your own systems can follow Whop payments through the `payment.succeeded` and `document.paid` [webhook events](/api-reference/webhook-events), the same events as Stripe payments. ## What the client sees - Whop listed as a payment method on the pay page, next to any other methods you accept. - A redirect to Whop's own checkout for the amount due. - After paying, a page saying the payment is being confirmed. The invoice shows paid as soon as Whop's webhook is verified. ## Rules and edge cases - **Your Whop account, your money.** Whop's own fees and payout rules apply to your Whop account. - **Currencies** are those Whop supports. - **Refunds.** The Whop rail's listed capabilities are a checkout link per invoice and webhooks. Refunding from the app is a Stripe capability; if a rail cannot refund, the API returns `rail_capability_missing` and you record the refund manually after refunding in Whop. See [Refunds](/payments/refunds). - **Voided invoices** have their payment links cancelled, so a client cannot start a new Whop checkout for them. - **Secrets** are never shown again after saving and never appear in logs or errors. ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `rails.manage` | Connecting, verifying and removing Whop | Owner, Admin | | `payments.view` | Seeing Whop payments | Owner, Admin, Editor, Viewer, Accountant | ## Errors | Code | When | | --- | --- | | `fresh_signin_required` | Connecting Whop without a recent sign-in | | `connection_failed` | The test call with your API key failed | | `rail_not_connected` | The invoice offers Whop but the connection is not active | | `upstream_error` | Whop returned an error; try again | | `webhook_signature_invalid` | An incoming event failed verification and was ignored | ## Related How payments work Rails and the paid rule Stripe The other gateway rail The pay page Where clients choose Whop Refunds Record refunds for rails without refunds Source: https://docs.freeinvoiceautomation.com/payments/whop --- # Local QR payments Show a UPI, Raast, JazzCash or Easypaisa payment QR code on your invoices so clients can pay by scanning with their banking or wallet app. Local QR rails put a payment QR code on your invoice. Your client scans it with their banking or wallet app and pays you directly, in seconds. Four rails are supported: **UPI** for invoices in Indian rupees, and **Raast**, **JazzCash** and **Easypaisa** for invoices in Pakistani rupees. They work like bank transfer: the money goes straight to your account, the client tells you they have paid, and you record the payment. ## The four rails | Rail | Currency | What you enter | The QR on the invoice | | --- | --- | --- | --- | | **UPI** | INR | Your UPI ID (VPA) and payee name | **Generated for each invoice** in the open `upi://pay` format, with the amount, currency INR and the invoice number as the note | | **Raast** | PKR | Your Raast ID (mobile number) or IBAN, account title and bank | Your **merchant Raast QR**, uploaded as an image from your bank or app | | **JazzCash** | PKR | Your mobile account or till number and account title | Your **merchant QR**, uploaded as an image | | **Easypaisa** | PKR | Your mobile account or till number and account title | Your **merchant QR**, uploaded as an image | UPI QR codes carry the exact amount and invoice number, so your client does not have to type anything. For Raast, JazzCash and Easypaisa, the QR is your own merchant code, so the client enters the amount shown on the invoice. A per-invoice Raast QR with the amount built in is planned only once a partner bank provides the State Bank of Pakistan's merchant QR format. ## Where the QR appears The QR code and your plain account details appear on: - the [pay page](/invoicing/pay-page), - the invoice [PDF](/invoicing/pdf), - the [share link](/invoicing/share-links). The plain details are always shown next to the QR, for clients who cannot scan (for example when they open the invoice on the same phone they would pay with). A QR rail is shown **only when the invoice currency matches the rail**: UPI on INR invoices, Raast, JazzCash and Easypaisa on PKR invoices. A USD or EUR invoice never shows these rails, even if they are enabled for the business. ## How a QR payment is confirmed QR rails are **instruction** rails. We show where to pay, but the payment happens in your client's app, outside our system. So: 1. The client scans and pays. 2. The client clicks **I have paid** on the pay page and can upload a screenshot or receipt as proof. 3. You check that the money arrived in your account. 4. You [accept the proof](/payments/payment-proofs) or [record the payment](/payments/recording). The invoice moves to paid or partially paid, with an audit entry. Scanning the code, or clicking **I have paid**, never marks the invoice paid by itself. ## Set up a QR rail Payment methods"> Choose the business. Businesses created in Pakistan have Raast, JazzCash and Easypaisa pre-selected, and businesses in India have UPI pre-selected; you still need to fill in your details. Enter your details For UPI, enter your UPI ID and payee name. For Raast, JazzCash or Easypaisa, enter your account details as listed above. Upload your merchant QR (Pakistan rails) Download or screenshot the merchant QR from your bank or wallet app and upload it. PNG, JPEG and WebP images up to 2 MB are accepted (proposed limit). The image is re-encoded and stored privately, like a logo. Confirm and enable Adding a payment method asks for a fresh sign-in. Then enable the rail. It appears on every new invoice in its currency. Scan your own invoice once with your phone before you send it to clients, to make sure the uploaded QR is sharp and points to your account. ## Through the API QR rails use the same connection capabilities as every other rail. For Raast, JazzCash and Easypaisa, upload the merchant QR image first, then pass its attachment id when you create the connection. | Action | REST | Permission | | --- | --- | --- | | [Upload a file](/api-reference/account/upload-file) (merchant QR image) | `POST /v1/files` | the permission of what the file is for | | [List rails](/api-reference/connections/list-rails) | `GET /v1/rails` | `rails.manage` | | [Create a connection](/api-reference/connections/create-connection) | `POST /v1/connections` | `rails.manage` (fresh sign-in) | | [Set payment methods](/api-reference/connections/set-payment-methods) | `PUT /v1/businesses/{id}/payment-methods` | `rails.manage` | | [Record a payment](/api-reference/payments/record-payment) | `POST /v1/payments` | `payments.record` | `GET /v1/rails` returns each rail's connection schema, so you can see exactly which fields UPI, Raast, JazzCash and Easypaisa expect. The details you enter (UPI ID and payee name, Raast ID, till numbers, account titles) are not secret: they are shown on pay pages. Recording the payment after you checked your account works with an API key or over MCP: ```json MCP tool call { "name": "record_payment", "arguments": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "amount_minor": "2500000", "currency": "PKR", "reference": "Raast TXN 88231" } } ``` Here `"2500000"` is 25,000.00 PKR in minor units. ## Rules and edge cases - **Currency must match.** If you try to accept a QR rail on an invoice in another currency, you get `currency_mismatch`. - **No refunds through QR rails.** To refund, pay the client back from your account and record the refund manually. See [Refunds](/payments/refunds). - **Partial payments** are recorded as they arrive, like bank transfers. - **Voided invoices** have their pay link cancelled, so the QR is no longer shown on the pay page for that invoice. - **No fees from us.** Your bank or wallet's own terms apply. - **Other local rails** (for example bKash, M-Pesa or PIX) are added when users ask for them. ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `rails.manage` | Adding QR rails and uploading merchant QR images | Owner, Admin | | `payments.record` | Recording QR payments and accepting proofs | Owner, Admin, Editor | | `payments.view` | Seeing payments and proofs | Owner, Admin, Editor, Viewer, Accountant | ## Errors | Code | When | | --- | --- | | `currency_mismatch` | The invoice is not in the rail's currency (PKR or INR) | | `fresh_signin_required` | Adding or changing a rail without a recent sign-in | | `validation_failed` | A field is missing, or the QR image has the wrong type or size | | `rail_not_connected` | The rail is offered but its connection is not active | ## Related Bank transfer The same flow without a QR code Payment proofs Review "I have paid" notices PDFs and templates Where the QR is printed How payments work Rails and the paid rule Source: https://docs.freeinvoiceautomation.com/payments/qr-rails --- # Deposits and schedules Ask for a deposit up front and split the rest into instalments, each with its own due date, reminders and late fees. A payment schedule splits one invoice or estimate into parts. You can ask for a **deposit** before you start work, then spread the rest into **instalments**, for example three monthly payments. Each part has its own due date, so reminders, late fees and (from March 2027) automatic card charges work part by part. The client always sees what is due now and can pay it in one click. ## How schedules work A schedule is attached to one invoice or estimate and is made of parts: | Part | What you set | | --- | --- | | **Deposit** | A percent of the total or a fixed amount, due **on acceptance** (of an estimate) or **on a date** | | **Instalments** | Equal splits such as 3 monthly payments, or custom dates and amounts | Every part has a label, an amount, a due date (or "on acceptance") and its own status. ### The parts always add up Amounts are worked out in integer minor units by the same money engine that computes your totals, and the parts always add up **exactly** to the document total. When a split does not divide evenly, the **last part absorbs the rounding**. For example, 1,000.00 USD in three equal instalments becomes 333.33 + 333.33 + 333.34. A schedule whose parts do not add up to the total is refused with `schedule_total_mismatch`. ### Instalment statuses | Status | Meaning | | --- | --- | | `pending` | Not yet due | | `due` | Due now | | `partially_paid` | Part of this instalment is paid | | `paid` | Fully paid | | `overdue` | Past its due date and not fully paid | | `void` | The document was voided | ## Paying a scheduled invoice - The [pay page](/invoicing/pay-page) and the [client portal](/clients/portal) show the whole schedule and ask for the **next amount due**. - The client may pay more than the next amount if they want to get ahead. - Every payment, by card or recorded by you, is **allocated to the parts oldest first**. A payment that covers the deposit and part of the first instalment marks the deposit paid and the first instalment partially paid. ## Deposits on estimates Add a deposit to an [estimate](/invoicing/estimates) to be paid when the client agrees. When the client accepts the estimate online, a **deposit invoice is created automatically** and linked to the estimate, ready to pay. From March 2027 the same happens when a client signs a [contract](/features/contracts) that has a deposit. ## Reminders, late fees and auto-charge per part Because each part has its own due date: - [Reminder sequences](/collections/reminders) run against each instalment's due date and amount. - [Late fees](/collections/late-fees) are worked out per instalment. - From March 2027, each instalment can be [auto-charged](/payments/auto-charge) to a client's saved card on its due date. ## Add a schedule in the app Open the invoice or estimate Open the document in the editor. The schedule is part of the form, next to lines, taxes and notes. Add a deposit Enter a percent or a fixed amount and choose when it is due: on acceptance or on a date. Add instalments Split the rest into equal parts with a regular interval, or enter custom dates and amounts. Check the parts Review each part's amount before you continue. The last part includes any rounding so the total matches exactly. Save or send Save the draft or send it. The pay page and the portal show the schedule. ## Through the API and MCP | Action | REST | MCP tool | Permission | | --- | --- | --- | --- | | [Set a payment schedule](/api-reference/reports/set-payment-schedule) | `PUT /v1/documents/{id}/payment-schedule` | `set_payment_schedule` | `documents.edit` | | [Get a payment schedule](/api-reference/reports/get-payment-schedule) | `GET /v1/documents/{id}/payment-schedule` | none | `documents.view` | | [Remove a payment schedule](/api-reference/reports/delete-payment-schedule) | `DELETE /v1/documents/{id}/payment-schedule` | none | `documents.edit` | `PUT` replaces the whole schedule. Send the parts in order; for each part give either `input_percent` or `amount_minor`, and either a `due_date` or `due_trigger: "on_acceptance"`. ```bash cURL curl -X PUT "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/payment-schedule" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: schedule-inv-0042-v1" \ -d '{ "installments": [ { "kind": "deposit", "label": "Deposit", "input_percent": "30", "due_trigger": "date", "due_date": "2027-02-01" }, { "kind": "installment", "label": "Instalment 1", "input_percent": "35", "due_trigger": "date", "due_date": "2027-03-01" }, { "kind": "installment", "label": "Instalment 2", "input_percent": "35", "due_trigger": "date", "due_date": "2027-04-01" } ] }' ``` ```json MCP tool call { "name": "set_payment_schedule", "arguments": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "installments": [ { "kind": "deposit", "label": "Deposit", "input_percent": "30", "due_trigger": "on_acceptance" }, { "kind": "installment", "label": "Balance", "input_percent": "70", "due_trigger": "date", "due_date": "2027-03-01" } ] } } ``` ```json Response { "data": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "installments": [ { "position": 1, "kind": "deposit", "label": "Deposit", "amount_minor": "36000", "due_date": "2027-02-01", "status": "pending", "paid_amount_minor": "0" }, { "position": 2, "kind": "installment", "label": "Instalment 1", "amount_minor": "42000", "due_date": "2027-03-01", "status": "pending", "paid_amount_minor": "0" }, { "position": 3, "kind": "installment", "label": "Instalment 2", "amount_minor": "42000", "due_date": "2027-04-01", "status": "pending", "paid_amount_minor": "0" } ] } } ``` Field names follow the stored schedule parts; see the endpoint pages for the exact request shape. Your systems can follow parts with the `installment.due` and `installment.paid` [webhook events](/api-reference/webhook-events). When a client pays on the pay page, the checkout request can name the instalment it pays (`installment_id`). ## Rules and edge cases - **Exact totals.** The parts must add up to the document total; the last part takes the rounding. - **Schedules on sent documents.** A sent document changes only by revision, so changing the schedule of a sent invoice is recorded as a new revision (proposed). - **Paying more** than the next amount is allowed by default; the extra goes to the next parts. - **Voiding** the invoice voids its open parts. - **One schedule per document.** Setting a new schedule replaces the old one. ## What the client sees - The schedule on the pay page and in the portal, with each part's label, amount, due date and status. - The **next amount due**, preselected for payment. - Reminders for each instalment as its due date comes. ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `documents.edit` | Setting or removing a schedule | Owner, Admin, Editor | | `documents.view` | Seeing schedules | Owner, Admin, Editor, Viewer, Accountant | ## Errors | Code | When | | --- | --- | | `schedule_total_mismatch` | The parts do not add up to the document total | | `validation_failed` | A part has neither a percent nor an amount, or no due date or trigger | | `document_voided` | The document is void | | `permission_denied` | Your role lacks `documents.edit` | ## Related Estimates Deposits that become invoices on acceptance Saved cards and auto-charge Charge each instalment automatically Reminders Reminders per instalment Late fees Late fees per instalment Source: https://docs.freeinvoiceautomation.com/payments/schedules --- # Saved cards and auto-charge Let clients save a card at Stripe with their consent, then charge recurring invoices and instalments automatically on their due date. Auto-charge gets you paid on time without asking every month. When a client pays by card through Stripe, they can choose to save the card for future invoices from your business. Recurring invoices and instalments can then be charged to that card automatically on their due date. The card stays at Stripe, on your own connected account: we never see the card number, and we keep only what is needed to show it and prove the client agreed. ## How saving a card works 1. A client pays an invoice through [Stripe](/payments/stripe) Checkout. 2. On Checkout they can tick **Save this card for future invoices from** followed by your business name. 3. Stripe saves the card on **your** connected Stripe account. 4. We store only: - the Stripe customer id and payment-method id, - the card brand and last 4 digits, for display, - the client's **consent record**: the exact consent text shown, the time, and the client's IP address and browser. There is no place in our database for a card number, expiry date or security code. You see "card on file" with the brand and last 4, and nothing more. Saving a card is the client's choice, on Stripe's own page. You cannot add a client's card yourself, and you can never see the full card. ## How auto-charge works Turn on auto-charge for a [recurring invoice](/collections/recurring) schedule or for an [instalment](/payments/schedules) whose client has a saved card with consent. Then, on each due date: 1. The auto-charge job creates an off-session payment on your connected Stripe account for the amount due. 2. Stripe processes it. 3. The invoice is marked paid **only when Stripe's verified webhook confirms the payment**. The charge being sent is not enough. ### When a charge does not go through - **The bank asks the client to authenticate**, or **the charge fails**: the client gets the normal pay link, and the [reminder sequence](/collections/reminders) runs as for any unpaid invoice. - **Retries** follow a setting: by default **3 tries over 7 days**. - After the last try, the invoice stays open with its pay link and reminders until it is paid another way. | Event | Webhook event to your systems | | --- | --- | | Charge confirmed by Stripe | `auto_charge.succeeded`, then `document.paid` | | Charge failed | `auto_charge.failed` | | Client removed the card | `saved_payment_method.removed` | See [Webhook events](/api-reference/webhook-events). ## Turn on auto-charge in the app Check the client has a card on file Open the client under **Clients**. A saved card shows as "card on file" with its brand and last 4 digits. Open the recurring schedule or the instalment Auto-charge is set on a recurring invoice schedule, or on an instalment of an invoice's payment schedule. Turn on auto-charge Choose the client's saved card and turn auto-charge on. Confirm Auto-charge moves money from your client's card, so you confirm the change before it is saved. It is recorded in the audit log. ## What the client sees - The **Save this card** option on Stripe Checkout, with your business name in the consent text. - The invoice shown as paid on its pay page once the charge is confirmed. - If a charge fails, the usual pay link and reminders. - Their saved card in the [client portal](/clients/portal), shown by brand and last 4, with a **Remove** button. Removing it detaches the card at Stripe and removes it from our records. Future auto-charges then fall back to the pay link. ## Through the API | Action | REST | Permission | | --- | --- | --- | | [List a client's saved cards](/api-reference/cards-contracts/list-saved-payment-methods) | `GET /v1/clients/{id}/saved-payment-methods` | `payments.view` | | [Set auto-charge](/api-reference/cards-contracts/set-auto-charge) | `PUT /v1/recurring-schedules/{id}/auto-charge` or `PUT /v1/schedule-installments/{id}/auto-charge` | `payments.auto_charge` | | [Remove a saved card](/api-reference/portal/portal-remove-saved-payment-method) (client, in the portal) | `DELETE /v1/portal/saved-payment-methods/{id}` | portal session | Setting auto-charge is a **destructive, money-moving** action: the call must include `"confirm": true`, otherwise it returns `confirmation_required` with a preview of what would be charged and when. It is not on the MCP tool list; and if an AI assistant or integration reaches it through the API, it still needs a person's confirmation. ```bash Turn on auto-charge for a recurring schedule curl -X PUT "https://api.freeinvoiceautomation.com/v1/recurring-schedules/0193f2e1-6c7d-7e8f-9a0b-1c2d3e4f5a6b/auto-charge" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: autocharge-retainer-acme" \ -d '{ "auto_charge": true, "saved_payment_method_id": "0193f2e0-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "confirm": true }' ``` ```json Saved cards (response) { "data": [ { "id": "0193f2e0-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "card_brand": "visa", "card_last4": "4242", "consent_at": "2027-03-08T14:02:11Z", "status": "active" } ], "next_cursor": null, "has_more": false } ``` ## Rules - **No consent, no charge.** Auto-charge can only point to an active saved card of the same client with a consent record. Without one, the call fails with `consent_required` and nothing is charged. - **Only what the client agreed to.** Auto-charge runs only for the recurring schedule or instalment it is set on. A change to the amount after consent needs new consent (proposed). - **Paid only from Stripe's webhook.** A pending charge never marks an invoice paid. - **Every off-session charge is audited** with the schedule, amount, consent record and Stripe payment id, so you can answer a client's question or a dispute. - **Removed cards** stop all future auto-charges for that card; those invoices fall back to the pay link. - **Stripe only.** Saved cards and auto-charge are part of the Stripe rail. Other rails have no saved cards. - **Offering the option.** The **Save this card** option is offered on Checkout when your business has a Stripe connection; you can turn the offer off for a business. ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `payments.auto_charge` | Turning auto-charge on or off | Owner, Admin | | `payments.view` | Seeing saved cards (brand and last 4) | Owner, Admin, Editor, Viewer, Accountant | | `documents.create` | Managing the recurring schedule itself | Owner, Admin, Editor | ## Errors | Code | When | | --- | --- | | `consent_required` | The client has no active saved card with consent | | `confirmation_required` | The call did not include `"confirm": true` | | `rail_not_connected` | The business's Stripe connection is not active | | `upstream_error` | Stripe returned an error | | `permission_denied` | Your role lacks `payments.auto_charge` | ## Related Recurring invoices Invoices created on a schedule Deposits and schedules Instalments with their own due dates Stripe Your connected Stripe account Client portal Where clients remove a saved card Source: https://docs.freeinvoiceautomation.com/payments/auto-charge --- # Pay-me page A public page at your business's own address where anyone can pay you with the methods you choose, without an invoice link. The pay-me page is a public payment page for your business at `freeinvoiceautomation.com/` followed by your business slug, for example `freeinvoiceautomation.com/acme-studio`. Anyone can open it and pay you with the payment methods you chose to show, without waiting for an invoice. Put the link in your email signature, on your website or in your social profiles. It has no analytics, no ads and no sponsors. ## What the page shows - Your business name and logo, themed with your branding. - An optional headline and description you write, for example "Pay for your session" or a short note about what payments are for. - Every payment method you enabled for the pay-me page: card through [Stripe](/payments/stripe), [Whop](/payments/whop), your [bank details](/payments/bank-transfer) and [local QR codes](/payments/qr-rails). Card and Whop payments open the provider's own hosted checkout; the page itself only shows details and links to checkout. That is also why it is safe to embed the page on other sites. The pay-me page follows the same paid rule as everything else: money arrives through a verified provider webhook or is recorded by you. A visitor returning from checkout does not mark anything paid. ## Your business slug The address comes from the business's **slug**, a short name that is unique across all businesses on Free Invoice Automation. Each business in your account has its own slug and its own pay-me page. If a slug is already taken, choose another. ## Set up your pay-me page Connect your payment methods Make sure the business has the rails you want: Stripe, Whop, bank transfer or a local QR rail. See [How payments work](/payments/overview). Choose a slug Set the business's slug in **Settings > Business**. It becomes the address of the page. Choose which methods to show In **Settings > Payment methods**, mark each method you want on the pay-me page. This choice is separate from whether the method is offered on invoices. Write a headline and turn the page on Add an optional headline and description, then enable the page. It is live straight away. Open your pay-me page in a private browser window to see exactly what a visitor sees. ## Embed it on WordPress The [WordPress plugin](/integrations/woocommerce) has a `[fia_pay_me]` shortcode and block that embeds your pay-me page on any WordPress site, with or without WooCommerce. The pay-me page is the one public page that allows being framed like this, because it has no actions other than links to hosted checkout. Every other public page (pay pages, the portal, estimate pages) refuses to be framed. ## Through the API | Action | REST | Permission | | --- | --- | --- | | [Get the pay-me page](/api-reference/agent/get-pay-me-page) | `GET /v1/businesses/{id}/pay-me` | `businesses.view` | | [Update the pay-me page](/api-reference/agent/update-pay-me-page) | `PUT /v1/businesses/{id}/pay-me` | `rails.manage` | | [Read a public pay-me page](/api-reference/public/get-pay-me-page-public) | `GET /v1/public/pay-me/{slug}` | none | The page's settings are whether it is enabled, the headline and the description. Which methods appear is set per payment method with [set payment methods](/api-reference/connections/set-payment-methods). ```bash Update the page curl -X PUT "https://api.freeinvoiceautomation.com/v1/businesses/0193f2a1-1b2c-7d3e-8f40-5a6b7c8d9e01/pay-me" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: payme-acme-2027-02" \ -d '{ "enabled": true, "headline": "Pay Acme Studio", "description": "Card, bank transfer or Whop. Thank you for your business." }' ``` ```bash Read the public page curl "https://api.freeinvoiceautomation.com/v1/public/pay-me/acme-studio" ``` ```json Public page (response) { "data": { "business_name": "Acme Studio", "logo_url": "https://...", "headline": "Pay Acme Studio", "description": "Card, bank transfer or Whop. Thank you for your business.", "rails": ["stripe", "whop", "bank_transfer"] } } ``` The public route needs no authentication and returns only what the page shows: the business name, logo and enabled rails, plus your headline and description. The pay-me page is not on the MCP tool list. ## Rules and edge cases - **Public by design.** Anything on the pay-me page, including bank details, is visible to anyone with the address. Show only what you would print on an invoice. - **No tracking.** The page never loads analytics, ads, affiliates or sponsors. - **Branding.** "Powered by Free Invoice Automation" appears for businesses on the Free plan only, as on other client-facing pages. - **QR rails** carry their own currency (INR for UPI, PKR for Raast, JazzCash and Easypaisa). - **Turning the page off** stops it from showing your payment methods. Your invoice pay links keep working. ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `rails.manage` | Turning the page on and off and editing it | Owner, Admin | | `businesses.view` | Reading the page settings | Owner, Admin, Editor, Viewer, Accountant | | `businesses.manage` | Changing the business slug | Owner, Admin | ## Errors | Code | When | | --- | --- | | `validation_failed` | A field is invalid, for example a slug that is already taken | | `not_found` | No enabled pay-me page exists for that slug | | `permission_denied` | Your role lacks `rails.manage` | ## Related How payments work Rails and the paid rule WordPress and WooCommerce Embed the page with the pay-me block Businesses Business profile and slug Branding Logo and colours on client-facing pages Source: https://docs.freeinvoiceautomation.com/payments/pay-me --- # Reminders Named reminder sequences that email clients before and after the due date, and stop on their own when an invoice is settled. Reminders chase unpaid invoices for you, politely and on time. You choose a **sequence**: a set of steps such as "3 days before the due date", "on the due date" and "a week late", each with its own message. Each step is sent once, at the right time, and the sequence stops on its own when the invoice is paid, voided, credited, or when the client says they paid. You can also send a single reminder by hand at any time. ## How sequences work A reminder sequence belongs to a business and has a name and a list of steps: | Part | What it is | | --- | --- | | **Offset** | Days from the due date: negative before, `0` on the day, positive after. For example −3, 0, +3, +7, +14, +30. | | **Channel** | Email. WhatsApp, Telegram and SMS become available as channels from April 2027. | | **Message template** | The text of the reminder, with variables such as the client name, invoice number, amount and pay link. See [Message templates](/messaging/templates). | Each step is sent **once per invoice**. Sends are protected against duplicates, so even if a job runs twice, a client never gets the same step twice. Reminders go out in the business's time zone. ### Presets Every business starts with three ready-made sequences, so you do not have to design one: - **Gentle** - **Standard** - **Firm** Each preset is a set of steps drawn from the usual offsets (for example −3, 0, +3, +7, +14 and +30 days). Open a preset to see its exact steps and messages before you use it. You can use a preset as it is, edit it, or create your own sequences. ### Which sequence applies One sequence is the business **default**. You can override it for a client, and for a single invoice. The most specific one wins: 1. The invoice's own sequence, if set 2. The client's sequence, if set 3. The business default ## When reminders stop A sequence stops sending further steps for an invoice when any of these happens: - The invoice is **paid**. - The invoice is **voided**. - A **credit note covers the balance**. - You set a **promise-to-pay date**: reminders pause until that date passes. See [Promise to pay](/collections/promise-to-pay). - The client reports payment **with a proof** through **I have paid**. See [Payment proofs](/payments/payment-proofs). A client who has **opted out** of reminders is skipped, and each skip is logged. Every reminder email has an opt-out link, and you can also set the opt-out on the client. ## Overdue invoices Unpaid invoices that are past their due date are marked **overdue** automatically. They then appear in the overdue list, in `list_overdue` over MCP and the API, and the `document.overdue` webhook event is sent. Reminder steps with a positive offset run against overdue invoices. ## Set up reminders in the app Reminders and late fees"> Choose the business. You see the three presets and any sequences you created. Pick or build a sequence Use a preset, or create a sequence: add steps with an offset in days, a channel and a message template. Make it the default Set the sequence as the business default. Reminders are off until the business picks a sequence (proposed default). Override where needed On a client, choose a different sequence or turn reminders off. On a single invoice, choose another sequence in the editor. ### Send one reminder now Open an unpaid invoice and choose to send a reminder. You see a draft of the message first, and it is sent only after you confirm. This is the same over MCP: ask your assistant to remind a client, it calls `send_reminder`, shows you the draft, and sends only when you confirm (unless its key is set to send automatically). ## Through the API and MCP | Action | REST | MCP tool | Permission | | --- | --- | --- | --- | | [List reminder sequences](/api-reference/collections/list-reminder-sequences) | `GET /v1/reminder-sequences` | none | `documents.view` | | [Create or update a sequence](/api-reference/collections/upsert-reminder-sequence) | `POST /v1/reminder-sequences` or `PATCH /v1/reminder-sequences/{id}` | none | `reminders.manage` | | [Delete a sequence](/api-reference/collections/delete-reminder-sequence) | `DELETE /v1/reminder-sequences/{id}` | none | `reminders.manage` | | [Send a reminder](/api-reference/collections/send-reminder) | `POST /v1/documents/{id}/reminders` | `send_reminder` | `documents.send` | | [List overdue invoices](/api-reference/documents/list-overdue) | `GET /v1/documents/overdue` | `list_overdue` | `documents.view` | ```bash Create a sequence curl -X POST "https://api.freeinvoiceautomation.com/v1/reminder-sequences" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: seq-retainers-v1" \ -d '{ "business_id": "0193f2a1-1b2c-7d3e-8f40-5a6b7c8d9e01", "name": "Retainer clients", "is_default": false, "enabled": true, "steps": [ { "offset_days": -3, "channel": "email" }, { "offset_days": 0, "channel": "email" }, { "offset_days": 7, "channel": "email" }, { "offset_days": 14, "channel": "email" } ] }' ``` ```bash Send a reminder now curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/reminders" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: remind-inv-0042-2027-02-10" \ -d '{ "confirm": true }' ``` Without `"confirm": true`, `send_reminder` returns `confirmation_required` together with a preview of the message, unless your API key or MCP token has `mode: auto_send`. Each reminder sent produces the `reminder.sent` [webhook event](/api-reference/webhook-events). From the week of 25 January 2027 the CLI has `freeinvoiceautomation doc remind` and `reminder list` (command names are proposed). ## Email quota Reminders are emails like any other, so they use the same sender as your documents: - With **your own SMTP** connected, there is no monthly limit. See [Your own SMTP](/messaging/smtp). - With the **platform mailer**, reminders count toward your monthly quota (Free 20, Team 100). When the quota is used up, reminder steps are not sent through the platform mailer and you are told, so you can connect your own SMTP. See [Platform email](/messaging/platform-email). ## Instalments From the week of 25 January 2027, invoices with a [payment schedule](/payments/schedules) run the sequence against **each instalment's** due date and amount, so a client is reminded about the part that is actually due. ## What the client sees - Emails from your business at the times you chose, with the amount due and a link to the pay page. - A link in each reminder email to opt out of reminders. - If a late fee was added, the next reminder tells them. See [Late fees](/collections/late-fees). ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `reminders.manage` | Creating, editing and deleting sequences | Owner, Admin, Editor | | `documents.send` | Sending a reminder now | Owner, Admin, Editor | | `documents.view` | Seeing sequences and overdue invoices | Owner, Admin, Editor, Viewer, Accountant | | `clients.edit` | Setting a client's sequence or opt-out | Owner, Admin, Editor | | `documents.edit` | Setting an invoice's own sequence | Owner, Admin, Editor | Reminders only ever act on your own account's invoices; a request from another account is refused. ## Errors | Code | When | | --- | --- | | `confirmation_required` | `send_reminder` without `"confirm": true` and without an `auto_send` key | | `quota_exceeded` | The platform email quota is used up; `details.suggestion` is `connect_smtp` | | `email_not_verified` | The platform mailer needs your email address verified first | | `document_voided` | The invoice is void | | `permission_denied` | Your role lacks `reminders.manage` or `documents.send` | ## Related Late fees Add a fair fee to late invoices Promise to pay Pause reminders until an agreed date Message templates Write the text of each reminder Due dates and payment terms Reminders count from the due date Source: https://docs.freeinvoiceautomation.com/collections/reminders --- # Late fees Add a fixed, percentage or statutory late fee to overdue invoices, stated in your terms in advance and waivable in one click. Late fees encourage clients to pay on time and compensate you when they do not. You set a policy once for a business (and, if you like, a different one for a particular client). When an invoice is overdue and its grace days have passed, the fee is added to the invoice as a traceable revision, and the next reminder tells the client. Late fees are **off by default**, and they only apply to invoices whose terms stated the fee before they were sent. ## The policy A late-fee policy belongs to a business, with an optional override per client. It has: | Setting | Options | | --- | --- | | **Type** | A **fixed amount**, a **percent of the overdue balance**, or a **statutory preset** | | **Grace days** | How many days after the due date before any fee applies | | **Repeat** | Apply **once**, or **every N days** while the invoice stays overdue | | **Maximum total** | A cap on the total of all fees on one invoice | | **On or off** | The whole policy, and separately each invoice | ### Statutory presets Statutory presets follow the late-payment rules of a jurisdiction, so you do not need to work out the interest yourself: | Preset | What it applies | | --- | --- | | EU Late Payment Directive | Statutory interest plus a fixed €40 compensation, for business-to-business invoices | | UK late payment | UK statutory late payment interest plus the fixed sum | The rates and fixed sums are kept as data, so they can be updated when the law changes. Other countries use a custom fixed or percent fee. Late fees must be agreed in advance to be fair and enforceable. That is why an invoice gets a late fee only if its terms showed the fee rule **before it was sent**. An invoice sent without the fee in its terms never gets one, even if you turn on a policy later. ## How a fee is applied 1. An invoice passes its due date unpaid and becomes overdue. 2. Once the grace days have passed, the fee is calculated from the policy (for a percent fee, on the overdue balance). 3. The fee is added as a **revision** of the invoice: a new version with a **Late fee** line, a new snapshot, and an audit entry. The previous version stays readable, so you and your client can always see what changed. 4. The client is told in the next reminder. See [Reminders](/collections/reminders). 5. If the policy repeats, the next fee is added after N more days, until the **maximum total** is reached. After that, no more fees are added. Each fee is recorded on its own, with the date it was applied for, so it is never added twice for the same day. Fees are calculated by the same money engine as your totals, in integer minor units. ### When no fee is added - The invoice is **void**. - Late fees are **off** for the business, the client or that invoice. - The invoice's terms did not show the fee rule when it was sent. - The **cap** is already reached. - The grace days have not yet passed. ## Waive a fee You can waive an applied fee in one click, for example for a good client who paid a day late. Waiving removes the fee through a **new revision**, with an audit entry recording who waived it. The fee record is kept with the status `waived`, so your history stays complete. ## Set up late fees in the app Reminders and late fees"> Choose the business. Late fees are off until you turn them on. Create the policy Pick the type (fixed, percent or a statutory preset), the grace days, whether it repeats, and a maximum total. Check your terms An invoice with late fees on must show the fee rule in its terms before it is sent. Check that your terms describe the fee on the invoices you send from now on. Override for a client (optional) On a client, set a different policy, or turn late fees off for them. Turn off for one invoice (optional) In the editor, switch late fees off for a single invoice before sending it. ## Through the API | Action | REST | Permission | | --- | --- | --- | | [List late-fee policies](/api-reference/collections/list-late-fee-policies) | `GET /v1/late-fee-policies` | `documents.view` | | [Create or update a policy](/api-reference/collections/upsert-late-fee-policy) | `POST /v1/late-fee-policies` or `PATCH /v1/late-fee-policies/{id}` | `late_fees.manage` | | [Delete a policy](/api-reference/collections/delete-late-fee-policy) | `DELETE /v1/late-fee-policies/{id}` | `late_fees.manage` | | [Waive a late fee](/api-reference/collections/waive-late-fee) | `POST /v1/late-fee-charges/{id}/waive` | `late_fees.waive` | Late-fee actions are not on the MCP tool list; use the app, the API or, from the week of 25 January 2027, the CLI (`freeinvoiceautomation late-fee upsert` and `late-fee waive`; command names are proposed). ```bash Percent fee, monthly, capped curl -X POST "https://api.freeinvoiceautomation.com/v1/late-fee-policies" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: latefee-acme-default" \ -d '{ "business_id": "0193f2a1-1b2c-7d3e-8f40-5a6b7c8d9e01", "type": "percent", "percent": "1.5", "grace_days": 7, "repeat": "every_n_days", "repeat_every_days": 30, "max_total_minor": "50000", "enabled": true }' ``` ```bash Statutory preset for one client curl -X POST "https://api.freeinvoiceautomation.com/v1/late-fee-policies" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: latefee-client-northwind" \ -d '{ "business_id": "0193f2a1-1b2c-7d3e-8f40-5a6b7c8d9e01", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "type": "statutory", "statutory_preset_key": "eu_late_payment", "grace_days": 0, "repeat": "once", "enabled": true }' ``` A policy with no `client_id` is the business policy; one with a `client_id` overrides it for that client. There is at most one of each. To turn fees off for a single invoice, set `late_fees_enabled` to `false` on the document. When a fee is applied, the invoice gets a new revision and the `late_fee.applied` [webhook event](/api-reference/webhook-events) is sent; waiving sends `late_fee.waived`. ## Instalments From the week of 25 January 2027, invoices with a [payment schedule](/payments/schedules) have late fees worked out **per instalment**, against each instalment's own due date. ## What the client sees - The fee rule in the invoice terms, before the invoice is sent. - A **Late fee** line on the invoice once a fee applies, with the new total on the pay page and PDF. - A note about the fee in the next reminder. - If you waive it, the line disappears in the new version of the invoice. ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `late_fees.manage` | Creating, editing and deleting policies | Owner, Admin | | `late_fees.waive` | Waiving an applied fee | Owner, Admin, Editor | | `documents.view` | Seeing policies | Owner, Admin, Editor, Viewer, Accountant | | `documents.edit` | Turning fees off on one invoice | Owner, Admin, Editor | Late-fee actions only ever act on your own account's invoices; a request from another account is refused. ## Errors | Code | When | | --- | --- | | `validation_failed` | A required field for the type is missing, for example `percent` on a percent policy | | `conflict` | A business or client policy already exists; update it instead | | `document_voided` | The invoice is void | | `permission_denied` | Your role lacks `late_fees.manage` or `late_fees.waive` | ## Related Reminders The next reminder tells the client about the fee Revisions How fees change a sent invoice safely Due dates and payment terms Terms and due dates that fees depend on Promise to pay Agree a later date with a client Source: https://docs.freeinvoiceautomation.com/collections/late-fees --- # Recurring invoices Create invoices automatically on a schedule from a template, as drafts for you to send or sent for you. Recurring invoices bill your regular clients on time without you remembering. You set up a schedule once (for example every month for a retainer) based on a template invoice, and on each date a new invoice is created from that template. By default the new invoice is a draft that you check and send; you can choose to have it sent automatically. From March 2027 it can also be charged to the client's saved card. ## How recurring works A recurring schedule belongs to a business and one client, and has: | Part | What it is | | --- | --- | | **Template** | A draft invoice whose lines, taxes, notes and terms are copied into each new invoice | | **Interval** | How often: a number of weeks, months or years (proposed units), for example every 1 month or every 3 months | | **Next run date** | When the next invoice will be created | | **End** | Optional: an end date, or a number of invoices after which the schedule ends | | **Auto-send** | Off by default: new invoices are drafts. On: each invoice is sent when it is created | | **Auto-charge** | From March 2027: charge the client's saved card on the due date | | **Status** | `active`, `paused` or `ended` | On each run date: 1. A new invoice is created by copying the template. 2. It is numbered from the business's own gap-free sequence, exactly like every other invoice. 3. If auto-send is off, it waits as a draft for you to review and send. If auto-send is on, it is sent to the client. 4. The next run date moves forward by the interval. When the end date or the number of invoices is reached, the schedule ends. A schedule never creates two invoices for the same date, even if the job runs twice. Invoices made by a schedule are marked as coming from it, and the `recurring.document_created` [webhook event](/api-reference/webhook-events) is sent for each one. Recurring invoices are drafts by default because integrations and automation should not send money requests without a person checking them. Turn on auto-send only for schedules you trust to be right every time, such as a fixed monthly retainer. ## Set up a recurring invoice in the app Open Recurring In the sidebar, open **Recurring** under **Documents**. Choose the client and template Pick the client and build the template invoice: lines, taxes, discounts, notes and terms, exactly as you want each invoice to look. Set the interval and first date Choose how often (for example every 1 month) and the date of the first invoice. Choose when it ends Leave it open, set an end date, or set a number of invoices. Decide on auto-send Leave auto-send off to review each invoice as a draft, or turn it on to send automatically. ### Pause, resume or stop - **Pause** a schedule while a client is on a break. Nothing is created while it is paused. - **Resume** it when work starts again. - **Delete** a schedule to stop it for good. Invoices it already created are not affected. Changing the template changes future invoices only. Invoices already created, and especially those already sent, keep their own content. ## Through the API | Action | REST | Permission | | --- | --- | --- | | [List recurring schedules](/api-reference/collections/list-recurring-schedules) | `GET /v1/recurring-schedules` | `documents.view` | | [Create or update a schedule](/api-reference/collections/upsert-recurring-schedule) | `POST /v1/recurring-schedules` or `PATCH /v1/recurring-schedules/{id}` | `documents.create` | | [Pause or resume a schedule](/api-reference/collections/pause-recurring-schedule) | `POST /v1/recurring-schedules/{id}/pause` or `/resume` | `documents.create` | | [Delete a schedule](/api-reference/collections/delete-recurring-schedule) | `DELETE /v1/recurring-schedules/{id}` | `documents.create` | | [Set auto-charge](/api-reference/cards-contracts/set-auto-charge) (March 2027) | `PUT /v1/recurring-schedules/{id}/auto-charge` | `payments.auto_charge` | First create the template as a draft invoice (see [Create an invoice](/api-reference/documents/create-invoice)), then create the schedule that points to it: ```bash Monthly retainer curl -X POST "https://api.freeinvoiceautomation.com/v1/recurring-schedules" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: recurring-acme-retainer" \ -d '{ "business_id": "0193f2a1-1b2c-7d3e-8f40-5a6b7c8d9e01", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "template_document_id": "0193f2e2-0a1b-7c2d-8e3f-4a5b6c7d8e9f", "interval_unit": "month", "interval_count": 1, "next_run_date": "2027-02-01", "remaining_runs": 12, "auto_send": false }' ``` ```json Response (201) { "data": { "id": "0193f2e1-6c7d-7e8f-9a0b-1c2d3e4f5a6b", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "template_document_id": "0193f2e2-0a1b-7c2d-8e3f-4a5b6c7d8e9f", "interval_unit": "month", "interval_count": 1, "next_run_date": "2027-02-01", "end_date": null, "remaining_runs": 12, "auto_send": false, "auto_charge": false, "status": "active", "last_run_at": null } } ``` Recurring schedules are not on the MCP tool list. Your assistant can still find the drafts they create with `list_documents` and send them with `send_document` after you confirm. From the week of 25 January 2027 the CLI has `freeinvoiceautomation recurring list`, `recurring upsert` and `recurring pause` (command names are proposed). ## Auto-charge (March 2027) If the client saved a card at Stripe with their consent, you can set the schedule to charge it on each invoice's due date. The invoice is marked paid only when Stripe confirms the charge; if the bank asks the client to authenticate or the charge fails, the client gets the normal pay link and reminders run. See [Saved cards and auto-charge](/payments/auto-charge). ## Rules and edge cases - **Same numbering.** Recurring invoices use the business's normal gap-free sequence; there is no separate series. - **Reminders and late fees** apply to recurring invoices like any other once they are sent. - **Email quota.** Auto-sent invoices count toward your platform email quota unless you send through your own SMTP. - **Ended schedules** have the status `ended` and create nothing more. ## What the client sees Each recurring invoice is a normal invoice: an email with a pay link if it was sent, a pay page, a PDF and, from January 2027, an entry in the [client portal](/clients/portal). ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `documents.create` | Creating, changing, pausing and deleting schedules | Owner, Admin, Editor | | `documents.view` | Seeing schedules | Owner, Admin, Editor, Viewer, Accountant | | `documents.send` | Sending the drafts a schedule creates | Owner, Admin, Editor | | `payments.auto_charge` | Turning on auto-charge | Owner, Admin | ## Errors | Code | When | | --- | --- | | `validation_failed` | A field is missing or invalid, for example an interval of 0 | | `not_found` | The template document or client does not exist | | `consent_required` | Auto-charge without a consented saved card | | `quota_exceeded` | An auto-send could not go through the platform mailer this month | | `permission_denied` | Your role lacks `documents.create` | ## Related Invoices Build the template invoice Saved cards and auto-charge Charge recurring invoices automatically Reminders Follow up when a recurring invoice is not paid Document numbering How numbers stay gap-free Source: https://docs.freeinvoiceautomation.com/collections/recurring --- # Promise to pay Record the date a client promised to pay, so automatic reminders pause until then and start again if the date passes. Sometimes a client tells you when they will pay: "our payment run is on the 28th" or "I can pay on Friday". A promise-to-pay date records that agreement on the invoice. While the date is in the future, the invoice's reminder sequence stops sending, so the client is not chased for something you already agreed. If the date passes and the invoice is still unpaid, reminders carry on. ## How it works A promise-to-pay date is a single date on an invoice, set by you (the business). It is one of the conditions that stop a [reminder sequence](/collections/reminders): | Stop condition | Effect on reminders | | --- | --- | | Paid | Stops for good | | Voided | Stops for good | | A credit note covers the balance | Stops for good | | Client reported payment with a proof | Stops | | **Promise-to-pay date set by the business** | **Paused until the promise-to-pay date passes** | So a promise is the only stop condition that is temporary. When the date passes and the invoice still has a balance, the sequence starts sending again. Only the business sets a promise-to-pay date. A client cannot set one from the pay page or the portal. If a client emails you a date, you record it. ### Example An invoice is due on 15 February 2027 and uses a sequence with steps at 0, +3, +7 and +14 days. On 17 February the client tells you they will pay on 28 February. You set the promise-to-pay date to `2027-02-28`: - The +3 day step (18 February) and the +7 day step (22 February) are not sent. - If the client pays by 28 February, the invoice is paid and the sequence stops for good. - If 28 February passes and the invoice is still unpaid, the sequence sends reminders again. ## Set a promise-to-pay date in the app Open the invoice Open the unpaid invoice from **Invoices** or from the overdue list. Set the promise Choose to set a promise to pay, and enter the date the client gave you. Save Reminders for this invoice pause until that date. Like every change, it is written to the audit log with your name. To change the promise, set a new date. If the client breaks the promise, you do not need to do anything: reminders start again once the date has passed. You can still [send a reminder](/collections/reminders) by hand at any time. Write down how the promise was made (email, phone call) in the invoice's notes for your team. It helps when you follow up later. ## Through the API | Action | REST | Permission | | --- | --- | --- | | [Set a promise to pay](/api-reference/collections/set-promise-to-pay) | `POST /v1/documents/{id}/promise-to-pay` | `documents.edit` | | [Get a document](/api-reference/documents/get-document) (read `promise_to_pay_date`) | `GET /v1/documents/{id}` | `documents.view` | ```bash Set a promise to pay curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/promise-to-pay" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: promise-inv-0042-2027-02-28" \ -d '{ "promise_to_pay_date": "2027-02-28" }' ``` ```json Response { "data": { "id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "type": "invoice", "number": "INV-2027-0042", "status": "overdue", "due_date": "2027-02-15", "promise_to_pay_date": "2027-02-28", "balance_minor": "120000", "currency": "USD" } } ``` Setting a promise is not on the MCP tool list. Your assistant can still read the date with `get_document`, and list unpaid invoices with `list_overdue`. From the week of 25 January 2027 the CLI has `freeinvoiceautomation doc promise` (command names are proposed). ## Rules and edge cases - **One date per invoice.** Setting a new date replaces the old one. - **The invoice stays overdue.** A promise does not change the due date or the status: an overdue invoice is still overdue, and still appears in overdue lists and [reports](/features/reports). It only pauses reminders. - **Late fees are separate.** A promise-to-pay date pauses reminders; it is not one of the conditions for [late fees](/collections/late-fees), which follow their own policy. If you agreed to waive late fees as part of the promise, turn late fees off for that invoice, or [waive](/collections/late-fees) any fee that was applied. - **Paying early** is always fine: payment stops the sequence for good. ## What the client sees Nothing changes on the pay page or the PDF. The client simply stops receiving reminder emails for this invoice until the agreed date. If they want to pay earlier, the pay link works as usual. ## Permissions | Key | Needed for | Default roles | | --- | --- | --- | | `documents.edit` | Setting or changing a promise-to-pay date | Owner, Admin, Editor | | `documents.view` | Seeing the date | Owner, Admin, Editor, Viewer, Accountant | ## Errors | Code | When | | --- | --- | | `validation_failed` | The date is missing or not a valid `YYYY-MM-DD` date | | `document_voided` | The invoice is void | | `not_found` | The invoice does not exist or belongs to another account | | `permission_denied` | Your role lacks `documents.edit` | ## Related Reminders Sequences and every stop condition Late fees Waive or turn off fees for an invoice Document statuses Sent, overdue, paid and void Payment proofs When a client says they already paid Source: https://docs.freeinvoiceautomation.com/collections/promise-to-pay --- # Platform email Send invoices and other documents by email from our address with no setup, within a monthly allowance of 20 emails on Free and 100 on Team. Platform email lets you email documents to your clients the moment you sign up, without connecting a mailbox of your own. Messages go out from our sending address on behalf of your business, and replies go straight to you. Each account gets a monthly allowance: **20 emails on Free** and **100 on Team**. If you need more, [connect your own SMTP](/messaging/smtp), which has no monthly limit from us. ## How it works Every document can be delivered by email, share link, PDF download or copied link, from the web app, the API and MCP. When you send by email, Free Invoice Automation chooses a **sender** for that message. Platform email is one sender; your own SMTP mailbox is the other one available at launch. ### What your client sees A platform email is sent: | Part | Value | | --- | --- | | From address | `email@freeinvoiceautomation.com` | | From name | `{Business} via Free Invoice Automation`, for example "Acme Studio via Free Invoice Automation" | | Reply-To | your business email, so a reply reaches you, not us | | Footer | follows your plan's branding: "Powered by Free Invoice Automation" on Free, none on Team | | Report link | every platform email has a link the recipient can use to report abuse | The email carries the document's pay page link, so your client can open, download and pay the invoice online. Nothing needs printing or posting. ### How the sender is chosen For each send, the sender is resolved in this order: 1. A sender set on the **document** itself. 2. The **client's** preferred sender. 3. The **business** default sender. 4. **Platform email**, but only if your account still has quota left this month. So once you connect your own SMTP for a business, it becomes that business's default and platform email is no longer used for it unless you choose it on a client or a document. ### The monthly allowance | Plan | Platform emails a month | | --- | --- | | Free | 20 | | Team | 100 | - The counter resets on the first of each month. Unused emails do not roll over. - Every email sent through platform email counts, including reminder and recurring-invoice emails that go out through it. - Emails sent through your own SMTP never count. - At **80%** of the allowance you see a warning when you send. - At **100%** the send is blocked. The send dialog offers **Connect your own SMTP (unlimited)**, and your document stays saved as a draft, so nothing is lost. You can see how many emails you have used and how many are left as a meter in **Settings > Email**. The same numbers appear in the usage card at the bottom of the app sidebar, with the reset date. The allowance values are plan settings. If they change, this page and the pricing page change with them, and the meter in **Settings > Email** always shows the value that applies to your account. ## Before your first platform email Platform email is available only after you **verify your email address**. We send a verification link when you sign up. Until you click it, sending through platform email is refused with "verify your email first" (API error `email_not_verified`). This rule protects your clients and our sending address from people who sign up only to send spam. ## Send a document with platform email Verify your email address Open the verification link we sent when you signed up. You only do this once. Open the document and click Send Choose **Email** as the delivery method. If no SMTP is connected for the business, platform email is used. Check the preview The app shows the email exactly as your client will receive it, with the sender name, subject, message and attachment, before anything is sent. Confirm the send The email goes out, the send is logged in the document's activity, and your monthly counter goes up by one. Clicking **Send** twice does not send two emails. Each send is unique per document, event and channel, so a retry or a double click is recognised and ignored. ## Through the API and MCP Sending works the same way on every surface. First preview, then send with `"confirm": true` (an API key or MCP token in `auto_send` mode can skip the confirmation). See [Drafts and confirmations](/api-reference/confirmations). ```bash Preview curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/send-preview" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ```bash Send curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/send" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: send-inv-2027-0001" \ -d '{ "confirm": true }' ``` Over MCP, your assistant calls `preview_send` and then `send_document`. Read your remaining allowance with [Get usage](/api-reference/messaging/get-usage), which returns platform emails used and the quota, with an 80% warning flag. [List messages](/api-reference/messaging/list-messages) shows each send, its sender and its status (`queued`, `sent`, `failed` or `bounced`). ```bash Usage curl "https://api.freeinvoiceautomation.com/v1/usage" \ -H "Authorization: Bearer $FIA_API_KEY" ``` Endpoint pages: [Preview a send](/api-reference/documents/preview-send) · [Send a document](/api-reference/documents/send-document) · [Get usage](/api-reference/messaging/get-usage) · [List messages](/api-reference/messaging/list-messages). ## Abuse controls Platform email shares one sending address across every account, so a few rules keep it trusted by mail providers: - **Verified email only**, as described above. - **Monthly allowance** per plan. - **Per-minute rate limit** per account. Sends over the limit wait or are refused with `rate_limited`. - **Link and content scoring** on every message before it goes out. - **Report link** in every platform email, and a takedown process for accounts that send abuse. These controls do not apply to your own SMTP in the same way, because mail from your own server is sent under your own name and reputation. ## Plan changes If a Team account moves back to Free (for example after a [failed renewal](/billing/failed-renewal)), the allowance becomes 20 a month from that moment. If the account has already sent 20 or more platform emails that month, platform email **pauses until the next month** (API error `platform_mail_paused`). Your own SMTP is not affected, and pay pages keep working. On Team, the "Powered by" footer is removed from new emails; on Free it is shown again on new emails. ## Errors | Code | When | | --- | --- | | `email_not_verified` | You have not verified your email address yet. | | `quota_exceeded` | Your monthly allowance is used up. `details.suggestion` is `connect_smtp`. | | `platform_mail_paused` | The account moved to Free after already sending 20 or more platform emails this month. `details.suggestion` is `connect_smtp`. | | `rate_limited` | Too many sends in one minute. Retry after the `Retry-After` header. | | `confirmation_required` | A send without `"confirm": true` from a `draft_only` key or token. The response includes the preview. | | `permission_denied` | Your role lacks `documents.send`. | See [Errors](/api-reference/errors) for the error shape. ## Permissions | Action | Permission key | | --- | --- | | Preview a send, list sent messages | `documents.view` | | Send a document by email | `documents.send` | | See the email meter and usage | `settings.view` | By default Owner, Admin and Editor can send; Viewer and Accountant can look but not send. See [Roles and permissions](/team/roles). Do unused emails carry over to next month? No. The counter resets on the first of each month and unused emails do not roll over. Can I change the From address of platform email? No. Platform email always sends from our address with your business name in the From name, and replies go to your business email. To send from your own address, connect your own SMTP. What happens to a send that was blocked by the quota? Nothing is lost: the document stays a draft. Connect your own SMTP and send again, or wait for the counter to reset on the first of the month. ## Related pages Your own SMTP Send from your own mailbox with no monthly limit from us. Message templates Change the subject and text of the emails your clients receive. Send a document Email, share links, PDF download and copy link. Free and Team plans What each plan includes, including the email allowance. Source: https://docs.freeinvoiceautomation.com/messaging/platform-email --- # Your own SMTP Connect your own mailbox so documents are sent from your address, with no monthly email limit from Free Invoice Automation. Connecting your own SMTP server lets Free Invoice Automation send your invoices, estimates, receipts and reminders **from your own email address**, through your own mail server. Your clients see your address, replies land in your inbox, and the sends never count against the [platform email](/messaging/platform-email) allowance. This is the way to send more than 20 emails a month on Free or 100 on Team. ## How it works Your SMTP mailbox is a **sender connection** on a business. Once it is connected and verified, it becomes that business's default sender, and every email for that business goes through it unless a client or a document says otherwise. The sender for each email is resolved in this order: 1. A sender set on the **document**. 2. The **client's** preferred sender. 3. The **business** default sender (your SMTP once connected). 4. **Platform email**, only if the account still has quota left. Because a sender connection belongs to a business, each business can send from its own mailbox. If you run two businesses, connect one SMTP mailbox to each. ### No monthly limit from us Emails sent through your own SMTP have **no monthly quota** on any plan. Your mail provider may have its own sending limits, and those still apply. On Free, the general [fair-use limits](/billing/fair-use) on sends per minute also apply to protect the service. ## What you need From your email provider or hosting control panel, collect your mailbox's SMTP details: - the SMTP **host** name; - the **port**: 25, 465, 587 or 2525; - the **username** (often the full email address); - the **password** (or an app password, if your provider uses them). Many email providers ask you to create an app password for third-party sending instead of using your normal password. Check your provider's help pages if the connection test fails with an authentication error. ## Connect SMTP in the web app Email"> Pick the business at the top of the app first, because the connection belongs to that business. You can also start from the send dialog: when platform email is used up, it offers **Connect your own SMTP (unlimited)**. Enter your SMTP details Host, port, username and password, plus a label so you can recognise this connection later. Save and let us test it When you save, we run a **handshake check** against your server. The connection becomes active only if the server answers and accepts the login. Send a document The next email for this business goes through your mailbox. The document's activity and the message log show which sender was used. ## Connect SMTP through the API A sender connection is created with [Create a connection](/api-reference/connections/create-connection), with `kind: "sender"` and `provider: "smtp"`. The exact fields come from the `smtp` sender's connection schema, listed on the endpoint page. The password is accepted on write and never returned. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/connections" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: smtp-acme-studio" \ -d '{ "business_id": "0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51", "kind": "sender", "provider": "smtp", "label": "Acme Studio mailbox", "host": "smtp.example.com", "port": 587, "username": "billing@acmestudio.example", "password": "app-password-here" }' ``` Then use [Verify a connection](/api-reference/connections/verify-connection) to run the handshake again at any time, [Update a connection](/api-reference/connections/update-connection) to change the details, and [Delete a connection](/api-reference/connections/delete-connection) to remove it. [List connections](/api-reference/connections/list-connections) shows each sender with its status. SMTP connections are not managed over MCP. Your assistant still sends through them: when it calls `send_document`, the same sender resolution picks your SMTP mailbox. ## Connection status | Status | Meaning | | --- | --- | | `pending` | Saved, check not finished yet. | | `active` | The handshake succeeded; emails are sent through it. | | `failing` | A later check or a send failed, for example after a password change at your provider. | | `disconnected` | The connection was removed or switched off. | We re-check connections every night. If your connection starts failing, update the password or settings and verify again. Each failed send is recorded in the message log with its error, so you can see which emails did not go out. ## Security Your SMTP password is a secret, and it is treated like one: - It is **sealed** (encrypted) as soon as it reaches us, is **write-only** (no screen or API call ever shows it again, only the last few characters for recognition) and is **never written to logs**. - An **SSRF guard** checks the host before we connect. Private, loopback, link-local and other internal network addresses are blocked, we connect to the exact address that was checked, and only the SMTP ports 25, 465, 587 and 2525 are allowed. This stops anyone from using the SMTP form to reach systems inside our network. - Connection attempts use short timeouts. Your own mail server is outside our control. Its security, deliverability and limits are between you and your mail provider. ## Rules and edge cases - **One send per event.** Each send is unique per document, event and channel, so a retried or double-clicked send never emails your client twice. - **Per business.** A connection belongs to one business. Members who can reach that business send through it according to their permissions; they never see the password. - **Overrides.** You can set a different sender for a single client or a single document, for example to send one client's invoices from a different mailbox. - **No "Powered by" changes.** The email footer follows your plan's branding, whichever sender is used. - **Deliverability.** Mail from your own server is sent under your own domain's reputation. If emails land in spam, ask your provider about SPF, DKIM and DMARC for your domain. ## Errors | Code | When | | --- | --- | | `validation_failed` | A required field is missing or the port is not allowed. | | `connection_failed` | The handshake check failed: wrong host, port, username or password, or the server refused the connection. | | `upstream_error` | Your SMTP server failed while sending a message. | | `permission_denied` | Your role lacks `senders.manage`. | | `business_out_of_scope` | You cannot reach the business you named. | See [Errors](/api-reference/errors) for the error shape. ## Permissions | Action | Permission key | | --- | --- | | Connect, change, verify or delete an SMTP sender | `senders.manage` | | Send documents (through any sender) | `documents.send` | By default only Owner and Admin have `senders.manage`. See [Roles and permissions](/team/roles). ## Related pages Platform email Sending from our address within the monthly allowance. Message templates The subject and text of each email. Send a document Preview, send and share documents. How we protect data How secrets are sealed and never logged. Source: https://docs.freeinvoiceautomation.com/messaging/smtp --- # Message templates Set the subject and text of the emails your clients receive, per business, per event and per language, with details filled in for each send. Message templates are the words your clients read when a document reaches them: the subject line and body of the email that carries an invoice, a reminder, a receipt or a credit note. Each business keeps its own templates, so a design studio and a consulting firm under one account can sound different. Templates are stored as data with variables, and the business, client and document details are filled in when each message is sent. ## How templates are organised A message template belongs to one business and is chosen by three things: | Part | What it means | | --- | --- | | **Event** | Why the message is sent, for example `document_sent`, `reminder`, `receipt` or `credit_note_issued`. | | **Channel** | How it is delivered: `email` at launch; `whatsapp`, `telegram` and `sms` when [other channels](/messaging/other-channels) arrive. | | **Language** | The language of the text. Each business and client has a messaging language, which defaults to the country's language. | For each combination you can keep several templates and mark one as the **default**. When a message goes out, the default template for that business, event, channel and language is used. Every template has: - a **subject** (for email; channels such as SMS have no subject); - a **body**, the text of the message; - the **variables** in the text, which are replaced with real values for each send. ### Variables Variables let one template serve every client. They come from the business, the client and the document being sent, and they are filled in at send time, so the same "invoice sent" template works for every client and every invoice of that business. Templates are data, not code. They cannot run scripts or fetch anything; they only hold text and the variables to fill in. ### What is always added Some parts of a client email are not in your template, because they keep every message safe and useful: - the **pay page or document link**, so your client can open and pay online; - the **PDF attachment**, when attaching the PDF is on for the business, client or document; - the **footer** that follows your plan's branding ("Powered by Free Invoice Automation" on Free, none on Team); - on [platform email](/messaging/platform-email), the **report link** that every platform email carries. ## Edit a template in the web app Templates and numbering"> Choose the business at the top of the app first. Its templates are listed by event and language. Pick the event and language For example the message sent with a new invoice, in English. Open the default template or create a new one. Write the subject and body Use variables for anything that changes from client to client or from document to document, instead of typing it in. Save and mark it as default The default template is used for every new send of that event and language. Documents already sent keep the message they were sent with. Check it in a send preview Open any document and click **Send**. The preview shows your template with the real values filled in, before anything is sent. ## Templates through the API Message templates are managed with [List message templates](/api-reference/messaging/list-message-templates) and [Upsert a message template](/api-reference/messaging/upsert-message-template). `POST /v1/message-templates` creates a template; `PATCH /v1/message-templates/{id}` changes one. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/message-templates" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: tmpl-acme-sent-en" \ -d '{ "business_id": "0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51", "event": "document_sent", "channel": "email", "language": "en", "subject": "Your invoice from Acme Studio", "body": "Hello, please find your invoice attached. You can pay online using the link below. Thank you for your business.", "is_default": true }' ``` ```json Response { "data": { "id": "0193f2c8-1d2e-7f3a-8b4c-5d6e7f8a9b0c", "business_id": "0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51", "event": "document_sent", "channel": "email", "language": "en", "subject": "Your invoice from Acme Studio", "is_default": true, "created_at": "2027-01-05T09:30:00Z" } } ``` To see what a client will receive, call [Preview a send](/api-reference/documents/preview-send) on a document: it returns the rendered message without sending it. Template management is not on the MCP tool list; your assistant uses your templates automatically when it calls `preview_send` and `send_document`. ## Templates in reminders [Reminder sequences](/collections/reminders) (January 2027) are built from steps, and each step has a channel and a message template. That is how a gentle note three days before the due date and a firmer one a week after it can say different things. Edit those templates the same way, under the `reminder` event. ## PDF templates are separate Message templates control the email. The look of the document itself (layout, colours, fonts, labels, footer, paper size) is a **PDF template**, managed with [List PDF templates](/api-reference/messaging/list-pdf-templates) and [Upsert a PDF template](/api-reference/messaging/upsert-pdf-template). See [PDFs and templates](/invoicing/pdf). ## Rules and edge cases - **Per business.** Templates never cross businesses. Copy a template to another business by creating it there. - **Sent messages do not change.** Editing a template affects future sends only. Each past send stays in the message log with its recipient, subject, sender and status. - **Language follows the client.** If a client has a language set, its template is used; otherwise the business language is used. - **No ads.** Client emails never carry ads, affiliate links or sponsors, whatever the template says around them. - **One send per event.** A template change never causes a message to be sent again; each send is unique per document, event and channel. ## Errors | Code | When | | --- | --- | | `validation_failed` | A required field is missing, or the event, channel or language is not recognised. | | `permission_denied` | Your role lacks `templates.manage`. | | `business_out_of_scope` | You cannot reach the business you named. | | `not_found` | The template id does not exist in your account. | ## Permissions | Action | Permission key | | --- | --- | | List, create and edit message templates | `templates.manage` | | List PDF templates | `documents.view` | | Create and edit PDF templates | `templates.manage` | By default Owner, Admin and Editor have `templates.manage`. See [Roles and permissions](/team/roles). ## Related pages Send a document Preview and send with your templates. Reminders Reminder steps each use a message template. PDFs and templates The look of the document itself. Branding Logo, colours and the plan footer. Source: https://docs.freeinvoiceautomation.com/messaging/templates --- # WhatsApp, Telegram and SMS Send documents and reminders on WhatsApp, Telegram or SMS through your own accounts, alongside email, with the same previews and logs. Some clients answer a message long before they open an email. WhatsApp, Telegram and SMS let you send documents and reminder steps on the channel your client actually reads. You connect **your own** WhatsApp Business (Meta Cloud API), Telegram bot or Twilio SMS account, and Free Invoice Automation sends through it, so messages come from you and are billed by your provider to your account. ## How it works Every way of delivering a message is a **sender** in one registry. Email has two senders at launch, [platform email](/messaging/platform-email) and [your own SMTP](/messaging/smtp). These channels add three more: | Channel | Sender | Your account | | --- | --- | --- | | WhatsApp | Meta Cloud API | your own WhatsApp Business account on Meta | | Telegram | Telegram bot | your own bot | | SMS | Twilio | your own Twilio account | They arrive in this order: WhatsApp first, then Telegram, then SMS. Each one is a single new sender in the registry, so documents, reminders, previews, permissions and logs work exactly as they do for email. Nothing else about how you invoice changes. ### What stays the same as email - **Preview before sending.** The send preview shows the message for the chosen channel before anything goes out. - **One send per event.** Every send is unique per document, event and channel. A retry never sends the same message twice on the same channel. - **Logged.** Each send is recorded in the message log with the channel, sender, recipient and status (`queued`, `sent`, `failed`). - **Templates.** Each channel uses [message templates](/messaging/templates) for its event and language. Channels without a subject line use only the body. - **Drafts first.** Integrations, MCP clients and the AI agent create drafts; a person confirms the send unless auto-send is turned on for that key or automation. ### The platform email allowance does not apply The monthly platform email allowance (20 on Free, 100 on Team) counts only emails sent through platform email. Messages on WhatsApp, Telegram and SMS go through your own provider account, so they never use that allowance. Your provider's own pricing and limits apply. ## Choosing the channel The channel is a setting you can set at three levels. The most specific one wins: 1. **Document**: send this one document on WhatsApp. 2. **Client**: this client prefers Telegram. 3. **Business**: the default channel for the business (email unless you change it). [Reminder sequences](/collections/reminders) pick a channel per step, so one sequence can email before the due date and send an SMS a week after it. ## Connect a channel in the web app Create the account at the provider Set up your WhatsApp Business account on Meta, create a Telegram bot, or open a Twilio account with an SMS number. You own this account and its billing. Integrations"> Choose the business first. Pick WhatsApp, Telegram or SMS and enter the credentials the provider gives you. Save and test We check the connection when you save. The credentials are sealed and write-only: you can replace them later, but nobody can read them back. Choose where to use it Set the channel on the business, a client or a single document, or add a step on that channel to a reminder sequence. ## Through the API and MCP Messaging channels are sender connections, created with [Create a connection](/api-reference/connections/create-connection) using `kind: "sender"` and the provider key of the channel: `meta_whatsapp`, `telegram` or `twilio_sms`. The fields come from each sender's connection schema, listed on the endpoint page. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/connections" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: telegram-acme-studio" \ -d '{ "business_id": "0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51", "kind": "sender", "provider": "telegram", "label": "Acme Studio bot" }' ``` Once a channel is connected, sending is the same call as email. [Send a document](/api-reference/documents/send-document) and [Send a reminder](/api-reference/collections/send-reminder) use the channel resolved for that document, and [List messages](/api-reference/messaging/list-messages) shows every send on every channel. Over MCP, your assistant uses `preview_send`, `send_document` and `send_reminder` exactly as for email. Connecting a channel is not an MCP tool, because it involves your provider credentials. ## Rules and edge cases - **Your accounts, your costs.** We never send WhatsApp, Telegram or SMS messages from a shared account of ours. Fees for messages are charged by your provider to you; we take nothing. - **Your provider's rules apply.** Messaging providers have their own rules about who you may message and when. Follow them for your account. - **Per business.** A sender connection belongs to one business, like an SMTP connection. - **Opt-out still applies.** A client who opted out of reminders gets no reminders on any channel. - **Failures are visible.** A failed send is logged with its error, and the connection shows as failing after repeated problems, so you can fix the credentials and send again. - **Inbound messages come later.** Replies that act on documents (for example a "PAID" reply on WhatsApp or commands to a Telegram bot) are planned to enter through the same sender registry. The WhatsApp and Telegram bots are on the later roadmap, not part of this release. ## Errors | Code | When | | --- | --- | | `validation_failed` | A required credential is missing or malformed. | | `connection_failed` | The test call to the provider failed when saving or verifying. | | `upstream_error` | The provider failed while sending a message. | | `confirmation_required` | A send without `"confirm": true` from a `draft_only` key or token. | | `permission_denied` | Your role lacks the permission for the action. | ## Permissions | Action | Permission key | | --- | --- | | Connect, change or remove a channel | `senders.manage` | | Send a document or reminder on any channel | `documents.send` | | Add a channel step to a reminder sequence | `reminders.manage` | | Edit the message templates of a channel | `templates.manage` | By default only Owner and Admin can connect channels. See [Roles and permissions](/team/roles). ## Related pages Reminders Use a different channel for each reminder step. Message templates The text sent on each channel. Your own SMTP Email from your own mailbox with no monthly limit. Send a document Preview and send on any channel. Source: https://docs.freeinvoiceautomation.com/messaging/other-channels --- # Reports See who owes you and for how long, revenue per month, tax collected and payments received, filtered and exported to CSV or PDF. Reports turn your invoices, credit notes and payments into the numbers you need to run the business and to hand to your accountant: who owes you and for how long, how much you invoiced and collected each month, the tax you collected, and which payment methods your clients used. Reports are included on every plan, Free and Team. Each report is also an API call and an MCP tool, so your own code or your AI assistant gets exactly the same figures as the screen. ## The reports | Report | What it shows | Report name | | --- | --- | --- | | **Aged receivables** | Who owes you and for how long. Open balances grouped **Current, 1-30, 31-60, 61-90 and 90+ days** past due, per client, with totals. | `aged-receivables` | | **Outstanding by client** | The open balance of each client. | `outstanding-by-client` | | **Client statement** | For one client and a date range: invoices, credit notes and payments with a **running balance**. Ready to send to the client. | `client-statement` | | **Revenue** | Amounts **invoiced** and **paid** per month. | `revenue` | | **Tax collected** | Tax per **tax rate** and period, **net of credit notes**. Useful for your tax return. | `tax-collected` | | **Payments received** | Payments per **payment method** (Stripe, bank transfer and so on) and month. | `payments-received` | ### Filters Every report takes the same filters: - **Business**: one business, or every business you can reach. - **Client**: one client. - **Date range**: from and to. - **Currency**: limit the report to one currency. Your filters change only the view. The numbers come from the same documents and payments, so a CSV or PDF export of a filtered report shows exactly the figures on screen. ### Several currencies If you invoice in more than one currency, amounts are shown **per currency**, plus a **total in the business currency**. That total uses the exchange rate frozen on each document when it was issued, not today's rate, so last year's revenue does not change when rates move. ### What counts - Credit notes reduce an invoice's balance, and tax collected is shown net of credit notes, so reports show the position after corrections. - A payment counts once it is recorded: from a verified payment-provider event, or a payment recorded by a person. A browser redirect after checkout never counts as a payment. - Each instalment of a [payment schedule](/payments/schedules) has its own due date. ## View a report in the web app Open Reports **Reports** is in the app sidebar. Pick a report For example **Aged receivables** to see overdue balances, or **Client statement** for one client. Set the filters Choose the business, client, date range and currency. The report updates as you change them. Export Download the report as **CSV** for a spreadsheet or your accountant, or as **PDF** to share or send. Both give the same numbers as the screen. A client statement as PDF is a quick way to show a client everything open on their account in one document. Clients with portal access can also see their own statement in the [client portal](/clients/portal). ## Reports through the API Each report is a `GET` request. [Get a report](/api-reference/reports/get-report) takes the report name in the path; aged receivables also has its own endpoint, [Get aged receivables](/api-reference/reports/get-aged-receivables). Filters are query parameters: `business_id`, `client_id`, `from`, `to`, `currency` and `format`. ```bash Aged receivables curl "https://api.freeinvoiceautomation.com/v1/reports/aged-receivables?business_id=0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ```bash Revenue for 2027 curl "https://api.freeinvoiceautomation.com/v1/reports/revenue?from=2027-01-01&to=2027-12-31¤cy=USD" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ```bash Client statement as PDF curl "https://api.freeinvoiceautomation.com/v1/reports/client-statement?client_id=0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60&from=2027-01-01&to=2027-03-31&format=pdf" \ -H "Authorization: Bearer $FIA_API_KEY" \ -o statement.pdf ``` `format` is `json` (the default), `csv` or `pdf`. Asking for `csv` or `pdf` is an export ([Export a report](/api-reference/reports/export-report)) and needs the `reports.export` permission. Money in JSON reports follows the usual rule: strings of integer minor units with a currency, so `"125000"` with `"currency": "USD"` is 1,250.00 USD. See [Money and currencies](/api-reference/money). ### With your AI assistant Two MCP tools read reports: `get_aged_receivables` and `get_report` (with a report name). Both are read-only, so your assistant can call them without a confirmation step. Ask in plain language: ```text Which clients owe me money for more than 60 days? How much tax did I collect between January and March 2027? ``` The assistant gets the same numbers as the Reports screen. See the [MCP tool reference](/ai/mcp/tools). ## Rules and limits - **Read-only.** Reports never change your data. - **Your scope only.** A member who can reach only some businesses sees only those businesses in every report. - **Exact maths.** All totals are computed from integer minor units by the same money code that computes invoice totals, so a report total always matches the documents it lists. - **Not tax filing.** Tax collected is a figure to give your accountant. Free Invoice Automation does not file tax returns. - **Full accounting reports** (trial balance, profit and loss, balance sheet, general ledger) belong to the [accounting ledger](/features/ledger). ## Errors | Code | When | | --- | --- | | `validation_failed` | An unknown report name, a bad date or an invalid filter value. | | `permission_denied` | Your role lacks `reports.view`, or `reports.export` for CSV or PDF. | | `business_out_of_scope` | You asked for a business you cannot reach. | | `not_found` | The client id does not exist in your account. | ## Permissions | Action | Permission key | Default roles | | --- | --- | --- | | View reports | `reports.view` | Owner, Admin, Editor, Viewer, Accountant | | Export to CSV or PDF | `reports.export` | Owner, Admin, Editor, Accountant | See [Roles and permissions](/team/roles). ## Related pages Accounting ledger Trial balance, profit and loss and balance sheet. Reminders Follow up the balances aged receivables shows. Currencies and exchange rates How each document's exchange rate is frozen. QuickBooks Online Send your invoices and payments to your accountant's books. Source: https://docs.freeinvoiceautomation.com/features/reports --- # Contracts and e-signature Build contracts from templates, have clients sign online, counter-sign, and keep a sealed signed PDF that can never be changed. Contracts let you agree the terms of a job with your client before the work starts, entirely online. You build a contract from a template, your client's details are filled in, the client reads and signs it in the browser, you counter-sign, and the final PDF is **sealed**: it gets a signature certificate page and a SHA-256 fingerprint, and it never changes afterwards. Attach a contract to an estimate and your client can accept and sign in one step. Nothing needs printing, scanning or posting. ## How it works A contract is its own document type, alongside invoices, estimates, credit notes and receipts. It has its own number sequence per business and moves through these statuses: | Status | Meaning | | --- | --- | | `draft` | Being written. You can still change everything. | | `sent` | Sent to the client for signature. | | `partially_signed` | At least one signer has signed, others have not yet. | | `signed` | Every signer has signed. | | `sealed` | The final PDF has its certificate page and stored hash. It can no longer change. | | `superseded` | A newer version of this contract replaced it. | ### Templates and variables Contracts are built from **templates**, stored as data. When you create a contract for a client, the template's variables are filled in from the business and the client, so you do not retype names, addresses and other details each time. The filled-in text is saved with the contract as its content. ### Signer order Each contract has a fixed signer order, for example the client first and then your business. A signer who is not next cannot sign yet. When the last signer signs, the contract is complete. ### What we record for each signature When someone signs, we store: - the signer's **name and email**; - the **method**: a typed name or a drawn signature; - the **consent text** exactly as it was shown; - the **IP address**, **browser (user agent)** and **time** of signing. This evidence goes on the **signature certificate page** of the sealed PDF and stays with the contract for the life of your account. ### Sealing When every signer has signed, the final PDF is produced with the certificate page, and its **SHA-256 hash** is stored. Every time anyone downloads the signed PDF, from the app, the API or the client portal, the file's hash is checked against the stored one. If they ever differed, the download would be blocked and an alert raised, so the PDF you or your client download is always the one that was signed. A signed contract never changes. If the terms need to change, you create a **new version**, which must be signed again by every signer. The old version stays on record, marked as superseded. ## Create and send a contract in the web app Open Contracts **Contracts** is in the app sidebar under Documents. Create a new contract and pick the client. Choose a template The business and client details are filled in. Review the text and adjust anything specific to this job while it is still a draft. Attach it to an estimate (optional) Attached to an estimate, the contract turns acceptance into **accept and sign**: the client accepts the estimate and signs the contract in one place. If the estimate has a deposit, signing creates the deposit invoice. Send it Send the contract like any other document. The client receives a single-purpose signing link at their email address. Counter-sign When it is your turn in the signer order, a member with permission to sign for the business counter-signs. The contract is then sealed. ## How your client signs Your client opens the signing link (the estimate page) or the [client portal](/clients/portal), reads the contract, ticks the consent text, and **types or draws** their signature. They never need an account or a password. After sealing, the client can download the signed contract from the portal at any time. ## Through the API and MCP [Create a contract](/api-reference/cards-contracts/create-contract) creates a draft. Sending it uses [Send a document](/api-reference/documents/send-document), the same call as for invoices. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/contracts" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: contract-website-redesign" \ -d '{ "business_id": "0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "template_key": "services_agreement", "estimate_document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "signer_order": [{ "role": "client" }, { "role": "business" }] }' ``` `template_key` names one of your contract templates; `estimate_document_id` is optional and attaches the contract to an estimate. The full field list is on the endpoint page. Other endpoints: - [List contracts](/api-reference/cards-contracts/list-contracts) and [Get a contract](/api-reference/cards-contracts/get-contract). - [Counter-sign a contract](/api-reference/cards-contracts/countersign-contract) for the business signature. - [Create a new contract version](/api-reference/cards-contracts/new-contract-version) when the terms change. - [Download a signed contract](/api-reference/cards-contracts/download-signed-contract): the hash is checked on every download. - [Sign a contract](/api-reference/public/sign-contract) is the public call behind the client's signing page. Over MCP, `create_contract` creates a draft contract for your assistant. As with every integration, a person confirms the send. See the [MCP tool reference](/ai/mcp/tools). [Webhooks](/integrations/webhooks) report `contract.sent`, `contract.signed` and `contract.sealed`, so your own systems know when work can start. ## Legal scope Signatures made here are **simple electronic signatures** under the EU's eIDAS regulation, the US ESIGN Act and UETA, and Pakistan's Electronic Transactions Ordinance 2002. The contract screen states this too, so both you and your client know what kind of signature is being made. **Qualified or certified electronic signatures are not offered.** Some documents in some countries require them, or require a handwritten signature or a notary. If you are unsure which kind your contract needs, ask a legal advisor before relying on an electronic signature. ## Rules and edge cases - **Sealed means final.** Content, signer order and the sealed file cannot change once sealed (API error `contract_sealed`). Make a new version instead. - **Out-of-order signing is refused.** Only the next signer in the order can sign. - **Signing links are single-purpose.** A link only lets its holder view and sign that contract, and is sent to the signer's own email address. - **Deposit invoice.** On an estimate with a deposit, the client's signature creates the deposit invoice automatically. See [Deposits and schedules](/payments/schedules). - **Evidence is kept.** Signature evidence and sealed PDFs are kept for the life of the account. If you delete your account, you are warned to export them first. ## Errors | Code | When | | --- | --- | | `contract_sealed` | You tried to change a signed contract. Create a new version. | | `contract_hash_mismatch` | The stored file does not match its hash. The download is blocked and we are alerted. | | `conflict` | The signer is not next in the signer order, or the contract is not in a state that allows the action. | | `token_expired`, `token_revoked` | The client's signing link expired or was revoked. | | `confirmation_required` | A send without `"confirm": true` from a `draft_only` key or token. | | `permission_denied` | Your role lacks the key for the action. | ## Permissions | Action | Permission key | Default roles | | --- | --- | --- | | View contracts and download signed PDFs | `contracts.view` | Owner, Admin, Editor, Viewer, Accountant | | Create contracts and new versions | `contracts.manage` | Owner, Admin, Editor | | Counter-sign for the business | `contracts.sign` | Owner, Admin | | Send a contract | `documents.send` | Owner, Admin, Editor | See [Roles and permissions](/team/roles). ## Related pages Estimates Attach a contract for accept and sign. Deposits and schedules Signing can create the deposit invoice. Client portal Clients sign and download contracts there. Revisions Why sent documents change only by a new version. Source: https://docs.freeinvoiceautomation.com/features/contracts --- # Time tracking Track billable hours with a timer or manual entries, per client, and turn unbilled time into a draft invoice in one step. Time tracking is for anyone who bills by the hour. Start a timer when you begin work, or add an entry afterwards, and each entry is saved against a client with its rate. When it is time to bill, **Invoice unbilled time** turns everything not yet invoiced into a draft invoice, one line per project or per day. Billed entries are then locked and linked to the invoice, so no hour is billed twice and none is forgotten. ## How it works A time entry records: | Field | Meaning | | --- | --- | | Client | Who the work was for. Required. | | Member | Who did the work. | | Project tag | An optional free-text tag, for example "Website redesign", used to group lines on the invoice. | | Description | What you did. | | Start and end, or duration | A timer fills start and end; a manual entry can give just a duration. | | Rate | The hourly rate, resolved when the entry is created and stored on it. | | Billable | Whether the time should be invoiced. Entries are billable by default. | ### Hourly rates You can set an hourly rate at three levels: 1. **Member**: this person's rate. 2. **Client**: what you charge this client. 3. **Business**: the default for the business. When an entry is created, the **most specific** rate that applies is used and **stored on the entry**. Changing a rate later does not change entries you already logged, so the amount you see today is the amount that will be invoiced. ### The timer Start the timer from the app or ask your AI assistant. Each member can have **one running timer** at a time; starting a new one is refused until the running one is stopped. Stopping it saves the entry with its start and end time. ### Projects are tags There is no separate projects module. The project tag is enough to group time on invoices and in lists. Mileage and payroll are not tracked. ## Track time in the web app Open Time **Time** is in the app sidebar, under Work. Start the timer or add an entry Pick the client, add a project tag and description if you like, then start the timer. For past work, add a manual entry with the date and duration instead. Stop the timer The entry is saved with its rate and billable flag. You can still edit it until it is billed. ## Invoice unbilled time Choose Invoice unbilled time Pick the client. You see every billable entry not yet on an invoice. Choose how to group Group by **project** (one line per project tag) or by **day** (one line per day). The business default is set in settings. Review the draft invoice A **draft** invoice is created. Each line's amount is time multiplied by rate, computed exactly in integer minor units. Billable [expenses](/features/expenses) for the same client are added as lines too, with their markup. Send it Send the invoice as usual. The entries are now **locked** and linked to the invoice. Invoicing unbilled time always creates a draft. Nothing is sent to your client until a person confirms the send, unless an automation you set up is allowed to send automatically. ## Through the API and MCP | Action | Endpoint | MCP tool | | --- | --- | --- | | Log a finished entry | [Log time](/api-reference/time-expenses/log-time) | `log_time` | | Start or stop the timer | [Start a timer](/api-reference/time-expenses/start-timer), [Stop the timer](/api-reference/time-expenses/stop-timer) | via `log_time` | | List entries | [List time entries](/api-reference/time-expenses/list-time-entries) | | | Edit or delete an entry | [Update a time entry](/api-reference/time-expenses/update-time-entry), [Delete a time entry](/api-reference/time-expenses/delete-time-entry) | | | See what is not billed | [List unbilled time](/api-reference/time-expenses/list-unbilled-time) | `list_unbilled_time` | | Make the draft invoice | [Invoice unbilled time](/api-reference/time-expenses/invoice-unbilled) | `invoice_unbilled` | ```bash Log time curl -X POST "https://api.freeinvoiceautomation.com/v1/time-entries" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: time-2027-04-12-design" \ -d '{ "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "project_tag": "Website redesign", "description": "Homepage layout", "started_at": "2027-04-12T09:00:00Z", "ended_at": "2027-04-12T11:30:00Z", "billable": true }' ``` ```bash Invoice unbilled time curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/from-unbilled" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: unbilled-acme-april" \ -d '{ "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "group_by": "project" }' ``` With MCP or the [AI agent](/ai/agent/overview) you can simply say what you want: ```text Log 2.5 hours for Acme today on the website redesign. Invoice Acme for last week's time. ``` An [automation](/ai/agent/automations) such as "every Monday, invoice Client X for last week's time entries" uses exactly these capabilities, and creates a draft each week. [Webhooks](/integrations/webhooks) send `time_entry.created` when an entry is saved. ## Rules and edge cases - **Billed entries are locked.** Once an entry is on an invoice, it cannot be edited (API error `time_entry_locked`). To correct billed time, change the invoice by [revision](/invoicing/revisions) or issue a [credit note](/invoicing/credit-notes). - **One running timer per member.** - **Rates are frozen per entry.** The rate stored on an entry is the one used when it is invoiced. - **Non-billable time** is kept for your records and never added to an invoice. - **Your own entries and others'.** With `time.log` you log and edit your own entries; editing or deleting other members' entries needs `time.manage`. ## Errors | Code | When | | --- | --- | | `time_entry_locked` | The entry is billed and linked to an invoice. | | `conflict` | A timer is already running for you. | | `validation_failed` | Missing client, or neither a start and end nor a duration. | | `permission_denied` | Your role lacks the key for the action. | | `business_out_of_scope` | The client belongs to a business you cannot reach. | ## Permissions | Action | Permission key | Default roles | | --- | --- | --- | | View time entries and unbilled time | `time.view` | Owner, Admin, Editor, Viewer, Accountant | | Log time, run the timer, edit your own entries | `time.log` | Owner, Admin, Editor | | Edit or delete other members' entries | `time.manage` | Owner, Admin | | Invoice unbilled time | `documents.create` | Owner, Admin, Editor | See [Roles and permissions](/team/roles). ## Related pages Expenses Bill costs to clients with a markup. Automations Invoice last week's time automatically. Invoices What happens after the draft is created. Line items How invoice lines and amounts work. Source: https://docs.freeinvoiceautomation.com/features/time-tracking --- # Expenses Record what your business spends, keep the receipt with it, and bill costs back to clients with an optional markup on their next invoice. Expenses let you keep a record of what your business spends, with the receipt attached, so you know your real costs. When an expense was for a client, for example materials, travel or a software licence bought for their project, mark it **billable**, add a markup if you charge one, and it goes onto that client's next invoice. With AI, a photo or PDF of a receipt can fill in the details for you. ## What an expense records | Field | Meaning | | --- | --- | | Date | When you spent the money. | | Vendor | Who you paid. | | Category | The kind of cost, from a list that fits your business's country. | | Amount and currency | What you paid, in the currency you paid it in. | | Tax | The tax included, if any, with its tax rate. | | Receipt | A photo or PDF of the receipt, stored privately. | | Billable | Whether to bill it to a client. A billable expense needs a client. | | Markup | An optional percentage added when the expense is billed. | | Notes | Anything you want to remember. | ### Categories Categories come from a list that adapts to your business's country, so the names make sense locally. The starting list covers advertising, bank fees, equipment, meals, office supplies, professional fees, rent, software, travel, utilities and other. Each business uses the category set for its country by default. ### Receipts A receipt can be a **photo or a PDF**. It is stored privately: only members who can see the expense can open it, through a short-lived link. Uploads have size and type limits, and images are re-encoded when they are stored. ### Billable expenses and markup A billable expense waits for the client's next invoice. When you [invoice unbilled time](/features/time-tracking) or create the client's next invoice, it can be added as a line, with the markup applied. Once billed, the expense is linked to that invoice, so it is never billed twice. The markup default can be set per business or per client; it starts at 0. ## Record an expense in the web app Open Expenses **Expenses** is in the app sidebar, under Work. Add the expense Enter the date, vendor, category, amount, currency and tax. Attach the receipt photo or PDF. Bill it to a client (optional) Turn on **billable**, choose the client and add a markup if you charge one. Save The expense is saved. If it is billable, it appears with the client's unbilled work until it goes on an invoice. ## Read a receipt with AI Instead of typing, upload the receipt and ask AI to read it. The date, vendor, amounts and other fields are filled in **for you to check** before you save; nothing is saved without you. - Reading a receipt costs **2 AI credits** on our AI. With [your own AI key](/ai/agent/own-key), it uses your key and no credits. - Team includes 1,000 credits a month. On Free, use your welcome credits, a [credit pack](/billing/credit-packs) or your own key. Without credits or a key, the feature is shown locked. See [Read receipts](/ai/agent/receipts) and [Credits and costs](/ai/agent/credits). ## Through the API and MCP | Action | Endpoint | MCP tool | | --- | --- | --- | | Record an expense | [Record an expense](/api-reference/time-expenses/record-expense) | `record_expense` | | List and read | [List expenses](/api-reference/time-expenses/list-expenses), [Get an expense](/api-reference/time-expenses/get-expense) | | | Change or delete | [Update an expense](/api-reference/time-expenses/update-expense), [Delete an expense](/api-reference/time-expenses/delete-expense) | | | Read a receipt with AI | [Read a receipt](/api-reference/time-expenses/read-receipt) | | | Bill to the client | [Invoice unbilled time](/api-reference/time-expenses/invoice-unbilled) | `invoice_unbilled` | Upload the receipt file first, then pass its id as the receipt. Money is a string of integer minor units, so `"4599"` with `"currency": "EUR"` is 45.99 EUR. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/expenses" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: expense-2027-04-08-fonts" \ -d '{ "business_id": "0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51", "expense_date": "2027-04-08", "vendor": "Type foundry", "category_key": "software", "amount_minor": "4599", "currency": "EUR", "tax_minor": "766", "receipt_attachment_id": "0193f2d0-3a4b-7c5d-8e6f-7a8b9c0d1e2f", "billable": true, "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "markup_percent": "10" }' ``` Over MCP, your assistant can record an expense from what you tell it ("I spent 45.99 EUR on fonts for Acme today, bill it with 10% markup"). [Webhooks](/integrations/webhooks) send `expense.created` when an expense is saved. ## Where expenses go next - **Invoices**: billable expenses become lines on the client's invoice. - **QuickBooks Online and Xero**: billable expenses are pushed with your invoices once [accounting sync](/integrations/quickbooks) is connected. - **Accounting ledger**: expenses post journal entries automatically in the [ledger](/features/ledger). ## Rules and edge cases - **Billable needs a client.** A billable expense without a client is refused. - **Billed expenses are linked.** Once an expense is on an invoice, it stays linked to that invoice and is not offered again. - **No mileage or payroll.** Expenses record money spent; mileage tracking and payroll are not part of the product. - **AI reads, you confirm.** AI only fills in the form; a person always checks and saves. - **Deleted, not destroyed.** Deleting an expense removes it from your lists, but it is kept in the background like other business records. ## Errors | Code | When | | --- | --- | | `validation_failed` | A required field is missing, the amount is not a whole number of minor units, or a billable expense has no client. | | `ai_credits_exhausted` | No AI credits left to read a receipt. Buy a credit pack, upgrade to Team or add your own key. | | `feature_locked` | AI receipt reading is not available on your account without credits or an own key. | | `plan_limit_reached` | Your storage fair-use limit is reached. | | `permission_denied` | Your role lacks `expenses.manage` or `ai.use`. | ## Permissions | Action | Permission key | Default roles | | --- | --- | --- | | View expenses and receipts | `expenses.view` | Owner, Admin, Editor, Viewer, Accountant | | Record, change, delete expenses and upload receipts | `expenses.manage` | Owner, Admin, Editor | | Read a receipt with AI | `ai.use` | Owner, Admin, Editor | | Bill expenses on an invoice | `documents.create` | Owner, Admin, Editor | See [Roles and permissions](/team/roles). ## Related pages Time tracking Bill hours and expenses together. Read receipts Let AI fill in the details from a photo or PDF. Accounting ledger Expenses posted to your books automatically. AI credits What a receipt read costs and how credits work. Source: https://docs.freeinvoiceautomation.com/features/expenses --- # Saved views Save a list's filters, sort order and columns under a name, so you can come back to the same view of your invoices or clients in one click. A saved view remembers how you like to look at a list. If you check "overdue invoices for this business, largest first" every Monday, or keep a list of clients with only the columns you care about, save it once and open it again with one click. A saved view stores the list's **filters**, **sort** and **columns** under a name you choose, and it is yours: each member keeps their own views. ## What a view stores | Part | Example | | --- | --- | | **Name** | "Overdue, largest first" | | **List** | Which list the view belongs to, for example documents or clients. | | **Filters** | Status, client, dates, currency and the other filters the list offers, for example status `overdue`. | | **Sort** | The column and direction, for example total, highest first. | | **Columns** | Which columns are shown, and in what order. | | **Business** | Optionally, the business the view is for. | A view stores the **question**, not the answer. Opening it runs the filters again on today's data, so an "Overdue" view always shows what is overdue now, including invoices that became overdue since you saved it. ## Save a view in the web app Open a list For example **Invoices**. Lists have a toolbar with search, filter chips, saved views and a column picker. Set it up the way you want Add filters (status, client, date), choose the sort order, and use the column picker to choose the columns. Save as a view Use **saved views** in the toolbar to save the current list as a view, and give it a name. Open it later Your views appear in the saved views list on that page. Pick one to apply its filters, sort and columns at once. To change a view, open it, adjust the list and save it again under the same name. To remove one, delete it from the saved views list. Deleting a view never deletes any document or client; it only forgets the settings. ## Saved views through the API Views are available through the API, so a custom dashboard or script can reuse the same filters as the app. There is no MCP tool or CLI command for views. | Action | Endpoint | | --- | --- | | List your views | [List saved views](/api-reference/cards-contracts/list-saved-views) | | Create or change a view | [Save a view](/api-reference/cards-contracts/upsert-saved-view) (`POST /v1/saved-views`, `PATCH /v1/saved-views/{id}`) | | Delete a view | [Delete a saved view](/api-reference/cards-contracts/delete-saved-view) | ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/saved-views" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: view-overdue-largest" \ -d '{ "entity": "documents", "name": "Overdue, largest first", "business_id": "0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51", "filters": { "type": "invoice", "status": ["overdue"] }, "sort": "-total_minor", "columns": ["number", "client", "due_date", "total_minor", "balance_minor", "status"] }' ``` The example uses the filter names of the list endpoints, such as `status`, `type`, `client_id`, `currency`, `due_before` and `due_after`, and their sort syntax, where a leading `-` means descending. The exact shape of `filters`, `sort` and `columns` is on the endpoint page. To use a view from your own code, read it with List saved views and pass its filters and sort to the list endpoint, for example [List documents](/api-reference/documents/list-documents). See [Pagination and filters](/api-reference/pagination). ## Rules and edge cases - **Personal.** Your saved views are visible to you only. Other members of the account keep their own. - **Permissions still apply.** A view never shows anything you are not allowed to see. If you lose access to a business, a view filtered to that business shows nothing from it. - **Live results.** Views run on current data every time you open them. - **Only settings are saved.** A view holds no copy of your data. For totals and CSV or PDF exports, use [Reports](/features/reports). ## Errors | Code | When | | --- | --- | | `validation_failed` | Missing name, an unknown list, or a filter or column the list does not support. | | `not_found` | The view does not exist or belongs to another member. | | `permission_denied` | Your role lacks `documents.view`. | ## Permissions | Action | Permission key | | --- | --- | | List, create, change and delete your saved views | `documents.view` | Every preset role (Owner, Admin, Editor, Viewer and Accountant) has `documents.view`, so every member can save views. See [Roles and permissions](/team/roles). ## Related pages Document statuses The statuses you can filter by. Reports Totals, aging and exports. Pagination and filters Filter and sort syntax for list endpoints. Clients and contacts The client list you can save views of. Source: https://docs.freeinvoiceautomation.com/features/saved-views --- # Accounting ledger A double-entry ledger per business, posted automatically from your invoices, payments and expenses, with locked periods and standard accounting reports. The accounting ledger keeps proper double-entry books for each business, right next to your invoices. It is for small businesses that do not use QuickBooks or Xero: your invoices, payments, credit notes, refunds, late fees and expenses post journal entries automatically, you or your accountant add manual entries when needed, and you get a trial balance, profit and loss, balance sheet and general ledger whenever you want them. Posted entries can never be edited or deleted, so the books stay auditable. ## How it works ### Chart of accounts When you turn the ledger on for a business, it gets a **chart of accounts** from a country template, which you can then edit. The generic small-business template comes first; templates for Pakistan, the UK, India and the EU follow, each checked by an accountant before release. Some accounts have a role in automatic posting, for example accounts receivable, revenue, tax payable, bank, payment-provider clearing, payment-provider fees, client credit and late-fee income. Every template includes them, so automatic entries always have somewhere to go. ### Automatic journal entries Your everyday work posts to the ledger for you: | What happens | What is posted | | --- | --- | | An invoice is issued | Accounts receivable, revenue and tax payable | | A payment arrives | Bank or payment-provider clearing, and accounts receivable | | A credit note is issued | A balanced entry for the credited amount | | A refund is made | A balanced entry for the refund | | A late fee is applied | A balanced entry for the fee | | An expense is recorded | A balanced entry for the expense | | A payment provider takes a fee (Stripe) | The fee as an expense | Each source event posts **once**: if the same event is processed twice, no duplicate entry is created. ### Manual journal entries A member with the `ledger.journal` permission can post manual entries, for example opening balances or an adjustment from your accountant. **Every entry must balance**: total debits must equal total credits, or the entry is rejected. ### Append-only books Posted entries are never edited or deleted, by anyone. To correct a mistake, post a **reversing entry**, which cancels the original, and then post the correct entry. The original and its reversal both stay in the books, so anyone can see what changed and when. An entry can be reversed only once. ### Locked periods When a month is closed, **lock** the period. Nothing can post into a locked period: not a manual entry, not an automatic entry, not a reversal. Periods never overlap. Unlocking a period is treated as a destructive action and needs confirmation. ### Currencies Ledger lines are kept in the business's base currency. For a document in another currency, the line also keeps the original amount and currency with the exchange rate frozen on that document, so the books match your invoices exactly. ## Set up and use the ledger in the web app Open Ledger **Ledger** is in the app sidebar. Turn it on for the business you have selected. Apply a chart of accounts Choose the template for your country, or the generic one. Then rename, add or archive accounts to fit your business. Let entries post From now on, your invoices, payments, credit notes, refunds, late fees and expenses post entries automatically. Add manual entries when needed Enter the date, a description and the lines. The entry is saved only if debits equal credits. Lock closed periods After a month is reviewed, lock it so nothing can change its figures. Run reports Open trial balance, profit and loss, balance sheet or general ledger, on cash or accrual basis, and export to CSV or PDF. ## Bank reconciliation Import a bank statement as **CSV or OFX** and match each line to a payment, an expense or a journal entry. Lines show as `unmatched`, `matched` or `ignored`, so you can see at a glance what still needs attention. The same statement line is never imported twice for a bank account. Live bank feeds are not offered. ## Reports | Report | Report name | | --- | --- | | Trial balance | `trial-balance` | | Profit and loss | `profit-and-loss` | | Balance sheet | `balance-sheet` | | General ledger | `general-ledger` | Each report runs on **cash** or **accrual** basis for a date range, and exports to CSV or PDF. The trial balance always balances. The ledger produces reports your accountant can use. It does not prepare or file tax returns for any country. ## Through the API and MCP ```bash Post a manual entry curl -X POST "https://api.freeinvoiceautomation.com/v1/journal-entries" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: je-opening-balance-2027" \ -d '{ "business_id": "0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51", "entry_date": "2027-06-01", "description": "Opening bank balance", "lines": [ { "ledger_account_id": "0193f3a0-1b2c-7d3e-8f4a-5b6c7d8e9f01", "debit_minor": "500000", "credit_minor": "0", "currency": "USD" }, { "ledger_account_id": "0193f3a0-1b2c-7d3e-8f4a-5b6c7d8e9f02", "debit_minor": "0", "credit_minor": "500000", "currency": "USD" } ] }' ``` ```bash Profit and loss curl "https://api.freeinvoiceautomation.com/v1/ledger/reports/profit-and-loss?business_id=0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51&basis=accrual&from=2027-01-01&to=2027-06-30" \ -H "Authorization: Bearer $FIA_API_KEY" ``` Every line has exactly one side: a debit or a credit, never both. Ledger report queries take `basis=cash|accrual`, `from`, `to` and `format=json|csv|pdf`. | Action | Endpoint | | --- | --- | | Chart of accounts | [List ledger accounts](/api-reference/ledger/list-ledger-accounts), [Upsert a ledger account](/api-reference/ledger/upsert-ledger-account), [Apply a chart template](/api-reference/ledger/apply-coa-template) | | Journal entries | [List journal entries](/api-reference/ledger/list-journal-entries), [Post a journal entry](/api-reference/ledger/post-journal-entry), [Reverse a journal entry](/api-reference/ledger/reverse-journal-entry) | | Periods | [List accounting periods](/api-reference/ledger/list-accounting-periods), [Lock a period](/api-reference/ledger/lock-period), [Unlock a period](/api-reference/ledger/unlock-period) | | Reports | [Get a ledger report](/api-reference/ledger/get-ledger-report) | | Bank reconciliation | [Import a bank statement](/api-reference/ledger/import-bank-statement), [List bank transactions](/api-reference/ledger/list-bank-transactions), [Match a bank transaction](/api-reference/ledger/match-bank-transaction) | Reversing an entry and unlocking a period need `"confirm": true`. Over MCP, `get_ledger_report` gives your assistant read-only access to the four reports ("What was my profit in the first half of 2027?"). [Webhooks](/integrations/webhooks) send `journal_entry.posted` and `accounting_period.locked`. ## Ledger, QuickBooks and Xero If your accountant already keeps your books in [QuickBooks Online](/integrations/quickbooks) or [Xero](/integrations/xero), use the sync instead; you do not need this ledger. The two can also run side by side. ## Errors | Code | When | | --- | --- | | `journal_unbalanced` | Debits do not equal credits. | | `period_locked` | The entry date falls in a locked period. | | `entry_already_reversed` | The entry was reversed before. | | `confirmation_required` | A reversal or unlock without `"confirm": true`. The response includes the preview. | | `validation_failed` | A line with both or neither side, an unknown account, or a bad date. | | `permission_denied` | Your role lacks the key for the action. | ## Permissions | Action | Permission key | Default roles | | --- | --- | --- | | View accounts, entries, periods, reports and bank lines | `ledger.view` | Owner, Admin, Accountant | | Post and reverse manual journal entries | `ledger.journal` | Owner, Admin | | Edit the chart of accounts, lock and unlock periods, import and match bank statements | `ledger.manage` | Owner, Admin | The Accountant role is view-only in the ledger: it can read everything and run reports, but not post. See [Roles and permissions](/team/roles). ## Related pages Reports Aged receivables, revenue and tax collected. Expenses Expenses post to the ledger automatically. QuickBooks Online Send your data to QuickBooks instead. Audit log Who did what, alongside the books. Source: https://docs.freeinvoiceautomation.com/features/ledger --- # Members and invitations Invite people into your account, choose their role and which businesses they can reach, and manage everyone from Settings > Team. Members are the people who work inside your account with you: a bookkeeper, a colleague who sends invoices, an accountant who only needs to look. Each member signs in with their own login, gets a role that decides what they can do, and a business scope that decides which of your businesses they can see. You invite them by email from **Settings > Team**, or through the API. ## How membership works Your account is the unit that holds your businesses, your plan and your billing. When you sign up, an account is created on the Free plan with you as its **Owner**. Everyone else you bring in becomes a member of that account. A membership has three parts: | Part | What it means | | --- | --- | | **Person** | One login, identified by one email address. | | **Role** | Owner, Admin, Editor, Viewer, Accountant, or a custom role on Team. The role is a list of permission keys. See [Roles and permissions](/team/roles). | | **Business scope** | Either **all businesses** in the account, or a **list** of specific businesses. The scope applies on top of the role for every role except Owner. | So an Editor scoped to one business can create and send invoices for that business only. They cannot see the other businesses in the account at all, in the app, the API, MCP or the AI agent. ### One login, many accounts A person signs in once, with one email, and that login can be a member of many accounts at the same time. Each membership has its own role and its own business scope. - **One member, several businesses:** inside one account, a membership can cover one, several or all businesses. - **Your own account and someone else's:** the same email can be the Owner of their own account and a member of a client's or employer's account. - **The switcher:** the account and business switcher at the top of the app lists every account and business you can reach, grouped by account, with your role shown. Permissions, plan limits, billing, branding and the audit log always follow the account you are viewing, never the person. Deactivating someone in one account never affects their other memberships or their own account. ## Plan limits | | Free | Team | | --- | --- | --- | | Owner | 1 | 1 or more | | Other members | 1 free view-only **Accountant** | **5 members included**, then $2/month ($20/year) per extra member; regional price $1/month ($10/year) | | Roles | Owner and Accountant, both fixed | Every role except Owner editable; custom roles | On **Free** you can invite one member, and that member must have the Accountant role, which is view-only. Trying to invite a second member, or a member with any other role, shows the Team upgrade instead. On **Team**, five members are included. When you invite a sixth, the app shows you the extra-member price before the invitation is sent. The extra seat is added to your Team subscription and billed with proration. See [Member seats](/billing/members). Every number here (members included, the extra-member price) is a plan setting we can change without a release. The pricing page and **Settings > Billing** always show the current values. ## Invite a member Team"> You need the `team.manage` permission. The Owner and Admin have it by default. Choose Invite Enter the person's email address. Pick a role On Free, only **Accountant** is available. On Team, pick any role except Owner, including your custom roles. Pick the business scope Choose **All businesses**, or tick the businesses this person should reach. New businesses you add later are included automatically only when the scope is **All businesses**. Send the invitation The person receives an invitation link. When they accept it, they sign in (or create a login) and the membership becomes active with the role and scope you chose. If they already have a login, the new account simply appears in their switcher. Invitation links are single-purpose, stored only as a hash, and expire. Until it is accepted, you can **revoke** an invitation from the same screen; the link then stops working. ## Change a member's role or scope Open **Settings > Team**, choose the member, and change their role, their business scope, or both. The change applies on the member's next request: permissions are read on every request, not cached in the session, so there is no need for them to sign out. The Owner's membership cannot be changed this way. An account always keeps at least one Owner; a change that would leave it without one is refused with `last_owner`. To hand the account to someone else, use [Transfer ownership](/account/ownership). ## Deactivate or remove Two separate actions end a member's access: - **Deactivate** ends access at once but keeps the membership, so you can reactivate it later. - **Remove** ends the membership. Their work stays and is still attributed to them. Both are available on every plan and are described in [Deactivate and remove](/team/deactivate). ## Through the API Every member action is a capability, so the API, CLI and AI agent can do the same as the web app, with the same permission checks. ```bash Invite a member curl -X POST "https://api.freeinvoiceautomation.com/v1/invitations" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: invite-sam-2027-01-04" \ -d '{ "email": "sam@example.com", "role_id": "0193f2a1-4d2e-7a10-8b3c-5e6f7a8b9c01", "business_scope": "list", "business_ids": ["0193f2a1-0b0b-7c4e-9a10-2d3e4f5a6b70"] }' ``` ```bash List members curl "https://api.freeinvoiceautomation.com/v1/members?limit=50" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ```bash Change role and scope curl -X PATCH "https://api.freeinvoiceautomation.com/v1/members/0193f2a1-7a7a-7b1c-8d2e-3f4a5b6c7d81" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: member-sam-scope-all" \ -d '{ "business_scope": "all" }' ``` `business_ids` is used only when `business_scope` is `list`. Get role ids from [List roles](/api-reference/team/list-roles). | Capability | Route | Permission | | --- | --- | --- | | [List members](/api-reference/team/list-members) | `GET /v1/members` | `settings.view` | | [Invite a member](/api-reference/team/invite-member) | `POST /v1/invitations` | `team.manage` | | [Revoke an invitation](/api-reference/team/revoke-invitation) | `DELETE /v1/invitations/{id}` | `team.manage` | | [Update a member](/api-reference/team/update-member) | `PATCH /v1/members/{id}` | `team.manage` | | [Deactivate a member](/api-reference/team/deactivate-member) | `POST /v1/members/{id}/deactivate` | `team.manage` | | [Reactivate a member](/api-reference/team/reactivate-member) | `POST /v1/members/{id}/reactivate` | `team.manage` | | [Remove a member](/api-reference/team/remove-member) | `DELETE /v1/members/{id}` | `team.manage` | An API key acts for one account only. If your login belongs to several accounts, create a key in each account you want to automate. See [Authentication](/api-reference/authentication). ## Rules - Only the Owner, or a member whose role holds `team.manage`, can invite, change, deactivate or remove members. - The Owner can never be deactivated or removed. - On Free, the only other member is one view-only Accountant. - Every invite, change, deactivation, reactivation and removal writes an [audit log](/team/audit-log) entry, on every plan. - API keys and MCP tokens a member created can never do more than that member's **current** role allows. If you narrow their role, their keys narrow too on the next call. ## Errors | Code | HTTP | When | | --- | --- | --- | | `permission_denied` | 403 | Your role does not hold `team.manage` (or `settings.view` to list members). | | `plan_limit_reached` | 403 | Free already has its one Accountant, or the invite is for a role other than Accountant on Free. | | `feature_locked` | 403 | A Team-only feature, such as a custom role, was requested on Free. | | `last_owner` | 409 | The change would leave the account without an Owner. | | `business_out_of_scope` | 403 | The request names a business that is outside your own business scope. | | `member_deactivated` | 403 | The member is deactivated in this account. | | `not_found` | 404 | The member or invitation does not exist in this account. | The full list is on [Errors](/api-reference/errors). ## Related pages Roles and permissions The five presets and every permission key they hold Deactivate and remove End a member's access at once, or for good Member seats Included members and the price of extra seats on Team Audit log See who did what in your account Source: https://docs.freeinvoiceautomation.com/team/members --- # Roles and permissions Every action needs a permission key. Roles are lists of keys; the five presets, what each one can do, and how roles apply on every surface. A role decides what a member can do in your account. Behind every button, API route, MCP tool, CLI command and AI agent tool there is one **permission key**, written `module.action` (for example `documents.void`). A role is simply a named list of those keys. Each account starts with five roles: **Owner**, **Admin**, **Editor**, **Viewer** and **Accountant**. ## How permissions work - **No action without a key.** Every capability declares the key it needs. There are 48 keys across every module, from `businesses.view` to `account.delete`. The full catalogue is on [Permission keys](/api-reference/permissions). - **Checked everywhere, the same way.** The check runs in the API, so the web app, REST API, MCP server, CLI, store connectors and the AI agent all refuse the same things. The web app also hides what your role cannot do. - **Read on every request.** Permissions are loaded per request, not stored in your session. When a role changes, the next request already uses the new keys. - **Business scope on top.** Each member also has a business scope (all businesses, or a list). The role says what they can do; the scope says where. The Owner always reaches every business. - **Keys and tokens follow their creator.** An API key or MCP token carries scopes from the same list, but at call time it can only do what both its scopes and its creator's **current** permissions allow. If the creator loses a key, or leaves the account, their API keys and MCP tokens lose it on the next request. ## The five presets | Role | Meant for | In short | | --- | --- | --- | | **Owner** | The person who owns the account | Every permission. Fixed: cannot be edited or deleted. The only role that can delete the account, export everything, manage billing by default, or transfer ownership. | | **Admin** | A partner or office manager | Everything except billing, account export and account deletion. Can manage the team and roles. | | **Editor** | Someone who does the day-to-day invoicing | Creates, edits and sends documents, records payments, manages saved items, templates and reminders. Cannot void, credit, refund or change settings. | | **Viewer** | Someone who only needs to look | Read-only across documents, clients, payments, contracts, time, expenses and reports. | | **Accountant** | Your bookkeeper or accountant | Read-only finance access plus report exports and the ledger view. This is the one extra seat on the Free plan. | ## Default permission matrix This table is the default set of keys each preset gets when your account is created, and what **Reset to default** restores. On Team, every role except Owner can be edited, so the roles in your account may differ from this table. The roles in **Settings > Team > Roles** are always the source of truth. | Key | Owner | Admin | Editor | Viewer | Accountant | | --- | --- | --- | --- | --- | --- | | `businesses.view` | Y | Y | Y | Y | Y | | `businesses.manage` | Y | Y | - | - | - | | `documents.view` | Y | Y | Y | Y | Y | | `documents.create` | Y | Y | Y | - | - | | `documents.edit` | Y | Y | Y | - | - | | `documents.send` | Y | Y | Y | - | - | | `documents.void` | Y | Y | - | - | - | | `documents.credit` | Y | Y | - | - | - | | `clients.view` | Y | Y | Y | Y | Y | | `clients.create` / `clients.edit` | Y | Y | Y | - | - | | `clients.delete` | Y | Y | - | - | - | | `items.view` | Y | Y | Y | Y | Y | | `items.manage` | Y | Y | Y | - | - | | `payments.view` | Y | Y | Y | Y | Y | | `payments.record` | Y | Y | Y | - | - | | `payments.refund` | Y | Y | - | - | - | | `payments.auto_charge` | Y | Y | - | - | - | | `rails.manage` | Y | Y | - | - | - | | `senders.manage` | Y | Y | - | - | - | | `templates.manage` | Y | Y | Y | - | - | | `reminders.manage` | Y | Y | Y | - | - | | `late_fees.manage` | Y | Y | - | - | - | | `late_fees.waive` | Y | Y | Y | - | - | | `contracts.view` | Y | Y | Y | Y | Y | | `contracts.manage` | Y | Y | Y | - | - | | `contracts.sign` | Y | Y | - | - | - | | `time.view` | Y | Y | Y | Y | Y | | `time.log` | Y | Y | Y | - | - | | `time.manage` | Y | Y | - | - | - | | `expenses.view` | Y | Y | Y | Y | Y | | `expenses.manage` | Y | Y | Y | - | - | | `reports.view` | Y | Y | Y | Y | Y | | `reports.export` | Y | Y | Y | - | Y | | `accounting.manage` | Y | Y | - | - | - | | `ledger.view` | Y | Y | - | - | Y | | `ledger.journal` | Y | Y | - | - | - | | `ledger.manage` | Y | Y | - | - | - | | `ai.use` | Y | Y | Y | - | - | | `ai.manage` | Y | Y | - | - | - | | `settings.view` | Y | Y | Y | Y | Y | | `settings.manage` | Y | Y | - | - | - | | `team.manage` | Y | Y | - | - | - | | `integrations.manage` | Y | Y | - | - | - | | `audit.view` | Y | Y | - | - | - | | `billing.manage` | Y | - | - | - | - | | `account.export` | Y | - | - | - | - | | `account.delete` | Y | - | - | - | - | Business scope (all businesses or a list) applies on top of the role for every role except Owner. Transferring ownership is not a key in this table. It is checked against the Owner role itself, so it can never be ticked on another role. See [Transfer ownership](/account/ownership). ## The Owner role is fixed - The Owner holds **every** permission key. - The Owner role cannot be edited, reset or deleted (`role_locked`). - An account always has at least one Owner (`last_owner`). - `account.delete` and ownership transfer are Owner-only. They cannot be ticked on any other role (`permission_not_grantable`). `account.export` is also Owner-only by default. - The Owner can never be deactivated or removed. ## Nobody can grant what they do not have Only a member whose role holds `team.manage` can create, edit, delete or reset roles, and even then they cannot tick a key they do not hold themselves. An Admin, for example, does not hold `billing.manage` by default, so an Admin cannot give `billing.manage` to any role, including their own. The same check stops a member from changing the role they are on in a way that adds keys. Refused changes return `permission_not_grantable` and nothing is saved. ## Plan differences | | Free | Team | | --- | --- | --- | | Roles in the account | Owner and one view-only Accountant, both fixed | Owner (fixed) plus Admin, Editor, Viewer, Accountant and any custom roles | | Edit a preset (tick or untick keys) | - | Yes, every role except Owner | | Reset a preset to default | - | Yes | | Create custom roles | - | Yes, from scratch or by duplicating a role. See [Custom roles](/team/custom-roles). | | Delete a role | - | Yes, except Owner, once no members are on it | On Free, **Settings > Team > Roles** shows the Owner and the Accountant as fixed, and role editing as a locked Team feature. Calls to the role capabilities return `feature_locked`. If a Team account goes back to Free (for example after a [failed renewal](/billing/failed-renewal)), custom and edited roles are kept but inactive, and every member except the Owner is deactivated. On re-upgrade, the roles come back as they were. ## Edit a preset role Team > Roles"> You need `team.manage` and a Team plan. Choose a role Admin, Editor, Viewer, Accountant or a custom role. The Owner role is shown but cannot be opened for editing. Tick or untick permission keys Keys you do not hold yourself cannot be ticked. Save The change applies to every member on that role from their next request, and an audit entry records the old and new key lists. To undo your edits on a preset, choose **Reset to default**. Its keys go back to the defaults in the matrix above, and the reset is audited. ## Through the API ```bash List roles curl "https://api.freeinvoiceautomation.com/v1/roles" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ```bash Remove a key from Editor curl -X PATCH "https://api.freeinvoiceautomation.com/v1/roles/0193f2a1-4d2e-7a10-8b3c-5e6f7a8b9c02" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: editor-no-send" \ -d '{ "permissions": [ "businesses.view", "documents.view", "documents.create", "documents.edit", "clients.view", "clients.create", "clients.edit", "items.view", "items.manage", "payments.view", "payments.record", "templates.manage", "reminders.manage", "late_fees.waive", "contracts.view", "contracts.manage", "time.view", "time.log", "expenses.view", "expenses.manage", "reports.view", "reports.export", "ai.use", "settings.view" ] }' ``` ```bash Reset a preset curl -X POST "https://api.freeinvoiceautomation.com/v1/roles/0193f2a1-4d2e-7a10-8b3c-5e6f7a8b9c02/reset" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Idempotency-Key: editor-reset" ``` ```json Role (response) { "data": { "id": "0193f2a1-4d2e-7a10-8b3c-5e6f7a8b9c02", "name": "Editor", "preset_key": "editor", "is_owner": false, "is_active": true, "permissions": ["businesses.view", "documents.view", "documents.create", "..."] } } ``` `preset_key` is `owner`, `admin`, `editor`, `viewer` or `accountant` for presets, and `null` for custom roles. | Capability | Route | Permission | | --- | --- | --- | | [List roles](/api-reference/team/list-roles) | `GET /v1/roles` | `team.manage` | | [Create a role](/api-reference/team/create-role) | `POST /v1/roles` | `team.manage` (Team) | | [Update a role](/api-reference/team/update-role) | `PATCH /v1/roles/{id}` | `team.manage` (Team) | | [Delete a role](/api-reference/team/delete-role) | `DELETE /v1/roles/{id}` | `team.manage` (Team) | | [Reset a role](/api-reference/team/reset-role) | `POST /v1/roles/{id}/reset` | `team.manage` (Team, presets only) | ## Errors | Code | HTTP | When | | --- | --- | --- | | `permission_denied` | 403 | Your role lacks the key the action needs, for example `team.manage`. | | `scope_missing` | 403 | The API key or MCP token lacks the scope. | | `feature_locked` | 403 | Role editing or custom roles on the Free plan. | | `permission_not_grantable` | 403 | The change would grant a key you do not hold, or an Owner-only key to another role. | | `role_locked` | 409 | Someone tried to edit, reset or delete the Owner role. | | `role_in_use` | 409 | The role still has members. Move them first. | | `last_owner` | 409 | The change would leave the account without an Owner. | ## Related pages Custom roles Build your own role from scratch or from a copy Members and invitations Give people a role and a business scope Permission keys The full catalogue of 48 keys, with what each one allows Audit log Every role change is recorded with the old and new keys Source: https://docs.freeinvoiceautomation.com/team/roles --- # Custom roles On Team, create your own roles from scratch or by duplicating an existing one, pick exactly the permission keys they hold, and give them to members. The five preset roles cover most teams, but not every team works the same way. On the Team plan you can create **custom roles**: a role with its own name and exactly the permission keys you choose. You might make a "Sales" role that can create and send invoices but never record payments, or a "Collections" role that can send reminders and waive late fees but cannot create documents. Custom roles work exactly like presets. They are checked on every surface (web app, API, MCP, CLI, connectors and the AI agent), they combine with each member's business scope, and every change to them is audited. ## Before you start - You need the **Team** plan. On Free, the account has only the fixed Owner and one view-only Accountant, and creating a role returns `feature_locked`. See [Upgrade to Team](/billing/team). - You need a role that holds `team.manage`. The Owner and Admin presets have it by default. - You can only tick keys **you hold yourself**. Nobody can grant a permission they do not have. ## Two ways to create a role | Method | When to use it | | --- | --- | | **From scratch** | You want a small, precise role. You start with no keys and tick only what is needed. | | **Duplicate a role** | You want something close to an existing role. The copy starts with that role's keys, and you tick or untick from there. You can duplicate a preset or another custom role. | A custom role has no preset behind it, so it has no **Reset to default** action. Presets do. ## Create a custom role in the web app Team > Roles"> The list shows the Owner (fixed), the four editable presets and any custom roles. Choose New role, or Duplicate on an existing role Duplicating copies the source role's keys into the new role. The source role is not changed. Name the role Role names are unique in your account, ignoring upper and lower case. "Sales" and "sales" count as the same name. Tick the permission keys Keys are grouped by module: businesses, documents, clients, items, payments, rails, senders, templates, reminders, late fees, contracts, time, expenses, reports, accounting, ledger, AI, settings, team, integrations, audit and billing. Keys you do not hold yourself cannot be ticked. `account.delete` and ownership transfer never appear as options because they belong only to the Owner. Save, then give the role to members The role appears in the role picker in **Settings > Team** for invitations and for existing members. See [Members and invitations](/team/members). ## Choosing keys well A few patterns make roles easier to live with: - **Pair a view key with every action key.** Action keys such as `documents.send` are most useful together with the matching view key (`documents.view`), so the member can see what they act on. Most modules have a `.view` key; include it. - **Keep money-moving keys narrow.** `payments.refund`, `payments.auto_charge`, `documents.void` and `documents.credit` move money or change records your clients have already seen. Give them only to people who need them. - **Use business scope for "where".** Do not create one role per business. Create one role for the job and set each member's business scope to the businesses they work on. - **Think about API keys.** A member's API keys and MCP tokens can never do more than the member's current role allows. If you give someone a narrow role, their automations are narrow too. The full list of keys, with what each one unlocks, is on [Permission keys](/api-reference/permissions). The defaults of each preset are on [Roles and permissions](/team/roles). ## Edit or delete a custom role Open the role in **Settings > Team > Roles**, tick or untick keys, and save. Members on that role get the new permissions on their next request, with no need to sign in again. Their API keys and MCP tokens follow on their next call. To delete a role, first move every member on it to another role. A role that still has members is refused with `role_in_use`. Once it is empty, delete it. The Owner role can never be edited or deleted. ## Through the API Create a role from scratch by sending a name and a list of keys, or duplicate one with `duplicate_of`. ```bash From scratch curl -X POST "https://api.freeinvoiceautomation.com/v1/roles" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: role-collections" \ -d '{ "name": "Collections", "permissions": [ "businesses.view", "documents.view", "clients.view", "payments.view", "reminders.manage", "late_fees.waive" ] }' ``` ```bash Duplicate Viewer curl -X POST "https://api.freeinvoiceautomation.com/v1/roles" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: role-viewer-plus" \ -d '{ "name": "Viewer plus exports", "duplicate_of": "0193f2a1-4d2e-7a10-8b3c-5e6f7a8b9c03" }' ``` ```json Created role (201) { "data": { "id": "0193f2b0-1c2d-7e3f-8a4b-5c6d7e8f9a10", "name": "Collections", "preset_key": null, "is_owner": false, "is_active": true, "permissions": [ "businesses.view", "documents.view", "clients.view", "payments.view", "reminders.manage", "late_fees.waive" ] } } ``` Then change the copy with [Update a role](/api-reference/team/update-role), and assign it with [Update a member](/api-reference/team/update-member) by setting the member's `role_id`. | Capability | Route | Notes | | --- | --- | --- | | [Create a role](/api-reference/team/create-role) | `POST /v1/roles` | From scratch, or `{ "duplicate_of": role_id }` | | [Update a role](/api-reference/team/update-role) | `PATCH /v1/roles/{id}` | Not the Owner role | | [Delete a role](/api-reference/team/delete-role) | `DELETE /v1/roles/{id}` | Destructive: send `"confirm": true` over the API; refused while members are on it | | [List roles](/api-reference/team/list-roles) | `GET /v1/roles` | All roles in the account | All four need `team.manage` and a Team plan. ## What happens on a downgrade If your account moves from Team back to Free, for example after a [failed renewal](/billing/failed-renewal), custom and edited roles are **kept but inactive**. Every member except the Owner is deactivated, and the Owner can reactivate one member into the free view-only Accountant seat. When you upgrade again, your custom roles come back as they were. Deactivated members are not restored automatically: the Owner reactivates each one in **Settings > Team**, and they create new API keys. ## Audit Every create, update, duplicate and delete of a role writes an entry in the [audit log](/team/audit-log), with who made the change and the old and new key lists. Entries are written on every plan; viewing them is a Team feature. ## Errors | Code | HTTP | When | | --- | --- | --- | | `feature_locked` | 403 | The account is on Free. | | `permission_denied` | 403 | Your role does not hold `team.manage`. | | `permission_not_grantable` | 403 | You ticked a key you do not hold, or an Owner-only key. | | `role_in_use` | 409 | You tried to delete a role that still has members. | | `role_locked` | 409 | You tried to change the Owner role. | | `validation_failed` | 422 | For example, an unknown permission key or a missing name. | | `confirmation_required` | 428 | A delete over the API without `"confirm": true`. | ## Related pages Roles and permissions Presets, the default matrix and the rules every role follows Members and invitations Assign roles and business scope to people Permission keys Every key and what it allows Upgrade to Team Roles, the audit log and more members Source: https://docs.freeinvoiceautomation.com/team/custom-roles --- # Deactivate and remove End a member's access at once with deactivate, bring them back with reactivate, or end the membership for good with remove. Their work always stays. When someone leaves, changes job, or should not have access for a while, you have two actions on every plan. **Deactivate** cuts off access immediately but keeps the membership, so you can turn it back on later. **Remove** ends the membership. In both cases nothing they created is deleted: their documents, comments and audit history stay, still attributed to them. ## Deactivate, reactivate or remove: which one? | | Deactivate | Reactivate | Remove | | --- | --- | --- | --- | | Access to this account | Ends at once | Comes back with their role | Ends | | Sessions in this account | Signed out | They sign in again | Signed out | | Their API keys and MCP tokens for this account | Revoked | Stay revoked; they create new ones | Stop working | | Their work | Kept | Kept | Kept and attributed to them | | Can be undone | Yes, with reactivate | | No. Invite them again if needed | | Member status | `deactivated` | `active` | `removed` | | Email to the member | Yes | Yes | Yes | | Audit entry | Yes | Yes | Yes | Use **deactivate** when you might want the person back: a contractor between projects, someone on leave, or a seat you are reorganising. Use **remove** when the person has left for good. ## What deactivation does Deactivation takes effect at once, not at the end of a session: - **Signed out:** their sessions in this account end. They can no longer open the account from the switcher. - **Keys revoked:** every API key and MCP token they created for this account is revoked. Any automation, CLI script or AI assistant using them stops working on the next call. - **Refused everywhere:** if they, or one of their old keys or tokens, call the API or MCP, the call is refused. A deactivated member gets `member_deactivated`; a revoked key or token gets `unauthenticated`. - **Work kept:** documents, payments they recorded, comments and audit entries stay exactly as they are. - **Other accounts untouched:** their own login still works, and so do their memberships in other accounts and their own account. Deactivation applies only to the account you deactivated them in. ## Who can do it - The **Owner**, or any member whose role holds `team.manage`. - The **Owner can never be deactivated or removed.** To leave an account you own, first [transfer ownership](/account/ownership) or [delete the account](/account/delete). - These actions are available on **every plan**, Free and Team. ## Deactivate a member Team"> You see every member with their role, business scope and status. Choose the member, then Deactivate The app shows what will happen: access ends now, sessions are signed out, their API keys and MCP tokens for this account are revoked, and their work stays. Confirm Deactivation is a destructive action, so it needs your confirmation. The member's status changes to deactivated, they get an email, and an audit entry is written. ## Reactivate a member Open **Settings > Team**, choose the deactivated member, and choose **Reactivate**. They can sign in to the account again with their role and business scope. Their old API keys and MCP tokens **stay revoked**, so they create new ones in **Settings > API keys** if they need them. On **Free**, a member can only be reactivated into the one view-only **Accountant** seat. If that seat is taken, upgrade to Team to bring more people back. ## Remove a member Open **Settings > Team**, choose the member, choose **Remove**, and confirm. The membership ends. Everything they did stays in your account and keeps their name: documents show who created them, and the [audit log](/team/audit-log) keeps their entries. If you want them back later, send a new invitation. ## Deactivation after a downgrade If a Team account moves back to Free, for example when a renewal payment fails and the grace period ends, the account keeps the Owner and one view-only Accountant. **Every other member is deactivated automatically**, with the same effects as a manual deactivation: sessions signed out, API keys and MCP tokens revoked, work kept. The Owner chooses which member stays as the Accountant. When the account upgrades to Team again, deactivated members are **not** restored automatically. The Owner reactivates each one with one click in **Settings > Team**, or removes them. Reactivated members get new sessions and create new API keys. See [Failed renewal and downgrade](/billing/failed-renewal). ## Through the API Deactivate and remove are destructive capabilities, so over the API and MCP the call needs `"confirm": true`. Without it, you get the preview and `confirmation_required`. See [Drafts and confirmations](/api-reference/confirmations). ```bash Deactivate curl -X POST "https://api.freeinvoiceautomation.com/v1/members/0193f2a1-7a7a-7b1c-8d2e-3f4a5b6c7d81/deactivate" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: deactivate-sam-2027-02-01" \ -d '{ "confirm": true }' ``` ```bash Reactivate curl -X POST "https://api.freeinvoiceautomation.com/v1/members/0193f2a1-7a7a-7b1c-8d2e-3f4a5b6c7d81/reactivate" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Idempotency-Key: reactivate-sam-2027-03-01" ``` ```bash Remove curl -X DELETE "https://api.freeinvoiceautomation.com/v1/members/0193f2a1-7a7a-7b1c-8d2e-3f4a5b6c7d81" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: remove-sam" \ -d '{ "confirm": true }' ``` ```json Deactivated member (response) { "data": { "id": "0193f2a1-7a7a-7b1c-8d2e-3f4a5b6c7d81", "user_id": "0193f2a1-1111-7a2b-8c3d-4e5f6a7b8c90", "role_id": "0193f2a1-4d2e-7a10-8b3c-5e6f7a8b9c02", "business_scope": "all", "status": "deactivated", "deactivated_at": "2027-02-01T10:15:00Z", "deactivated_reason": "manual" } } ``` `deactivated_reason` is `manual` when a person deactivated the member and `downgrade` when it happened because the account moved to Free. | Capability | Route | Permission | Hint | | --- | --- | --- | --- | | [Deactivate a member](/api-reference/team/deactivate-member) | `POST /v1/members/{id}/deactivate` | `team.manage` | Destructive | | [Reactivate a member](/api-reference/team/reactivate-member) | `POST /v1/members/{id}/reactivate` | `team.manage` | Write | | [Remove a member](/api-reference/team/remove-member) | `DELETE /v1/members/{id}` | `team.manage` | Destructive | ## Errors | Code | HTTP | When | | --- | --- | --- | | `permission_denied` | 403 | Your role does not hold `team.manage`. | | `plan_limit_reached` | 403 | On Free, the one Accountant seat is already taken. | | `member_deactivated` | 403 | Returned to a deactivated member who tries to use the account. | | `unauthenticated` | 401 | Returned to a revoked API key or MCP token. | | `confirmation_required` | 428 | Deactivate or remove over the API without `"confirm": true`. | | `not_found` | 404 | The member does not exist in this account. | A request to deactivate or remove the Owner is always refused, and the Owner's membership is not changed. ## Related pages Members and invitations Invite people and set their role and business scope Failed renewal and downgrade What happens to members when Team ends Transfer ownership Hand the account to someone else before you leave Audit log Every deactivation, reactivation and removal is recorded Source: https://docs.freeinvoiceautomation.com/team/deactivate --- # Audit log A permanent record of who did what in your account, from people, API keys, AI assistants, the agent, connectors and scheduled jobs. Viewable on Team. The audit log answers "who changed this, and when?". Every action that changes something in your account writes an entry: a person voiding an invoice, an API key creating a client, an AI assistant drafting an estimate, a store connector mirroring a refund, a scheduled job applying a late fee. Entries are written on every plan, from day one. Viewing them in the app and through the API is a Team feature. ## How it works - **Every change is recorded.** Every capability that changes data, every MCP tool that writes, and every background job that acts on your account writes an entry. There is no way to make a change without one. - **Append-only.** Entries are never edited or deleted by anyone in your account. They are kept for the retention period published in our privacy policy. - **Written on every plan.** On Free the entries are recorded but the screen is locked. If you upgrade to Team, you see your history from the start, not just from the day you upgraded. - **Secrets never appear.** If an action changes a secret, such as an SMTP password or a connection key, the entry only says it was changed. The value itself is never stored in the log. ## What an entry contains | Field | What it tells you | | --- | --- | | **Time** | When the action happened (`created_at`). | | **Actor** | Who or what acted (`actor_type` and `actor_id`), see the table below. | | **Action** | The capability or job that ran, for example `void_document`, `update_role` or `deactivate_member`. | | **Target** | What was acted on (`target_type`, `target_id`), for example a document or a role. | | **Business** | The business the change belongs to, when there is one (`business_id`). | | **Changes** | A before and after summary of the fields that changed (`changes`). For a role change this is the old and new list of permission keys. | | **Reason** | The reason given, when the action asks for one, such as a void reason or a late-fee waiver reason. | | **Request** | The `request_id`, IP address and user agent, so support can trace a single request. | ### Actor types | `actor_type` | Meaning | | --- | --- | | `user` | A person signed in to the web app. | | `api_key` | A call made with an API key, including the CLI. | | `mcp_token` | An AI assistant connected through the MCP server. | | `agent` | The built-in AI invoice agent. | | `connector` | A store connection, such as Shopify or WooCommerce, mirroring an order event. | | `client` | Your client, acting through a pay page, estimate page or the portal. | | `system` | A scheduled job, such as marking invoices overdue or running a recurring invoice. | | `platform_support` | Our support team, during a support session you granted. | ### What gets logged Some examples of entries you will see: - Documents: create, edit, send, revise, void, credit note, delete a draft. - Payments: record a payment, refund. - Team: invitation sent or revoked, role or business scope changed, member deactivated, reactivated or removed. - Roles: role created, edited, duplicated, reset to default or deleted, with old and new keys. - Security: sign-in, sign-out, password change, two-factor turned on or off, new device, with time and IP. - Settings, branding, connections, API keys, webhooks, billing and plan changes. Support sessions Our support team can only enter your account when you grant access for a set time. A banner shows while the session is active, it ends by itself when the time is over, and every action in it is written to the audit log with the actor type `platform_support`. ## View the audit log Open the audit log You need a Team plan and a role that holds `audit.view`. The Owner and Admin have it by default, and you can add it to any custom role on Team. Browse the newest entries first Each row shows the actor, action, object and time. Open an entry for the details See the before and after summary, the reason, and the request id. Quote the request id when you contact support. ## Through the API `GET /v1/audit-log` returns entries newest first, with the standard cursor pagination. See [Pagination and filters](/api-reference/pagination). ```bash cURL curl "https://api.freeinvoiceautomation.com/v1/audit-log?limit=50" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ```json Response { "data": [ { "id": "0193f2c4-8e9f-7a0b-9c1d-2e3f4a5b6c7d", "business_id": null, "actor_type": "user", "actor_id": "0193f2a1-1111-7a2b-8c3d-4e5f6a7b8c90", "action": "update_role", "target_type": "role", "target_id": "0193f2a1-4d2e-7a10-8b3c-5e6f7a8b9c02", "request_id": "req_0193f2c4-8e9f-7a0b", "changes": { "permissions": { "before": ["documents.view", "documents.create", "documents.send"], "after": ["documents.view", "documents.create"] } }, "reason": null, "created_at": "2027-01-18T09:42:11Z" } ], "next_cursor": "eyJ0IjoiMjAyNy0wMS0xOFQwOTo0MjoxMVoifQ", "has_more": true } ``` | Capability | Route | Permission | Plan | | --- | --- | --- | --- | | [List the audit log](/api-reference/team/list-audit-log) | `GET /v1/audit-log` | `audit.view` | Team | The audit log is read-only. There is no endpoint to change or remove an entry. ## Plan differences | | Free | Team | | --- | --- | --- | | Entries written | Yes, for every change | Yes, for every change | | Audit log screen | Locked, shown as a Team feature | Yes | | `list_audit_log` | `feature_locked` | Yes | If a Team account moves back to Free, the audit log screen is hidden again, but entries keep being recorded. See [Failed renewal and downgrade](/billing/failed-renewal). ## Why it matters - **Trust your team.** See who sent, voided or refunded what, without asking around. - **Fix mistakes.** The before and after summary shows exactly what changed. - **Answer disputes.** When a client says they never received an invoice or a payment was never recorded, the log has the record. - **Check automations.** Entries from API keys, MCP tokens, the agent and connectors are clearly marked, so you can tell people and software apart. ## Errors | Code | HTTP | When | | --- | --- | --- | | `feature_locked` | 403 | The account is on Free. | | `permission_denied` | 403 | Your role does not hold `audit.view`. | | `scope_missing` | 403 | The API key or MCP token lacks the `audit.view` scope. | ## Related pages Roles and permissions Who holds audit.view by default How we protect data Logging, retention and how secrets are kept out of logs Upgrade to Team Unlock the audit log screen, roles and more members Errors Every error code and what to do about it Source: https://docs.freeinvoiceautomation.com/team/audit-log --- # Sign-in methods Sign in with email and password, Google, Apple, a magic link or a passkey. How each works, how sessions last, and when we ask you to sign in again. You can sign in to Free Invoice Automation in several ways, and you can use more than one on the same login. Whichever you choose, you end up with one login identified by one email address, which can belong to your own account and to any accounts you are a member of. This page covers each method, how sessions work, and the sensitive actions that ask you to sign in again. ## Methods at a glance | Method | Good for | Notes | | --- | --- | --- | | **Email and password** | Everyone | Breached-password check; reset by email link. | | **Google** | One-click sign-in with a Google account | | | **Apple** | Sign in with your Apple ID | Arrives in the week of 11 January 2027. Apple's private relay email addresses are accepted. | | **Magic link** | No password to remember | A single-use link emailed to you, valid for a short time. | | **Passkey** | Fast, phishing-resistant sign-in | Add one after you sign in. See [Two-factor and passkeys](/security/two-factor). | | **Two-factor (TOTP)** | An extra step on top of the above | Optional for everyone. See [Two-factor and passkeys](/security/two-factor). | Enterprise single sign-on (SSO) is planned for after launch, with the Enterprise plan. ## Email and password Create your login with your email address and a password at [app.freeinvoiceautomation.com/sign-up](https://app.freeinvoiceautomation.com/sign-up). - **Length, not complexity.** We ask for a minimum length rather than forcing symbols or numbers. A long passphrase is easier to remember and harder to guess. - **Breached passwords are refused.** When you choose a password, we check it against a list of passwords known from public data breaches. Only the first few characters of a hash of the password are sent for this check, never the password itself. If it is on the list, choose another. - **Forgotten password.** Choose **Forgot password** on the sign-in page. We email a single-use reset link that expires after a short time. Passwords can only be reset by email. - **Lockout.** After several wrong passwords in a row, the login is locked for a while and further tries are slowed down. This stops people guessing passwords. ### Verify your email After sign-up we send a verification link. You can use the app straight away, but you need a verified email before you can send documents through our [platform email](/messaging/platform-email). Your own [SMTP sender](/messaging/smtp) does not need it. ## Google and Apple Choose **Continue with Google** or **Continue with Apple** on the sign-in page. If you do not have a login yet, one is created and a Free account is set up with you as Owner. - **Linking to an existing login.** A social sign-in is joined to an existing login with the same email only when the provider confirms that the email address is verified. Otherwise, sign in with your existing method first and link the provider from **Settings > Security**. This stops anyone taking over your login with an unverified address at a provider. - **Apple private relay.** If you choose "Hide my email", Apple gives us a relay address. We accept it as your login email. - **What we receive.** Your name, email address and the provider's id for you. We use them only to sign you in. ## Magic link Enter your email and choose to receive a sign-in link. The link: - works **once**; a second use is refused, - expires after a short time; if it has expired, ask for a new one, - is stored on our side only as a hash, and is tied to your email. ## Sessions When you sign in, you get a session in the web app. Session cookies are secure, HTTP-only and protected against cross-site request forgery. - **How long you stay signed in.** Sessions renew while you use the app and end after a period of inactivity, with a maximum lifetime after which you sign in again. - **Sign out everywhere.** **Settings > Security** lets you end your sessions on every device. Changing your password also ends your other sessions. - **Which account is active.** If your login belongs to several accounts, the switcher at the top of the app sets the account you are working in. Permissions, plan and branding always follow that account. ## Fresh sign-in for sensitive actions Some actions could do real harm in the wrong hands, so they ask you to sign in again if you have not done so in the last few minutes. That way a session left open on a shared computer cannot be used for them. | Action | Where it is explained | | --- | --- | | Connect a payment method (rail) | [Stripe](/payments/stripe) | | Delete a business | [Businesses](/account/businesses) | | Change billing, add a billing card, buy a credit pack | [Billing card](/billing/billing-card), [Credit packs](/billing/credit-packs) | | Create an API key with auto-send | [Authentication](/api-reference/authentication) | | Turn on auto-send for an AI automation | [Automations](/ai/agent/automations) | | Connect QuickBooks or Xero, add your own AI key | [QuickBooks Online](/integrations/quickbooks), [Use your own AI key](/ai/agent/own-key) | | Turn on "send automatically" for a store connection | [WordPress and WooCommerce](/integrations/woocommerce) | | Change two-factor settings or your email | [Two-factor and passkeys](/security/two-factor) | | Transfer ownership, delete the account | [Transfer ownership](/account/ownership), [Delete your account](/account/delete) | If your session is not fresh, the API answers `fresh_signin_required` (HTTP 401) and the app shows the sign-in step. After you sign in, repeat the action. ## Signing in from code and AI assistants The API, CLI and connectors do not use your password or session. They use **API keys**, created in **Settings > API keys**, which are bound to one account, carry scopes and can be revoked at any time. AI assistants connect to the MCP server with **OAuth**: you sign in with any of the methods above and approve the scopes on a consent screen. See [Authentication](/api-reference/authentication) and [MCP authentication](/ai/mcp/authentication). API keys cannot perform the fresh-sign-in actions above; those are done by a person in the web app. ## Protection against abuse - Sign-in, sign-up, magic-link and password-reset requests are rate limited per IP address and per email. - Sign-up, password reset and magic-link requests give the same answer whether or not an email is registered, so nobody can use them to find out who has a login. - Sign-in events (sign-in, sign-out, password change, two-factor change, new device) are written to your [audit log](/team/audit-log) with time and IP. ## Errors | Code | HTTP | When | | --- | --- | --- | | `unauthenticated` | 401 | No session, or an invalid or revoked API key or token. | | `fresh_signin_required` | 401 | A sensitive action needs you to sign in again. | | `email_not_verified` | 403 | Platform email needs a verified address. | | `member_deactivated` | 403 | Your membership in this account is deactivated. Your login still works for your other accounts. | | `rate_limited` | 429 | Too many attempts. Wait for the time in `Retry-After`. | ## Related pages Two-factor and passkeys Add a second step and sign in without a password Create your account Sign up and set up your first business How we protect data Encryption, isolation, backups and privacy API authentication API keys, scopes and modes Source: https://docs.freeinvoiceautomation.com/security/sign-in --- # Two-factor and passkeys Protect your login with a time-based code from an authenticator app, keep recovery codes safe, and sign in without a password using a passkey. A password alone can be guessed, reused or phished. Two-factor authentication (2FA) adds a second step: after your password, you enter a six-digit code from an authenticator app on your phone. Passkeys go further and replace the password altogether with a key stored on your device. Both are free on every plan and set up in **Settings > Security**. ## Which should I use? | | Two-factor (TOTP) | Passkey | | --- | --- | --- | | What it is | A code that changes every 30 seconds, from an authenticator app | A cryptographic key on your phone, computer or security key, unlocked with your fingerprint, face or device PIN | | How it signs you in | Password (or another method) **plus** the code | On its own, with no password | | Protects against | Stolen or guessed passwords | Stolen passwords and phishing sites, because a passkey only works on our real domain | | Works with | Any standard authenticator app | Devices and browsers that support passkeys (WebAuthn) | Many people use both: a passkey for everyday sign-in on their own devices, and a password with two-factor as a backup. Two-factor is optional for your login. It is always required for our own staff console, which you never see. ## Turn on two-factor Security"> Because changing two-factor settings is a sensitive action, you may be asked to sign in again first. Choose to turn on two-factor A QR code appears. Scan the QR code with your authenticator app The app adds an entry for Free Invoice Automation and starts showing six-digit codes. If you cannot scan, the screen also shows the setup key to type in. Enter the current code to confirm This proves the app is set up correctly. Two-factor is not switched on until this step succeeds. Save your recovery codes You get a set of one-time recovery codes. Store them somewhere safe and separate from your phone, such as a password manager. Each code works once. From then on, every sign-in asks for a code from the app after your first step. The change is written to your [audit log](/team/audit-log). ### Recovery codes Recovery codes are your way back in if you lose your phone. Each one can be used once in place of an authenticator code. We store them only as hashes, so we cannot show them to you again. Save them the moment they appear. ### Turn off two-factor Open **Settings > Security** and turn two-factor off. This needs a fresh sign-in, and the change is audited. We recommend keeping it on, especially if you are the Owner of an account with members or connected payment methods. ## Add a passkey You add a passkey while signed in, then use it next time. Security"> Choose to add a passkey. Follow your device's prompt Your browser or operating system asks you to confirm with your fingerprint, face, device PIN or a security key. The private key never leaves your device; we only store its public half. Name it Give the passkey a name you will recognise, such as "Work laptop", so you can tell your passkeys apart later. Next time, choose to sign in with a passkey on the sign-in page and confirm on your device. You are signed in with no password. You can add several passkeys, for example one per device, and remove any of them from **Settings > Security**. ## Good practice - **Turn on two-factor or use a passkey** if you are the Owner, or if your role can move money (record payments, refunds, auto-charge) or manage the team. - **Keep recovery codes offline or in a password manager**, never in the same place as your authenticator. - **Remove passkeys for devices you no longer use.** - **Use Sign out everywhere** in **Settings > Security** if you think someone else has used your session. - Remember that **API keys are separate from your login.** Two-factor protects the web app and MCP sign-in; API keys are protected by keeping them secret, giving them narrow scopes and revoking them when no longer needed. See [Authentication](/api-reference/authentication). ## If you lose access | Situation | What to do | | --- | --- | | New phone, old phone still works | Sign in, turn two-factor off and on again with the new phone. | | Phone lost, recovery codes available | Sign in with a recovery code, then turn two-factor off and on again with your new phone. You get a new set of recovery codes. | | Passkey device lost | Sign in with another method (another passkey, password and two-factor, or a magic link) and remove the lost passkey. | | Everything lost | Contact [support](/help/support) from the email address on your login. We verify that the request really comes from you before changing anything. | ## Through the API Two-factor and passkeys are settings of your personal login, not of an account, so they are managed only in the web app. There is no API endpoint for them, and an API key cannot change them. The API returns `fresh_signin_required` for actions that need a recent sign-in, and those must be done in the web app. ## Plans and permissions - Available on **Free and Team**, at no cost. - No permission key is needed: two-factor and passkeys belong to your personal login, so every person manages their own. ## Related pages Sign-in methods Passwords, social sign-in, magic links and sessions How we protect data Encryption, isolation and backups Audit log Security events are recorded with time and IP Contact support When you cannot get back in Source: https://docs.freeinvoiceautomation.com/security/two-factor --- # How we protect data Account isolation, permissions on every surface, encryption of secrets, no card data on our servers, daily tested backups, EU hosting and your privacy rights. Your invoices, clients and payments are the records of your business, so we treat them that way. This page explains, in plain terms, how your data is kept separate from everyone else's, who can reach it, how secrets and payments are handled, where everything is stored, and what rights you have over it. ## Our security goals 1. **One account can never see or change another account's data.** 2. **We never touch card data and never hold your money.** 3. **Payment status is trustworthy.** An invoice turns paid only from a verified payment event or a recorded, audited human confirmation. 4. **Secrets stay secret.** Encrypted at rest, write-only, never logged. 5. **Every change is traceable** in an audit log. 6. **Evidence cannot be changed.** Sent documents change only by revision; signed contracts never change. 7. **We can always recover** from daily, encrypted, regularly tested backups. 8. **Personal data is handled lawfully and kept in the EU**, and only as long as needed. We build against recognised standards: OWASP ASVS Level 2 for the web app, API, MCP server and pay pages, the OWASP Top 10 and API Security Top 10, PCI DSS SAQ-A for payments, and the GDPR. ## Your account is isolated Every record that belongs to an account carries that account's id, and the database itself enforces that a request can only read and write rows of the account it is acting for. This is not just a check in our code: it is a rule in the database, so a mistake in one feature cannot expose another account. Records that belong to a business are also limited to the businesses the member can reach. Ids of records in other accounts behave as if they do not exist: the API answers `not_found`, so ids do not leak. ## Permissions on every surface Every action, in the web app, REST API, MCP server, CLI, store connectors and the AI agent, needs a permission key, and the check runs on every request. A member can only do what their role allows, inside their business scope. API keys and MCP tokens are limited to their scopes **and** their creator's current permissions. See [Roles and permissions](/team/roles). Integrations and AI assistants create **drafts** by default. A person confirms each send unless you choose auto-send for a specific key, automation or store connection. Destructive actions, such as voiding an invoice, always need an explicit confirmation. ## Sign-in and sessions - Email and password with a breached-password check and lockout, Google, Apple, magic links and passkeys. Optional two-factor for everyone. See [Sign-in methods](/security/sign-in) and [Two-factor and passkeys](/security/two-factor). - Secure, HTTP-only session cookies with protection against cross-site request forgery. - Sensitive actions, such as connecting a payment method, changing billing or deleting a business, ask you to sign in again. - Sign-in events are written to the audit log with time and IP. ## Encryption and secrets - **In transit:** every connection to our sites, app, API and MCP server uses HTTPS (TLS). - **Secrets at rest:** connection credentials you give us, such as your SMTP password, your own AI key or the tokens for a connected accounting system, are sealed with AES-256-GCM using a key unique to your account. They are **write-only**: no screen, API call, MCP tool or log ever returns them. The app shows at most that a value is set. - **Passwords** are stored only as salted hashes. - **Tokens:** pay links, portal links, magic links, invitations, API keys and session tokens are long random values stored only as hashes. Public links are single-purpose, expire and can be revoked. API keys are shown once, when you create them. - **Logs:** we never log passwords, tokens, API keys, cookies, sealed values, email bodies or webhook bodies. ## Payments: no card data, no holding funds - Card details are entered only on Stripe's hosted checkout pages. Our servers never receive, store or send card numbers. - Money goes straight from your client to your own Stripe account, bank or wallet. We never hold it and never take a percentage. - Saved cards stay at Stripe. We keep only Stripe's ids, the card brand, the last 4 digits and your client's consent. - An invoice is marked paid only from a verified payment event, a reconciliation with the payment provider, or a payment recorded by a person and written to the audit log. A browser being redirected to a "thank you" page never marks anything paid. - Incoming payment webhooks are verified by signature, stored, and processed once even if they arrive twice. See [How payments work](/payments/overview). ## Documents and evidence - Sent documents are never edited in place. A change creates a revision, so what your client received stays on record. See [Revisions](/invoicing/revisions). - Documents and payments are soft-deleted only. - Every PDF carries a verification QR code that lets anyone check that the document is genuine. See [Verification codes](/invoicing/verification). - Signed contracts are sealed with a hash, checked on every download, and never change. - Ledger entries are append-only; corrections are reversing entries. ## Uploads and client pages - Logos, payment proofs and receipts are checked for type and size, images are re-encoded, and files are stored privately and served through short-lived signed links. - Pay pages, the client portal, estimate pages and verification pages run **no analytics** and no third-party scripts, and are protected against being framed by other sites. - There are **no ads, affiliates or sponsors** on the generator, PDFs, pay pages, the portal or client emails. ## Backups and recovery - The database is backed up **every day**, encrypted before it leaves the server, and stored in the EU. - We keep 30 daily and 12 monthly backups. - Every week a backup is restored into a temporary database to prove it works. A failed restore test blocks the next release. ## Where your data lives Production data, files and backups are stored in the **EU**. Some services we rely on process data elsewhere: | Service | Why | Where | | --- | --- | --- | | Email delivery (platform email) | Sending your documents and reminders | EU region | | AI provider for the AI invoice agent | Reading your requests when you use the agent | USA. You are told before you use the agent, and you can use your own AI key instead | | Stripe | Team billing, and your own Stripe account for client payments | Global | | Error monitoring | Fixing bugs, with personal data scrubbed | As listed in the privacy policy | | Google, Apple | Only if you choose to sign in with them | Global | The full, current sub-processor list is in our privacy policy, and business users get notice before it changes. The visitor [invoice generator](/generator) is different: without an account, your invoices never reach our servers at all. They stay in your browser until you sign up. ## Privacy and your rights - **Who is responsible.** Genyune (SMC-Private) Limited is the controller for your account data (your login, members and billing). For the data you enter about your own clients, **you are the controller and we are your processor**, under our Data Processing Agreement. - **Export.** The Owner can download everything in the account (JSON, PDFs and CSV) at any time. See [Export your data](/account/export). - **Delete.** The Owner can delete the account. It is hidden at once and permanently deleted later. See [Delete your account](/account/delete). - **Correct.** You edit your own data directly. Sent documents change by revision, which also keeps the legal record. - **Clients' requests.** If one of your clients asks us about their data, we pass the request to you, because you are their controller. - **Analytics** run only on the marketing site and docs, and only after you consent. Never in the signed-in app, and never on pay, portal or verification pages. ## Incidents We have a written incident response plan. If a personal data breach happens, we notify the supervisory authorities within the GDPR's 72-hour window where required, and tell affected account holders by email and in the app. Where we hold data about your clients as your processor, we tell you without undue delay, so you can meet your own duties to them. ### Report a vulnerability Email **security@freeinvoiceautomation.com**. The same address is published in our `security.txt` file at `https://freeinvoiceautomation.com/.well-known/security.txt`. Please do not test against other users' data. For anything else, contact [support](/help/support). ## Related pages Sign-in methods How you sign in, sessions and fresh sign-in Roles and permissions Who can do what in your account Export your data Download everything, any time Delete your account What is deleted, when, and what stays in backups Source: https://docs.freeinvoiceautomation.com/security/overview --- # Businesses A business is the company or trading name that issues your documents. Add as many as you need, each with its own profile, logo, numbering, taxes and payment methods. A business is the legal entity, or trading name, that appears at the top of your invoices. Everything you send belongs to one business: its documents, its number sequences, its tax rates, its payment methods and its templates. One account can hold as many businesses as you like, on every plan, so a freelancer with a side company or an agency running several brands can keep them all in one place and switch between them in one click. ## How businesses fit in ```text Account (Free or Team, your members and billing) └─ Business (1..n) ├─ Profile, logo, country, currency, tax ids ├─ Numbering sequences (INV-, EST-, CN-, RCT-) ├─ Tax rates ├─ Payment methods (Stripe, bank transfer, ...) ├─ Templates └─ Clients and their documents ``` - **Unlimited on every plan.** There is no limit on the number of businesses, on Free or Team. - **Separate numbering.** Each business has its own gap-free number sequences, so INV-2027-0001 can exist in two businesses without clashing. See [Document numbering](/invoicing/numbering). - **Separate payment methods.** Each business connects its own Stripe account and shows its own bank details. See [How payments work](/payments/overview). - **Business scope for members.** When you invite someone, you choose whether they reach all businesses or only some. See [Members and invitations](/team/members). - **The switcher.** The account and business switcher at the top of the app lists every business you can reach, grouped by account. Your plan, members, billing and audit log belong to the **account**, not to a business. Settings can be set for the whole account and then overridden per business. See [Settings](/account/settings). ## The business profile | Field | What it is used for | | --- | --- | | **Legal name** (`legal_name`) | Required. The name on your documents. | | **Display name** (`display_name`) | Optional shorter or trading name. | | **Country** (`country`) | Required. Sets sensible defaults: currency, tax presets, locale and paper size. | | **Base currency** (`base_currency`) | Required. Reports for this business total in this currency. | | **Time zone** (`timezone`) | Required. Reminders and scheduled work run on this business's local time. | | **Locale** (`locale`) | Required. Date and number formats. | | **Email** (`email`) | Used as the Reply-To address when we send through platform email, so client replies reach you. | | **Phone**, **address** | Printed on your documents. | | **Tax ids** (`tax_ids`) | A list of tax registrations, each with a type and a value, such as a VAT or GST number. | | **Logo** | Shown on PDFs, pay pages, the portal and emails. See [Branding](/account/branding). | | **Slug** (`slug`) | The address of your public pay-me page, from February 2027. See [Pay-me page](/payments/pay-me). | ### Country defaults When you create a business, its country fills in a starting currency and tax presets, along with the locale and paper size. You can change all of them afterwards. From the week of 25 January 2027, businesses in countries with a supported local QR payment method also get that method pre-selected. See [Local QR payments](/payments/qr-rails). ## Add a business Open the business switcher At the top of the app. You need `businesses.manage`. When you first sign up, onboarding asks for your first business straight away. Choose to add a business Enter the legal name and pick the country. The currency, tax presets and locale are filled in from the country. Complete the profile Add your address, email, phone and tax ids, then upload a logo. Connect a payment method Connect Stripe or add bank transfer details, so your invoices can be paid. See [Stripe](/payments/stripe) and [Bank transfer](/payments/bank-transfer). Edit any of this later in **Settings > Business**, for the business currently selected in the switcher. ## Delete or archive a business - **A business with no sent documents** can be deleted. Deleting is a sensitive action, so you sign in again first, and you confirm it. - **A business with sent documents** cannot be deleted. Sent invoices, credit notes and payments are legal records, and documents and payments are never permanently deleted. Instead, the app offers **Archive business**, which takes it out of your way while keeping every record. Deletion is a soft delete. It is written to the [audit log](/team/audit-log). ## Through the API Every business action is a capability, so you can manage businesses from the API, CLI or AI agent. ```bash Create a business curl -X POST "https://api.freeinvoiceautomation.com/v1/businesses" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: business-studio-north" \ -d '{ "legal_name": "Studio North Ltd", "display_name": "Studio North", "country": "IE", "base_currency": "EUR", "timezone": "Europe/Dublin", "locale": "en-IE", "email": "billing@studionorth.example", "tax_ids": [{ "type": "vat", "value": "IE1234567T" }] }' ``` ```bash List businesses curl "https://api.freeinvoiceautomation.com/v1/businesses" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ```bash Update a business curl -X PATCH "https://api.freeinvoiceautomation.com/v1/businesses/0193f2a1-0b0b-7c4e-9a10-2d3e4f5a6b70" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: business-studio-north-phone" \ -d '{ "phone": "+353 1 555 0100" }' ``` ```json Business (201) { "data": { "id": "0193f2a1-0b0b-7c4e-9a10-2d3e4f5a6b70", "legal_name": "Studio North Ltd", "display_name": "Studio North", "slug": null, "country": "IE", "base_currency": "EUR", "timezone": "Europe/Dublin", "locale": "en-IE", "email": "billing@studionorth.example", "phone": null, "address": null, "tax_ids": [{ "type": "vat", "value": "IE1234567T" }], "logo_attachment_id": null, "created_at": "2027-01-04T09:00:00Z" } } ``` Most business-owned endpoints take a `business_id`. If your key can reach exactly one business, you can leave it out. To add a logo, upload the image with [Upload a file](/api-reference/account/upload-file), then set `logo_attachment_id` on the business. | Capability | Route | Permission | | --- | --- | --- | | [List businesses](/api-reference/businesses/list-businesses) | `GET /v1/businesses` | `businesses.view` | | [Get a business](/api-reference/businesses/get-business) | `GET /v1/businesses/{id}` | `businesses.view` | | [Create a business](/api-reference/businesses/create-business) | `POST /v1/businesses` | `businesses.manage` | | [Update a business](/api-reference/businesses/update-business) | `PATCH /v1/businesses/{id}` | `businesses.manage` | | [Delete a business](/api-reference/businesses/delete-business) | `DELETE /v1/businesses/{id}` | `businesses.manage`, fresh sign-in | | [List tax rates](/api-reference/businesses/list-tax-rates) | `GET /v1/tax-rates` | `businesses.view` | | [Create or update a tax rate](/api-reference/businesses/upsert-tax-rate) | `POST /v1/tax-rates` | `settings.manage` | | [List numbering sequences](/api-reference/businesses/list-numbering-sequences) | `GET /v1/numbering-sequences` | `businesses.view` | | [Update a numbering pattern](/api-reference/businesses/update-numbering-pattern) | `PATCH /v1/numbering-sequences/{id}` | `settings.manage` | Deleting a business needs a fresh sign-in, so it is done by a person in the web app; an API key cannot do it. ## Permissions | Key | Allows | Default roles | | --- | --- | --- | | `businesses.view` | See businesses, their tax rates and numbering | All five presets | | `businesses.manage` | Create, edit and delete businesses, upload the logo | Owner, Admin | | `settings.manage` | Tax rates and numbering patterns | Owner, Admin | A member with a business scope only sees the businesses in their scope. ## Errors | Code | HTTP | When | | --- | --- | --- | | `permission_denied` | 403 | Your role lacks `businesses.manage` (or `businesses.view`). | | `business_out_of_scope` | 403 | The business is outside your business scope. | | `fresh_signin_required` | 401 | Deleting a business needs a recent sign-in. | | `conflict` | 409 | The business has sent documents and cannot be deleted. Archive it instead. | | `validation_failed` | 422 | A required field is missing or invalid, such as an unknown currency code. | | `not_found` | 404 | The business does not exist in this account. | ## Related pages Branding Logo, colours and how your documents look Document numbering Gap-free number sequences per business Taxes Tax rates and presets per business Settings Account-wide defaults and per-business overrides Source: https://docs.freeinvoiceautomation.com/account/businesses --- # Settings How settings work at every level, from the whole account down to one business, one client or one document, and which value wins when several are set. Almost everything about how Free Invoice Automation behaves is a setting rather than something fixed: your payment terms, number patterns, which sender emails your invoices, whether reminders and late fees are on, the PDF template, your theme. You can set a value once for the whole account, change it for one business, again for one client, and even for a single document. This page explains how those levels work together. ## Where you find settings Open **Settings** in the sidebar. The settings pages are grouped as: **Account** · **Business** · **Team and roles** · **Billing and plan** · **Email** · **Payment methods** · **Templates and numbering** · **Taxes** · **Reminders and late fees** · **Branding** · **Integrations** · **API keys and MCP** · **Security** · **Data export and delete** Some settings also live where you use them: client-level settings on the client's page, and document-level overrides in the document editor. Your own preferences, such as theme and language, are in your profile menu. ## Levels, from most specific to least | Level | Set where | Applies to | Example | | --- | --- | --- | --- | | **Document** | The document editor | That one document | Different terms text on one invoice | | **Client** | The client's page | Every document for that client | 30-day payment terms for a slow-paying client | | **Business** | Settings, with that business selected | Every document of that business | Invoice number pattern, default PDF template, reminder hours | | **Account** | Settings | Every business in the account | Default currency for new documents | | **Plan** | Set by your plan | Every account on that plan | Members included, platform emails a month, AI credits | | **Platform** | Set by us | Everyone | Session and sign-in safety values | | **Default** | Built in | Everyone | The starting value when nothing else is set | ## Which value wins When a value is needed, we look from the most specific level to the least, and **the first level that has a value wins**: **document > client > business > account > plan > platform > default** A worked example with payment terms: 1. Your business sets payment terms to 14 days. 2. One client, Acme, has 30 days set on their client page. 3. You create an invoice for Acme. No override on the document, so the client value is used: **due in 30 days**. 4. You create an invoice for another client with nothing set. The business value is used: **due in 14 days**. 5. For one special job you set 7 days in the editor. The document value wins for that invoice only. Not every setting can be set at every level. Each one lists the levels where it makes sense. Number patterns, for example, are set per business only, because each business has its own sequences. Plan and platform values are not editable by you; they are shown so you know your limits. Set things as high up as you can, and override only where you need to. Change the business value once, and every client without their own value follows. ## Some common settings and their levels | Setting | Levels you can use | | --- | --- | | Invoice, estimate, credit note and receipt number patterns, such as `INV-{YYYY}-{SEQ:4}` | Business | | Payment terms (days until due) | Business, client | | Terms text and footer notes | Business, client, document | | PDF template | Business, client, document | | Paper size (A4 or Letter) | Business | | Default tax rate | Business, client | | Currency for new documents | Account, business, client, document | | Which sender emails your documents (your SMTP or platform email) | Business, client, document | | Attach the PDF to emails | Business, client, document | | Accepted payment methods | Business, client, document | | Allow partial payments | Business, client, document | | Reminders on or off | Business, client, document | | Late fees on or off | Business, client, document | | Theme (light, dark, auto) and language | You, for yourself | The pages for each feature explain their settings in detail, for example [Document numbering](/invoicing/numbering), [Due dates and payment terms](/invoicing/due-dates), [Your own SMTP](/messaging/smtp) and [Reminders](/collections/reminders). ## Who can change settings | Who | What they can change | | --- | --- | | Members with `settings.manage` (Owner and Admin by default) | Account and business settings on the Settings pages | | Members with a feature's own key | That feature's settings, for example `reminders.manage` for reminder sequences or `late_fees.manage` for late-fee policies | | Members with `clients.edit` | Client-level settings, on the client's page | | Members with `documents.edit` | Document-level overrides, in the editor | | Every person | Their own theme and language | Members with `settings.view` (every preset role) can read the account's settings, except a few private values that only `settings.manage` can see. Every change to a setting is written to the [audit log](/team/audit-log). ## Account details The account itself has a name and a few details, shown in **Settings > Account**. The account is the unit that holds your plan, members, billing and businesses. Your personal login (name, email, avatar) is separate and belongs to you, not to the account. ## Through the API Read settings for one level with `GET /v1/settings`, naming the scope and the id of the thing at that scope. Change them with `PATCH /v1/settings`. ```bash Read a business's settings curl "https://api.freeinvoiceautomation.com/v1/settings?scope=business&scope_id=0193f2a1-0b0b-7c4e-9a10-2d3e4f5a6b70" \ -H "Authorization: Bearer $FIA_API_KEY" ``` Scopes you can use are `account`, `business`, `client` and `user` (your own preferences). Document overrides are part of the document itself and are changed through the document endpoints. | Capability | Route | Permission | | --- | --- | --- | | [Get settings](/api-reference/account/get-settings) | `GET /v1/settings?scope=&scope_id=` | `settings.view` | | [Update settings](/api-reference/account/update-settings) | `PATCH /v1/settings` | `settings.manage` (your own `user` scope: yourself) | | [Get the account](/api-reference/account/get-account) | `GET /v1/account` | `settings.view` | | [Update the account](/api-reference/account/update-account) | `PATCH /v1/account` | `settings.manage` | `PATCH` is a partial update: only the values you send change. Values are validated against each setting's type, so an invalid value returns `validation_failed` with the problem field listed. ## Errors | Code | HTTP | When | | --- | --- | --- | | `permission_denied` | 403 | Your role lacks `settings.manage` (or `settings.view` to read). | | `business_out_of_scope` | 403 | The business is outside your business scope. | | `validation_failed` | 422 | The value has the wrong type or is out of range, or the setting cannot be set at that level. | | `not_found` | 404 | The `scope_id` does not exist in this account. | ## Related pages Businesses Business profiles and per-business defaults Branding Logo, colours and the look of your documents Roles and permissions Who holds settings.view and settings.manage Core concepts Accounts, businesses, clients and documents Source: https://docs.freeinvoiceautomation.com/account/settings --- # Branding Put your logo and colour on your PDFs, pay pages, client portal and emails, choose a template, and remove the \"Powered by\" line on Team. Your clients should see your business, not ours. Each business has its own logo and colour, which carry through to everything your clients see: the PDF, the pay page, the client portal and the emails you send. On the Team plan, the small "Powered by Free Invoice Automation" line disappears too, so your documents carry only your brand. ## Where your brand appears | Surface | Your logo and colour | "Powered by" line on Free | | --- | --- | --- | | PDFs (invoices, estimates, credit notes, receipts) | Yes | Yes | | Pay page | Yes | Yes | | Client portal | Yes | Yes | | Emails to your clients | Yes | Yes | | Estimate page, pay-me page and verification page | Yes, with a simple business-branded layout | | Client-facing pages use a plain layout themed by your business, with no app menus and no analytics. There are never any ads, affiliate links or sponsors on your PDFs, pay pages, portal or client emails, on any plan. ## Your logo Upload a logo per business. - **Formats:** PNG, JPEG or WebP. SVG files are not accepted; export your logo as PNG instead. - **Size:** keep the file small; as designed, logos are limited to 2 MB. - **Processing:** every upload is re-encoded, resized and stripped of hidden metadata (such as camera or location details) before it is stored. Files are stored privately and served through short-lived links. Select the business Use the business switcher at the top of the app. Branding"> You need `businesses.manage` to change the logo. The Owner and Admin have it by default. Upload the logo Choose a PNG, JPEG or WebP file. Pick your brand colour The colour themes the client-facing pages of this business, such as the pay page and the portal. Save New PDFs, pay pages, portal pages and emails use the new logo and colour. Sent documents keep the snapshot they were sent with, so what your client already received never changes. ## Templates PDFs are drawn from templates, and the same template is used in the browser and on our servers, so a document looks the same everywhere: in the free generator, in the app preview, as a download and as an email attachment. - **Options:** a template's layout, colours, fonts, labels, footer and paper size (A4 or Letter) can be adjusted. Choose the template at the level you need: per business, per client or on one document. See [Settings](/account/settings) for how levels work. - **Variants:** every template has a **VOID** variant with a stamp, and a **CREDIT NOTE** variant that references the original invoice number, so your clients never pay the wrong thing by mistake. - **Verification QR:** every PDF carries a QR code that opens a page where anyone can check the document is genuine. It cannot be removed. See [Verification codes](/invoicing/verification). - **Who can edit templates:** members with `templates.manage` (Owner, Admin and Editor by default). More about templates on [PDFs and templates](/invoicing/pdf). ## The "Powered by" line | | Free | Team | | --- | --- | --- | | "Powered by Free Invoice Automation" on PDFs, pay pages, portal and emails | Shown | **Removed** | | Your logo and colour | Yes | Yes | | Templates and template options | Yes | Yes | | Custom domains and full white-label | No | No (Enterprise, after launch) | On Team, the line is removed as soon as you upgrade, and it stays removed during the grace period if a renewal payment fails. If a Team account moves back to Free, the line returns on **new** PDFs, pay pages, portal pages and emails. Documents already sent are not changed. Your branding settings are kept, and when you upgrade again they come back as they were. See [Failed renewal and downgrade](/billing/failed-renewal). Custom domains and full white-label are planned for the Enterprise plan after launch. On Free and Team, client pages are served from `freeinvoiceautomation.com`, themed with your business's logo and colour. ## Through the API Branding is part of the business and its settings, so the same calls work from the API, CLI and AI agent. Upload the logo file `POST /v1/files` with the image. You need `businesses.manage`. The response gives you the file's id. Set it on the business `PATCH /v1/businesses/{id}` with `logo_attachment_id` set to that id. ```bash Set the logo on a business curl -X PATCH "https://api.freeinvoiceautomation.com/v1/businesses/0193f2a1-0b0b-7c4e-9a10-2d3e4f5a6b70" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: studio-north-logo" \ -d '{ "logo_attachment_id": "0193f2b2-9d8c-7b6a-8f5e-4d3c2b1a0f9e" }' ``` The business colour and default template are business settings; read and change them with the settings endpoints. | Capability | Route | Permission | | --- | --- | --- | | [Upload a file](/api-reference/account/upload-file) | `POST /v1/files` | `businesses.manage` for a logo | | [Update a business](/api-reference/businesses/update-business) | `PATCH /v1/businesses/{id}` | `businesses.manage` | | [Get settings](/api-reference/account/get-settings) | `GET /v1/settings` | `settings.view` | | [Update settings](/api-reference/account/update-settings) | `PATCH /v1/settings` | `settings.manage` | ## Permissions | Key | Allows | Default roles | | --- | --- | --- | | `businesses.manage` | Upload and change the business logo | Owner, Admin | | `settings.manage` | Change the business colour and default template | Owner, Admin | | `templates.manage` | Edit templates | Owner, Admin, Editor | ## Errors | Code | HTTP | When | | --- | --- | --- | | `validation_failed` | 422 | The file is not PNG, JPEG or WebP, is too large, or the colour is not a valid hex value. | | `permission_denied` | 403 | Your role lacks the key for the change. | | `business_out_of_scope` | 403 | The business is outside your business scope. | | `plan_limit_reached` | 403 | The account has reached its storage fair-use limit. | ## Related pages PDFs and templates How PDFs are drawn and what templates can change The pay page What your clients see when they pay Free and Team plans What Team adds, including removing "Powered by" Businesses Business profiles, logos and defaults Source: https://docs.freeinvoiceautomation.com/account/branding --- # Export your data Download everything in your account as JSON, PDFs and CSV, at any time and on any plan. Only the Owner can export the whole account. Your data is yours, and you should never feel locked in. **Export everything** packages the whole account into one download: structured data as JSON, every document as a PDF, and spreadsheets as CSV. It is free on every plan, available at any time, and it is the right thing to do before you delete an account, change tools, or simply keep your own copy of your tax records. ## What is included The export covers the **whole account**, across every business: | Format | What you get | | --- | --- | | **JSON** | The structured data of the account: businesses, clients and contacts, saved items, documents with their lines, revisions and links, payments, settings, and the other records you have created. Ideal for moving to another system or for your own scripts. | | **PDFs** | Your documents as PDFs, the way your clients see them, including VOID and CREDIT NOTE variants. | | **CSV** | Spreadsheet files you can open in any spreadsheet program, for a quick look or for your accountant. | Money in the JSON follows the same rules as the API: amounts are strings of integer minor units with a currency, so `"120000"` with `"currency": "USD"` is 1,200.00 USD. See [Money and currencies](/api-reference/money). Secrets are never exported. Passwords, API keys, connection credentials (such as your SMTP password or AI key) and other sealed values are write-only and stay out of every export. ## Who can export Exporting the whole account is **Owner-only**. The `account.export` permission belongs to the Owner role by default and is not part of any other preset. Other members get `permission_denied`. Members can still export what their role allows in each area, for example: - clients and saved items as CSV, see [Import and export](/clients/import-export), - reports as CSV or PDF with `reports.export`, see [Reports](/features/reports), - single documents as PDF from the document itself. ## Export everything in the web app Data export and delete"> You must be the Owner of the account selected in the switcher. Choose Export everything We start preparing the export in the background. A large account can take a while; you can keep working in the meantime. Download it when it is ready When the export is ready, download it from the same page. The download link is private to you and expires after a few days, so download it soon. You can start a new export at any time. ## Through the API An export is an asynchronous job. You start it, then check its status until it is ready. ```bash Start an export curl -X POST "https://api.freeinvoiceautomation.com/v1/account/exports" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Idempotency-Key: export-2027-01-31" ``` ```bash Check its status curl "https://api.freeinvoiceautomation.com/v1/account/exports/0193f2d0-3a4b-7c5d-8e6f-7a8b9c0d1e2f" \ -H "Authorization: Bearer $FIA_API_KEY" ``` Starting an export returns `202 Accepted` with the export's id: ```json 202 Accepted { "data": { "id": "0193f2d0-3a4b-7c5d-8e6f-7a8b9c0d1e2f", "status": "queued", "requested_by": "0193f2a1-1111-7a2b-8c3d-4e5f6a7b8c90", "created_at": "2027-01-31T17:00:00Z" } } ``` The export moves through these statuses: | Status | Meaning | | --- | --- | | `queued` | Waiting to start. | | `running` | Being prepared. | | `ready` | Done. The response includes where to download it and `expires_at`. | | `failed` | Something went wrong. Start a new export, or contact support with the request id. | | `expired` | The download is no longer available. Start a new export. | Poll at a gentle pace, for example once every few seconds or minutes, rather than in a tight loop. | Capability | Route | Permission | | --- | --- | --- | | [Export the account](/api-reference/account/export-account) | `POST /v1/account/exports` · `GET /v1/account/exports/{id}` | `account.export` (Owner only) | An API key can only hold the `account.export` scope if its creator holds the permission, which in practice means a key created by the Owner. Keys are also limited at call time to their creator's current permissions. ## Good reasons to export - **Before deleting your account.** Deletion hides your data at once and removes it permanently 30 days later. Export first. See [Delete your account](/account/delete). - **For your tax records.** Laws in many places ask you to keep invoices for years. We keep your documents while your account exists, but your own copy is always good practice. - **Moving to another tool.** The JSON is structured data that another system or a script can read. - **Your rights under data protection law.** Export is how you get a copy of your data in a portable format, at any time. ## Questions Does exporting change anything in my account? No. An export is read-only. It is recorded in the audit log so you can see who exported and when. Can I export one business only? Export everything covers the whole account. To work with one business, use the business filters on the API list endpoints, or the CSV exports and reports for that business. I am not the Owner. How do I get an export? Ask the Owner of the account. If the Owner cannot use the export, they can contact support from the account's email address. ## Errors | Code | HTTP | When | | --- | --- | --- | | `permission_denied` | 403 | You are not the Owner. | | `scope_missing` | 403 | The API key lacks the `account.export` scope. | | `not_found` | 404 | The export id does not exist in this account. | | `rate_limited` | 429 | Too many requests. Wait for the time in `Retry-After`. | ## Related pages Delete your account Export first, then delete Import and export CSV for clients and saved items How we protect data Privacy, retention and your rights Transfer ownership Hand the account to someone else instead Source: https://docs.freeinvoiceautomation.com/account/export --- # Transfer ownership Hand your account to another person, for example when you sell the business or step back. Only the Owner can do it, after signing in again. Every account has an **Owner**: the person with every permission, including the ones no other role can ever have, such as deleting the account. When the business changes hands, a partner takes over, or you simply want someone else in charge, you transfer ownership so the account, its businesses, documents, clients and history carry on without interruption under a new Owner. ## Why ownership matters The Owner role is special: - It holds **every** permission key, and it cannot be edited or deleted. - Some powers belong to the Owner only and can never be given to another role: **deleting the account** (`account.delete`) and **transferring ownership**. - By default it is also the only role with **billing** (`billing.manage`) and **Export everything** (`account.export`). - The Owner can never be deactivated or removed by anyone. - An account always has at least one Owner. Nothing, including a role change or a removal, can leave it without one. Because the Owner cannot be removed, transferring ownership is the way to hand over the account. Deleting your login or leaving is not. ## Who can transfer ownership Only the **current Owner**. Ownership transfer is checked against the Owner role itself rather than a permission key, so there is nothing to tick on another role: an Admin, even one with `team.manage`, cannot transfer ownership or give themselves the right to do it. Trying to put it on another role returns `permission_not_grantable`. ## Before you start - **Think about billing.** If the account is on Team, the subscription belongs to the account and the new Owner manages it from then on. Check **Settings > Billing** together so the new Owner knows the renewal date and card. - **Export a copy if you need one.** **Export everything** is an Owner power. Do it before you hand over, while you still hold it. See [Export your data](/account/export). - **Make sure the new Owner can sign in.** They should have a login and be able to receive email at the address on it. Turning on [two-factor](/security/two-factor) is strongly recommended for any Owner. ## Transfer ownership in the web app Team"> You must be the Owner of the account selected in the switcher. Choose to transfer ownership Pick the person who will become the Owner. Sign in again Transferring ownership is a sensitive action. If you have not signed in within the last few minutes, the app asks you to sign in again before it continues. Confirm The app shows what will change and asks you to confirm. After confirming, the new Owner holds every permission, including billing, export and account deletion. The transfer is written to the [audit log](/team/audit-log). Permissions are read on every request, so the change applies straight away for both of you. ## Through the API The capability is `transfer_ownership`, at `POST /v1/account/transfer-ownership`. Two safeguards apply: - **Fresh sign-in.** The action needs a recent sign-in, so it is done by a person in the web app session. API keys cannot perform fresh-sign-in actions. A request without a fresh session returns `fresh_signin_required`. - **Confirmation.** It is a destructive action, so it needs `"confirm": true`. Without it you get a preview of the change and `confirmation_required`. See [Drafts and confirmations](/api-reference/confirmations). ```json 401 fresh_signin_required { "error": { "code": "fresh_signin_required", "message": "Please sign in again to transfer ownership.", "details": {}, "request_id": "req_0193f2e1-5b6c-7d8e" } } ``` | Capability | Route | Who | Hint | | --- | --- | --- | --- | | [Transfer ownership](/api-reference/account/transfer-ownership) | `POST /v1/account/transfer-ownership` | The Owner only | Destructive, fresh sign-in | ## Rules | Rule | Why | | --- | --- | | Only the Owner can transfer ownership. | It hands over every power in the account. | | It cannot be granted to another role. | Prevents anyone from promoting themselves. | | It needs a fresh sign-in. | A session left open on a shared computer cannot give your account away. | | It needs explicit confirmation. | It is hard to undo: only the new Owner can transfer it back. | | An account always keeps at least one Owner. | There is always someone who can manage billing, export and deletion. | | Every transfer is audited. | You can always see who owned the account and when it changed. | ## Plans Ownership transfer is available on **every plan**. On Free, the account has only the Owner and at most one view-only Accountant. ## Errors | Code | HTTP | When | | --- | --- | --- | | `fresh_signin_required` | 401 | You need to sign in again first. | | `permission_denied` | 403 | You are not the Owner. | | `permission_not_grantable` | 403 | Someone tried to give ownership transfer to a non-Owner role. | | `last_owner` | 409 | The change would leave the account without an Owner. | | `confirmation_required` | 428 | The request did not include `"confirm": true`. | ## Related pages Roles and permissions Why the Owner role is fixed Members and invitations Bring the new Owner into the account Delete your account Close the account instead of handing it over Upgrade to Team Billing passes to the new Owner Source: https://docs.freeinvoiceautomation.com/account/ownership --- # Delete your account The Owner can delete the account at any time. It is hidden at once and permanently deleted after 30 days. Export your data first. If you no longer need Free Invoice Automation, the Owner can delete the account. Deletion happens in two steps: the account is **hidden at once**, so nobody can use it, and it is **permanently deleted** after a 30-day period. Because your invoices are often records you must keep for tax purposes, export everything before you delete. Deleting an account deletes every business in it, with their clients, documents, payments, contracts and settings, for every member. Download your data first with [Export everything](/account/export). ## Who can delete an account - Only the **Owner**. The `account.delete` permission belongs to the Owner role and **cannot be given to any other role**, not even Admin. Any other member who tries, from any surface, is refused. - The Owner must **sign in again** right before deleting. A session left open somewhere cannot be used to delete your account. - Over the API, an API key cannot delete the account: it is a fresh-sign-in action, done by a person in the web app. If you are a member of someone else's account and want to leave it, ask the Owner or an Admin to [remove you](/team/deactivate). If you want someone else to carry on with the account, [transfer ownership](/account/ownership) instead of deleting. ## What happens, step by step | When | What happens | | --- | --- | | **At once** | The account is soft-deleted and hidden. Members can no longer open it, and it disappears from everyone's switcher. Connections to Stripe, QuickBooks and Xero are revoked. | | **During the next 30 days** | The data is kept but hidden, then removed by our scheduled clean-up. | | **After 30 days** | The account and its data are permanently deleted from our live systems. | | **Up to about 12 months** | Copies remain inside our encrypted backups until those backups age out. If we ever restore a backup, deletions are applied again, so a deleted account does not come back. | Some records are kept for longer because the law requires it: the billing records of a Team subscription (the invoices we issued to you) are kept for up to 10 years. Contracts, signature evidence and sealed contract PDFs are deleted with the account, so export them first if you need them as evidence. ## Before you delete Export everything Download your data in **Settings > Data export and delete** with **Export everything**. You get JSON, PDFs and CSV for the whole account. See [Export your data](/account/export). Tell your clients Pay links and the client portal belong to your account. Let clients with open invoices know how you would like to be paid from now on. Check your Team subscription If the account is on Team, review **Settings > Billing** so there are no surprises on your card. See [Upgrade to Team](/billing/team). Revoke API keys and integrations you set up elsewhere Remove the API key from any scripts, WordPress sites or automation tools that used it, so they stop trying to connect. ## Delete the account in the web app Delete account"> You must be the Owner of the account selected in the switcher. It sits next to **Export everything** in the **Data export and delete** section. Sign in again The app asks you to sign in again before continuing. Read what will be deleted and confirm Deletion is a destructive action, so the app shows what will happen and asks you to confirm. The account is hidden straight away, and the deletion is recorded. ## Through the API `DELETE /v1/account` is the capability behind the button. It needs the Owner, a fresh sign-in, and `"confirm": true`, so it runs from the web app session rather than from an API key. ```json 401 fresh_signin_required { "error": { "code": "fresh_signin_required", "message": "Please sign in again to delete this account.", "details": {}, "request_id": "req_0193f2e9-0a1b-7c2d" } } ``` | Capability | Route | Permission | Hint | | --- | --- | --- | --- | | [Delete the account](/api-reference/account/delete-account) | `DELETE /v1/account` | `account.delete` (Owner only) | Destructive, fresh sign-in; soft delete at once, permanent deletion later | ## Deleting single things instead You may not need to delete the whole account: | You want to | Do this | | --- | --- | | Stop using one business | Delete it if it has no sent documents, or archive it. See [Businesses](/account/businesses). | | Get rid of a draft | Delete the draft. Sent documents are voided or credited instead. See [Void and delete](/invoicing/void). | | Remove a person | Deactivate or remove the member. See [Deactivate and remove](/team/deactivate). | | Stop paying for Team | Cancel in the Stripe Customer Portal from **Settings > Billing**. The account moves to Free at the end of the paid period and everything keeps working. See [Free and Team plans](/billing/plans). | | Hand the account over | [Transfer ownership](/account/ownership). | ## Your rights Deleting your account is how you exercise your right to erasure under data protection law. If you cannot use the button, for example because you lost access, contact [support](/help/support) from the email address on the account. We check that the request really comes from the account holder before acting, and answer within one month. If you are a client of one of our users and want your data removed, contact that business: they decide about the data on their invoices. If you write to us, we pass your request to them. ## Errors | Code | HTTP | When | | --- | --- | --- | | `fresh_signin_required` | 401 | Sign in again first. | | `permission_denied` | 403 | You are not the Owner. | | `confirmation_required` | 428 | The request did not include `"confirm": true`; the response shows what will be deleted. | | `unauthenticated` | 401 | No valid session. | ## Related pages Export your data Download everything before you delete Transfer ownership Hand the account to someone else How we protect data Retention, backups and privacy Contact support Help with access or a deletion request Source: https://docs.freeinvoiceautomation.com/account/delete --- # FAQ Answers to the questions people ask most about pricing, invoices, payments, clients, teams, AI, data and the API. Short answers to common questions, grouped by topic. Each answer links to the page with the full details. If your question is not here, see [Troubleshooting](/help/troubleshooting) or [contact support](/help/support). ## Pricing and plans Is Free Invoice Automation really free? Yes. The Free plan costs $0 forever and includes every invoicing feature: invoices, estimates, credit notes, receipts, saved items, unlimited businesses, card and bank payments, the API, the MCP server and the connectors. Features that ship after launch, such as reminders, recurring invoices, reports and contracts, arrive on Free too. Fair-use limits exist only to stop abuse. See [Free and Team plans](/billing/plans). Do you take a cut of my payments? No. We never take a percentage of an invoice payment, on any plan. Card payments go straight to your own Stripe account and bank transfers straight to your bank. We never hold your money. Your payment provider charges its own fees as usual. See [How payments work](/payments/overview). What does Team cost and what does it add? Team is $10 a month or $96 a year, with a regional price of $5 a month or $48 a year in some countries. It includes 5 members (then $2 a month each), editable and custom roles, the audit log screen, 100 platform emails and 1,000 AI credits a month (2,000 in your first paid month), priority email support, and removes the "Powered by" line. It does not unlock any invoicing features. See [Upgrade to Team](/billing/team). Is there a free trial of Team? No. Instead, your first Team payment is covered by a 30-day money-back guarantee, once per account. If Team is not right for you, ask within 30 days for a full refund. See [30-day money-back](/billing/money-back). Who will I be paying? Free Invoice Automation is owned by Genyune (SMC-Private) Limited. Team subscriptions and AI credit packs are sold by Genyune Inc. through its Stripe account, so Genyune Inc. is the name on your receipt. How does the regional price work? If your billing country is on our regional list, Team costs $5 a month or $48 a year and extra members $1 a month or $10 a year. Checkout shows the price for your card's country. If the card's country and your billing address do not match, the standard price applies. See [Regional price](/billing/plans#regional-price). What happens if my Team payment fails? You get a 5-day grace period with everything still working while Stripe retries your card. If it stays unpaid, the account moves to Free. Nothing is deleted and your pay pages keep working, but extra members are deactivated. See [Failed renewal and downgrade](/billing/failed-renewal). ## Getting started Do I need an account to make an invoice? No. The [free invoice generator](/generator) makes a PDF invoice in your browser with no sign-up. Your data stays on your device. Create an account when you want to email invoices, get paid from a pay page or keep records you can reach anywhere. What happens to my generator invoices if I sign up? After you sign up, your browser uploads them once into your account's first business and then clears its own copy. Signing in again on the same browser does not upload them twice. See [Move your invoices into an account](/generator#move-your-invoices-into-an-account). How do I sign up? Go to [app.freeinvoiceautomation.com/sign-up](https://app.freeinvoiceautomation.com/sign-up) and use email and password, Google, Apple or a magic link. No card is needed. See [Create your account](/account/sign-up). Can I run more than one business? Yes, as many as you like on every plan. Each business has its own name and logo, numbering, tax rates, payment methods and templates. See [Businesses](/account/businesses). ## Invoices and documents Can I edit an invoice after I send it? Yes, but not in place. A change to a sent invoice creates a new revision, and the earlier versions stay on record, so what your client received is never lost. Drafts can be changed freely. See [Revisions](/invoicing/revisions). Can I delete an invoice? You can delete a draft, because it has no number yet. A sent invoice is never deleted: you void it with a reason, and it keeps its number with a VOID stamp. If the invoice already has payments, issue a credit note instead. See [Void and delete](/invoicing/void). How does invoice numbering work? Each business has its own gap-free sequence per document type, such as `INV-2027-0001` for invoices and `CN-2027-0001` for credit notes. A number is given when a document is first sent, so deleted drafts never leave gaps. You can set your own pattern. See [Document numbering](/invoicing/numbering). Can I invoice in any currency? Yes. Choose the currency per document. When it differs from your business's base currency, the exchange rate is frozen on the document so totals never shift later. See [Currencies and exchange rates](/invoicing/currencies). How can my client check that an invoice is real? Every PDF sent from an account carries a verification QR code. It opens a page showing the issuing business, document number, date, total and current status. See [Verification codes](/invoicing/verification). ## Payments How can my clients pay me? At launch: by card through your own connected Stripe account, or by bank transfer using the details you add. Local QR payments for UPI, Raast, JazzCash and Easypaisa arrive in late January 2027 and Whop in February 2027. See [How payments work](/payments/overview). My client says they paid by bank transfer. Why is the invoice not paid? A client clicking "I have paid" is a claim, not a confirmed payment. You are notified and can see any proof they uploaded. Once the money is in your account, record the payment and the invoice moves to paid. See [Record a payment](/payments/recording) and [Payment proofs](/payments/payment-proofs). Can a client pay only part of an invoice? Yes. Partial payments are allowed by default. The balance updates when each payment is confirmed, and the invoice shows as partly paid until the rest arrives. Deposits and instalment plans arrive in late January 2027. See [Deposits and schedules](/payments/schedules). How do I refund a client? Issue a credit note on the paid invoice and choose to refund. A Stripe payment is refunded through Stripe; a bank transfer refund is recorded by you after you send the money back. You can also keep the amount as client credit for the next invoice. See [Refunds](/payments/refunds). ## Clients and email Do my clients need an account or a password? No, never. Clients open documents from a secure link. From January 2027 they can also use a client portal by entering their email to get a one-time sign-in link. See [Client portal](/clients/portal). How many emails can I send? Through our platform mailer: 20 a month on Free and 100 on Team, after you verify your email address. Through your own SMTP mailbox: as many as you like. See [Platform email](/messaging/platform-email) and [Your own SMTP](/messaging/smtp). Who do my clients see as the sender? With the platform mailer, emails come from "Your Business via Free Invoice Automation", and replies go to your business email. With your own SMTP, they come from your own address. ## Teams and permissions Can my accountant see my invoices on the Free plan? Yes. Every plan includes one free view-only Accountant who can see documents, clients, payments and reports and export them, but cannot change anything. See [Member seats](/billing/members). Can I control what each team member can do? On Team, yes. You can edit the Admin, Editor, Viewer and Accountant roles or create custom roles, and limit each member to some businesses. The Owner always has every permission. See [Roles and permissions](/team/roles). How do I remove someone's access straight away? Deactivate them in **Settings > Team**. Their sessions are signed out and their API keys and MCP tokens for your account are revoked at once. Their work stays. This works on every plan. See [Deactivate and remove](/team/deactivate). ## AI and automation What is the AI invoice agent? An assistant inside the app, in **Automate > AI agent**, that turns plain language or pasted text into draft invoices, estimates and clients. It always creates drafts; nothing is sent until you confirm. See [Agent overview](/ai/agent/overview). How do AI credits work on Free? A Free account gets 250 welcome credits in its first month after sign-up when it adds a card, which is verified for $0 and never charged. After that, buy a pack of 1,000 credits for $5 or connect your own AI key. See [AI credits](/billing/ai-credits). Will you ever charge my card for credits automatically? No. Credit packs are bought only when you click "Buy 1,000 credits" and complete Stripe Checkout. There is no auto top-up and no charge to a saved card. See [Credit packs](/billing/credit-packs). Can I use Free Invoice Automation from Claude or ChatGPT? Yes. Connect your assistant to our MCP server at `https://mcp.freeinvoiceautomation.com/mcp` and sign in. Your assistant does the AI work, so this never uses credits. It creates drafts and asks you before it sends or voids anything. See [MCP overview](/ai/mcp/overview). ## Data, privacy and security Where is my data stored? In the European Union, on our own server and an EU storage bucket. Generator documents stay in your own browser until you sign up. See [How we protect data](/security/overview). Do you store my clients' card details? No. Card payments happen on Stripe's hosted pages and card data never touches our servers. When saved cards arrive in March 2027, the card still stays at Stripe and we keep only the brand, last 4 digits and the client's consent. Can I export everything and leave? Yes. The Owner can export the whole account as JSON, PDFs and CSV at any time, and can delete the account. A deleted account is hidden at once and permanently erased after 30 days. See [Export your data](/account/export) and [Delete your account](/account/delete). Are there ads on my invoices or pay pages? Never. There are no ads, affiliate links or sponsors on the generator, PDFs, pay pages, the client portal or client emails, and no analytics on pay, portal, pay-me or verification pages. ## Developers Is there an API, and does it cost extra? Yes, and no. The REST API at `https://api.freeinvoiceautomation.com/v1` is on every plan and does everything the web app does. Create a key in **Settings > API keys**. See the [API reference](/api-reference/introduction). Can my script send invoices without anyone confirming? Only if you choose so. API keys are `draft_only` by default, so sends need `"confirm": true` after a preview. A key created in `auto_send` mode can send directly; creating one needs a fresh sign-in. See [Drafts and confirmations](/api-reference/confirmations). How do I get notified when an invoice is paid? Add a webhook endpoint. We send signed events such as `document.paid` and `payment.recorded` to your URL and retry if it is down. See [Webhooks](/integrations/webhooks). ## Related Troubleshooting Fixes for common problems and error codes Glossary Every term used in the product Contact support How to reach us and what to include Changelog What ships when Source: https://docs.freeinvoiceautomation.com/help/faq --- # Troubleshooting Common problems with signing in, sending, documents, payments, links, teams, AI credits and the API, with the error codes you may see and how to fix them. Most problems have a clear cause and a quick fix. This page is grouped by what you were trying to do. Each entry gives the error code you may see in the app or the API, what it means and what to do. Every error from the API has the same shape, with a stable `code`, a plain-English `message` and a `request_id`: ```json Error response { "error": { "code": "document_has_payments", "message": "This invoice has recorded payments and cannot be voided. Create a credit note instead.", "details": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "amount_paid_minor": "50000" }, "request_id": "req_0193f2c0-1d2e-7f30-8a41-5b6c7d8e9f01", "docs_url": "https://docs.freeinvoiceautomation.com/errors/document_has_payments" } } ``` Code against `code`, not `message`: messages can be reworded, codes do not change. If you need to contact us, include the `request_id`. See [Errors](/api-reference/errors) for the full list. ## Signing in My magic link does not work Magic links work once and expire after a short time. If you already used the link, or it is old, ask for a new one from the sign-in page. Open the newest email only. I am locked out after wrong passwords After too many wrong passwords the account is locked for a while and further attempts are slowed down. Wait, then try again, or sign in with a magic link or reset your password from the sign-in page. I am asked to sign in again (fresh_signin_required) Sensitive actions need a recent sign-in: connecting Stripe or another payment method, deleting a business, changing billing, adding a billing card, buying a credit pack, creating an `auto_send` API key, turning on auto-send for an automation or a store, and deleting the account. Sign in again and retry. API keys cannot perform these actions; use the web app. I do not see the Apple sign-in button A sign-in button appears only when that provider is set up on our side. Sign in with Apple is planned for launch, or for the week of 11 January 2027 if it is not ready on launch day. Use another method in the meantime. I cannot open an account I was invited to (member_deactivated) The Owner, or someone with `team.manage`, deactivated you in that account, or the account moved from Team to Free. Ask the Owner to reactivate you. Your own account and any other accounts are not affected. ## Sending documents | Code | Cause | Fix | | --- | --- | --- | | `email_not_verified` | The platform mailer needs a verified email address. | Click the verification link we emailed you, or ask for a new one in the app. Your own SMTP works without it. | | `quota_exceeded` | You used all your platform emails this month (20 on Free, 100 on Team). | Connect your own SMTP (unlimited), or wait for the reset on the first of the month. Your draft is kept. | | `platform_mail_paused` | The account moved from Team to Free and has already sent 20 or more platform emails this month. | Connect your own SMTP, or wait for next month. | | `confirmation_required` | A send through the API or MCP needs confirmation. | Check the preview in the response, then send again with `"confirm": true`. | | `document_voided` | The document is void. | Voided invoices cannot be sent. Create a new invoice. | | `rate_limited` | Too many sends in one minute. | Wait for the seconds in `Retry-After`. | My client did not get the email Ask them to check spam or junk. Check the document's activity to see that the email was sent. Platform emails come from "Your Business via Free Invoice Automation" with replies to your business email. You can always **Copy link** and share the pay link another way, or download the PDF. I clicked send twice. Did my client get two emails? No. Sends are protected against duplicates per document, event and channel, so a double click or a retried request sends one email. My SMTP connection fails (connection_failed) We test your SMTP details when you save them. Check the host, port, username and password with your email provider. For safety, SMTP servers on private network addresses are refused. See [Your own SMTP](/messaging/smtp). ## Changing documents | Code | Cause | Fix | | --- | --- | --- | | `document_not_editable` | The document was sent, so it cannot be edited in place. | Make your change as a revision. See [Revisions](/invoicing/revisions). | | `document_has_payments` | You tried to void an invoice that has recorded payments. | Issue a credit note instead. See [Credit notes](/invoicing/credit-notes). | | `document_voided` | The action is not allowed on a void document. | Create a new document. | | `credit_exceeds_balance` | The credit note is larger than what can be credited. | Lower the amount or the lines you credit. | | `currency_mismatch` | A payment method or amount does not fit the document's currency, for example a QR rail on an invoice that is not in PKR or INR. | Use a method that matches the currency. | | `schedule_total_mismatch` | Instalments do not add up to the total. | Adjust the parts; the last part absorbs rounding when you let us split it. | I cannot delete a sent invoice Sent documents are never deleted, so your records and numbering stay complete. Void the invoice with a reason, or issue a credit note if it has payments. Only drafts can be deleted. See [Void and delete](/invoicing/void). My business cannot be deleted A business with sent documents cannot be deleted, because documents and payments are never hard-deleted. Archive the business instead. See [Businesses](/account/businesses). ## Payments My client paid by card but the invoice still shows unpaid An invoice is marked paid when Stripe confirms the payment to us, not when the client's browser returns from Checkout. This usually takes moments. If a confirmation is ever lost, a regular check against Stripe corrects it. If it still shows unpaid after a while, check the payment in your Stripe dashboard and [contact support](/help/support) with the invoice number. My client clicked I have paid but the invoice is not paid That is expected. For bank transfers and QR payments, "I have paid" tells you to check; it does not mark the invoice paid. Confirm the money arrived, then accept the proof or record the payment. See [Payment proofs](/payments/payment-proofs). The pay page shows no way to pay (rail_not_connected) The business has no active payment method for this invoice. Connect Stripe or add bank details in the business's payment settings. If Stripe was connected before, our nightly check may have found the connection broken; you see a warning and can reconnect. See [Stripe](/payments/stripe). I cannot refund through the app (rail_capability_missing) The payment method has no refunds through us, for example bank transfer. Send the money back yourself, then record the refund manually. See [Refunds](/payments/refunds). Stripe or another provider returns an error (upstream_error) The provider did not answer as expected. Wait a moment and retry. If it keeps happening, check the provider's own status page and [contact support](/help/support) with the request id. ## Links your clients open | Code | Cause | Fix | | --- | --- | --- | | `token_invalid` | The link is wrong or incomplete. | Ask the business to send the link again. Check that it was not cut off in the email. | | `token_expired` | The link has expired. | Send the document again or create a new share link. | | `token_revoked` | The link was cancelled, for example because the invoice was voided. | If the invoice is still valid, create a new share link. | ## Team and roles | Code | Cause | Fix | | --- | --- | --- | | `permission_denied` | Your role does not include the permission this action needs. | Ask the Owner or someone with `team.manage` to change your role. | | `business_out_of_scope` | You cannot reach this business. | Ask for the business to be added to your business scope. | | `feature_locked` | A Team feature on the Free plan, such as role editing, the audit log screen or the AI agent without credits. | Upgrade to Team, or for the agent buy a pack or connect your own key. | | `plan_limit_reached` | Your plan has no more seats, or you reached a fair-use limit. | Upgrade to Team or add a seat. See [Fair-use limits](/billing/fair-use). | | `permission_not_grantable` | A role change would give a permission you do not hold, or an Owner-only permission. | Only grant keys you hold yourself. Account deletion and ownership transfer stay with the Owner. | | `role_locked` | The Owner role cannot be edited, reset or deleted. | No action needed; the Owner always has every permission. | | `role_in_use` | You tried to delete a role that members still use. | Move those members to another role first. | | `last_owner` | The change would leave the account without an Owner. | Make sure another Owner exists first. | ## AI credits and billing | Code | Cause | Fix | | --- | --- | --- | | `ai_credits_exhausted` | No AI credits left. | Buy a credit pack, upgrade to Team (on Free) or connect your own AI key. | | `billing_card_required` | The Free welcome credits need a verified card. | Add a card in **Settings > Usage**. It is verified for $0 and never charged. | | `card_not_eligible` | Prepaid cards cannot unlock the welcome credits. | Use a debit or credit card, or buy a credit pack (prepaid cards work there). | | `welcome_credits_not_eligible` | This card or account already had welcome credits, or the first month has passed. | Buy a credit pack or connect your own AI key. | | `money_back_not_eligible` | Not the first Team payment, more than 30 days ago, or the guarantee was used. | You can still cancel Team in the Stripe Customer Portal. | I see a red Payment failed banner Your Team renewal failed. Everything keeps working for 5 days. Update your card in the Stripe Customer Portal from **Settings > Billing** and the banner goes away once Stripe confirms the payment. See [Failed renewal and downgrade](/billing/failed-renewal). I bought a credit pack but the credits are not there yet Credits are added when Stripe confirms the payment to us, which usually takes moments. Refresh **Settings > Usage**. If they do not appear, [contact support](/help/support) with the time of purchase. ## The API | Code | HTTP | Cause | Fix | | --- | --- | --- | --- | | `unauthenticated` | 401 | Missing, wrong or revoked API key or token. | Send `Authorization: Bearer `. Create a new key if it was revoked. | | `scope_missing` | 403 | The key or token lacks the scope for this action. | Create a key with the scope you need. | | `validation_failed` | 422 | The input is not valid. | Read `details.issues` for each field and its problem. | | `invalid_json` | 400 | The body is not valid JSON. | Check quotes and commas. | | `not_found` | 404 | The resource does not exist or you cannot see it. | Check the id and that the key belongs to the right account. | | `idempotency_key_reused` | 409 | The same `Idempotency-Key` was sent with a different body. | Use a new key for a new request. | | `idempotency_in_progress` | 409 | A request with the same key is still running. | Wait and retry with the same key. | | `confirmation_required` | 428 | A destructive action or send needs `"confirm": true`. | Check the preview, then retry with `"confirm": true`. | | `rate_limited` | 429 | Too many requests. | Wait for `Retry-After` seconds. | | `not_configured` | 503 | A service on our side is not set up yet. | Try again later. | | `maintenance` | 503 | The service is in maintenance. | Check the status page and retry later. | | `internal_error` | 500 | Something went wrong on our side. | Retry. If it persists, contact support with the `request_id`. | Send money as a string of integer minor units, such as `"120000"` for 1,200.00. Floating-point amounts such as `1200.00` are rejected with `validation_failed`. See [Money and currencies](/api-reference/money). ## Is it us? If several things fail at once, check [status.freeinvoiceautomation.com](https://status.freeinvoiceautomation.com) for outages before you dig further. ## Related Errors Every error code in one reference FAQ Answers to common questions Contact support How to reach us and what to include Rate limits Limits, headers and retry advice Source: https://docs.freeinvoiceautomation.com/help/troubleshooting --- # Glossary Every term used in Free Invoice Automation, from account to webhook, explained in plain words with links to the full pages. The words we use in the app, the API and these docs, A to Z. Where a term has its own page, the entry links to it. Terms for features that ship after launch are marked with the month they arrive. ## A **Account**: Your workspace. It holds your businesses, members and billing, and is on one plan, Free or Team. One login can belong to many accounts. See [Core concepts](/concepts). **Accountant**: A preset role that can view documents, clients, payments and reports and export them, but not change anything. Every plan includes one free Accountant seat. See [Roles and permissions](/team/roles). **Account switcher**: The menu at the top of the app that lists every account and business you can reach. **AI credit**: The unit the AI invoice agent uses. One request that creates or updates up to one document costs 1 credit. See [AI credits](/billing/ai-credits). **AI invoice agent**: The assistant in **Automate > AI agent** that turns plain language into draft invoices, estimates and clients. See [Agent overview](/ai/agent/overview). **API key**: A secret that lets your code call the REST API for one account. Created in **Settings > API keys**, shown once, with scopes and a mode. See [Authentication](/api-reference/authentication). **Audit log**: The record of who did what and when. Entries are written on every plan; the screen to view them is a Team feature. See [Audit log](/team/audit-log). **Auto-charge** (March 2027): Charging a client's saved card automatically on a due date, with the client's consent. See [Saved cards and auto-charge](/payments/auto-charge). **Auto-send**: A mode for an API key, MCP token, automation or store install that lets it send documents without a person confirming each send. Turning it on needs a fresh sign-in. See [Drafts and confirmations](/api-reference/confirmations). ## B **Balance**: What is still owed on a document: total minus payments minus credits applied. **Bank transfer**: A payment method where the pay page shows your bank details, the client pays from their bank, and you record the payment. See [Bank transfer](/payments/bank-transfer). **Base currency**: A business's main currency. Reports total in it, and documents in other currencies freeze an exchange rate to it. **Billing card**: A card added on Free to unlock the welcome AI credits. Verified for $0 and never charged. See [Billing card](/billing/billing-card). **Business**: The person or company whose name is on your documents. Each has its own numbering, tax rates, payment methods and templates. Unlimited on every plan. See [Businesses](/account/businesses). **Business scope**: The businesses a member can reach: all, or a chosen list. ## C **Capability**: One action, such as "create an invoice", defined once and offered in the app, REST API, MCP server, CLI and AI agent with the same rules. See [Core concepts](/concepts#capabilities-and-surfaces). **CLI** (late January 2027): The `freeinvoiceautomation` command-line tool. A thin client of the API. See [CLI](/integrations/cli). **Client**: Someone you bill. Belongs to a business and never has a password. See [Clients and contacts](/clients/clients). **Client credit**: Money you owe a client, kept from a credit note on a paid invoice and applied to their next invoice. See [Client credit](/clients/client-credit). **Client portal** (January 2027): A page where a client signs in with a one-time email link to see all their documents from one business. See [Client portal](/clients/portal). **Confirmation**: The extra step for sends and destructive actions: a dialog in the app, or `"confirm": true` in the API and MCP. **Connection**: A link between a business and an outside service, such as Stripe, an SMTP mailbox or an AI provider. **Contact**: A person at a client who can receive documents. **Credit note**: A document that reduces what an invoice asks for, fully or in part, with its own number. See [Credit notes](/invoicing/credit-notes). **Credit pack**: 1,000 AI credits for $5, bought only when you click. Lasts 12 months. See [Credit packs](/billing/credit-packs). ## D **Deactivate**: Ending a member's access to an account at once while keeping their work. They can be reactivated. See [Deactivate and remove](/team/deactivate). **Deposit** (January 2027): An upfront part of a document's total, due on acceptance or a date. See [Deposits and schedules](/payments/schedules). **Destructive action**: An action that cannot easily be undone or that moves money, such as voiding or issuing a credit note. Always needs confirmation. **Draft**: A document that has not been sent. It has no number and can be changed or deleted freely. ## E **Estimate**: A price you offer before invoicing. The client accepts or asks for changes online. See [Estimates](/invoicing/estimates). **Exchange rate**: The rate between a document's currency and the business's base currency, frozen on the document. See [Currencies and exchange rates](/invoicing/currencies). ## F **Fair-use limits**: Limits such as API calls per month, storage and sends per minute that protect the free service from abuse. See [Fair-use limits](/billing/fair-use). **Fresh sign-in**: Signing in again before a sensitive action, such as connecting Stripe or changing billing. ## G **Gap-free numbering**: Document numbers per business and type that never skip or repeat. See [Document numbering](/invoicing/numbering). **Generator**: The free invoice maker at freeinvoiceautomation.com/generator that works with no account and keeps data in your browser. See [Free invoice generator](/generator). **Grace period**: The 5 days after a failed Team renewal during which Team keeps working. See [Failed renewal and downgrade](/billing/failed-renewal). ## I **I have paid**: The button a client clicks on the pay page after paying by bank transfer or QR. It notifies you; it does not mark the invoice paid. **Idempotency key**: A value you send with a write so a retried request is not done twice. See [Idempotency](/api-reference/idempotency). **Instalment** (January 2027): One part of a payment schedule, with its own amount and due date. **Invoice**: A request for payment with a pay page. See [Invoices](/invoicing/invoices). ## L **Late fee** (January 2027): A charge added to an overdue invoice by a policy you set. Off by default. See [Late fees](/collections/late-fees). **Ledger** (June to July 2027): A double-entry accounting ledger per business. See [Accounting ledger](/features/ledger). ## M **Magic link**: A one-time sign-in link sent by email. **MCP server**: Our Model Context Protocol server at `https://mcp.freeinvoiceautomation.com/mcp`, which lets AI assistants work with your invoices. See [MCP overview](/ai/mcp/overview). **Member**: A person who can sign in to an account, with a role and a business scope. See [Members and invitations](/team/members). **Minor units**: The smallest unit of a currency, such as cents. All amounts are whole numbers of minor units: `"120000"` USD is 1,200.00 USD. See [Money and currencies](/api-reference/money). **Money-back guarantee**: A full refund of your first Team payment if you ask within 30 days, once per account. See [30-day money-back](/billing/money-back). ## O **Overdue**: An invoice status for an unpaid invoice past its due date. **Owner**: The role with every permission. It cannot be edited, deactivated or removed, and only the Owner can delete the account or transfer ownership. See [Transfer ownership](/account/ownership). ## P **Pay page**: The page your client opens from the invoice link to see the invoice and pay. See [The pay page](/invoicing/pay-page). **Pay-me page** (February 2027): A public page where anyone can pay your business without an invoice. See [Pay-me page](/payments/pay-me). **Payment proof**: A file a client uploads after clicking "I have paid", such as a bank receipt. See [Payment proofs](/payments/payment-proofs). **Permission key**: The name of one permission, written `module.action`, such as `documents.send`. See [Permission keys](/api-reference/permissions). **Platform mailer**: Our email service for sending your documents if you have not connected your own SMTP. 20 emails a month on Free, 100 on Team. See [Platform email](/messaging/platform-email). **"Powered by" line**: The small "Powered by Free Invoice Automation" text on Free plan PDFs, pay pages, portal pages and emails. Removed on Team. **Preview**: What an action would do, shown before it runs. Every send shows a preview first. ## R **Rail**: A payment method a business can accept, such as Stripe or bank transfer. See [How payments work](/payments/overview). **Receipt**: A document confirming a payment, with its own number. See [Receipts](/invoicing/receipts). **Recurring invoice** (January 2027): An invoice created again on a schedule. See [Recurring invoices](/collections/recurring). **Regional price**: Team at $5 a month or $48 a year when your billing country is on our regional list. See [Free and Team plans](/billing/plans#regional-price). **Reminder sequence** (January 2027): A set of reminder steps before and after a due date. See [Reminders](/collections/reminders). **Request id**: The id on every API response and error. Include it when you contact support. **Revision**: A new version of a sent document. Earlier versions stay readable. See [Revisions](/invoicing/revisions). **Role**: A named list of permission keys given to members. Presets are Owner, Admin, Editor, Viewer and Accountant. See [Roles and permissions](/team/roles). ## S **Saved item**: A product or service you save so document lines fill themselves. See [Saved items](/invoicing/saved-items). **Scope**: A permission an API key or MCP token carries. A key can never do more than its creator. **Share link**: A secure link to a document's pay page that you can send through any channel. See [Share links](/invoicing/share-links). **SMTP**: Your own email server or mailbox, used to send documents from your own address with no monthly limit. See [Your own SMTP](/messaging/smtp). **Snapshot**: The exact copy of a document stored when it is sent and with each revision. **Status**: Where a document stands, such as `draft`, `sent`, `paid` or `void`. See [Document statuses](/invoicing/statuses). ## T **Team plan**: The paid plan for working with others: 5 members, roles, audit log, more emails and credits. See [Upgrade to Team](/billing/team). **Token (public)**: The random part of a link your client opens. Stored hashed, made for one purpose, expiring and revocable. ## V **Verification code**: The QR code and code on every PDF sent from an account, which opens a page confirming the document is real. See [Verification codes](/invoicing/verification). **Void**: Cancelling a sent invoice with a reason. It keeps its number and shows a VOID stamp. See [Void and delete](/invoicing/void). ## W **Webhook**: A signed message we send to your URL when something changes, such as an invoice being paid. See [Webhooks](/integrations/webhooks). **Welcome credits**: One-time AI credits: 250 on Free in your first month after sign-up, once you add a card, and a 1,000 bonus in your first paid Team month. See [AI credits](/billing/ai-credits). ## Related Core concepts How these ideas fit together FAQ Answers to common questions Source: https://docs.freeinvoiceautomation.com/help/glossary --- # Changelog What ships on launch day, 1 January 2027, and in each planned release through July 2027. Free Invoice Automation launches on **Friday 1 January 2027**. After launch, new features arrive in releases through the first half of 2027. This page lists what ships in each one. These entries are planned Nothing below has shipped yet. Dates and contents are our current plan and can move. When a release ships, its entry is updated to say so, and the pages for its features are updated in the same change. Every feature listed here comes to the Free plan as well as Team. Each feature arrives everywhere at once: in the web app, the REST API and, where it has a tool, the MCP server, because every feature is one capability. Pages for features that ship after launch carry a "Coming" note with their month. ## Launch: 1 January 2027 The first public version: everything you need to create invoices, send them and get paid, from the browser, your AI assistant or your own code. **Website and generator** - [Free invoice generator](/generator): create, preview and download a PDF invoice with no account; data stays in your browser and moves into your account once on sign-up. - Marketing site with pricing and legal pages. The Shopify app and WordPress plugin shown as "Coming soon" with a "Notify me" button. **Accounts and sign-in** - [Sign-up and sign-in](/account/sign-up) with email and password, magic link, passkeys, two-factor authentication and Google. Sign in with Apple as soon as Apple approves our developer account. - Unlimited [businesses](/account/businesses), members with roles, deactivate and remove on every plan, [export everything](/account/export) and [delete your account](/account/delete). **Documents** - [Invoices](/invoicing/invoices), [estimates](/invoicing/estimates), [credit notes](/invoicing/credit-notes), [receipts](/invoicing/receipts) and [void](/invoicing/void). - [Saved items](/invoicing/saved-items) with CSV import and export. - Gap-free [numbering](/invoicing/numbering), [revisions](/invoicing/revisions), [PDFs](/invoicing/pdf), [share links](/invoicing/share-links) and [verification codes](/invoicing/verification). **Getting paid** - [The pay page](/invoicing/pay-page) with card payments through your own [Stripe](/payments/stripe) account and [bank transfer](/payments/bank-transfer). - [Payment proofs](/payments/payment-proofs) ("I have paid") and [manual payment records](/payments/recording); [refunds](/payments/refunds) through Stripe. **Email** - Send documents by email through [your own SMTP](/messaging/smtp) or the [platform mailer](/messaging/platform-email) (20 a month on Free, 100 on Team). **Developers and AI** - REST API `/v1` with API keys, idempotency and signed [webhooks](/integrations/webhooks); the OpenAPI reference on this site. - [MCP server](/ai/mcp/overview) for Claude, ChatGPT, Grok and Gemini, with OAuth sign-in or an API key, and a no-account `generate_pdf` tool. - [AI invoice agent](/ai/agent/overview) with credits: Team 1,000 a month (2,000 in the first paid month), Free 250 welcome credits after adding a card, credit packs, or your own AI key. **Plans** - [Team plan](/billing/team) bought in the app, with the regional price, the [30-day money-back guarantee](/billing/money-back) and the [failed-renewal grace period](/billing/failed-renewal). ## Release 1: week of 11 January 2027 Getting paid on time. - [Reminder sequences](/collections/reminders) with Gentle, Standard and Firm presets, and a per-client opt-out. - [Late fees](/collections/late-fees), off by default, including statutory presets. - [Recurring invoices](/collections/recurring). - [Promise to pay](/collections/promise-to-pay), which pauses reminders until a date. - [Client portal](/clients/portal) with a one-time email link. - Sign in with Apple, if it was not ready on launch day. - New MCP tool: `send_reminder`. ## Release 2: week of 25 January 2027 Deposits, reports, local payments and new ways to connect. - [Deposits and payment schedules](/payments/schedules), with reminders per instalment. - [Reports](/features/reports): aged receivables, outstanding by client and client statement, revenue, tax collected and payments received, with CSV and PDF export. - [Local QR payments](/payments/qr-rails): UPI, Raast, JazzCash and Easypaisa, on the pay page and the PDF. - [WordPress and WooCommerce plugin](/integrations/woocommerce): direct download and submission to WordPress.org. - [CLI](/integrations/cli) on npm, and the stdio MCP client. - New MCP tools: `set_payment_schedule`, `get_aged_receivables`, `get_report`. ## Release 3: February 2027 Stores and more ways to be paid. - [Shopify app](/integrations/shopify), submitted for App Store review: invoices for orders, void on cancel and credit notes on refunds. - [Whop](/payments/whop) as a payment method for your invoices. - [Pay-me page](/payments/pay-me), and the pay-me block in the WordPress plugin. - More AI features on the same credits: reading receipts and PDFs, voice input and AI-written reminders. - A donations page with a transparency chart. ## Release 4: March 2027 - [Saved cards and auto-charge](/payments/auto-charge) through Stripe, with the client's consent; clients can remove a saved card from the portal. - [Contracts and e-signature](/features/contracts), sealed with a hash once signed. - [Saved views](/features/saved-views) for your document lists. - New MCP tool: `create_contract`. ## Release 5: April 2027 - [Time tracking](/features/time-tracking) with a timer and manual entries, and invoicing of unbilled time. - [Expenses](/features/expenses), billable to clients with an optional markup. - [WhatsApp, Telegram and SMS](/messaging/other-channels) for sending documents and reminders. - New MCP tools: `log_time`, `list_unbilled_time`, `invoice_unbilled`, `record_expense`. ## Release 6: May 2027 - [QuickBooks Online](/integrations/quickbooks) and [Xero](/integrations/xero) sync, one way from us to them. ## Release 7: June to July 2027 - [Accounting ledger](/features/ledger): chart of accounts, automatic entries, trial balance, profit and loss, balance sheet and general ledger, and bank statement import. - New MCP tool: `get_ledger_report`. ## Later Planned without a date and built when users ask for them: the [n8n node](/integrations/n8n), more payment methods, e-invoice export, an Enterprise plan with custom domains and single sign-on, and mobile apps. ## Stay up to date - Check [status.freeinvoiceautomation.com](https://status.freeinvoiceautomation.com) for live service status. - API changes within `/v1` are additive only, and any deprecation is announced in these docs first. See [Versioning](/api-reference/versioning). - Questions about a planned feature: [info@freeinvoiceautomation.com](mailto:info@freeinvoiceautomation.com). Source: https://docs.freeinvoiceautomation.com/changelog --- # Contact support How to get help with Free Invoice Automation, what to include so we can answer quickly, and where to report security issues. We answer every question by email. Before you write, two quick checks often solve the problem straight away: the [status page](https://status.freeinvoiceautomation.com) tells you whether something is down, and [Troubleshooting](/help/troubleshooting) explains every error code and its fix. ## How to reach us | For | Where | | --- | --- | | Questions, problems and account help | [info@freeinvoiceautomation.com](mailto:info@freeinvoiceautomation.com) | | Service status and outages | [status.freeinvoiceautomation.com](https://status.freeinvoiceautomation.com) | | Security vulnerabilities | [security@freeinvoiceautomation.com](mailto:security@freeinvoiceautomation.com) | | Abuse, spam or fake invoices sent through us | [info@freeinvoiceautomation.com](mailto:info@freeinvoiceautomation.com) | | Feedback on these docs | [info@freeinvoiceautomation.com](mailto:info@freeinvoiceautomation.com?subject=Docs%20feedback) | ### Response times - **Team plan:** priority email support, with a target first reply within **1 business day**. - **Free plan:** we answer by email as soon as we can. Write from the email address you sign in with. It helps us find your account, and we only discuss an account with the people who belong to it. ## What to include A good first message saves a round of questions. Include: Who and where Your account email, the business name, and the surface you were using: web app, API, MCP (and which assistant), CLI, generator, or a client's pay page. What you did and what happened The steps you took, what you expected and what you saw instead. Copy the exact error message. The request id Every API error includes a `request_id`, and every API response carries it in the `X-Request-Id` header. It lets us find the exact request in our logs. When The date and time it happened, with your time zone, or in UTC. Which document The document number, such as `INV-2027-0042`, or its id. For payments, the amount and the payment method. ### Find the request id In an API error, the request id is inside the error body: ```json Error response { "error": { "code": "permission_denied", "message": "Your role does not allow sending documents.", "request_id": "req_0193f2c0-1d2e-7f30-8a41-5b6c7d8e9f01", "docs_url": "https://docs.freeinvoiceautomation.com/errors/permission_denied" } } ``` With cURL, add `-i` to see the response headers, including the request id header: ```bash cURL curl -i "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ## What never to send us Never send passwords, API keys, MCP tokens, SMTP passwords, AI provider keys or full card numbers by email, to us or anyone else. We will never ask for them. If you shared a key by mistake, revoke it in **Settings > API keys** and create a new one. A screenshot is welcome, but cover any secrets or your clients' personal details first. ## If we need to look inside your account Sometimes the fastest way to fix a problem is for us to see what you see. We only do this with your permission: - You grant access for a set time. - While the session is active, a banner shows in your app. - The session ends by itself when the time is up. - Every action taken during it is recorded in an audit log. ## Common requests I want a refund of my first Team payment Email us within 30 days of your account's first Team payment. See [30-day money-back](/billing/money-back). I want a refund of a credit pack Unused credit packs can be refunded within 14 days of purchase. Email us to ask. See [Credit packs](/billing/credit-packs). I need more than the fair-use limits Tell us what you are building and how much you need. See [Fair-use limits](/billing/fair-use). I want a copy of my data or to delete my account The Owner can do both in the app at any time. See [Export your data](/account/export) and [Delete your account](/account/delete). For any other request about your personal data, email us. Someone sent me a fake invoice that looks like it came from you Every real PDF from an account carries a verification code. Scan it or open the verification page to check the document. If it does not match, or there is no code, forward the email to us. See [Verification codes](/invoicing/verification). I need an Enterprise plan Enterprise is planned after launch, with custom domains, white-label and single sign-on. Email us to talk about it. See [Free and Team plans](/billing/plans#enterprise). ## Report a security issue If you find a security vulnerability, email [security@freeinvoiceautomation.com](mailto:security@freeinvoiceautomation.com) with the steps to reproduce it. Please do not test against other people's accounts or data, and give us time to fix it before you share details publicly. Our contact details for security reports are also published in `security.txt` at `https://freeinvoiceautomation.com/.well-known/security.txt`. See [How we protect data](/security/overview). ## Privacy questions Free Invoice Automation is owned by Genyune (SMC-Private) Limited, and your data is hosted in the European Union. Our EU representative's details are available on request at [info@freeinvoiceautomation.com](mailto:info@freeinvoiceautomation.com). The Privacy Policy on [freeinvoiceautomation.com](https://freeinvoiceautomation.com) explains how we use your data. ## Related Troubleshooting Fix common problems and error codes FAQ Answers to common questions Errors The full list of API error codes Changelog What ships when Source: https://docs.freeinvoiceautomation.com/help/support --- # Integrations overview Connect Free Invoice Automation to your code, your store and your accounting tool. Every integration uses the same API, permissions and safety rules. Free Invoice Automation is built API-first. The web app is only one way in: your own code, a command line, a Shopify store, a WordPress site, QuickBooks or Xero, and your AI assistant can all create and manage invoices. Every one of them calls the same API, with the same permissions, the same validation and the same audit trail, so no integration can do something the app would not allow. All integrations are included on the **Free** plan. There is no extra charge for API access, connectors or accounting sync, and we never take a cut of your payments. ## What is available and when | Integration | What it does | Available | | --- | --- | --- | | [REST API](/api-reference/introduction) | Every feature as an HTTP endpoint, with OpenAPI | At launch, 1 January 2027 | | [TypeScript SDK](/integrations/sdk) | A typed client generated from the OpenAPI document | At launch | | [Webhooks](/integrations/webhooks) | Signed events sent to your URL when anything changes | At launch | | [MCP server](/ai/mcp/overview) | Use Claude, ChatGPT, Grok or Gemini to invoice | At launch | | [CLI](/integrations/cli) | `freeinvoiceautomation` in your terminal and scripts | Week of 25 January 2027 | | [WordPress and WooCommerce](/integrations/woocommerce) | Invoices from WooCommerce orders, a pay-me block | Week of 25 January 2027 | | [Shopify](/integrations/shopify) | Invoices or receipts from Shopify orders | February 2027 | | [QuickBooks Online](/integrations/quickbooks) | One-way push of clients, items, invoices, credit notes and payments | May 2027 | | [Xero](/integrations/xero) | The same one-way push into Xero | May 2027 | | [n8n](/integrations/n8n) | An n8n node for your workflows | Later, on demand | At launch, the store connectors appear in **Settings > Integrations** with a "Coming soon" badge and a **Notify me** button, so you hear the day they ship. ## How every integration works ### One capability layer Each action in the product, such as "create an invoice" or "record a payment", is defined once with its input, output, permission key and safety hints. The REST endpoint, the MCP tool, the CLI command, the SDK method and the connector actions are all generated from or call that one definition. If you learn one surface, the others behave the same way. ### API keys with scopes Integrations authenticate with an **API key** created in **Settings > API keys**. A key belongs to one account, carries **scopes** chosen from the [permission keys](/api-reference/permissions), and can never do more than the person who created it can do right now. Store connectors get their own key for their install, limited to one business with only the scopes the connector needs (proposed). See [Authentication](/api-reference/authentication). ### Drafts first Integrations and AI create **drafts**. A person confirms before anything is sent to a client, unless you deliberately turn on auto-send for a key, an AI automation, or a store install. Turning it on always needs a fresh sign-in. Destructive actions such as voiding or refunding always need explicit confirmation. See [Drafts and confirmations](/api-reference/confirmations). ### Verified payments only An invoice is marked paid only from a verified event: a signed webhook from Stripe, a verified store channel, a reconciliation run, or a person recording the payment. A browser redirect never marks anything paid. Shopify and WooCommerce payments count as verified only when they arrive through a signed channel, and are recorded with the source `connector`. ### Everything is audited Every write from every integration lands in the audit log with its actor: a user, an API key, an MCP client, the AI agent, a store connector, or the system. On Team you can view it under [Audit log](/team/audit-log); on every plan it is recorded. ## Choosing an integration I want to create invoices from my own app or backend Use the [REST API](/api-reference/introduction) directly, or the [TypeScript SDK](/integrations/sdk) if you write TypeScript. Create a `draft_only` key, create drafts, and send them after a person approves; or create an `auto_send` key if your system should send by itself. I want my systems to react when an invoice is paid Add a [webhook endpoint](/integrations/webhooks) and subscribe to `document.paid` and `payment.succeeded`. See the [event list](/api-reference/webhook-events#event-list). I sell on Shopify or WooCommerce Install the [Shopify app](/integrations/shopify) or the [WordPress plugin](/integrations/woocommerce). Orders become invoices or receipts, numbered from your own sequence, and cancels and refunds are mirrored automatically. My accountant uses QuickBooks or Xero Connect [QuickBooks Online](/integrations/quickbooks) or [Xero](/integrations/xero). Clients, items, invoices, credit notes and payments are pushed one way into your books. We never read or change your ledger. I script things in the terminal Use the [CLI](/integrations/cli). Every capability is a command, with the same permissions as your API key. I work inside an AI assistant Connect the [MCP server](/ai/mcp/overview) to Claude, ChatGPT, Grok or Gemini. Your assistant does the AI work, so you need no AI credits for MCP. ## Managing integrations in the app Open **Settings > Integrations** to see what is connected: API keys, webhook endpoints, connected stores, and (from May 2027) QuickBooks or Xero. You need the `integrations.manage` permission for API keys, webhooks and store installs, and `accounting.manage` for accounting sync. Owners and Admins have both by default. From there you can revoke a key, disable a webhook endpoint, unlink a store, or disconnect an accounting tool. Revoking or unlinking takes effect on the next request. ## Security in short - Keys are shown once and stored hashed. Revoke a key the moment you suspect it leaked. - Webhook payloads are signed; verify every delivery. - Store events are verified (Shopify's HMAC signature, or our signature on WordPress plugin calls) before anything is created. - Accounting tokens are encrypted at rest and refreshed automatically. - No card data ever reaches our servers. See [How we protect data](/security/overview) for the full picture. ## Related pages API reference Every endpoint, with examples Webhooks Get changes pushed to your systems Shopify Invoices from Shopify orders Build with AI MCP server and the AI invoice agent Source: https://docs.freeinvoiceautomation.com/integrations/overview --- # TypeScript SDK The typed TypeScript client generated from our OpenAPI document, what it covers, how it is distributed, and how to get the same types in your own project today. The Free Invoice Automation SDK is a typed TypeScript client for the REST API. It is generated from the same OpenAPI document that describes `/v1`, which is itself generated from the capability definitions the server runs. That chain means the SDK's types always match the API: if a field exists in the API, it exists in the SDK with the same name and type, and nothing else does. ## Who uses it The SDK is the one client that all of our own software uses to talk to the API: - the web app and the marketing site, for every call they make, - the [CLI](/integrations/cli), which is a thin client of the SDK, - the [MCP server](/ai/mcp/overview), which runs each tool by calling the matching REST route through the SDK with your token, - the [Shopify app](/integrations/shopify) and the [n8n node](/integrations/n8n), - the mobile app we plan for later. Because our own products depend on it, the SDK is kept complete and current. A capability that is not in the SDK does not exist. ## Packages and distribution Our packages use the npm scope `@freeinvoiceautomation`. We publish only **thin clients** to npm: small programs that hold no business logic and only call the API. The ones planned are: | Package | What it is | Available | | --- | --- | --- | | `freeinvoiceautomation` | The [CLI](/integrations/cli) | Week of 25 January 2027 | | `freeinvoiceautomation-mcp` | The stdio MCP client, which forwards to our hosted MCP server with your API key | Week of 25 January 2027 | | The n8n node | A node for n8n workflows | Later, on demand | The SDK itself is the typed core these clients are built on. Its exact package name and publishing date are not set in our plans yet; this page will show the install command when it is published. Until then, you can generate an equivalent typed client from the OpenAPI document in a minute, as shown below. Every thin client, and the SDK, can only do what your API key allows. They add convenience and types, never extra access. ## Generate typed access from OpenAPI The OpenAPI document is published at `https://api.freeinvoiceautomation.com/v1/openapi.json` (proposed path). Any OpenAPI generator works. With the widely used open-source tools `openapi-typescript` and `openapi-fetch`: ```bash Generate types npm install openapi-fetch npx openapi-typescript https://api.freeinvoiceautomation.com/v1/openapi.json -o src/fia.d.ts ``` ```typescript Typed client baseUrl: "https://api.freeinvoiceautomation.com/v1", headers: { Authorization: `Bearer ${process.env.FIA_API_KEY}` }, }); const { data, error } = await fia.POST("/documents", { headers: { "Idempotency-Key": "billing:invoice:customer-4821:2027-01" }, body: { type: "invoice", client_id: "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", currency: "USD", lines: [{ description: "Website design", quantity: "1", unit_price_minor: "120000" }], }, }); if (error) throw new Error(`${error.error.code}: ${error.error.message}`); console.log(data.data.id, data.data.total_minor); // "120000" ``` Your editor now checks paths, parameters, bodies and responses against the API. Regenerate the types when you want new endpoints; within `v1` changes are additive, so regenerating never breaks existing calls. See [Versioning](/api-reference/versioning). ## What the generated client gives you ### One method per capability Each operation in the OpenAPI document has an `operationId` equal to the capability name, such as `create_invoice`, `list_documents` or `record_payment`. Generated clients use these names, so the SDK, the [MCP tools](/ai/mcp/tools), the CLI and the endpoint pages all share one vocabulary. Each operation also carries its [permission key](/api-reference/permissions) and its read-only or destructive hints as OpenAPI extensions (`x-fia-permission`, `x-fia-hints`, proposed names), which you can read to build your own safety checks. ### Types that follow the API rules - **Money fields** are typed as `string`. Convert them to `BigInt` for arithmetic. See [Money and currencies](/api-reference/money). - **Ids** are uuid strings; **dates** and **timestamps** are strings in `YYYY-MM-DD` and RFC 3339 format. - **Enums** such as document `status` and `type` are string unions. New values can be added within `v1`, so keep a default branch. - **Errors** share one type: `{ error: { code, message, details?, request_id, docs_url? } }`. See [Errors](/api-reference/errors). ## Things the SDK does not do for you The SDK is a client, not a framework. You still: - send an `Idempotency-Key` on writes, required on creates, sends, payments, voids and credit notes. See [Idempotency](/api-reference/idempotency). - send `"confirm": true` on sends from a `draft_only` key and on every destructive action. See [Drafts and confirmations](/api-reference/confirmations). - walk pages with `cursor` and `next_cursor`. See [Pagination and filters](/api-reference/pagination). - honour `Retry-After` on `429`. See [Rate limits](/api-reference/rate-limits). - verify webhook signatures yourself. See [Webhook events](/api-reference/webhook-events#verifying-signatures). ## Use it safely - Keep API keys on the server. Never ship a key in browser code or a mobile app. - Give each service its own key with the fewest scopes it needs. - Prefer `draft_only` keys, and send after a person approves the [send preview](/api-reference/documents/preview-send). ## Related pages API introduction Base URL, conventions and the OpenAPI document CLI The same capabilities from your terminal Authentication API keys, scopes and modes Webhooks React to changes instead of polling Source: https://docs.freeinvoiceautomation.com/integrations/sdk --- # CLI The freeinvoiceautomation command line tool runs every API capability from your terminal and scripts, with the same permissions and modes as your API key. The `freeinvoiceautomation` CLI brings the whole API to your terminal. List overdue invoices, create a client, record a bank payment or export your items to CSV, all from a shell or a script. It is a thin client of the same API that the web app uses: it has no business logic of its own, so it can do exactly what your API key allows and nothing more. ## Install The CLI is published on npm as `freeinvoiceautomation`. ```bash Install npm install -g freeinvoiceautomation ``` The stdio MCP client, `freeinvoiceautomation-mcp`, ships on npm at the same time. It lets MCP clients that only speak stdio reach our hosted MCP server with your API key. See [Other MCP clients](/ai/mcp/other-clients). ## Sign in with an API key The CLI authenticates with an **API key**, never with your password. Create a key In the web app, open **Settings > API keys** and create a key for the CLI. Choose the scopes you need. Keep the mode on **draft only** unless your scripts must send documents without a person confirming. Give the key to the CLI Set the key in your shell (or your CI secrets) so the CLI can read it. The exact sign-in options are set in the CLI spec before release. ```bash export FIA_API_KEY=fia_your_key ``` Check it works List the businesses the key can reach: ```bash freeinvoiceautomation business list ``` The key is bound to one account, so the CLI works in that account. To work in another account, create a key there. ## How commands are named Commands follow the pattern `freeinvoiceautomation ` (proposed; confirmed in the CLI specification). Each command is generated from one API capability, so it has the same inputs, the same [permission key](/api-reference/permissions) and the same errors as the endpoint. The endpoint pages in the [API reference](/api-reference/introduction) show the CLI command for each capability. ### Commands available at release | Area | Commands | | --- | --- | | Businesses | `business list`, `business get`, `business create`, `business update`, `business delete`, `business methods` | | Tax rates and numbering | `tax-rate list`, `tax-rate upsert`, `tax-rate archive`, `numbering list`, `numbering update` | | Clients | `client list`, `client get`, `client upsert`, `client delete`, `client contacts`, `client contact-upsert`, `client contact-delete`, `client credit` | | Items | `item list`, `item get`, `item upsert`, `item archive`, `item import`, `item export` | | Documents | `doc list`, `doc get`, `doc delete`, `doc versions`, `doc activity`, `doc overdue`, `doc send`, `doc send --preview`, `doc pdf`, `doc share`, `doc unshare`, `doc void`, `doc apply-credit` | | Invoices, estimates, receipts, credit notes | `invoice create`, `invoice update`, `estimate create`, `estimate convert`, `receipt create`, `credit-note create` | | Payments | `payment record`, `payment status`, `payment list`, `payment get`, `payment delete`, `payment refund`, `proof list`, `proof accept`, `proof reject` | | Connections | `rail list`, `connection list`, `connection create`, `connection stripe`, `connection update`, `connection verify`, `connection delete` | | Messages and templates | `message list`, `template messages`, `template message-upsert`, `template pdfs`, `template pdf-upsert`, `usage` | | Settings and account | `settings get`, `settings set`, `account get`, `account update`, `account export`, `account delete`, `account transfer`, `file upload` | | Team | `member list`, `member invite`, `member uninvite`, `member update`, `member deactivate`, `member reactivate`, `member remove`, `role list`, `role create`, `role update`, `role delete`, `role reset`, `audit list` | | Developers | `key list`, `key create`, `key revoke`, `webhook list`, `webhook upsert`, `webhook delete`, `webhook test`, `webhook deliveries`, `webhook redeliver`, `connector list`, `connector unlink` | | Billing | `billing get`, `billing card-add`, `billing card-remove`, `credits buy`, `credits packs` | | Reminders, late fees, recurring | `reminder list`, `reminder upsert`, `reminder delete`, `doc remind`, `doc promise`, `late-fee list`, `late-fee upsert`, `late-fee delete`, `late-fee waive`, `recurring list`, `recurring upsert`, `recurring pause`, `recurring resume`, `recurring delete` | | Schedules and reports | `doc schedule`, `doc schedule --show`, `doc schedule --remove`, `report aged`, `report get`, `report export` | | AI agent | `agent run`, `agent runs`, `agent run-get`, `agent automations`, `agent automation-upsert`, `agent automation-delete` | `credits buy` does not charge anything: it prints the Stripe Checkout link for a credit pack, and you pay in the browser. Commands for features that ship later, such as contracts (`contract create`), time tracking (`time log`), expenses (`expense record`), accounting sync (`accounting status`) and the ledger (`ledger report`), are added in the CLI release that matches each feature. A few actions need a fresh sign-in in the web app and cannot be done with an API key (proposed): connecting a payment rail, deleting a business, billing changes, creating an `auto_send` key, adding a billing card, buying a credit pack and deleting the account. The matching commands return `fresh_signin_required`; finish those actions in the app. ## Examples Each command's inputs are the capability's fields, as documented on its endpoint page. The exact flag syntax is set in the CLI specification; the examples show the intent. ```bash List and export # Invoices that are overdue freeinvoiceautomation doc overdue # All saved items of your business as CSV freeinvoiceautomation item export > items.csv ``` ```bash Preview, then send freeinvoiceautomation doc send --preview freeinvoiceautomation doc send ``` ## Safety rules are the same The CLI does not relax any rule of the API: - **Drafts first.** `invoice create` and `estimate create` create drafts. - **Sends need confirmation** from a `draft_only` key. Without it you get the preview and `confirmation_required`. With an `auto_send` key, sends run directly. - **Destructive commands** (`doc void`, `credit-note create`, `payment refund`, `key revoke` and the others marked destructive in the API reference) always need confirmation, whatever the key's mode. - **Idempotency.** Writes carry an `Idempotency-Key`, so a retried command does not create anything twice. See [Idempotency](/api-reference/idempotency). - **Audit.** Every write is recorded in the audit log with your API key as the actor. See [Drafts and confirmations](/api-reference/confirmations) for the details. ## Using the CLI in scripts and CI - Create a separate key for each script or pipeline, with only the scopes it needs. - Store the key in your CI secret store, never in the repository. Keys start with `fia_` (proposed), which secret scanners can recognise. - Respect [rate limits](/api-reference/rate-limits): the CLI uses the same per-key budget as any other API client. - Keys follow their creator's permissions. If the person who created the key loses a permission or leaves the account, the script loses it too. ## Errors The CLI shows the API's error `code`, `message` and `request_id`. Look the code up on [Errors](/api-reference/errors). Quote the `request_id` if you contact support. ## Related pages API reference Every capability the CLI exposes Authentication API keys, scopes and modes TypeScript SDK The typed client the CLI is built on Other MCP clients The stdio MCP client from npm Source: https://docs.freeinvoiceautomation.com/integrations/cli --- # Webhooks Set up webhook endpoints in the app or through the API, choose events, send a test, read the delivery log and redeliver failed events. Webhooks push events to your own systems the moment something happens in your account: an invoice is sent or paid, a client submits a payment proof, an estimate is accepted. You add an HTTPS URL, choose the events you care about, and we `POST` a signed JSON event to it every time one of them happens. This page shows how to set up and manage endpoints. The payload, signature scheme and full event list are in [Webhook events](/api-reference/webhook-events). Webhooks are available on every plan from launch. ## Before you start - You need the `integrations.manage` permission. Owners and Admins have it by default. - You need a public **HTTPS** URL that can receive `POST` requests. Plain `http`, private network addresses and internal hostnames are refused (proposed: port 443 only, and redirects are not followed). - Your endpoint should verify the signature on every request. The [Node.js sample](/api-reference/webhook-events#nodejs) does it in a few lines. ## Add an endpoint in the app Integrations"> Go to the webhooks section and choose to add an endpoint. Enter the URL Paste your HTTPS URL, for example `https://example.com/webhooks/fia`. Choose events Tick the event types this endpoint should receive, such as `document.paid`, `payment.succeeded` and `credit_note.created`. Only ticked events are sent. See the [event list](/api-reference/webhook-events#event-list). Limit to one business (optional) If your account has several businesses, you can limit the endpoint to one. Leave it on all businesses to receive events for every business. Save and copy the signing secret The endpoint gets its own **signing secret**. Copy it into your server's secret store. Afterwards the app only shows its last four characters. Send a test Send a test delivery from the endpoint to get a sample event, and check that your endpoint returns `200` and verifies the signature. ## Add an endpoint through the API Use [Create or update a webhook endpoint](/api-reference/developers/upsert-webhook-endpoint). The same call updates an endpoint when you pass its `id`; `PATCH /v1/webhook-endpoints/{id}` also works. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/webhook-endpoints" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: webhook-endpoint-billing-server" \ -d '{ "url": "https://example.com/webhooks/fia", "events": ["document.sent", "document.paid", "payment.succeeded", "credit_note.created"], "business_id": "0193f29e-11aa-7c3b-8d2e-5a6b7c8d9e0f" }' ``` The endpoint fields are: | Field | Meaning | | --- | --- | | `url` | Your HTTPS URL | | `events` | The event types to send | | `business_id` | Optional. Only events of this business; leave out (or `null`) for all businesses | | `status` | `active` or `disabled` | The signing secret is write-only after creation: no later response contains it, only its last four characters. ### Manage endpoints | Task | Endpoint | CLI | | --- | --- | --- | | List endpoints | [List webhook endpoints](/api-reference/developers/list-webhook-endpoints) | `webhook list` | | Create or change one | [Create or update a webhook endpoint](/api-reference/developers/upsert-webhook-endpoint) | `webhook upsert` | | Delete one | [Delete a webhook endpoint](/api-reference/developers/delete-webhook-endpoint) | `webhook delete` | | Send a test event | [Test a webhook endpoint](/api-reference/developers/test-webhook-endpoint) | `webhook test` | | See the delivery log | [List webhook deliveries](/api-reference/developers/list-webhook-deliveries) | `webhook deliveries` | | Redeliver one delivery | [Redeliver a webhook](/api-reference/developers/redeliver-webhook) | `webhook redeliver` | All of them need `integrations.manage`. ## Test your endpoint The test sends a sample signed event to your URL, so you can check three things before real events arrive: 1. Your server is reachable over HTTPS from the internet. 2. Your signature check accepts our signature with your secret. 3. You return a `2xx` status quickly. ```bash Send a test curl -X POST "https://api.freeinvoiceautomation.com/v1/webhook-endpoints/0193f3b0-2c3d-7e4f-8a9b-0c1d2e3f4a5b/test" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Idempotency-Key: webhook-test-1" ``` While developing, use a tunnelling tool that gives your local server a public HTTPS URL, and point a test endpoint at it. Use a separate endpoint (with its own secret) for production. ## The delivery log Every delivery is recorded per endpoint, with its event id, event type, status, number of attempts, the last HTTP status your server returned, the next attempt time and when it was delivered. | Status | Meaning | | --- | --- | | `pending` | Waiting for its first or next attempt | | `succeeded` | Your endpoint answered with success | | `failed` | The last attempt failed; another attempt is scheduled | | `dead` | All attempts failed; it will not be retried automatically | Failed deliveries are retried with exponential backoff, about 8 attempts over roughly 24 hours (proposed). The event keeps the **same id** on every retry, so your endpoint can ignore repeats. ## Redeliver an event After you fix a problem on your side, send dead or failed deliveries again from the delivery log, or with [Redeliver a webhook](/api-reference/developers/redeliver-webhook): ```bash Redeliver curl -X POST "https://api.freeinvoiceautomation.com/v1/webhook-deliveries/0193f3c4-5d6e-7f80-9a1b-2c3d4e5f6a7b/redeliver" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Idempotency-Key: redeliver-0193f3c4" ``` A redelivered event has the same `id` as the original, so if you already processed it, your deduplication skips it safely. ## Endpoints that keep failing An endpoint whose deliveries fail again and again can be disabled automatically (proposed: after 50 failed deliveries in a row). Its status becomes `disabled` and nothing more is sent until you fix the problem and set it back to `active`. Events that happened while it was disabled can be caught up through the API using the list endpoints with `updated_after`. See [Pagination and filters](/api-reference/pagination). ## Rotate the signing secret If a secret may have leaked, rotate it. For a grace period we sign each delivery with **both** the old and the new secret, so your server keeps accepting deliveries while you deploy the new one. See [Rotating the secret](/api-reference/webhook-events#rotating-the-secret). ## Writing a reliable receiver Respond fast Verify the signature, store the event, return `200`, and do the real work in a background job. Slow responses time out and are retried. Deduplicate on the event id Keep the ids you have processed. The same event can arrive more than once, after a retry or a redelivery. Do not rely on order Events can arrive out of order. Use `created_at`, or fetch the current state from the API, for decisions. Confirm important state with the API Before you ship goods because an invoice is paid, fetch the document with [Get a document](/api-reference/documents/get-document) and check `status` and `balance_minor`. Accept new event types and fields New events and fields are added as features ship. Ignore what you do not recognise. ## Common uses | Goal | Events | | --- | --- | | Mark an order paid in your system | `document.paid`, `document.partially_paid` | | Keep a CRM up to date | `client.created`, `client.updated`, `document.sent` | | Alert your team about bank transfer proofs | `payment_proof.submitted` | | Start work when a client accepts a quote | `estimate.accepted` | | Track AI agent results | `agent_run.completed`, `agent_run.failed` | | Chase late payers in your own tools | `document.overdue` | ## Related pages Webhook events Payload, signatures and the full event list Integrations overview All the ways to connect Idempotency Safe retries on both sides Permission keys `integrations.manage` and the rest Source: https://docs.freeinvoiceautomation.com/integrations/webhooks --- # Shopify The free Shopify app turns orders into invoices or receipts numbered from your own sequence, records Shopify payments, and mirrors cancels and refunds. The Free Invoice Automation app for Shopify creates an invoice or receipt for every order in your store, with no limit on the number of orders. It records the payment Shopify took, voids the invoice when an order is cancelled, and issues a credit note when you refund. Your documents are numbered from your own business's gap-free sequence, not from Shopify order numbers, so your books stay tidy. The app is free, listed on the Shopify App Store, and needs only a Free account. It is a thin client of our API: it holds no business data of its own, and every action goes through the same API, permissions and audit log as the web app. ## How it fits together - You install the app from the Shopify App Store and approve it in Shopify. - The app runs on `shopify.freeinvoiceautomation.com`, embedded in your Shopify admin. It keeps only the install record and its session. - Shopify sends order events to the app. The app checks Shopify's signature on each one, then calls our API with the install's own API key. - Documents, clients and payments live in your Free Invoice Automation business, where you can view, send, download and report on them like any other document. ## Install and link your shop Install the app Find "Free Invoice Automation" on the Shopify App Store and install it. Shopify asks you to approve the app's access to your store. Sign in to Free Invoice Automation The app opens in Shopify admin and asks you to sign in, or to create a free account if you do not have one. Pick the business Choose which of your businesses the shop belongs to. Each shop links to exactly one business. The link is saved as a connector install, and the install gets its own API key scoped to that business. Choose your settings On the app's settings page in Shopify admin, choose which events create documents, the document type (invoice or receipt), the numbering prefix, tax mapping, and whether to send documents to customers automatically. "Send automatically" is off by default. The install then appears in **Settings > Integrations** as a connected store, where you can see it and unlink it. ## What each Shopify event does | Shopify event | What happens | API call the app makes | | --- | --- | --- | | `orders/create` | An invoice or receipt is created for the order, per your settings, with the Shopify order id stored as its external reference | `POST /v1/documents` with `Idempotency-Key: shopify:{shop}:{order_id}:orders/create` | | `orders/paid` | The payment Shopify took is recorded against the document | `POST /v1/payments`, recorded with source `connector` | | `orders/cancelled` | The invoice is voided; if payments already exist, a credit note is issued instead | `POST /v1/documents/{id}/void` with `confirm: true`, or `POST /v1/documents/{id}/credit-notes` | | `refunds/create` | A credit note for the refunded amount is issued, with the settlement recorded as refunded outside our system | `POST /v1/documents/{id}/credit-notes` with `confirm: true` | | `app/uninstalled` (proposed) | The install is unlinked | `DELETE /v1/connector-installs/{id}` | The endpoints behind these calls are [Create an invoice](/api-reference/documents/create-invoice), [Create a receipt](/api-reference/documents/create-receipt), [Record a payment](/api-reference/payments/record-payment), [Void a document](/api-reference/documents/void-document), [Create a credit note](/api-reference/documents/create-credit-note) and [Unlink a connector install](/api-reference/developers/unlink-connector-install). ### No duplicates Each order event uses a deterministic idempotency key built from the shop, the order id and the topic, and each document stores the order id as its external reference. If Shopify sends the same event twice, the second one changes nothing. See [Idempotency](/api-reference/idempotency). ### Numbering Invoice and receipt numbers come from your business's own gap-free sequences, with the prefix you choose. Receipts have their own sequence. Shopify order numbers are never used as document numbers, so a gap in Shopify (a deleted test order, for example) never becomes a gap in your invoices. See [Document numbering](/invoicing/numbering). ## Payments from Shopify are verified Normally an invoice becomes paid only from a verified payment rail event, a reconciliation, or a person recording the payment. A Shopify payment counts as a verified rail event **only** when it arrives through Shopify's signed webhook: - Every webhook is checked with Shopify's HMAC-SHA256 signature of the raw body (the `X-Shopify-Hmac-Sha256` header) before anything else happens, and deduplicated by Shopify's webhook id. - A verified `orders/paid` event is recorded as a payment with source `connector`, and the audit log entry names the store install as the actor, with actor type `connector`. - An event with a missing or wrong signature **creates nothing**. ## Cancels and refunds are mirrored automatically A store order is different from an invoice you send: by the time the event arrives, Shopify has already cancelled the order or refunded the customer. Mirroring that is recording a fact, not making a new decision. So: - On `orders/cancelled`, the app voids the invoice with `confirm: true`, without asking you. If the invoice already has payments, it issues a credit note instead, because a paid invoice cannot be voided. - On `refunds/create`, the app issues a credit note for the refunded amount with `confirm: true`. - Each of these is written to the audit log with actor type `connector` and shown in the document's activity list, so you can always see what the store did and when. Everything else stays under your control. In particular, the app never sends anything to your customer unless you have turned that on. ## Sending documents to your customers By default, documents created from orders stay **drafts** as far as your customer is concerned: nothing is emailed until a member of your team sends it from the app, the API or an assistant. If you want every order's invoice or receipt emailed to the customer automatically, turn on **send automatically** in the app's settings. Because this lets the store email your customers with no person in the loop, turning it on requires a **fresh sign-in**, the same as creating an `auto_send` API key. You can turn it off at any time. Emails go out through your usual sender: your own SMTP, or the platform mailer within your plan's monthly quota (20 on Free, 100 on Team). A busy store will usually want [your own SMTP](/messaging/smtp). ## Customer data and privacy The app handles Shopify's mandatory privacy webhooks: | Shopify request | What we do | | --- | --- | | `customers/data_request` | Export that customer's client data to you, the merchant (proposed endpoint) | | `customers/redact` | Remove the customer's personal data, except what tax law requires to stay on documents already issued (proposed endpoint) | | `shop/redact` | Unlink the install and remove the shop's data (proposed endpoint) | For the client data you put into your account, you are the controller and we act as your processor. See [How we protect data](/security/overview). ## Unlinking or uninstalling - **Uninstall the app in Shopify:** the install is unlinked. Documents already created stay in your account. - **Unlink in Settings > Integrations**, or with [Unlink a connector install](/api-reference/developers/unlink-connector-install): the install's API key stops working at once. Unlinking is destructive, so through the API it needs `"confirm": true`. ## Permissions Linking, changing and unlinking a store install needs `integrations.manage`. The install's own API key is limited to its one business and to the scopes the app needs (proposed), and it can never exceed the permissions of the member who linked it. ## Troubleshooting An order did not create a document Check the app's settings: is that event switched on? Check the install is active in **Settings > Integrations**. Events that fail signature verification create nothing by design. A cancelled order produced a credit note instead of a void The invoice already had a payment recorded. Paid invoices cannot be voided; a credit note is the correct record. See [Void and delete](/invoicing/void). Customers are not receiving invoices "Send automatically" is off by default. Turn it on in the app's settings (fresh sign-in needed), or send the drafts yourself. Also check your [platform email](/messaging/platform-email) quota. ## Related pages WordPress and WooCommerce The same flow for WooCommerce stores Credit notes How refunds become credit notes Receipts Receipts for orders that are already paid Drafts and confirmations Why sends stay drafts by default Source: https://docs.freeinvoiceautomation.com/integrations/shopify --- # WordPress and WooCommerce The free WordPress plugin creates invoices from WooCommerce orders, records verified payments, mirrors refunds and cancels, and adds a Create invoice button. The `free-invoice-automation` WordPress plugin connects your WordPress site to Free Invoice Automation. With WooCommerce, it creates an invoice for each order, records the payment when the order is paid, issues a credit note when you refund and voids the invoice when an order is cancelled. It also adds a **Create invoice** button to manual orders and a pay-me block for any page. Without WooCommerce, you can still use the pay-me block. The plugin is free and works with a Free account. It is a thin client of our API: it contains no business logic or money maths of its own, and every action goes through the same API, permissions and audit log as the web app. ## Get the plugin The plugin is published on WordPress.org under the slug `free-invoice-automation`, and as a direct zip download from this docs site. Install it like any other plugin: from **Plugins > Add New** in your WordPress admin, or by uploading the zip. ## Connect your site Create an API key for the site In Free Invoice Automation, open **Settings > API keys** and create a key for this site. Give it only the scopes the plugin needs: creating and reading documents and clients, recording payments, voiding and crediting. Keep it on **draft only**. Open the plugin settings In WordPress, the plugin's settings page is under **WooCommerce**, or under **Settings** if WooCommerce is not installed. Paste the key and test Paste the API key and use the connection test. The plugin lists your businesses, which also proves the key works. Pick the business and events Choose the business this site belongs to and which order events create documents. Save. The site is recorded as a connector install and appears as a connected site in **Settings > Integrations**. Each install also has its own **webhook secret**. The plugin signs every call it makes to our API with it, on top of the API key, so we can tell that a call really came from your site. ## What each WooCommerce event does | WooCommerce hook | What happens | API call | | --- | --- | --- | | `woocommerce_order_status_processing` | An invoice is created for the order | `POST /v1/documents` with `Idempotency-Key: woo:{site}:{order_id}:processing` | | `woocommerce_order_status_completed` | The payment is recorded if the order is paid, or the invoice is created if it does not exist yet | `POST /v1/payments` | | `woocommerce_order_status_refunded` | A credit note is issued | `POST /v1/documents/{id}/credit-notes` with `confirm: true` | | `woocommerce_order_status_cancelled` | The invoice is voided | `POST /v1/documents/{id}/void` with `confirm: true` | | **Create invoice** button on a manual order | An invoice is created for that order | `POST /v1/documents` | | Connection test on the settings page | Lists your businesses for the picker | `GET /v1/businesses` | | `[fia_pay_me]` shortcode or block | Shows your business's pay-me page | `GET /v1/public/pay-me/{slug}`, or an embed of your public business page | The plugin finds documents it created earlier by the WooCommerce order id, which it stores as the document's external reference (proposed: `GET /v1/documents?external_ref={order_id}&source=woocommerce`). See [List documents](/api-reference/documents/list-documents). The endpoints behind these calls are [Create an invoice](/api-reference/documents/create-invoice), [Record a payment](/api-reference/payments/record-payment), [Create a credit note](/api-reference/documents/create-credit-note), [Void a document](/api-reference/documents/void-document) and [List businesses](/api-reference/businesses/list-businesses). ### No duplicates Order events use a deterministic idempotency key built from the site, the order id and the status, so a hook that fires twice does not create a second invoice. See [Idempotency](/api-reference/idempotency). ### Numbering Invoices are numbered from your business's own gap-free sequence, not from WooCommerce order numbers. See [Document numbering](/invoicing/numbering). ## Verified payments only An invoice is marked paid only from a verified payment event, a reconciliation or a person recording the payment. A WooCommerce "paid" call counts as a verified rail event **only** when it is signed with the install's webhook secret and the signature checks out: - The plugin signs each call with an HMAC-SHA256 of a timestamp and the raw body (proposed header: `FIA-Signature`). - A correctly signed paid call is recorded as a payment with source `connector`, and the audit log names the install as the actor, with actor type `connector`. - An unsigned or wrongly signed call **creates no payment**. ## Refunds and cancels are mirrored automatically When an order is refunded or cancelled in WooCommerce, it has already happened in your store. Recording it is recording a fact, so the plugin does it without asking: - **Refunded:** a credit note is issued with `confirm: true`. - **Cancelled:** the invoice is voided with `confirm: true`. Both are written to the audit log with actor type `connector` and appear in the document's activity list. If a cancelled order's invoice already has payments, voiding is refused (`document_has_payments`), because a paid invoice is corrected with a credit note instead. See [Void and delete](/invoicing/void). ## Sending documents to customers Documents created for your store's customers stay **drafts** until someone on your team sends them. If you want them emailed automatically, turn on **send automatically** for the install. This needs a **fresh sign-in**, like creating an `auto_send` key, because it lets your site email customers with no person in the loop. It is off by default. Emails go through your own SMTP or the platform mailer within your plan's quota (20 a month on Free, 100 on Team). For a store, [your own SMTP](/messaging/smtp) is usually the better choice. ## The pay-me block The `[fia_pay_me]` shortcode, or the matching block, embeds your business's pay-me page on any WordPress page. Visitors see your business name, logo and the payment methods you enabled, and pay through hosted checkout. It works with or without WooCommerce. The block is shown once the pay-me page is live in February 2027. See [Pay-me page](/payments/pay-me). ## Security notes - The API key is stored in your WordPress database. Give it only the scopes the plugin needs, scope it to one business (proposed), and revoke it from **Settings > API keys** if your site is ever compromised. - The install's webhook secret is also stored on our side, encrypted. - Download the plugin only from WordPress.org or this docs site. ## Unlinking Unlink the site in **Settings > Integrations** or with [Unlink a connector install](/api-reference/developers/unlink-connector-install). Its key stops working at once. Documents already created stay in your account. Unlinking through the API is destructive and needs `"confirm": true`. ## Permissions Linking and managing installs needs `integrations.manage`. The plugin's API key can never do more than the member who created it can do right now. ## Troubleshooting The connection test fails Check that the key was pasted in full and has not been revoked. The test calls `GET /v1/businesses`, so the key needs `businesses.view`. See [Errors](/api-reference/errors#unauthenticated). Orders are not creating invoices Check that the matching order statuses are ticked in the plugin settings and that the install is active in **Settings > Integrations**. Paid orders are not marked paid Paid calls must be signed with the install's webhook secret. If the signature does not check out, no payment is recorded, by design. Check that the install is active and the plugin is up to date. Meanwhile you can [record the payment](/payments/recording) yourself. ## Related pages Shopify The same flow for Shopify stores Pay-me page The page behind the pay-me block Credit notes How refunds become credit notes Authentication API keys and scopes Source: https://docs.freeinvoiceautomation.com/integrations/woocommerce --- # QuickBooks Online Push clients, items, invoices, credit notes and payments one way into QuickBooks Online, so your accountant's books stay current without double entry. If your accountant keeps your books in QuickBooks Online, connect it and Free Invoice Automation will push your invoicing into it automatically. Clients, items, invoices, credit notes and payments (and, once time and expenses ship, billable expenses) appear in QuickBooks as you work, with no copying and no exports to import. The sync is **one way**: we write to QuickBooks, and we never read or change anything else in your books. Accounting sync is included on the Free plan. ## What is synced - **Clients** - **Saved items** (products and services) - **Invoices** - **Credit notes** - **Payments**, into the payment (deposit) account you map - **Billable expenses**, once [time tracking and expenses](/features/expenses) ship in April 2027 **Direction:** one way only, from us to QuickBooks. We never read your QuickBooks ledger or change anything in it beyond what we push, and two-way sync is not planned. Your accountant can keep working in QuickBooks as usual. ## Before you start - You need the `accounting.manage` permission. Owners and Admins have it by default; the Accountant role does not, but it can be given on Team with a [custom role](/team/custom-roles). - You need to be able to sign in to the QuickBooks Online company you want to connect and approve access for it. - Each business connects to **one** QuickBooks company. If you have several businesses, connect each one separately. ## Connect QuickBooks Integrations"> Choose the business, then choose QuickBooks Online. Connecting asks you to sign in again first (fresh sign-in), because it gives an outside service access to your financial data. Approve access at Intuit You are sent to Intuit's sign-in page. Choose the company and approve. We ask only for the access needed to write the records above (proposed: the `com.intuit.quickbooks.accounting` scope). Review the mapping Back in **Settings > Integrations**, the mapping screen opens with defaults for your country. Check the tax rates, the income account, the payment (deposit) account and the item mapping, then save. Choose a start date Pick the date from which documents should sync. Documents dated before it are not pushed unless you run a backfill. From then on, every change to a synced record is pushed automatically. ## The mapping | Setting | What it decides | | --- | --- | | Tax rates | How each of your tax rates maps to tax in QuickBooks | | Income account | Where invoice income is posted | | Payment (deposit) account | Where received payments are deposited | | Item mapping | Which QuickBooks item each saved item corresponds to | Sensible defaults are filled in for your business's country. You can change the mapping at any time in the app or with [Update the accounting mapping](/api-reference/accounting/update-accounting-mapping); new pushes use the new mapping. ## Backfill older documents To bring documents from before your start date into QuickBooks, run a backfill. It pushes the older documents in one batch, in the background. Use the app, or [Backfill accounting](/api-reference/accounting/backfill-accounting), which answers `202 Accepted` and runs as a job. ## How the sync runs - Every change to a client, item, invoice, credit note or payment queues a push for that record. - Each push is an **idempotent upsert**: we keep a map of our ids to QuickBooks ids, so a record is created once and then updated. Running the sync twice never creates duplicates in QuickBooks. - Access tokens are encrypted at rest and refreshed automatically before they expire. - **Nothing blocks invoicing.** If QuickBooks is down or rejects a record, you can still create, send and get paid. The record waits with an error until it is retried. ## Errors and retry Each synced record has a status: `pending`, `synced` or `error`. A record in `error` shows the reason on the document, with a **Retry** button. - See every record's status with [List sync items](/api-reference/accounting/list-sync-items). - Retry one record with [Retry an accounting sync](/api-reference/accounting/retry-accounting-sync). - Subscribe to the `accounting_sync.failed` [webhook event](/api-reference/webhook-events) to be told when a push fails. For example, if the mapping points to an account that no longer exists in QuickBooks, pushes fail until you fix the mapping. Fix the cause, then retry. ### Reconnecting If the connection's status becomes `needs_reauth`, for example because access was revoked in QuickBooks, reconnect from **Settings > Integrations**. Pushes resume after you reconnect. ## Disconnect Disconnect in **Settings > Integrations** or with [Disconnect accounting](/api-reference/accounting/disconnect-accounting). Disconnecting is a destructive action, so through the API it needs `"confirm": true`. We revoke the access at Intuit and delete our copy of the tokens (proposed). Records already in QuickBooks stay there; nothing is deleted from your books. ## Through the API | Task | Endpoint | CLI | | --- | --- | --- | | Start the connection (returns the Intuit sign-in URL) | [Connect accounting](/api-reference/accounting/connect-accounting) | none | | List connections and their status | [List accounting connections](/api-reference/accounting/list-accounting-connections) | `accounting list` | | Change the mapping | [Update the accounting mapping](/api-reference/accounting/update-accounting-mapping) | none | | Push older documents | [Backfill accounting](/api-reference/accounting/backfill-accounting) | `accounting backfill` | | See per-record status | [List sync items](/api-reference/accounting/list-sync-items) | `accounting status` | | Retry one record | [Retry an accounting sync](/api-reference/accounting/retry-accounting-sync) | `accounting retry` | | Disconnect | [Disconnect accounting](/api-reference/accounting/disconnect-accounting) | none | All need `accounting.manage`. Connecting needs a fresh sign-in, so it is started from the web app. ## QuickBooks or our own ledger From June to July 2027, Free Invoice Automation also has its own [accounting ledger](/features/ledger) with a chart of accounts, journal entries and financial reports. If you keep your books here you do not need QuickBooks sync, but the two can run side by side. ## Related pages Xero The same sync for Xero Accounting ledger Keep your books in Free Invoice Automation Integrations overview Every way to connect Permission keys `accounting.manage` and the others Source: https://docs.freeinvoiceautomation.com/integrations/quickbooks --- # Xero Push clients, items, invoices, credit notes and payments one way into Xero, so your books stay up to date while you invoice in Free Invoice Automation. If you or your accountant keep the books in Xero, connect your Xero organisation and Free Invoice Automation will push your invoicing into it as you work. Clients, items, invoices, credit notes and payments (and billable expenses once time and expenses ship) arrive in Xero automatically, so nobody types the same invoice twice. The sync is **one way**: we write to Xero and never read or change anything else in your books. Accounting sync is included on the Free plan. It works exactly like the [QuickBooks Online](/integrations/quickbooks) sync; this page covers what is specific to Xero. ## What is synced - **Clients** - **Saved items** (products and services) - **Invoices** - **Credit notes** - **Payments**, into the payment (deposit) account you map - **Billable expenses**, once [time tracking and expenses](/features/expenses) ship in April 2027 **Direction:** one way only, from Free Invoice Automation to Xero. We never read your Xero ledger or change anything in it beyond what we push. Two-way sync is not planned. ## Before you start - You need the `accounting.manage` permission. Owners and Admins have it by default. - You need to be able to sign in to the Xero organisation you want to connect and approve access for it. - Each business connects to **one** Xero organisation. Connect each business separately if you have several. ## Connect Xero Integrations"> Choose the business, then choose Xero. You are asked to sign in again first (fresh sign-in), because you are giving an outside service access to your financial data. Approve access at Xero Sign in to Xero, choose the organisation and approve. We ask only for what we need to write the records above (proposed: accounting transactions and contacts, plus `offline_access` so the connection keeps working). Check the mapping The mapping screen opens with defaults for your business's country: tax rates, income account, payment (deposit) account and item mapping. Adjust and save. Pick a start date Documents from this date on are pushed. Older ones can be pushed later with a backfill. The connection then shows as active in **Settings > Integrations**, with the name of the Xero organisation and the time of the last sync. ## Mapping | Setting | What it decides | | --- | --- | | Tax rates | How each of your tax rates maps to tax in Xero | | Income account | The account invoice income is posted to | | Payment (deposit) account | The account received payments go into | | Item mapping | Which Xero item each saved item corresponds to | Change the mapping at any time in the app or with [Update the accounting mapping](/api-reference/accounting/update-accounting-mapping). Pushes after the change use the new mapping. ## Backfill Run a backfill to push documents dated before your start date in one batch. It runs in the background: start it in the app or with [Backfill accounting](/api-reference/accounting/backfill-accounting), which answers `202 Accepted`. ## How the sync behaves - **Automatic.** Every change to a client, item, invoice, credit note or payment queues a push. - **No duplicates.** We keep a map of our ids to Xero ids, so each record is created once and updated afterwards. Running a push twice changes nothing. - **Never in the way.** If Xero is unavailable or rejects a record, invoicing carries on as normal; the record waits with an error. - **Tokens stay safe.** Access tokens are encrypted at rest and refreshed automatically. ## Errors and retry Each record has a sync status of `pending`, `synced` or `error`. When a push fails, the document shows the error and a **Retry** button. You can also: - list every record's status with [List sync items](/api-reference/accounting/list-sync-items) or `freeinvoiceautomation accounting status`, - retry one with [Retry an accounting sync](/api-reference/accounting/retry-accounting-sync) or `freeinvoiceautomation accounting retry`, - get the `accounting_sync.failed` [webhook event](/api-reference/webhook-events) whenever a push fails. If the connection becomes `needs_reauth`, for example because access was removed in Xero, reconnect from **Settings > Integrations**. Waiting records are pushed once the connection is back. ## Disconnect Disconnect in **Settings > Integrations** or with [Disconnect accounting](/api-reference/accounting/disconnect-accounting) (destructive, so it needs `"confirm": true` in the API). We revoke our access at Xero and delete our copy of the tokens (proposed). Everything already pushed stays in Xero. ## Through the API | Task | Endpoint | | --- | --- | | Start the connection; returns the Xero sign-in URL | [Connect accounting](/api-reference/accounting/connect-accounting) | | List connections | [List accounting connections](/api-reference/accounting/list-accounting-connections) | | Change the mapping | [Update the accounting mapping](/api-reference/accounting/update-accounting-mapping) | | Backfill older documents | [Backfill accounting](/api-reference/accounting/backfill-accounting) | | Per-record status | [List sync items](/api-reference/accounting/list-sync-items) | | Retry one record | [Retry an accounting sync](/api-reference/accounting/retry-accounting-sync) | | Disconnect | [Disconnect accounting](/api-reference/accounting/disconnect-accounting) | All need `accounting.manage`. Because connecting needs a fresh sign-in, start it from the web app; after that, the other calls work with an API key that has the scope. ```bash List sync status curl "https://api.freeinvoiceautomation.com/v1/accounting-connections/0193f4a0-6b7c-7d8e-9f01-2a3b4c5d6e7f/sync-items" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ## Your choice of books Free Invoice Automation's own [accounting ledger](/features/ledger) arrives in June to July 2027. Use Xero, our ledger, or both side by side; the Xero sync does not depend on the ledger. ## Related pages QuickBooks Online The same sync for QuickBooks Accounting ledger Books inside Free Invoice Automation Credit notes What a credit note pushes Webhook events Get told when a sync fails Source: https://docs.freeinvoiceautomation.com/integrations/xero --- # n8n An n8n node for Free Invoice Automation is planned. How it will work, and how to connect n8n to the API and webhooks today with built-in nodes. [n8n](https://n8n.io) is a workflow automation tool. A Free Invoice Automation node for n8n is on our roadmap, to be built when users ask for it. Like our CLI and store connectors, it will be a thin client of the API: every action it offers is an API capability, with the same permissions, drafts-first safety and audit trail as the web app. You do not have to wait for the node. n8n's built-in HTTP Request and Webhook nodes already work with our REST API and signed webhooks, and this page shows how. ## How the node is designed - **Published on npm.** The n8n node is one of the few thin clients we publish to npm, alongside the CLI and the stdio MCP client. - **Built on the same typed client.** It is built on our TypeScript SDK, which is generated from the OpenAPI document, so its fields match the API exactly. See [TypeScript SDK](/integrations/sdk). - **API key authentication.** You create an API key in **Settings > API keys** and add it to n8n as a credential. The key carries scopes and a mode, exactly as described in [Authentication](/api-reference/authentication). - **Same rules as every surface.** Drafts first; sends need confirmation unless the key is `auto_send`; destructive actions such as void and credit note always need confirmation. See [Drafts and confirmations](/api-reference/confirmations). The exact list of operations and triggers is decided when the node is scheduled. Because every capability already exists in the API, the node can offer any of them. ## Connect n8n today ### Call the API with the HTTP Request node Create an API key In **Settings > API keys**, create a key for n8n with only the scopes your workflows need, for example `clients.view`, `clients.create`, `documents.view` and `documents.create`. Keep the mode on **draft only** to start with. Add a credential in n8n Create a header authentication credential with the name `Authorization` and the value `Bearer` followed by a space and your key. Add an HTTP Request node Set the method and URL, for example `POST https://api.freeinvoiceautomation.com/v1/documents`, choose the credential, and send a JSON body. Add an Idempotency-Key header Build it from data in the workflow, such as your order id, so a retried execution does not create a second invoice. See [Idempotency](/api-reference/idempotency). A body for a draft invoice looks like this: ```json Create a draft invoice { "type": "invoice", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "currency": "USD", "lines": [ { "description": "Monthly retainer", "quantity": "1", "unit_price_minor": "250000" } ] } ``` Money is always a string of integer minor units: `"250000"` is 2,500.00 USD. See [Money and currencies](/api-reference/money). ### Start workflows from our webhooks Add a Webhook node in n8n Set it to accept `POST` and copy its production URL. It must be a public HTTPS URL. Add the endpoint in Free Invoice Automation In **Settings > Integrations**, add a webhook endpoint with that URL and choose events such as `document.paid` or `payment_proof.submitted`. Copy the signing secret. See [Webhooks](/integrations/webhooks). Verify the signature Check the `FIA-Signature` header against the raw body and your secret before the workflow acts. The scheme and a Node.js sample are in [Webhook events](/api-reference/webhook-events#verifying-signatures); n8n's Code node can run the same logic. Deduplicate The same event can arrive more than once. Use the event `id` to skip repeats. ## Example workflows | Trigger | Action | | --- | --- | | A new row in a spreadsheet | Create or update a client, then create a draft invoice | | `document.paid` webhook | Post a message to your team chat and update your CRM | | `payment_proof.submitted` webhook | Notify whoever checks bank transfers, with a link to the invoice | | A schedule every Monday | List overdue invoices with [List overdue documents](/api-reference/documents/list-overdue) and email a summary | ## Keep it safe - Use a dedicated key for n8n with the fewest scopes, and revoke it from **Settings > API keys** if the n8n instance is ever exposed. - Prefer `draft_only` and review drafts before sending. If a workflow must send on its own, create an `auto_send` key, which needs a fresh sign-in. - Respect [rate limits](/api-reference/rate-limits): on a `429`, wait for `Retry-After` before retrying. ## Ask for the node If an n8n node would help you, tell us at [info@freeinvoiceautomation.com](mailto:info@freeinvoiceautomation.com). Connectors after launch are built on demand, so requests help us decide. ## Related pages API introduction Base URL, conventions and OpenAPI Webhooks Set up endpoints and test them CLI Script invoicing from a terminal Integrations overview Every way to connect Source: https://docs.freeinvoiceautomation.com/integrations/n8n --- # Introduction The Free Invoice Automation REST API. Base URL, authentication, conventions, the OpenAPI document and how the endpoint pages are organised. The Free Invoice Automation API lets your own code do everything the web app does: create businesses, clients and items, issue invoices and estimates, send them, record payments, issue credit notes and listen for changes with webhooks. It is a JSON REST API over HTTPS, and it is the same API that the web app, the MCP server, the CLI, the Shopify app, the WordPress plugin and the AI agent use. ## One capability, every surface Every action in the product is defined once, as a **capability**. Each capability has a name (such as `create_invoice`), a validated input and output, one [permission key](/api-reference/permissions), a hint that says whether it only reads, writes, or is destructive, and a preview of what it would do. From that one definition we build: - the REST route and its entry in the OpenAPI document, - the MCP tool, for capabilities on the MCP tool list (see the [tool reference](/ai/mcp/tools)), - the CLI command (see [CLI](/integrations/cli)), - the method in the typed client (see [TypeScript SDK](/integrations/sdk)), - the tools the [AI invoice agent](/ai/agent/overview) can call. Because of this, no feature exists only in the web UI. If you can click it in the app, you can call it through the API, with the same permissions, the same validation and the same audit entry. ## Base URL All endpoints live under one base URL: ```text https://api.freeinvoiceautomation.com/v1 ``` The major version is part of the path. Within `v1` we only make additive changes, so code you write today keeps working. See [Versioning](/api-reference/versioning). ## Surfaces | Surface | Where | How you authenticate | | --- | --- | --- | | REST API `/v1` | `https://api.freeinvoiceautomation.com/v1` | API key, web app session or MCP token | | MCP server | `https://mcp.freeinvoiceautomation.com/mcp` | OAuth sign-in or an API key; `generate_pdf` needs no sign-in | | CLI | your terminal, `freeinvoiceautomation` on npm | API key | | AI invoice agent | inside the app, **Automate > AI agent** | your signed-in session and role | | Shopify app, WordPress plugin | your store | the install's own API key | | Public pages | pay page, verification page, client portal | public tokens, no account | ## Authentication in short Most integrations use an **API key**. Create one in **Settings > API keys**, then send it as a bearer token: ```bash cURL curl "https://api.freeinvoiceautomation.com/v1/businesses" \ -H "Authorization: Bearer $FIA_API_KEY" ``` A key belongs to one account. It carries **scopes** (the permission keys it may use) and a **mode**: `draft_only` (the default) or `auto_send`. A key can never do more than the person who created it can do right now. The full details are in [Authentication](/api-reference/authentication). ## Conventions These rules hold for every endpoint. Each one has its own page with examples. - **JSON only, UTF-8.** Field names are `snake_case`. Send `Content-Type: application/json` with every body. See [Requests and responses](/api-reference/requests). - **Ids** are uuid v7 strings, for example `0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51`. - **Dates** are `YYYY-MM-DD`. **Timestamps** are RFC 3339 in UTC, for example `2027-01-04T09:12:44Z`. - **Money** is a string of integer minor units with a `currency` field. `"total_minor": "120000"` with `"currency": "USD"` is 1,200.00 USD. Floating-point amounts are rejected. See [Money and currencies](/api-reference/money). - **Idempotency.** Every write accepts an `Idempotency-Key` header, and creates, sends, payments, voids and credit notes require one from API keys and MCP clients. See [Idempotency](/api-reference/idempotency). - **Lists** use Grok pagination with `limit` and `Grok`, and filter with plain query parameters. See [Pagination and filters](/api-reference/pagination). - **Drafts first.** Integrations create drafts. Sends need `"confirm": true` unless your key is `auto_send`, and destructive actions always need `"confirm": true`. See [Drafts and confirmations](/api-reference/confirmations). - **Errors** share one shape with a stable `code` and a `request_id`. See [Errors](/api-reference/errors). - **Rate limits** apply per key and per account, with standard headers. See [Rate limits](/api-reference/rate-limits). - **Deletes are soft.** A delete returns `204` and keeps history. Documents and payments are never hard-deleted, and a sent document changes only by revision. ## A first request Create a draft invoice for an existing client. The response contains the totals computed by the server. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/documents" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 2c1f5a7e-invoice-acme-jan" \ -d '{ "type": "invoice", "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "currency": "USD", "lines": [ { "description": "Website design", "quantity": "1", "unit_price_minor": "120000" } ] }' ``` ```javascript JavaScript const response = await fetch("https://api.freeinvoiceautomation.com/v1/documents", { method: "POST", headers: { Authorization: `Bearer ${process.env.FIA_API_KEY}`, "Content-Type": "application/json", "Idempotency-Key": "2c1f5a7e-invoice-acme-jan", }, body: JSON.stringify({ type: "invoice", client_id: "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", currency: "USD", lines: [{ description: "Website design", quantity: "1", unit_price_minor: "120000" }], }), }); const { data } = await response.json(); ``` The invoice is a draft with no number yet. It gets its number from the business's gap-free sequence when it is first sent. To send it, call [Send a document](/api-reference/documents/send-document) with `"confirm": true`. ## OpenAPI The API is described by an OpenAPI document generated from the same capability definitions as the routes, so it always matches what the server does. It is published at: ```text GET https://api.freeinvoiceautomation.com/v1/openapi.json ``` This path is the planned (proposed) location. Every operation in it has: - an `operationId` equal to the capability name, for example `create_invoice` or `list_items`, - tags by module (documents, clients, payments and so on), - the permission key in an `x-fia-permission` extension and the read-only or destructive hints in `x-fia-hints` (proposed extension names), - request and response examples. Use it to generate a client in your own language, to import the API into an HTTP tool, or to check your requests. Our own typed TypeScript client is generated from the same document; see [TypeScript SDK](/integrations/sdk). A check in our build fails on any breaking change to `/v1`, so the document is a reliable contract. ## How the endpoint pages are organised The sidebar of this tab has two parts. **Using the API** (this section) explains the rules that apply to every call. Below it, the endpoints are grouped by module, in the same order as the product: businesses, clients, items, documents, payments, connections, messaging, account, team, developers, billing, then the features that ship after launch. Each endpoint page shows: - the method and path, and the capability name, - the [permission key](/api-reference/permissions) it needs and whether it is read-only, an ordinary write, or destructive, - the matching MCP tool and CLI command, when there is one, - the release it ships in, if it comes after launch, - every path, query and body parameter, with types and which are required, - example requests and responses, and the errors it can return besides the common ones. A few pages to start with: List items A simple paginated list with filters and sorting. Create or update an item An upsert with money fields and an idempotency key. Create an invoice The main write: a draft invoice with lines and taxes. Record a payment Mark money received with an audited record. ## Where to go next Authentication API keys, scopes, modes and sessions Errors Every error code, what it means and how to fix it Webhook events Signed events for every change, with verification code Build with AI Use the same capabilities from Claude, ChatGPT, Grok or Gemini Source: https://docs.freeinvoiceautomation.com/api-reference/introduction --- # Authentication Authenticate with API keys, web app sessions or MCP tokens. Scopes, draft-only and auto-send modes, account selection and fresh sign-in. Every call to `https://api.freeinvoiceautomation.com/v1` runs as someone: a person signed in to the web app, an API key, an MCP client or a public link. The API works out who that is, which account and businesses they can reach, and which [permission keys](/api-reference/permissions) they hold, before it does anything else. For your own scripts and servers, use an **API key**. The other methods are listed below so you know how the rest of the product signs in. ## Methods at a glance | Method | Used by | How it is sent | | --- | --- | --- | | API key | your code, the CLI, the Shopify app, the WordPress plugin | `Authorization: Bearer ` | | Session | the web app at `app.freeinvoiceautomation.com` | a secure, HttpOnly cookie, with a CSRF token on writes | | MCP OAuth token | Claude, ChatGPT, Grok, Gemini and other MCP clients | issued after you sign in and approve on a consent screen | | Client scope | the pay page, estimate page and client portal | a public token in the link | | None | `generate_pdf`, document verification, the waitlist, pay-page reads | no credentials, limited per IP address | ## API keys ### Create a key API keys"> Sign in to the [web app](https://app.freeinvoiceautomation.com) and open **Settings > API keys**. You need the `integrations.manage` permission, which Owners and Admins have by default. Name the key and choose its scopes Give the key a name that says where it is used, such as "Billing server". Tick only the scopes it needs. Scopes are permission keys, for example `documents.create` and `clients.view`. You can only tick keys that you hold yourself. Choose the mode Leave the mode on **draft only** unless the key must send documents to clients without a person confirming. Creating an `auto_send` key asks you to sign in again. Copy the key The full key is shown **once**. Store it in your secret manager or environment. We keep only a hash, so we cannot show it again; if you lose it, revoke it and create a new one. You can do the same through the API with [Create an API key](/api-reference/developers/create-api-key), list keys with [List API keys](/api-reference/developers/list-api-keys) and revoke one with [Revoke an API key](/api-reference/developers/revoke-api-key). ### Use a key Send the key as a bearer token on every request: ```bash cURL curl "https://api.freeinvoiceautomation.com/v1/documents?status=sent,overdue" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ```javascript JavaScript const response = await fetch( "https://api.freeinvoiceautomation.com/v1/documents?status=sent,overdue", { headers: { Authorization: `Bearer ${process.env.FIA_API_KEY}` } }, ); ``` Keys start with the prefix `fia_` (proposed), so secret scanners such as GitHub's can find a key that was committed by mistake. The first characters of each key are shown in **Settings > API keys** so you can tell keys apart. ### What a key carries - **One account.** A key is bound to the account it was created in. It cannot reach other accounts, even if you are a member of them. - **Scopes.** The list of permission keys it may use. A call that needs a key the scopes do not include fails with `scope_missing` (403). - **Mode.** `draft_only` (the default) or `auto_send`. See [Draft-only and auto-send](#draft-only-and-auto-send). - **Business restriction** (proposed). A key can be limited to some of the account's businesses. Calls about any other business fail with `business_out_of_scope`. - **A rate limit.** Each key has its own per-minute limit. See [Rate limits](/api-reference/rate-limits). - **Optional expiry.** A key can have an expiry date, after which it stops working. - **Last used.** The app shows when each key was last used, so you can spot keys nobody needs any more. ### A key never exceeds its creator At call time a key is limited to the **intersection** of its scopes and the **current** permissions of the person who created it. If that person's role loses a permission, the key loses it on the next request. If the person is deactivated or removed from the account, their keys are revoked and return `unauthenticated`. Reactivating a member does not bring their old keys back. They create new keys after reactivation. The same happens after a Team account drops to Free and the Owner later reactivates members. ### Draft-only and auto-send The mode decides what happens when the key asks to send something to a client. | Mode | `send_document` and other sends | Destructive actions | | --- | --- | --- | | `draft_only` (default) | Need `"confirm": true` in the body; without it you get the preview and `confirmation_required` | Always need `"confirm": true` | | `auto_send` | Run without `confirm` | Always need `"confirm": true` | An `auto_send` key can email your clients with no person in the loop, so creating one requires a fresh sign-in, and keys with `auto_send` should be reviewed from time to time. Read [Drafts and confirmations](/api-reference/confirmations) for the full rules. ### Revoke a key Revoke a key in **Settings > API keys** or with [Revoke an API key](/api-reference/developers/revoke-api-key). Revoking is immediate: the next request with that key returns `unauthenticated` (401). Revoking is destructive, so through the API it needs `"confirm": true`. Treat an API key like a password. Never put it in front-end code, a mobile app, a public repository or a support email. If a key leaks, revoke it straight away and create a new one. ## Sessions (web app) The web app signs you in with Better Auth and keeps the session in a secure, HttpOnly cookie. Unsafe requests carry a CSRF token. You do not use sessions from your own servers; they are listed here because they explain two behaviours you may see in the app: - **Active account.** One login can be a member of several accounts. The session remembers which account you are working in. A session caller can also name the account with the `FIA-Account-Id` header (proposed). API keys and MCP tokens never need this header, because they are bound to one account. - **Fresh sign-in.** Sensitive actions ask you to sign in again, even if your session is still valid. The API returns `fresh_signin_required` (401) when the session is too old. ### Actions that need a fresh sign-in - connecting a payment rail, deleting a business, changing billing, - creating an `auto_send` API key, turning on auto-send for an automation, turning on "send automatically" for a store install, - connecting QuickBooks or Xero, adding your own AI key, - buying a credit pack, adding a billing card, deleting the account. API keys cannot perform these actions (proposed). Do them in the web app. ## MCP tokens AI assistants connect to the MCP server at `https://mcp.freeinvoiceautomation.com/mcp`. When you add the server, your assistant opens a sign-in page and a consent screen. There you choose the account, the scopes and the mode. The token you approve behaves like an API key: bound to one user and one account, limited to its scopes and to your current permissions, and `draft_only` unless you chose `auto_send`. The MCP server also accepts an API key instead of OAuth, which is useful for clients that cannot do the OAuth flow. See [MCP authentication](/ai/mcp/authentication). ## Public tokens Links that you send to clients, such as the pay page, the estimate page and the client portal, carry a public token instead of a key. Each token is 256-bit random, stored hashed, made for one purpose, expiring and revocable. Voiding an invoice revokes its pay token. These routes are listed under the **Public pages** and **Client portal** groups of this reference; your integration rarely calls them. ## Errors you may see | Code | Status | Meaning | | --- | --- | --- | | `unauthenticated` | 401 | No key, a wrong key, or a revoked or expired key | | `fresh_signin_required` | 401 | Sign in again in the web app, then retry | | `permission_denied` | 403 | The person behind the key lacks the permission | | `scope_missing` | 403 | The key itself was not given this scope | | `business_out_of_scope` | 403 | The key or member cannot reach that business | | `member_deactivated` | 403 | The member is deactivated in this account | Every error includes a `request_id`. See [Errors](/api-reference/errors) for how to fix each one. ## Related pages Permission keys The 48 keys and what each default role holds Drafts and confirmations When `confirm: true` is needed and what the preview shows Roles and permissions How roles work in the app Sign-in methods Passwords, passkeys, Google and magic links Source: https://docs.freeinvoiceautomation.com/api-reference/authentication --- # Requests and responses How to shape requests to the API, which headers to send, and what successful responses look like for single resources, lists, creates, deletes and jobs. Every endpoint follows the same rules for URLs, bodies, headers and responses. Learn them once and every page in this reference reads the same way. This page covers the request side, the success envelopes and the headers you will see. Errors have their own page, [Errors](/api-reference/errors). ## URLs and methods All paths start at the base URL `https://api.freeinvoiceautomation.com/v1`. - **Resources are plural nouns:** `/documents`, `/clients`, `/items`, `/payments`. - **Actions that are not a plain create or update** are sub-resources named after the action: `POST /documents/{id}/send`, `POST /documents/{id}/void`, `POST /documents/{id}/credit-notes`. - **Business-owned resources use flat paths** with a `business_id` field (proposed): in the body for writes and in the query for reads. If you can reach exactly one business, you may leave `business_id` out. | Method | Use | | --- | --- | | `GET` | Read a resource or a list. Never changes anything. | | `POST` | Create a resource, or run an action such as send or void. `POST /clients` and `POST /items` with an existing `id` update that record (upsert). | | `PATCH` | Partial update. Send only the fields you want to change (JSON merge). | | `PUT` | Replace a whole sub-resource, such as a business's accepted payment methods. | | `DELETE` | Soft delete. Returns `204` with no body. | ## Request bodies - **JSON only, UTF-8.** Send `Content-Type: application/json`. A body that is not valid JSON returns `invalid_json` (400). - **Field names are `snake_case`.** - **Ids** are uuid v7 strings. - **Dates** are `YYYY-MM-DD`; **timestamps** are RFC 3339 in UTC, such as `2027-01-04T10:15:00Z`. - **Money** is a string of integer minor units plus a `currency` field. `"unit_price_minor": "45000"` with `"currency": "USD"` is 450.00 USD. Numbers with decimals are rejected. See [Money and currencies](/api-reference/money). - **File uploads** (such as a CSV import or a logo) use `multipart/form-data`, as shown on the endpoint page. Every input is validated before anything is saved. When a field is wrong you get `validation_failed` (422) with the list of problems in `details.issues`: ```json Validation error { "error": { "code": "validation_failed", "message": "Some fields are not valid.", "details": { "issues": [ { "path": ["lines", 0, "quantity"], "message": "Must be greater than 0" } ] }, "request_id": "req_0193f2a2c0e0" } } ``` ### A partial update `PATCH` changes only the fields you send. Fields you leave out keep their values. ```bash cURL curl -X PATCH "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 9a41c0de-update-due" \ -d '{ "due_date": "2027-02-15" }' ``` A sent document is never edited in place. Updating it creates a new revision, so what your client received stays on record. See [Revisions](/invoicing/revisions). ## Request headers | Header | When | Notes | | --- | --- | --- | | `Authorization` | Every authenticated call | `Bearer `. See [Authentication](/api-reference/authentication). | | `Content-Type` | Calls with a body | `application/json`, or `multipart/form-data` for uploads | | `Idempotency-Key` | Every write; required on creates, sends, payments, voids and credit notes from API keys and MCP clients | Up to 255 characters. See [Idempotency](/api-reference/idempotency). | | `FIA-Account-Id` | Web app sessions only (proposed) | Chooses the account when a person belongs to several. API keys never need it. | ## Success responses ### A single resource A single resource is wrapped in `data`: ```json Get an item (200) { "data": { "id": "0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51", "business_id": "0193f29e-11aa-7c3b-8d2e-5a6b7c8d9e0f", "name": "Logo design", "unit_price_minor": "45000", "currency": "USD", "created_at": "2027-01-04T09:12:44Z", "updated_at": "2027-01-04T09:12:44Z" } } ``` ### A list Lists return the page in `data`, plus `next_cursor` and `has_more` (proposed envelope). See [Pagination and filters](/api-reference/pagination). ```json List items (200) { "data": [ { "id": "0193f2a1-7c4e-7b9a-9f10-3c2d1e0a4b51", "name": "Logo design" } ], "next_cursor": null, "has_more": false } ``` ### Status codes for success | Status | When | | --- | --- | | `200 OK` | A read, an update, or an action that finished | | `201 Created` | A new resource; the `Location` header holds its URL | | `202 Accepted` | Work that runs in the background: account exports, accounting backfills, bank statement imports, agent runs. The body carries the id of a job or resource that you poll. | | `204 No Content` | A delete. There is no body. | ### Documents carry their totals Documents always come back with the totals the server computed: `subtotal_minor`, `discount_minor`, `tax_minor`, `total_minor`, `amount_paid_minor`, `amount_credited_minor` and `balance_minor`, plus `status` and `number`. They also include `links` to related documents (credit notes, the estimate an invoice came from, a contract), the `payment_schedule` when there is one, and `pay_url` when a share link exists. Do not calculate totals yourself; read them from the response. ## Response headers | Header | Meaning | | --- | --- | | `X-Request-Id` | The id of this request (proposed name). The same value appears as `request_id` in errors. Quote it when you contact support. | | `Idempotent-Replayed: true` | The response is a stored copy of an earlier request with the same `Idempotency-Key`. | | `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset` | Your rate-limit budget (proposed names). See [Rate limits](/api-reference/rate-limits). | | `Retry-After` | Sent with `429 rate_limited`: how many seconds to wait. | | `Location` | Sent with `201`: the URL of the new resource. | | `Deprecation` | Sent when an endpoint or field is scheduled for removal in a later major version (proposed). | ## Async jobs Some work takes longer than a request should. These endpoints answer `202 Accepted` at once with an id, and you fetch the result later: ```bash Start an account export curl -X POST "https://api.freeinvoiceautomation.com/v1/account/exports" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Idempotency-Key: 4d2e8b10-export-jan" ``` Then poll `GET /v1/account/exports/{id}` until it is ready. For agent runs, poll [Get an agent run](/api-reference/agent/get-agent-run), or subscribe to the `agent_run.completed` and `agent_run.failed` [webhook events](/api-reference/webhook-events) instead of polling. ## What the API never returns - **Secrets.** Connection secrets, SMTP passwords, AI keys and webhook signing secrets are write-only. You can set or replace them, but no response contains them; at most you see the last four characters. - **Other accounts' data.** Records you cannot reach answer `404 not_found`, the same as records that do not exist. - **Card numbers.** Card data never reaches our servers. Payments are taken on hosted checkout pages. ## Related pages Idempotency Retry safely without creating anything twice Pagination and filters Cursors, filters, search and sorting Money and currencies Minor units, currencies and exchange rates Errors The error shape and every code Source: https://docs.freeinvoiceautomation.com/api-reference/requests --- # Money and currencies Amounts are strings of integer minor units with an ISO 4217 currency. How to send prices, read totals, and work with several currencies. Every amount in the API is a **string of integer minor units** together with a **currency** code. There are no floating-point amounts anywhere, in requests or responses. This keeps totals exact to the last cent, in every language, and means your invoice adds up the same way in your code, in our app and on the PDF. ## The rule - Money fields end in `_minor`: `unit_price_minor`, `total_minor`, `amount_minor`, `balance_minor` and so on. - The value is a **string** holding an **integer**: `"120000"`, never `1200.00` and never `120000` as a JSON number. - The value counts the currency's **minor unit**. For USD that is cents, so `"120000"` is 1,200.00 USD. - Every money field sits next to a `currency` field holding an ISO 4217 code in capitals, such as `USD`, `EUR`, `PKR` or `INR`. ```json Money on a document { "currency": "USD", "subtotal_minor": "120000", "tax_minor": "0", "total_minor": "120000", "amount_paid_minor": "50000", "balance_minor": "70000" } ``` That document totals 1,200.00 USD, 500.00 USD has been paid, and 700.00 USD is still owed. ### Why strings JSON numbers are often parsed as double-precision floats, which cannot represent every large integer exactly and tempt code into decimal maths. Strings arrive in your program exactly as we sent them. Convert them to an integer type (`BigInt` in JavaScript, `int` in Python, `long` or a decimal type elsewhere) before doing arithmetic. ```javascript JavaScript const balance = BigInt(document.balance_minor); // 70000n const display = new Intl.NumberFormat("en", { style: "currency", currency: document.currency }) .format(Number(balance) / 100); // "$700.00" for a currency with 2 decimals ``` Sending a float such as `"unit_price_minor": 450.5` or a decimal string such as `"450.50"` is rejected with `validation_failed` (422). Multiply by the currency's minor unit first, then send the integer as a string. ## Converting to and from minor units The number of minor units in one major unit depends on the currency, following ISO 4217. For the many currencies with two decimals, multiply by 100: | You mean | Currency | Send | | --- | --- | --- | | 450.00 | USD | `"45000"` | | 1,200.50 | EUR | `"120050"` | | 25,000.00 | PKR | `"2500000"` | | 0.99 | GBP | `"99"` | When you read amounts back, divide by the same factor only for display. Keep stored values in minor units. The one place where the API accepts decimal prices is a CSV file: [Import items from CSV](/api-reference/items/import-items) reads `unit_price` as a decimal such as `450.00` and converts it with the currency's decimals. Everywhere in JSON, use minor units. ## Quantities and rates are not money Line quantities, discount values and tax rates are not money, so they are not minor units. A line quantity can be fractional, for example `"1.5"` hours. Send it as a decimal string as shown on the endpoint pages. The server multiplies quantity by the unit price and rounds the result into minor units for you. ## Who does the maths All totals are computed on our side by one shared, tested module: line subtotals, discounts, taxes, document totals, amounts paid, amounts credited and balances. You send the inputs (lines, quantities, unit prices, tax rates, discounts); the response gives you the results. - **Do not send totals.** Fields such as `total_minor` and `balance_minor` are read-only. - **Read totals from the response.** They match the PDF, the pay page and the reports exactly. - **Balance** is `total_minor - amount_paid_minor - amount_credited_minor`. ## Currencies on documents Each document has one currency. A business can issue documents in any currency; each line uses the document's currency. ### Exchange rates When a document's currency differs from the business's base currency, the document keeps an exchange rate to the base currency. The rate is **frozen** on the document, so a report run next year shows the same base-currency value as today. See [Currencies and exchange rates](/invoicing/currencies) for how the rate is chosen and when you can set it yourself. ### Reports in several currencies Reports never add different currencies together silently. Multi-currency report amounts are returned **per currency**, plus a total in the business currency calculated with each document's frozen rate. See [Reports](/features/reports). ### Payments must match A payment is recorded in the document's currency. Payment methods can also depend on the currency: the local QR rails (UPI for INR; Raast, JazzCash and Easypaisa for PKR) only appear on invoices in their own currency. Asking for one of them on an invoice in another currency returns `currency_mismatch` (422). See [Local QR payments](/payments/qr-rails). ## Money-related errors | Code | Status | When | | --- | --- | --- | | `validation_failed` | 422 | A money field is a float, a decimal string, negative where that is not allowed, or missing its currency | | `currency_mismatch` | 422 | The currency does not fit, for example a QR rail on an invoice in another currency | | `credit_exceeds_balance` | 422 | A credit note is larger than what can still be credited | | `schedule_total_mismatch` | 422 | Payment schedule instalments do not add up to the document total | | `journal_unbalanced` | 422 | A ledger journal entry's debits do not equal its credits | Details for each are on [Errors](/api-reference/errors). ## In webhooks [Webhook events](/api-reference/webhook-events) use exactly the same format: `"total_minor": "125000"` with `"currency": "PKR"` is 1,250.00 PKR. Parse them the same way as API responses. ## Related pages Line items Quantities, unit prices and discounts on a document Taxes Tax rates and how tax is calculated Currencies and exchange rates Invoicing in other currencies Create an invoice The endpoint with money fields in action Source: https://docs.freeinvoiceautomation.com/api-reference/money --- # Idempotency Send an Idempotency-Key with every write so a retried request never creates, sends or charges anything twice. Networks fail. A request can time out after the server finished the work but before you saw the answer. If you simply retry, you might create a second invoice, record a payment twice or email a client twice. The `Idempotency-Key` header prevents that: the same key with the same request always gives the same result, and the work happens only once. ## How to use it Add an `Idempotency-Key` header to every `POST`, `PUT`, `PATCH` and `DELETE`. The value is any string you choose, up to **255 characters**, that is unique for the operation you mean. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/payments" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: bank-2027-01-14-ref-88231" \ -d '{ "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "amount_minor": "70000", "currency": "USD", "received_at": "2027-01-14T00:00:00Z", "reference": "88231" }' ``` If the call times out, send **exactly the same request with the same key** again. You get the original result back. ### When it is required For calls made with an API key or an MCP token, the key is **required** (proposed) on: - creates, such as [Create an invoice](/api-reference/documents/create-invoice) and [Create or update a client](/api-reference/clients/upsert-client), - sends, such as [Send a document](/api-reference/documents/send-document), - payments, such as [Record a payment](/api-reference/payments/record-payment) and [Refund a payment](/api-reference/payments/refund-payment), - voids: [Void a document](/api-reference/documents/void-document), - credit notes: [Create a credit note](/api-reference/documents/create-credit-note). It is optional, and still recommended, on every other write. ## What happens on a repeat | You send | We answer | | --- | --- | | A new key | The request runs normally and its response is stored | | The same key and the **same body** again | The stored response, with the header `Idempotent-Replayed: true`. Nothing runs a second time. | | The same key with a **different body** | `409 idempotency_key_reused`. Nothing runs. | | The same key while the first request is **still running** | `409 idempotency_in_progress`. Wait a moment and retry. | If you change the request, for example to fix a field, it is a different operation: use a **new** key. ```json Replayed response headers HTTP/1.1 201 Created Idempotent-Replayed: true X-Request-Id: req_0193f2a2c0e1 ``` ## How long keys are kept Keys are stored per account and per caller (the API key, user or token that sent them) for a set time, proposed at **24 hours**. After that, the same key is treated as new. Retries normally happen within seconds or minutes, so this window is more than enough; do not rely on a key to block a duplicate days later. Because keys are scoped to the caller, two different API keys can use the same key string without affecting each other. ## Choosing good keys A good key identifies **the thing you mean to do**, not the attempt. - **Derive it from your own data** when you can: your order id, your payment reference, your job id. Then even a restarted process sends the same key for the same work. - **Otherwise generate a random UUID** when you decide to do the operation, store it with your pending work, and reuse it on every retry. - **Do not generate a new key per attempt.** A fresh key on each retry gives no protection. Our own connectors use deterministic keys built from the store event, for example `shopify:{shop}:{order_id}:orders/create` (proposed) and `woo:{site}:{order_id}:processing`. You can follow the same pattern: ```text billing-server:invoice:customer-4821:2027-01 billing-server:payment:stripe-export:py_3Px... ``` ## Sends are protected twice Sending a document is also protected on our side regardless of the header: each send is unique per document, event and channel. A retried send never emails the same document twice for the same event, even if the retry used a different key. This also covers reminders. ## Webhooks and the other direction Idempotency also matters when we call you. [Outgoing webhooks](/api-reference/webhook-events) keep the same event `id` on every retry, and your endpoint should ignore an `id` it has already processed. Inbound payment events from Stripe and the stores are deduplicated per connection and event on our side, so a provider that retries never marks an invoice paid twice. ## Retry strategy Pick the key before the first attempt Build it from your own ids or generate a UUID, and save it with the pending task. Retry on network errors and 5xx On a timeout, connection reset, `500 internal_error`, `502 upstream_error` or `503`, wait and retry with the **same** key and body. Use exponential backoff. Wait on 409 idempotency_in_progress and 429 The first attempt is still running, or you are over a rate limit. Wait (for `429`, wait as long as `Retry-After` says) and retry. Do not retry 4xx validation or permission errors unchanged `422`, `403` and `404` will fail the same way again. Fix the request and use a new key. ## Errors | Code | Status | Fix | | --- | --- | --- | | `idempotency_key_reused` | 409 | You reused a key for a different request. Use a new key for each distinct operation. | | `idempotency_in_progress` | 409 | The first request with this key has not finished. Retry after a short wait. | ## Related pages Requests and responses Headers, bodies and status codes Errors Every error code and how to fix it Drafts and confirmations Sends and destructive actions Webhook events Deduplicate deliveries on your side Source: https://docs.freeinvoiceautomation.com/api-reference/idempotency --- # Pagination and filters Page through any list with cursors, narrow it with field filters and text search, and sort it on documented fields. Every list endpoint, such as [List documents](/api-reference/documents/list-documents), [List clients](/api-reference/clients/list-clients) or [List items](/api-reference/items/list-items), works the same way. You get one page at a time using an opaque cursor, and you narrow the list with plain query parameters named after fields. ## Cursor pagination Lists use cursor pagination (proposed). | Query parameter | Default | Meaning | | --- | --- | --- | | `limit` | `50` | How many records per page, from 1 to **200** | | `cursor` | none | The `next_cursor` value from the previous page | Every list response has the same envelope: ```json List response { "data": [ { "id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "number": "INV-2027-0042", "status": "sent" } ], "next_cursor": "eyJ0IjoiMjAyNy0wMS0wNVQx...", "has_more": true } ``` - `data` is the records on this page. - `has_more` is `true` when another page exists. - `next_cursor` is the value to pass as `cursor` to get that page, or `null` on the last page. The cursor is **opaque**: do not build, change or decode it. Keep every other query parameter the same when you pass it, so the next page continues the same filtered list. ### Walk every page ```bash cURL # First page curl "https://api.freeinvoiceautomation.com/v1/documents?type=invoice&status=overdue&limit=100" \ -H "Authorization: Bearer $FIA_API_KEY" # Next page: same filters plus the cursor curl "https://api.freeinvoiceautomation.com/v1/documents?type=invoice&status=overdue&limit=100&cursor=eyJ0IjoiMjAyNy0wMS0wNVQx..." \ -H "Authorization: Bearer $FIA_API_KEY" ``` ```javascript JavaScript async function* listAll(path, params = {}) { let cursor; do { const query = new URLSearchParams({ ...params, limit: "200", ...(cursor ? { cursor } : {}) }); const response = await fetch(`https://api.freeinvoiceautomation.com/v1${path}?${query}`, { headers: { Authorization: `Bearer ${process.env.FIA_API_KEY}` }, }); const page = await response.json(); yield* page.data; cursor = page.has_more ? page.next_cursor : undefined; } while (cursor); } for await (const invoice of listAll("/documents", { type: "invoice", status: "overdue" })) { console.log(invoice.number, invoice.balance_minor, invoice.currency); } ``` ### Order is stable Unless you set `sort`, lists are ordered newest first by `created_at`, then by `id`, both descending. The order is stable, so the same query walks the records in the same order every time, and ties on `created_at` are broken by `id`. ## Filters Filters are plain query parameters named after the field they filter. The ones in common use are: | Parameter | Example | Meaning | | --- | --- | --- | | `business_id` | `business_id=0193f29e-...` | Only records of this business | | `client_id` | `client_id=0193f2a1-...` | Only records of this client | | `type` | `type=invoice` | Document type: `invoice`, `estimate`, `credit_note`, `receipt` | | `status` | `status=sent,overdue` | One or more statuses | | `currency` | `currency=EUR` | Only records in this currency | | `due_before`, `due_after` | `due_before=2027-02-01` | Due date range | | `issued_after`, `issued_before` | `issued_after=2027-01-01` | Issue date range | | `updated_after` | `updated_after=2027-01-10T00:00:00Z` | Changed since a moment; useful for syncing | | `external_ref` | `external_ref=5012` | Your own reference, such as a store order id | | `archived` | `archived=true` | Archived records instead of active ones | Each endpoint page lists exactly which filters it accepts, so check the page before relying on a filter. ### Several values Pass several values separated by commas. They are combined with OR inside one parameter, and different parameters are combined with AND: ```text GET /v1/documents?type=invoice&status=sent,overdue¤cy=USD ``` This returns USD invoices that are sent **or** overdue. ### Text search `q` searches the main text fields of the resource: names, document numbers and email addresses. ```text GET /v1/clients?q=acme GET /v1/documents?q=INV-2027-0042 ``` ## Sorting Set `sort` to a documented field. A leading `-` sorts descending. ```text GET /v1/documents?sort=due_date GET /v1/documents?sort=-total_minor ``` Sorting works on documented fields only; the endpoint page lists them, often as the allowed values of `sort`. ## Reports take their own parameters [Report endpoints](/api-reference/reports/get-report) are not paginated lists. They take a period and filters, and a format: | Parameter | Meaning | | --- | --- | | `from`, `to` | The period, as dates | | `client_id`, `currency`, `business_id` | Narrow the report | | `format` | `json`, `csv` or `pdf` | ## Keeping a copy in sync To mirror records into your own system, combine `updated_after` with pagination: store the time of your last successful sync, ask for records updated after it, walk every page, then save the new time. For changes as they happen, subscribe to [webhook events](/api-reference/webhook-events) instead of polling, and use the list endpoints to catch up after downtime. ## Limits and errors - `limit` must be between 1 and 200. Query parameters are validated like any other input, and invalid values return `validation_failed` (422) with the problem in `details.issues`. - Pass the cursor back unchanged, with the same filters. If you change the filters, start again from the first page. - Large lists count against [rate limits](/api-reference/rate-limits) one request per page, so use `limit=200` for bulk work. ## Related pages Requests and responses The list envelope and other response shapes List documents The busiest list, with every document filter Rate limits How many pages you can fetch per minute Webhook events Get changes pushed instead of polling Source: https://docs.freeinvoiceautomation.com/api-reference/pagination --- # Drafts and confirmations Integrations create drafts. Sends need confirm true unless the key is auto-send, and destructive or money-moving actions always need confirm true. Anything that reaches your client or cannot be undone needs a clear yes from a person. That is the rule behind two behaviours of the API: **drafts first** for everything integrations and AI create, and **explicit confirmation** for sends and destructive actions. It protects you from a buggy script, a confused AI assistant, or a prompt hidden in a pasted email. ## Drafts first Integrations, MCP clients, the CLI and the AI agent create **drafts**. A draft invoice has no number yet, is not visible to the client, and can be edited or deleted freely. Nothing leaves your account until something sends it. - [Create an invoice](/api-reference/documents/create-invoice) and [Create an estimate](/api-reference/documents/create-estimate) always create drafts. - The draft gets its number from the business's gap-free sequence when it is first sent, so abandoned drafts never leave gaps in your numbering. - A draft can be removed with [Delete a draft](/api-reference/documents/delete-draft). Once a document has been sent it can no longer be deleted; it can be voided or credited instead. ## Sending needs confirmation Sends deliver something to a client, so they need confirmation unless the caller is allowed to auto-send. | Caller | Send without `confirm` | Send with `"confirm": true` | | --- | --- | --- | | API key or MCP token in `draft_only` mode (default) | Returns the preview and `428 confirmation_required`; nothing is sent | Sends | | API key or MCP token in `auto_send` mode | Sends | Sends | | A person in the web app | The app shows a confirmation dialog | Sends after the person clicks | This applies to [Send a document](/api-reference/documents/send-document) and, from the week of 11 January 2027, to [Send a reminder](/api-reference/collections/send-reminder). ### Preview first Before you confirm, look at what will happen. [Preview a send](/api-reference/documents/preview-send) is read-only and returns what the send would do, without doing it. ```bash Preview curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/send-preview" \ -H "Authorization: Bearer $FIA_API_KEY" ``` ```bash Send curl -X POST "https://api.freeinvoiceautomation.com/v1/documents/0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f/send" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: send-0193f2b4-first" \ -d '{ "confirm": true }' ``` A good pattern for your own tools: call the preview, show it to a person, and only send `"confirm": true` once they approve. ## Destructive and money-moving actions Some actions change money or remove access and cannot be simply undone. These are marked **destructive** (hint **D**) on their endpoint pages. They need `"confirm": true` from **every** caller, including `auto_send` keys. Without it, the API runs the capability's preview, returns it, and answers `428 confirmation_required`. ```json Without confirm (428, preview shortened) { "error": { "code": "confirmation_required", "message": "This action needs confirmation. Send the request again with \"confirm\": true.", "details": { "preview": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "action": "void_document" } }, "request_id": "req_0193f2a2c0e2" } } ``` The preview describes what the action would do for that capability. Read it, then repeat the request with `"confirm": true` and an `Idempotency-Key`. ### Which actions are destructive | Area | Action | Notes | | --- | --- | --- | | Documents | [Void a document](/api-reference/documents/void-document) | Refused with `document_has_payments` when payments exist; issue a credit note instead | | Documents | [Create a credit note](/api-reference/documents/create-credit-note) | Can refund through the rail or leave client credit | | Documents | [Delete a draft](/api-reference/documents/delete-draft) | Drafts only | | Payments | [Refund a payment](/api-reference/payments/refund-payment) | Goes through the rail when it supports refunds | | Payments | [Delete a payment](/api-reference/payments/delete-payment) | Manual records only; soft delete | | Clients | [Delete a client](/api-reference/clients/delete-client) | Soft delete | | Businesses | [Delete a business](/api-reference/businesses/delete-business) | Also needs a fresh sign-in | | Connections | [Delete a connection](/api-reference/connections/delete-connection) | Removes a payment rail, sender or AI key | | Team | [Deactivate a member](/api-reference/team/deactivate-member), [Remove a member](/api-reference/team/remove-member), [Delete a role](/api-reference/team/delete-role) | Never the Owner | | Account | [Delete the account](/api-reference/account/delete-account) | Owner only, fresh sign-in | | Account | [Transfer ownership](/api-reference/account/transfer-ownership) | Owner only, fresh sign-in | | Developers | [Revoke an API key](/api-reference/developers/revoke-api-key), [Unlink a connector install](/api-reference/developers/unlink-connector-install) | Immediate | | Billing | [Remove the billing card](/api-reference/billing/remove-billing-card) | | | Saved cards (March 2027) | [Set auto-charge](/api-reference/cards-contracts/set-auto-charge) | Needs the client's consent | | Accounting sync (May 2027) | [Disconnect accounting](/api-reference/accounting/disconnect-accounting) | | | Ledger (June to July 2027) | [Reverse a journal entry](/api-reference/ledger/reverse-journal-entry), [Unlock a period](/api-reference/ledger/unlock-period) | Proposed as destructive | Over MCP, the same tools carry the `destructiveHint` annotation, so assistants ask you before calling them. `void_document` and `create_credit_note` always need `confirm: true` over MCP, whatever the token's mode. See [MCP drafts and confirmations](/ai/mcp/safety). ## Fresh sign-in is separate A few actions also need a **fresh sign-in** from a person in the web app: connecting a payment rail, deleting a business, billing changes, creating an `auto_send` key, turning on auto-send for an automation or a store install, connecting QuickBooks or Xero, adding an AI key, buying a credit pack, adding a billing card and deleting the account. `"confirm": true` does not replace that. API keys cannot perform these actions (proposed); the API answers `fresh_signin_required`. See [Authentication](/api-reference/authentication#actions-that-need-a-fresh-sign-in). ## Turning on auto-send You can let a trusted integration send without confirmation in three places. Each one needs a fresh sign-in and is recorded in the audit log: - an API key or MCP token created with `mode: auto_send`, - an [AI agent automation](/ai/agent/automations) with auto-send turned on, - a Shopify or WooCommerce install with **send automatically** turned on. Auto-send never covers destructive actions. Those still need `"confirm": true`. ## Store events that mirror what already happened Shopify and WooCommerce are a special case. When an order is cancelled or refunded in the store, the money has already moved or the order is already gone, so mirroring it is recording a fact, not making a new decision. The connector therefore calls [Void a document](/api-reference/documents/void-document) or [Create a credit note](/api-reference/documents/create-credit-note) with `"confirm": true` automatically. Each one is audited with actor type `connector` and shows up in the document's activity list. Sends to the store's customer still stay drafts unless "send automatically" is on. See [Shopify](/integrations/shopify) and [WordPress and WooCommerce](/integrations/woocommerce). ## Payments are never marked paid by a redirect A related rule: a document becomes paid only from a verified payment event (a signed Stripe webhook, a verified store channel), a reconciliation run, or a person recording the payment, which is audited. A client returning from a checkout page does not mark anything paid. [Record a payment](/api-reference/payments/record-payment) is the audited human confirmation. ## Related pages Authentication Draft-only and auto-send keys Errors `confirmation_required` and the rest Void and delete What voiding means for your client Credit notes Full and partial credits, refunds and client credit Source: https://docs.freeinvoiceautomation.com/api-reference/confirmations --- # Errors The error shape every endpoint uses, and every error code with its HTTP status, when it happens and how to fix it. When a request fails, the API answers with an HTTP status code and a JSON body in one fixed shape. The `code` in that body is stable: write your error handling against it, not against the message. This page explains the shape, then lists every code. ## The error shape ```json Error response { "error": { "code": "document_has_payments", "message": "This invoice has recorded payments and cannot be voided. Create a credit note instead.", "details": { "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "amount_paid_minor": "50000" }, "request_id": "req_0193f2a2c0e3", "docs_url": "https://docs.freeinvoiceautomation.com/errors/document_has_payments" } } ``` The error. properties A stable, snake_case code from the list below. New codes may be added within `v1`; treat an unknown code by its HTTP status. Plain English for a person. It can change at any time, so never parse it. Extra facts about this error, such as the ids and amounts involved. Validation errors put the field problems in `details.issues`. Some errors add `details.suggestion`. The id of the request, also sent in the `X-Request-Id` header (proposed name). Include it when you contact support. A link to the explanation of this code. ### Validation issues ```json validation_failed (422) { "error": { "code": "validation_failed", "message": "Some fields are not valid.", "details": { "issues": [ { "path": ["lines", 0, "quantity"], "message": "Must be greater than 0" }, { "path": ["currency"], "message": "Required" } ] }, "request_id": "req_0193f2a2c0e4" } } ``` Each issue has a `path` (field names and array positions from the top of the body) and a `message`. ### What errors never contain Secrets never appear in errors or in our logs: no API keys, passwords, tokens or connection secrets. ## Handling errors in code ```javascript JavaScript const response = await fetch(url, options); if (!response.ok) { const { error } = await response.json(); switch (error.code) { case "rate_limited": await sleep(Number(response.headers.get("Retry-After")) * 1000); return retry(); case "confirmation_required": return askPersonToConfirm(error.details); case "validation_failed": throw new Error(error.details.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("\n")); default: throw new Error(`${error.code} (${error.request_id}): ${error.message}`); } } ``` ### Which errors to retry | Retry? | Codes | | --- | --- | | Yes, with the same `Idempotency-Key` and backoff | `rate_limited` (after `Retry-After`), `idempotency_in_progress`, `upstream_error`, `internal_error`, `maintenance` | | After a person acts | `fresh_signin_required`, `confirmation_required`, `quota_exceeded`, `ai_credits_exhausted`, `not_configured` | | No; fix the request | every other code | ## Summary table | Code | HTTP | | --- | --- | | [`validation_failed`](#validation_failed) | 422 | | [`invalid_json`](#invalid_json) | 400 | | [`unauthenticated`](#unauthenticated) | 401 | | [`fresh_signin_required`](#fresh_signin_required) | 401 | | [`two_factor_required`](#two_factor_required) | 401 | | [`permission_denied`](#permission_denied) | 403 | | [`scope_missing`](#scope_missing) | 403 | | [`business_out_of_scope`](#business_out_of_scope) | 403 | | [`feature_locked`](#feature_locked) | 403 | | [`email_not_verified`](#email_not_verified) | 403 | | [`plan_limit_reached`](#plan_limit_reached) | 403 | | [`permission_not_grantable`](#permission_not_grantable) | 403 | | [`role_locked`](#role_locked) | 409 | | [`role_in_use`](#role_in_use) | 409 | | [`last_owner`](#last_owner) | 409 | | [`member_deactivated`](#member_deactivated) | 403 | | [`platform_mail_paused`](#platform_mail_paused) | 403 | | [`quota_exceeded`](#quota_exceeded) | 403 | | [`ai_credits_exhausted`](#ai_credits_exhausted) | 403 | | [`billing_card_required`](#billing_card_required) | 409 | | [`welcome_credits_not_eligible`](#welcome_credits_not_eligible) | 409 | | [`card_not_eligible`](#card_not_eligible) | 422 | | [`money_back_not_eligible`](#money_back_not_eligible) | 409 | | [`not_found`](#not_found) | 404 | | [`token_invalid`](#token_invalid) | 404 | | [`token_expired`](#token_expired) | 410 | | [`token_revoked`](#token_revoked) | 410 | | [`conflict`](#conflict) | 409 | | [`idempotency_key_reused`](#idempotency_key_reused) | 409 | | [`idempotency_in_progress`](#idempotency_in_progress) | 409 | | [`document_not_editable`](#document_not_editable) | 409 | | [`document_has_payments`](#document_has_payments) | 409 | | [`document_voided`](#document_voided) | 409 | | [`credit_exceeds_balance`](#credit_exceeds_balance) | 422 | | [`schedule_total_mismatch`](#schedule_total_mismatch) | 422 | | [`currency_mismatch`](#currency_mismatch) | 422 | | [`rail_not_connected`](#rail_not_connected) | 409 | | [`rail_capability_missing`](#rail_capability_missing) | 409 | | [`consent_required`](#consent_required) | 409 | | [`contract_sealed`](#contract_sealed) | 409 | | [`contract_hash_mismatch`](#contract_hash_mismatch) | 500 | | [`journal_unbalanced`](#journal_unbalanced) | 422 | | [`period_locked`](#period_locked) | 409 | | [`entry_already_reversed`](#entry_already_reversed) | 409 | | [`time_entry_locked`](#time_entry_locked) | 409 | | [`confirmation_required`](#confirmation_required) | 428 | | [`rate_limited`](#rate_limited) | 429 | | [`upstream_error`](#upstream_error) | 502 | | [`connection_failed`](#connection_failed) | 502 | | [`not_configured`](#not_configured) | 503 | | [`maintenance`](#maintenance) | 503 | | [`webhook_signature_invalid`](#webhook_signature_invalid) | 400 | | [`internal_error`](#internal_error) | 500 | The codes and HTTP statuses are proposed in the API specification; the behaviours behind them are fixed. ## Request errors ### validation_failed **HTTP 422.** The body or query did not pass input validation: a required field is missing, a value has the wrong type or format, a money amount is a float, or a value is out of range. **Fix:** read `details.issues`. Each issue names the field by `path` and says what is wrong. Correct those fields and send the request again with a new `Idempotency-Key`. For money rules see [Money and currencies](/api-reference/money). ### invalid_json **HTTP 400.** The request body is not valid JSON. **Fix:** check for trailing commas, unquoted keys or a body cut short. Send `Content-Type: application/json` and serialise the body with a JSON library rather than by hand. ## Authentication errors ### unauthenticated **HTTP 401.** There is no session, API key or token, or the one sent is wrong, expired or revoked. This is also what an API key or MCP token returns after its creator was deactivated or removed from the account, because those keys are revoked. **Fix:** send `Authorization: Bearer ` with a current key. If the key was revoked or has expired, create a new one in **Settings > API keys**. See [Authentication](/api-reference/authentication). ### fresh_signin_required **HTTP 401.** The action is sensitive and the session is not fresh enough. Examples: connecting a payment rail, deleting a business, changing billing, creating an `auto_send` key, buying a credit pack, deleting the account. **Fix:** sign in again in the web app and repeat the action there. API keys cannot perform fresh-sign-in actions (proposed). ### two_factor_required **HTTP 401.** A route that requires two-factor authentication was called from a session without it. This only applies to our internal administration routes, not to customer accounts or API keys. **Fix:** none for API users; you will not see this code from your own integrations. ## Permission and plan errors ### permission_denied **HTTP 403.** The person behind the request (or the creator of the API key) does not hold the [permission key](/api-reference/permissions) the action needs, in this account. **Fix:** ask an Owner or a member with `team.manage` to give your role the key, or to run the action for you. Remember that an API key is limited to its creator's **current** permissions. ### scope_missing **HTTP 403.** The API key or MCP token was not given the scope (permission key) this action needs, even though its creator may hold it. **Fix:** create a new key with the extra scope in **Settings > API keys**, or reconnect your MCP client and approve the scope on the consent screen. Keys cannot gain scopes after creation. ### business_out_of_scope **HTTP 403.** The caller cannot reach this business. Either the member's business scope does not include it, or the API key is restricted to other businesses. **Fix:** use a `business_id` you can reach (see [List businesses](/api-reference/businesses/list-businesses)), or ask the Owner to widen your business scope. ### feature_locked **HTTP 403.** The feature is not available on the account's current plan or setup. Examples: the AI agent on Free with no welcome credits, packs or own AI key; viewing the audit log on Free; editing roles on Free. **Fix:** follow the suggestion in the message: add a card for welcome credits, buy a credit pack, add your own AI key, or [upgrade to Team](/billing/team). ### email_not_verified **HTTP 403.** The platform mailer only sends for accounts whose email address is verified. **Fix:** open the verification email we sent and click the link, or send it again from the app. Alternatively connect [your own SMTP](/messaging/smtp). ### plan_limit_reached **HTTP 403.** A plan limit was reached, such as the number of members included, or a fair-use limit on Free. **Fix:** remove or deactivate a member, add a paid seat on Team, or wait for the monthly fair-use counter to reset. See [Fair-use limits](/billing/fair-use) and [Member seats](/billing/members). ### permission_not_grantable **HTTP 403.** A role change would grant a permission key that you do not hold yourself, or would put an Owner-only key (`account.delete`, ownership transfer) on a role other than Owner. **Fix:** only grant keys you hold. Ask the Owner to make the change if it needs keys you lack. Owner-only keys cannot be granted to any other role. ### role_locked **HTTP 409.** The Owner role cannot be edited, reset or deleted. **Fix:** edit another role, or create a [custom role](/team/custom-roles). ### role_in_use **HTTP 409.** A role that still has members cannot be deleted. **Fix:** move its members to another role with [Update a member](/api-reference/team/update-member), then delete the role. ### last_owner **HTTP 409.** The change would leave the account without an Owner. **Fix:** every account keeps at least one Owner. [Transfer ownership](/account/ownership) first if the current Owner is leaving. ### member_deactivated **HTTP 403.** The member is deactivated in this account, either by the Owner or a member with `team.manage`, or because a Team account dropped to Free. Their old API keys and MCP tokens were revoked and return `unauthenticated`. **Fix:** ask the Owner to reactivate the member in **Settings > Team**, then create new keys. See [Deactivate and remove](/team/deactivate). ## Usage, email and billing errors ### platform_mail_paused **HTTP 403.** The account moved to Free and had already sent 20 or more platform emails this month, so the platform mailer is paused until next month. `details.suggestion` is `"connect_smtp"`. **Fix:** connect [your own SMTP](/messaging/smtp), which has no monthly quota, or wait for the next month. Drafts are kept. ### quota_exceeded **HTTP 403.** The monthly platform email quota is used up (20 on Free, 100 on Team). `details.suggestion` is `"connect_smtp"`. **Fix:** connect your own SMTP, send by share link instead, or wait for the reset on the first of the month. See [Platform email](/messaging/platform-email). ### ai_credits_exhausted **HTTP 403.** The account has no AI credits left for the agent. **Fix:** buy a [credit pack](/billing/credit-packs) (1,000 credits for $5), upgrade to Team, or [use your own AI key](/ai/agent/own-key), which bypasses credits. ### billing_card_required **HTTP 409.** Free welcome credits need a verified billing card first. The card gets a $0 verification and is never charged. **Fix:** add a card in **Settings > Billing**. See [Billing card](/billing/billing-card). ### welcome_credits_not_eligible **HTTP 409.** This card or account already received welcome credits, or the first month after sign-up has passed. **Fix:** welcome credits are given once per card and per account. Use a credit pack, Team credits or your own AI key. ### card_not_eligible **HTTP 422.** The card cannot unlock the Free welcome credits because its funding type is blocked (by default, prepaid cards). **Fix:** add a regular debit or credit card for the welcome credits. Prepaid cards can still be used to buy credit packs. ### money_back_not_eligible **HTTP 409.** A money-back refund was asked for, but it is not the first Team payment, the payment was more than 30 days ago, or the account already used the guarantee. **Fix:** the guarantee covers the first Team payment only, within 30 days, once per account. See [30-day money-back](/billing/money-back). ## Lookup and token errors ### not_found **HTTP 404.** The resource does not exist, was deleted, or is not visible to you. Records of other accounts always answer `not_found`, never a permission error, so their existence is not revealed. **Fix:** check the id and the account your key belongs to. ### token_invalid **HTTP 404.** A public token (pay page, estimate page, portal link) is unknown. **Fix:** check that the full link was copied. Ask the business for a new link. ### token_expired **HTTP 410.** The public token has expired. **Fix:** the business creates a new [share link](/invoicing/share-links), or the client asks for a new portal link. ### token_revoked **HTTP 410.** The public token was revoked, for example because the invoice was voided. **Fix:** the old link will not work again. The business shares the current document if there is one. ## State errors ### conflict **HTTP 409.** The request conflicts with the current state of the resource in a way that has no more specific code. **Fix:** fetch the resource again, check its status, and retry with a request that fits it. ### idempotency_key_reused **HTTP 409.** The `Idempotency-Key` was already used with a different request body. **Fix:** use a new key for each distinct operation. See [Idempotency](/api-reference/idempotency). ### idempotency_in_progress **HTTP 409.** A request with the same `Idempotency-Key` is still running. **Fix:** wait briefly and retry with the same key and body. You will get the first request's result. ### document_not_editable **HTTP 409.** The document has been sent, so it cannot be edited in place. **Fix:** sent documents change only by [revision](/invoicing/revisions): update it through [Update an invoice](/api-reference/documents/update-invoice), which records a new version, or void or credit it. ### document_has_payments **HTTP 409.** The invoice has recorded payments, so it cannot be voided. **Fix:** issue a [credit note](/invoicing/credit-notes) with [Create a credit note](/api-reference/documents/create-credit-note), choosing a refund or client credit for the paid part. ### document_voided **HTTP 409.** The action is not allowed on a void document, for example sending it or recording a payment against it. **Fix:** create a new document if the work is still owed. ### credit_exceeds_balance **HTTP 422.** The credit note is larger than what can still be credited on the invoice. **Fix:** reduce the amount or lines. Check `amount_credited_minor` and `total_minor` on the invoice first. ### schedule_total_mismatch **HTTP 422.** The instalments of a payment schedule do not add up to the document total. **Fix:** adjust the instalment amounts or percentages so they sum exactly to the total. See [Deposits and schedules](/payments/schedules). ### currency_mismatch **HTTP 422.** The currency does not fit the request, for example a local QR rail on an invoice that is not in PKR or INR. **Fix:** use a payment method that supports the document's currency. See [Local QR payments](/payments/qr-rails). ### rail_not_connected **HTTP 409.** No active connection exists for the chosen payment method. **Fix:** connect the rail in **Settings > Payments** (for example [Stripe](/payments/stripe) or [bank transfer](/payments/bank-transfer)), or choose a method that is connected. ### rail_capability_missing **HTTP 409.** The rail cannot do what was asked, for example a refund on a rail without refunds. **Fix:** do it outside our system and [record it manually](/payments/recording). ### consent_required **HTTP 409.** Auto-charge was requested without a saved card that the client consented to. **Fix:** the client saves a card with consent on their next Stripe checkout. Until then, send the pay link. See [Saved cards and auto-charge](/payments/auto-charge). ### contract_sealed **HTTP 409.** A signed contract cannot change. **Fix:** create a new version with [New contract version](/api-reference/cards-contracts/new-contract-version); it must be signed again. ### contract_hash_mismatch **HTTP 500.** The stored fingerprint of a signed contract does not match the file. The download is refused and an alert is raised on our side. **Fix:** nothing to change in your request. Contact support with the `request_id`. ### journal_unbalanced **HTTP 422.** A journal entry's debits do not equal its credits. **Fix:** correct the lines so total debits equal total credits. See [Accounting ledger](/features/ledger). ### period_locked **HTTP 409.** The entry would post into a locked accounting period. **Fix:** post into an open period, or have someone with `ledger.manage` unlock the period (a destructive action). ### entry_already_reversed **HTTP 409.** The journal entry was already reversed. **Fix:** none needed; each entry can be reversed once. Post a new entry if you need a further correction. ### time_entry_locked **HTTP 409.** The time entry has been billed on an invoice, so it is locked. **Fix:** correct the invoice by revision or credit note instead. See [Time tracking](/features/time-tracking). ### confirmation_required **HTTP 428.** A destructive action, or a send from a `draft_only` caller, was requested without `"confirm": true`. The response includes the preview of what would happen. **Fix:** show the preview to a person, then repeat the request with `"confirm": true`. See [Drafts and confirmations](/api-reference/confirmations). ## Limits and service errors ### rate_limited **HTTP 429.** Too many requests for the key, the account or the IP address. The `Retry-After` header says how many seconds to wait. **Fix:** wait, then retry with the same `Idempotency-Key`. Spread out bulk work. See [Rate limits](/api-reference/rate-limits). ### upstream_error **HTTP 502.** A provider we called on your behalf failed: Stripe, Whop, an SMTP server, QuickBooks, Xero or an AI provider. **Fix:** retry later with the same key. If it persists, check the provider's status and your connection in **Settings > Integrations**. ### connection_failed **HTTP 502.** A connection test failed, for example SMTP or a rail's credentials. **Fix:** check the details you entered (host, port, username, API key), then run [Verify a connection](/api-reference/connections/verify-connection) again. ### not_configured **HTTP 503.** A feature depends on a platform integration that has not been set up yet. It shows as "Not configured" rather than failing in another way. **Fix:** try again later or contact support. ### maintenance **HTTP 503.** The feature is switched off for maintenance. **Fix:** retry later. Check [status.freeinvoiceautomation.com](https://status.freeinvoiceautomation.com). ### webhook_signature_invalid **HTTP 400.** An inbound webhook to us (for example from Stripe or a store connector) failed signature verification, so it was rejected and nothing was processed. **Fix:** if you build a connector, sign the raw body with the right secret and a current timestamp. This code is not about the [webhooks we send you](/api-reference/webhook-events); for those, you check our signature. ### internal_error **HTTP 500.** Something unexpected went wrong on our side. It is reported to our error tracking with the request id. **Fix:** retry with the same `Idempotency-Key`. If it keeps happening, email [info@freeinvoiceautomation.com](mailto:info@freeinvoiceautomation.com) with the `request_id`. ## Related pages Requests and responses Headers and success envelopes Troubleshooting Common problems in the app Source: https://docs.freeinvoiceautomation.com/api-reference/errors --- # Rate limits Per-key request limits, Free fair-use limits, email and AI limits, the rate-limit headers, and how to back off when you get 429. Rate limits keep the service fast and fair for everyone, and stop a runaway script or a stolen key from doing much harm. Most integrations never reach them. When you do, the API tells you exactly how long to wait. All limits are settings that we can tune without a release, so the numbers on this page are the starting values planned for launch (proposed). Read the headers on each response rather than hard-coding a number. ## The limits ### Per API key or MCP token Each API key and each MCP token has its own budget of **requests per minute**. The planned starting value is **600 requests per minute per key**. A key can also be given its own lower limit, which is useful for a script you want to keep gentle. Because the budget is per key, giving each integration its own key also gives each its own budget, and a busy integration cannot slow down another. ### Per account: fair use on Free The Free plan includes every feature. To stop abuse, Free accounts have monthly fair-use limits on: | Limit | Free (proposed) | Team (proposed) | | --- | --- | --- | | API calls per month | 10,000 | 100,000 | | File storage | 1 GB | 10 GB | | Sends per minute | 10 | 10 | You can see your usage against these limits in **Settings > Usage**, or with [Get usage](/api-reference/messaging/get-usage). A warning flag is set at 80%. Passing a monthly limit returns `plan_limit_reached`. See [Fair-use limits](/billing/fair-use). ### Sending email The platform mailer has its own limits: a per-minute limit (planned at 10 per minute per account) and the monthly quota of platform emails, **20 on Free** and **100 on Team**. When the quota is used up, sends return `quota_exceeded`. Your own SMTP server is not limited by the monthly quota. See [Platform email](/messaging/platform-email). ### AI agent The [AI invoice agent](/ai/agent/overview) is limited to about 10 requests per minute per account (proposed), on top of credits and a monthly ceiling per account. When credits run out you get `ai_credits_exhausted`. ### Sign-in Password sign-in is limited per IP address and locks an account for a short time after repeated failures (planned: 5 failures, then a 15-minute lock). Magic-link and password-reset requests are also limited. ### Public routes Routes that need no account are limited per IP address (proposed): | Route | Limit | | --- | --- | | No-auth [Generate a PDF](/api-reference/public/generate-pdf) | 30 per hour per IP | | Token pages: pay page, verification, estimate, portal | 60 per minute per IP | | "I have paid" with a proof upload | 5 per hour per token | ## Rate-limit headers Responses include three headers (proposed names, from the IETF draft for rate-limit headers): | Header | Meaning | | --- | --- | | `RateLimit-Limit` | The size of the current budget | | `RateLimit-Remaining` | How many requests are left in the current window | | `RateLimit-Reset` | Seconds until the window resets | ```text Response headers HTTP/1.1 200 OK RateLimit-Limit: 600 RateLimit-Remaining: 41 RateLimit-Reset: 17 X-Request-Id: req_0193f2a2c0e5 ``` Use `RateLimit-Remaining` to slow down before you hit the limit. ## When you are over the limit You get **HTTP 429** with the code `rate_limited` and a `Retry-After` header in seconds: ```json 429 response { "error": { "code": "rate_limited", "message": "Too many requests. Try again in 17 seconds.", "request_id": "req_0193f2a2c0e6" } } ``` Nothing was done for a rejected request, so it is always safe to retry it. Wait at least `Retry-After` seconds, then send the same request with the same `Idempotency-Key`. ```javascript JavaScript async function call(url, options, attempt = 0) { const response = await fetch(url, options); if (response.status !== 429 || attempt >= 5) return response; const wait = Number(response.headers.get("Retry-After") ?? 1); await new Promise((resolve) => setTimeout(resolve, wait * 1000)); return call(url, options, attempt + 1); // same body, same Idempotency-Key } ``` ```python Python def call(method, url, **kwargs): for attempt in range(6): response = requests.request(method, url, **kwargs) if response.status_code != 429: return response time.sleep(int(response.headers.get("Retry-After", "1"))) return response ``` ## Staying under the limits - **Use one key per integration.** Each key has its own per-minute budget. - **Page with `limit=200`** for bulk reads. Fewer, larger pages use fewer requests. See [Pagination and filters](/api-reference/pagination). - **Use webhooks instead of polling.** Subscribe to [webhook events](/api-reference/webhook-events) and fetch only what changed. - **Use `updated_after`** to sync only records that changed since your last run. - **Queue writes** in your own system and send them at a steady pace rather than in bursts. - **Do not retry in a tight loop.** Always honour `Retry-After`, and add backoff for other temporary errors. ## Related pages Fair-use limits What the Free fair-use limits cover Errors `rate_limited` and the other codes Idempotency Retry without doing anything twice Free and Team plans Email quotas and AI credits per plan Source: https://docs.freeinvoiceautomation.com/api-reference/rate-limits --- # Versioning The major version is in the path. Within v1 we only add things, so your code keeps working. How to stay compatible and how deprecations are announced. The API is versioned so that code you write today keeps working. The major version is part of every URL, `/v1`, and within a major version we only make changes that cannot break a well-behaved client. When something truly has to change in a breaking way, it goes into a new major version, announced well before anything is removed. ## The version is in the path ```text https://api.freeinvoiceautomation.com/v1/documents ``` There is no version header to send and no date to pin. Every request to `/v1` gets `v1` behaviour. ## What can change within v1 Only **additive** changes are made inside `v1`: | Change | Example | What you need to do | | --- | --- | --- | | New endpoints | A new capability ships with a release | Nothing | | New optional request fields | A new optional setting on a document | Nothing; leave it out until you need it | | New response fields | A new field on a client | Ignore fields you do not know | | New enum values | A new document status, payment rail or webhook event type | Handle unknown values gracefully | | New error codes | A new, more specific code | Fall back to the HTTP status for codes you do not know | ### What will not change within v1 - Removing an endpoint, a field or an enum value. - Renaming anything. - Making an optional request field required. - Changing a field's type or format, such as turning a money string into a number. - Changing the meaning of an existing error code. A check in our build compares the OpenAPI document of every change with the published one and **fails on any breaking change to `/v1`**. A breaking change cannot ship by accident. ## Write tolerant clients To benefit from this promise, your code should accept additions: - **Ignore unknown fields** in responses and webhook payloads. Do not fail validation because a new field appeared. - **Tolerate unknown enum values.** A `status`, `type` or event type you have not seen should be logged and skipped, not crash your code. For example, new [webhook events](/api-reference/webhook-events) are added as features ship through 2027. - **Branch on `error.code`, then on the HTTP status.** If a code is new to you, the HTTP status still tells you the category. - **Do not depend on field order** in JSON objects, or on the order of webhook deliveries. ```javascript JavaScript switch (event.type) { case "document.paid": await markPaid(event.data.object); break; case "document.voided": await markVoid(event.data.object); break; default: console.info("Ignoring event type", event.type); // new types are expected over time } ``` ## Releases add capabilities The product ships in steps through 2027: reminders and recurring invoices in January, schedules and reports later in January, Whop and the Shopify app in February, contracts and auto-charge in March, time and expenses in April, QuickBooks and Xero in May, and the ledger in June to July. Each release **adds** endpoints, MCP tools, CLI commands and webhook events to `v1`. Endpoints for later releases are already listed in this reference with a "Coming" badge, so you can plan ahead before they ship. ## Deprecations When an endpoint or field is going to be removed in a later major version: 1. It is marked deprecated in this reference and in the OpenAPI document. 2. Responses that use it carry a `Deprecation` header (proposed). 3. It keeps working in `v1`, because `v1` only ever grows. Removal happens only in a later major version. Watch for the `Deprecation` header in your logs, and follow the [Changelog](/changelog) for announcements. ## Webhook payloads are versioned too Every outgoing webhook payload carries `"api_version": "v1"`. The same additive rules apply: new event types and new fields may appear, existing ones keep their meaning. ```json Webhook payload (start) { "id": "0193f3c0-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "type": "document.sent", "api_version": "v1", "created_at": "2027-01-04T10:15:00Z" } ``` ## The OpenAPI document The OpenAPI document at `GET https://api.freeinvoiceautomation.com/v1/openapi.json` (proposed path) describes `v1` exactly as it runs, because it is generated from the same definitions as the routes. You can diff it yourself between your deploys to see what was added. See [Introduction](/api-reference/introduction#openapi). ## The CLI and SDK follow the API The [CLI](/integrations/cli) and the [TypeScript SDK](/integrations/sdk) are generated from the same capability definitions. A new CLI version adds commands for new capabilities; existing commands keep working against `v1`. ## Related pages Changelog What changed and when Webhook events The event list, by release Errors Stable error codes Requests and responses Headers including `Deprecation` Source: https://docs.freeinvoiceautomation.com/api-reference/versioning --- # Permission keys The 48 permission keys that guard every action, what each one allows, the default keys of each role, and how scopes and modes work for API keys and MCP tokens. Every action in Free Invoice Automation is guarded by exactly one **permission key**, written `module.action`, such as `documents.create` or `payments.refund`. No action exists without one. The same key is checked whether the action comes from the web app, the REST API, an MCP client, the CLI, a store connector or the AI agent, so no tool is a back door. This page lists all 48 keys, the default keys of each role, and how keys become **scopes** on API keys and MCP tokens. ## How permissions are checked For every request, the API: 1. works out the actor (a member's session, an API key, an MCP token) and the account, 2. loads the actor's role **at that moment** (permissions are read per request, never cached in the session), 3. checks the action's permission key against the role, and for keys and tokens also against their scopes, 4. checks the business scope: every role except Owner can be limited to some businesses. If the role lacks the key you get `permission_denied` (403). If the API key or token lacks the scope you get `scope_missing` (403). If the business is outside your scope you get `business_out_of_scope` (403). Each endpoint page names the key it needs. ## The full catalogue ### Businesses | Key | Allows | | --- | --- | | `businesses.view` | See businesses, their tax rates and numbering sequences, and the pay-me page settings | | `businesses.manage` | Create, update and delete businesses; upload a business logo | ### Documents | Key | Allows | | --- | --- | | `documents.view` | List and read documents, versions and activity, overdue lists, PDFs, send previews, messages, PDF templates, reminder sequences, late-fee policies, recurring schedules, payment schedules and saved views | | `documents.create` | Create invoices, estimates and receipts; convert estimates; manage recurring schedules; invoice unbilled time and expenses | | `documents.edit` | Update documents (a sent one gets a revision), delete drafts, apply client credit, set a promise to pay, manage payment schedules | | `documents.send` | Send documents and reminders; create and revoke share links | | `documents.void` | Void a document | | `documents.credit` | Create a credit note | ### Clients | Key | Allows | | --- | --- | | `clients.view` | List and read clients, their contacts and client credit | | `clients.create` | Create clients | | `clients.edit` | Update clients; add, change and delete client contacts | | `clients.delete` | Delete clients (soft delete) | ### Items | Key | Allows | | --- | --- | | `items.view` | List, read and export saved items | | `items.manage` | Create, update, archive and import saved items | ### Payments | Key | Allows | | --- | --- | | `payments.view` | See payments, payment status, payment proofs and a client's saved payment methods | | `payments.record` | Record and delete manual payments; accept or reject payment proofs | | `payments.refund` | Refund a payment | | `payments.auto_charge` | Turn auto-charge on or off for a recurring schedule or instalment (from March 2027) | ### Rails, senders and templates | Key | Allows | | --- | --- | | `rails.manage` | List rails; connect, update, verify and delete payment connections (Stripe, bank transfer, Whop, local QR rails); choose a business's payment methods; edit the pay-me page | | `senders.manage` | Connect and manage email senders, such as your own SMTP | | `templates.manage` | Create and edit message templates and PDF templates | ### Reminders and late fees (from January 2027) | Key | Allows | | --- | --- | | `reminders.manage` | Create, edit and delete reminder sequences | | `late_fees.manage` | Create, edit and delete late-fee policies | | `late_fees.waive` | Waive a late fee that was applied (audited) | ### Contracts (from March 2027) | Key | Allows | | --- | --- | | `contracts.view` | List and read contracts; download signed contracts | | `contracts.manage` | Create contracts and new versions | | `contracts.sign` | Countersign a contract for the business | ### Time and expenses (from April 2027) | Key | Allows | | --- | --- | | `time.view` | See time entries and unbilled time | | `time.log` | Log time, start and stop timers, change or delete your own entries | | `time.manage` | Change or delete other members' time entries | | `expenses.view` | See expenses | | `expenses.manage` | Record, change and delete expenses; upload receipts | ### Reports | Key | Allows | | --- | --- | | `reports.view` | Run reports, including aged receivables (from January 2027) | | `reports.export` | Export reports as CSV or PDF | ### Accounting sync and ledger | Key | Allows | | --- | --- | | `accounting.manage` | Connect QuickBooks Online or Xero, edit the mapping, backfill, see sync status, retry and disconnect (from May 2027) | | `ledger.view` | See ledger accounts, journal entries, periods, bank transactions and ledger reports (from June 2027) | | `ledger.journal` | Post and reverse journal entries | | `ledger.manage` | Manage the chart of accounts, apply a template, lock and unlock periods, import bank statements and match transactions | ### AI | Key | Allows | | --- | --- | | `ai.use` | Run the AI invoice agent and read its runs; read receipts with AI | | `ai.manage` | Manage agent automations and your own AI provider keys | ### Settings, team and integrations | Key | Allows | | --- | --- | | `settings.view` | Read settings, the account, members and usage | | `settings.manage` | Change settings, the account, tax rates and numbering patterns | | `team.manage` | Invite, update, deactivate, reactivate and remove members; create, edit, reset and delete roles | | `integrations.manage` | Create and revoke API keys; manage outgoing webhook endpoints, test them and redeliver; link and manage store connector installs | | `audit.view` | View the audit log (Team) | | `billing.manage` | Manage the Team subscription, the billing card and credit packs | ### Account (Owner only) | Key | Allows | | --- | --- | | `account.export` | Export all account data | | `account.delete` | Delete the account | `account.delete` and ownership transfer can never be given to a role other than Owner. Ownership transfer is not a key you can tick: it is checked against the Owner role itself. That is why the catalogue has 48 keys and no transfer key. ## Default roles Every account has five roles: **Owner**, **Admin**, **Editor**, **Viewer** and **Accountant**. The matrix shows the keys each one gets by default. Y means the role has the key. | Key | Owner | Admin | Editor | Viewer | Accountant | | --- | --- | --- | --- | --- | --- | | businesses.view | Y | Y | Y | Y | Y | | businesses.manage | Y | Y | | | | | documents.view | Y | Y | Y | Y | Y | | documents.create | Y | Y | Y | | | | documents.edit | Y | Y | Y | | | | documents.send | Y | Y | Y | | | | documents.void | Y | Y | | | | | documents.credit | Y | Y | | | | | clients.view | Y | Y | Y | Y | Y | | clients.create | Y | Y | Y | | | | clients.edit | Y | Y | Y | | | | clients.delete | Y | Y | | | | | items.view | Y | Y | Y | Y | Y | | items.manage | Y | Y | Y | | | | payments.view | Y | Y | Y | Y | Y | | payments.record | Y | Y | Y | | | | payments.refund | Y | Y | | | | | payments.auto_charge | Y | Y | | | | | rails.manage | Y | Y | | | | | senders.manage | Y | Y | | | | | templates.manage | Y | Y | Y | | | | reminders.manage | Y | Y | Y | | | | late_fees.manage | Y | Y | | | | | late_fees.waive | Y | Y | Y | | | | contracts.view | Y | Y | Y | Y | Y | | contracts.manage | Y | Y | Y | | | | contracts.sign | Y | Y | | | | | time.view | Y | Y | Y | Y | Y | | time.log | Y | Y | Y | | | | time.manage | Y | Y | | | | | expenses.view | Y | Y | Y | Y | Y | | expenses.manage | Y | Y | Y | | | | reports.view | Y | Y | Y | Y | Y | | reports.export | Y | Y | Y | | Y | | accounting.manage | Y | Y | | | | | ledger.view | Y | Y | | | Y | | ledger.journal | Y | Y | | | | | ledger.manage | Y | Y | | | | | ai.use | Y | Y | Y | | | | ai.manage | Y | Y | | | | | settings.view | Y | Y | Y | Y | Y | | settings.manage | Y | Y | | | | | team.manage | Y | Y | | | | | integrations.manage | Y | Y | | | | | audit.view | Y | Y | | | | | billing.manage | Y | | | | | | account.export | Y | | | | | | account.delete | Y | | | | | In short: **Owner** has everything. **Admin** has everything except billing and the two Owner-only account keys. **Editor** does the daily work: documents, clients, items, payments, templates, reminders, time and expenses, but cannot void, credit, refund or change settings. **Viewer** can only read. **Accountant** reads everything finance-related and can also export reports and see the ledger. ### Business scope On top of the role, every member except the Owner has a business scope: all businesses, or a list. A member scoped to one business cannot see or change the others, whatever their role. ## Editing roles The matrix above is the **default**. What applies at run time is the account's own roles. - **On Team**, every role except Owner can be edited by ticking or unticking keys, or deleted. Presets have **Reset to default**, which restores the keys in the matrix above. You can create **custom roles** from scratch or by duplicating a role. - **Owner is fixed.** It always has every key, cannot be edited, reset or deleted (`role_locked`), and an account always keeps at least one Owner (`last_owner`). - **No escalation.** Only a member with `team.manage` can change roles, and nobody can grant a key they do not hold (`permission_not_grantable`). - **Roles in use** cannot be deleted until their members are moved (`role_in_use`). - **Changes apply on the next request**, and every change is written to the audit log with the old and new key lists. - **On Free**, the account has the Owner and one view-only Accountant, both fixed. Role editing and custom roles return `feature_locked`. See [Roles and permissions](/team/roles) and [Custom roles](/team/custom-roles) for the app side, and the [Team](/api-reference/team/list-roles) endpoints for the API. ## Scopes on API keys and MCP tokens API keys and MCP tokens carry **scopes**, chosen from the same 48 keys, plus a **mode**. ### Scopes - When you create a key in **Settings > API keys**, or approve an MCP client on its consent screen, you choose the scopes. You can only choose keys you hold yourself. - At **call time**, a key is limited to the **intersection** of its scopes and its creator's **current** permissions. If the creator's role loses a key, or the creator is deactivated or leaves, the API key and MCP token lose it on the very next request. - A key cannot gain scopes after it is created. To widen access, create a new key. - Give each key the fewest scopes it needs. A script that only reads overdue invoices needs `documents.view`, nothing else. ### Modes | Mode | Behaviour | | --- | --- | | `draft_only` (default) | Creates drafts. Sends need `"confirm": true` from the caller, which should come from a person. | | `auto_send` | Sends run without `confirm`. Creating an `auto_send` key needs a fresh sign-in. | In both modes, destructive actions (void, credit note, refund, auto-charge settings and the others marked **D**) always need `"confirm": true`. See [Drafts and confirmations](/api-reference/confirmations). ### Typical scope sets | Integration | Scopes | | --- | --- | | Read-only dashboard | `documents.view`, `payments.view`, `clients.view` | | Billing server that creates and sends invoices | `clients.view`, `clients.create`, `clients.edit`, `items.view`, `documents.view`, `documents.create`, `documents.send` | | Payment reconciliation script | `documents.view`, `payments.view`, `payments.record` | | Webhook administration | `integrations.manage` | ## Related pages Authentication API keys, sessions and MCP tokens Roles and permissions The roles in the app Custom roles Build a role from the keys above Audit log Every permission change, recorded Source: https://docs.freeinvoiceautomation.com/api-reference/permissions --- # Webhook events Signed webhooks for every change. Payload shape, delivery and retries, verifying the FIA-Signature header in Node.js, and the full event list. Webhooks tell your systems the moment something changes in your account: an invoice is sent, a client pays, a credit note is created. Instead of polling the API, you give us an HTTPS URL and we `POST` a signed JSON event to it. This page is the reference for the payload, delivery, signatures and event types. To create and test endpoints, see [Webhooks](/integrations/webhooks). ## The payload Every delivery is a `POST` with a JSON body in the same shape: ```json document.sent { "id": "0193f3c0-1a2b-7c3d-8e4f-5a6b7c8d9e0f", "type": "document.sent", "api_version": "v1", "created_at": "2027-01-04T10:15:00Z", "account_id": "0193f29d-4b5c-7d6e-8f90-1a2b3c4d5e6f", "business_id": "0193f29e-11aa-7c3b-8d2e-5a6b7c8d9e0f", "data": { "object": { "id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "type": "invoice", "number": "INV-2027-0001", "status": "sent", "total_minor": "125000", "currency": "USD" } } } ``` " required> The event id. It is the **same on every retry** of this event, so use it to ignore duplicates. The event type, such as `document.sent`. See the [event list](#event-list). Always `v1` for this API version. See [Versioning](/api-reference/versioning). " required> When the change happened (RFC 3339, UTC). " required> The account the event belongs to. "> The business the event belongs to. properties The resource that changed (a document, payment, client and so on), in the same format the API returns. Money is a string of minor units with a `currency`. ## Headers | Header | Value | | --- | --- | | `Content-Type` | `application/json` | | `FIA-Signature` | `t=,v1=` | | `FIA-Event-Id` | The event `id`, so you can dedupe before parsing the body | | `FIA-Event-Type` | The event `type` | The signature scheme and header names are proposed and may be finalised before launch. ## Delivery - **Each endpoint chooses its events.** You only receive the types you subscribed to. An endpoint can also be limited to one business. - **HTTPS only.** Endpoint URLs must use `https` and may not point at private or internal network addresses. Redirects are not followed (proposed). - **Acknowledge with any 2xx status, quickly.** Return `200` as soon as you have stored the event, then do the work in the background. A delivery that times out counts as failed; the planned timeout is 10 seconds (proposed). - **Retries.** A failed delivery is retried with exponential backoff, about **8 attempts over roughly 24 hours** (proposed). After the last attempt the delivery is marked **dead** and shown in the delivery log, where you can redeliver it by hand. - **Same id on every retry.** Store the ids you have processed and skip repeats. - **Order is not guaranteed.** A `document.paid` can arrive before the `document.sent` that preceded it. Use `created_at`, or fetch the current state from the API, rather than relying on arrival order. - **Repeated failures.** An endpoint that keeps failing can be disabled automatically (proposed: after 50 failed deliveries in a row). Re-enable it once it is fixed. Delivery statuses in the log are `pending`, `succeeded`, `failed` and `dead`. You can see them in the app and with [List webhook deliveries](/api-reference/developers/list-webhook-deliveries), and redeliver with [Redeliver a webhook](/api-reference/developers/redeliver-webhook). ## Verifying signatures Every delivery is signed with your endpoint's **signing secret**, shown when you create the endpoint. Always verify the signature before trusting a delivery; anyone can send a `POST` to a public URL. The signature is: ```text v1 = HMAC-SHA256(endpoint_secret, ".") ``` where `t` is the timestamp from the header and `` is the exact bytes of the request body. To verify: 1. Read `FIA-Signature` and split it into `t` and one or more `v1` values. 2. Reject the request if `t` is more than **5 minutes** from your current time. This stops replays of old deliveries. 3. Compute the HMAC-SHA256 of `t`, a full stop, and the raw body, using your secret, as lowercase hex. 4. Compare it to each `v1` value in **constant time**. Accept if any one matches. Verify against the **raw** body. If your framework parses JSON first and you re-serialise it, the bytes change and the signature will not match. ### Node.js ```javascript Verify (node:crypto) const TOLERANCE_SECONDS = 5 * 60; if (!header) return false; const parts = header.split(",").map((part) => part.trim().split("=")); const timestamp = Number(parts.find(([key]) => key === "t")?.[1]); const signatures = parts.filter(([key]) => key === "v1").map(([, value]) => value); if (!Number.isFinite(timestamp) || signatures.length === 0) return false; const age = Math.abs(Math.floor(Date.now() / 1000) - timestamp); if (age > TOLERANCE_SECONDS) return false; const expected = crypto .createHmac("sha256", secret) .update(`${timestamp}.`) .update(rawBody) .digest("hex"); const expectedBuffer = Buffer.from(expected, "hex"); return signatures.some((signature) => { const given = Buffer.from(signature, "hex"); return given.length === expectedBuffer.length && crypto.timingSafeEqual(given, expectedBuffer); }); } ``` ```javascript Express const app = express(); // Keep the raw bytes for this route: do not use express.json() here. app.post("/webhooks/fia", express.raw({ type: "application/json" }), async (req, res) => { const ok = verifyFiaSignature(req.body, req.get("FIA-Signature"), process.env.FIA_WEBHOOK_SECRET); if (!ok) return res.status(400).send("bad signature"); const event = JSON.parse(req.body.toString("utf8")); if (await alreadyProcessed(event.id)) return res.sendStatus(200); await queue.add(event); // do the work later await markProcessed(event.id); res.sendStatus(200); }); ``` ```javascript Next.js route handler const rawBody = Buffer.from(await request.arrayBuffer()); const ok = verifyFiaSignature(rawBody, request.headers.get("FIA-Signature"), process.env.FIA_WEBHOOK_SECRET); if (!ok) return new Response("bad signature", { status: 400 }); const event = JSON.parse(rawBody.toString("utf8")); // store event.id, enqueue the work, then acknowledge return new Response(null, { status: 200 }); } ``` ### Rotating the secret When you rotate an endpoint's secret, we send **both** signatures for a grace period, as two `v1` values: `t=1767521700,v1=,v1=`. The code above accepts a match on either, so you can deploy the new secret at your own pace. After the grace period, only the new signature is sent. ## Event list Event names are proposed and each maps to a state change in the product. Events for later releases are added to `v1` when their feature ships. ### Documents | Event | When | Release | | --- | --- | --- | | `document.created` | A document (invoice, estimate, credit note, receipt) is created | Launch | | `document.updated` | A document is changed | Launch | | `document.revised` | A sent document gets a new revision | Launch | | `document.sent` | A document is sent to the client | Launch | | `document.viewed` | The client opens the document (proposed) | Launch | | `document.overdue` | An invoice passes its due date unpaid | Launch | | `document.voided` | A document is voided | Launch | | `document.partially_paid` | A payment covers part of the balance | Launch | | `document.paid` | The balance reaches zero | Launch | | `credit_note.created` | A credit note is issued against an invoice | Launch | ### Estimates | Event | When | Release | | --- | --- | --- | | `estimate.accepted` | The client accepts an estimate | Launch | | `estimate.changes_requested` | The client asks for changes | Launch | | `estimate.converted` | An estimate is converted to an invoice | Launch | ### Payments | Event | When | Release | | --- | --- | --- | | `payment.recorded` | A person records a payment manually | Launch | | `payment.succeeded` | A payment rail (such as Stripe) confirms a payment | Launch | | `payment.refunded` | A payment is refunded | Launch | | `payment_proof.submitted` | A client clicks "I have paid" and submits a proof | Launch | ### Clients and items | Event | When | Release | | --- | --- | --- | | `client.created`, `client.updated` | A client is created or changed | Launch | | `item.created`, `item.updated` | A saved item is created or changed | Launch | ### AI agent | Event | When | Release | | --- | --- | --- | | `agent_run.completed` | An agent run finished | Launch | | `agent_run.failed` | An agent run failed | Launch | ### Collections | Event | When | Release | | --- | --- | --- | | `reminder.sent` | A reminder was sent | January 2027 | | `late_fee.applied` | A late fee was added to an invoice | January 2027 | | `late_fee.waived` | A late fee was waived | January 2027 | | `recurring.document_created` | A recurring schedule created a document | January 2027 | | `installment.due` | A payment schedule instalment falls due | January 2027 | | `installment.paid` | An instalment is paid | January 2027 | ### Saved cards and contracts | Event | When | Release | | --- | --- | --- | | `auto_charge.succeeded` | An automatic charge on a saved card succeeded | March 2027 | | `auto_charge.failed` | An automatic charge failed | March 2027 | | `saved_payment_method.removed` | A client removed a saved card | March 2027 | | `contract.sent` | A contract was sent for signature | March 2027 | | `contract.signed` | The client signed a contract | March 2027 | | `contract.sealed` | A fully signed contract was sealed | March 2027 | ### Time, expenses, accounting and ledger | Event | When | Release | | --- | --- | --- | | `time_entry.created` | A time entry was logged | April 2027 | | `expense.created` | An expense was recorded | April 2027 | | `accounting_sync.failed` | A push to QuickBooks or Xero failed | May 2027 | | `journal_entry.posted` | A journal entry was posted | June to July 2027 | | `accounting_period.locked` | An accounting period was locked | June to July 2027 | ## Good practice - **Respond fast, work later.** Store the event, return `200`, then process it from a queue. - **Be idempotent.** Dedupe on `id`; the same event can arrive more than once. - **Treat the payload as a hint.** For decisions that matter, such as releasing goods when an invoice is paid, fetch the document from the API to confirm its current state. - **Ignore unknown types and fields.** New ones are added over time. - **Keep the secret secret.** Store it like an API key. If it leaks, rotate it. ## Related pages Webhooks Create, test and redeliver endpoints Create or update a webhook endpoint The endpoint for managing subscriptions Idempotency Safe retries in both directions Versioning Why new events never break your code Source: https://docs.freeinvoiceautomation.com/api-reference/webhook-events --- # List businesses GET https://api.freeinvoiceautomation.com/v1/businesses Returns the businesses of the account that you can reach. An account can hold any number of businesses on every plan. Members other than the Owner may be limited to some businesses; this list only shows the ones your role or API key can reach. Connectors use this call to fill their business picker and to test a connection. Results use [cursor pagination](/api-reference/pagination). See [Businesses](/account/businesses). Permission: `businesses.view`. Capability: `list_businesses`. CLI: `freeinvoiceautomation business list`. ## Query parameters - `q` (string): Text search over legal and display name. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The businesses on this page. - `id` (string): Unique id of the business (uuid v7). - `legal_name` (string): Registered name printed on documents. - `display_name` (string | null): Shorter trading name, shown where space is tight. - `slug` (string | null): Unique, URL-safe name used for the pay-me page `/[business-slug]` (from March 2027). - `country` (string): ISO 3166-1 alpha-2 country code. Drives the country defaults (currency, tax presets). - `base_currency` (string): ISO 4217 code. Reports total in this currency, using each document's frozen exchange rate. - `timezone` (string): IANA time zone, e.g. `Europe/Berlin`. Reminders and due dates run in it. - `locale` (string): Locale for dates and numbers on documents, e.g. `en-GB`. - `email` (string | null): Business email. Used as the Reply-To address when you send through the platform mailer. - `phone` (string | null): Phone number printed on documents. - `address` (object | null): Postal address printed on documents. - `tax_ids` (object[] | null): Tax registration numbers printed on documents. - `type` (string): Kind of number, e.g. `VAT` or `NTN`. - `value` (string): The number itself. - `logo_attachment_id` (string | null): Uploaded logo. Images are re-encoded, checked for size and type, and stored privately. - `created_at` (string): When the business was created (RFC 3339, UTC). - `updated_at` (string): When the business was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. Section: Businesses, tax rates and numbering Source: https://docs.freeinvoiceautomation.com/api-reference/businesses/list-businesses --- # Get a business GET https://api.freeinvoiceautomation.com/v1/businesses/{id} Returns one business profile. Permission: `businesses.view`. Capability: `get_business`. CLI: `freeinvoiceautomation business get`. ## Path parameters - `id` (string, required): Id of the business. ## Response - `id` (string): Unique id of the business (uuid v7). - `legal_name` (string): Registered name printed on documents. - `display_name` (string | null): Shorter trading name, shown where space is tight. - `slug` (string | null): Unique, URL-safe name used for the pay-me page `/[business-slug]` (from March 2027). - `country` (string): ISO 3166-1 alpha-2 country code. Drives the country defaults (currency, tax presets). - `base_currency` (string): ISO 4217 code. Reports total in this currency, using each document's frozen exchange rate. - `timezone` (string): IANA time zone, e.g. `Europe/Berlin`. Reminders and due dates run in it. - `locale` (string): Locale for dates and numbers on documents, e.g. `en-GB`. - `email` (string | null): Business email. Used as the Reply-To address when you send through the platform mailer. - `phone` (string | null): Phone number printed on documents. - `address` (object | null): Postal address printed on documents. - `tax_ids` (object[] | null): Tax registration numbers printed on documents. - `type` (string): Kind of number, e.g. `VAT` or `NTN`. - `value` (string): The number itself. - `logo_attachment_id` (string | null): Uploaded logo. Images are re-encoded, checked for size and type, and stored privately. - `created_at` (string): When the business was created (RFC 3339, UTC). - `updated_at` (string): When the business was last changed. ## Errors - `business_out_of_scope` Section: Businesses, tax rates and numbering Source: https://docs.freeinvoiceautomation.com/api-reference/businesses/get-business --- # Create a business POST https://api.freeinvoiceautomation.com/v1/businesses Adds a business to the account, with country defaults filled in. There is no limit on the number of businesses on any plan. Each business has its own profile, logo, numbering, tax rates, payment methods and templates. For businesses in the countries with launch presets, the currency and tax presets are pre-filled from the country. Numbering sequences are created on first use with the default patterns (`INV-{YYYY}-{SEQ:4}`, `EST-{YYYY}-{SEQ:4}`, `CN-{YYYY}-{SEQ:4}`, `RCT-{YYYY}-{SEQ:4}`); change them with [Update a numbering pattern](/api-reference/businesses/update-numbering-pattern). See [Businesses](/account/businesses). Permission: `businesses.manage`. Capability: `create_business`. CLI: `freeinvoiceautomation business create`. - Send an `Idempotency-Key` header so a retried request never creates the business twice. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `legal_name` (string, required): Registered name printed on documents. - `display_name` (string): Shorter trading name. - `slug` (string): Unique, URL-safe name for the pay-me page (from March 2027). - `country` (string, required): ISO 3166-1 alpha-2 code. Pre-fills currency and tax presets for supported countries. - `base_currency` (string): ISO 4217 code. Defaults to the country's currency. - `timezone` (string): IANA time zone. Defaults from the country. - `locale` (string): Locale for dates and numbers. Defaults from the country. - `email` (string): Business email, used as Reply-To on platform emails. - `phone` (string): Phone number. - `address` (object): Postal address printed on documents. - `tax_ids` (object[]): Tax registration numbers as `{ type, value }` pairs. - `logo_attachment_id` (string): Id of an uploaded logo image. ## Response - `id` (string): Unique id of the business (uuid v7). - `legal_name` (string): Registered name printed on documents. - `display_name` (string | null): Shorter trading name, shown where space is tight. - `slug` (string | null): Unique, URL-safe name used for the pay-me page `/[business-slug]` (from March 2027). - `country` (string): ISO 3166-1 alpha-2 country code. Drives the country defaults (currency, tax presets). - `base_currency` (string): ISO 4217 code. Reports total in this currency, using each document's frozen exchange rate. - `timezone` (string): IANA time zone, e.g. `Europe/Berlin`. Reminders and due dates run in it. - `locale` (string): Locale for dates and numbers on documents, e.g. `en-GB`. - `email` (string | null): Business email. Used as the Reply-To address when you send through the platform mailer. - `phone` (string | null): Phone number printed on documents. - `address` (object | null): Postal address printed on documents. - `tax_ids` (object[] | null): Tax registration numbers printed on documents. - `type` (string): Kind of number, e.g. `VAT` or `NTN`. - `value` (string): The number itself. - `logo_attachment_id` (string | null): Uploaded logo. Images are re-encoded, checked for size and type, and stored privately. - `created_at` (string): When the business was created (RFC 3339, UTC). - `updated_at` (string): When the business was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` - `conflict` Section: Businesses, tax rates and numbering Source: https://docs.freeinvoiceautomation.com/api-reference/businesses/create-business --- # Update a business PATCH https://api.freeinvoiceautomation.com/v1/businesses/{id} Changes the profile of a business. Only the fields you send are changed. A partial update (JSON merge). Changes apply to new documents and to drafts. Documents that were already sent keep the business details frozen in their snapshot, so an old invoice never shows a new address or logo. See [Revisions](/invoicing/revisions). Permission: `businesses.manage`. Capability: `update_business`. CLI: `freeinvoiceautomation business update`. ## Path parameters - `id` (string, required): Id of the business. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `legal_name` (string): Registered name printed on documents. - `display_name` (string): Shorter trading name. - `slug` (string): Unique, URL-safe name for the pay-me page (from March 2027). - `country` (string): ISO 3166-1 alpha-2 code. Pre-fills currency and tax presets for supported countries. - `base_currency` (string): ISO 4217 code. Defaults to the country's currency. - `timezone` (string): IANA time zone. Defaults from the country. - `locale` (string): Locale for dates and numbers. Defaults from the country. - `email` (string): Business email, used as Reply-To on platform emails. - `phone` (string): Phone number. - `address` (object): Postal address printed on documents. - `tax_ids` (object[]): Tax registration numbers as `{ type, value }` pairs. - `logo_attachment_id` (string): Id of an uploaded logo image. ## Response - `id` (string): Unique id of the business (uuid v7). - `legal_name` (string): Registered name printed on documents. - `display_name` (string | null): Shorter trading name, shown where space is tight. - `slug` (string | null): Unique, URL-safe name used for the pay-me page `/[business-slug]` (from March 2027). - `country` (string): ISO 3166-1 alpha-2 country code. Drives the country defaults (currency, tax presets). - `base_currency` (string): ISO 4217 code. Reports total in this currency, using each document's frozen exchange rate. - `timezone` (string): IANA time zone, e.g. `Europe/Berlin`. Reminders and due dates run in it. - `locale` (string): Locale for dates and numbers on documents, e.g. `en-GB`. - `email` (string | null): Business email. Used as the Reply-To address when you send through the platform mailer. - `phone` (string | null): Phone number printed on documents. - `address` (object | null): Postal address printed on documents. - `tax_ids` (object[] | null): Tax registration numbers printed on documents. - `type` (string): Kind of number, e.g. `VAT` or `NTN`. - `value` (string): The number itself. - `logo_attachment_id` (string | null): Uploaded logo. Images are re-encoded, checked for size and type, and stored privately. - `created_at` (string): When the business was created (RFC 3339, UTC). - `updated_at` (string): When the business was last changed. ## Errors - `validation_failed` - `business_out_of_scope` - `conflict` Section: Businesses, tax rates and numbering Source: https://docs.freeinvoiceautomation.com/api-reference/businesses/update-business --- # Delete a business DELETE https://api.freeinvoiceautomation.com/v1/businesses/{id} Soft-deletes a business that has never sent a document. Deleting a business is a sensitive action: it needs a fresh sign-in, so it works from a signed-in session and not from an API key. It also needs `confirm: true`; without it the API returns the preview and `confirmation_required`. See [Drafts and confirmations](/api-reference/confirmations). A business with sent documents cannot be deleted, because documents and payments are never removed (they keep your records complete for tax purposes). The request is refused with `conflict`. The delete is a soft delete and writes an audit entry. See [Businesses](/account/businesses). Permission: `businesses.manage`. Capability: `delete_business`. CLI: `freeinvoiceautomation business delete`. - Needs a fresh sign-in (`fresh_signin_required` otherwise). - Refused while the business has sent documents. ## Path parameters - `id` (string, required): Id of the business. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true` to delete. Without it you get the preview and `confirmation_required`. ## Errors - `fresh_signin_required` - `confirmation_required` - `conflict` - `business_out_of_scope` Section: Businesses, tax rates and numbering Source: https://docs.freeinvoiceautomation.com/api-reference/businesses/delete-business --- # List tax rates GET https://api.freeinvoiceautomation.com/v1/tax-rates Returns the tax rates of a business. Tax rates are saved per business and picked on document lines. Each line freezes the rate's name, percent and calculation, so changing or archiving a rate never changes documents already created. Archived rates are left out unless you pass `archived=true`. See [Taxes](/invoicing/taxes). Permission: `businesses.view`. Capability: `list_tax_rates`. CLI: `freeinvoiceautomation tax-rate list`. ## Query parameters - `business_id` (string): Only rates of this business. May be left out when you can reach exactly one business. - `archived` (boolean, default: false): Set to `true` to list archived rates instead. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The tax rates on this page. - `id` (string): Unique id of the tax rate. - `business_id` (string): The business the rate belongs to. - `name` (string): Label printed on lines and totals, e.g. `VAT 20%`. - `rate` (string): Percent as a decimal string with up to four decimals, e.g. `"20.0000"` = 20%. - `calculation` (enum): `exclusive` adds the tax on top of the line price; `inclusive` treats the price as already containing the tax. One of: `exclusive`, `inclusive`. - `compound` (boolean): When `true`, the tax is charged on the line amount plus the other taxes on that line. - `preset_key` (string | null): Key of the tax preset the rate was created from, if any. - `archived_at` (string | null): When the rate was archived. Archived rates cannot be picked for new lines but stay on old documents. - `created_at` (string): When the rate was created. - `updated_at` (string): When the rate was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Businesses, tax rates and numbering Source: https://docs.freeinvoiceautomation.com/api-reference/businesses/list-tax-rates --- # Create or update a tax rate POST https://api.freeinvoiceautomation.com/v1/tax-rates Creates a tax rate, or updates it when the body contains the `id` of an existing rate. The same capability also answers `PATCH /v1/tax-rates/{id}` for partial updates. Rates support exclusive, inclusive and compound tax, and every tax amount is computed in integer minor units. Document lines keep a frozen copy of the rate, so an update only affects lines added afterwards. See [Taxes](/invoicing/taxes). Permission: `settings.manage`. Capability: `upsert_tax_rate`. CLI: `freeinvoiceautomation tax-rate upsert`. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `id` (string): Id of an existing rate to update. Leave out to create a new rate. - `business_id` (string): Business that owns the rate. May be left out when you can reach exactly one business. - `name` (string, required): Label printed on documents. - `rate` (string, required): Percent as a decimal string, e.g. `"20"` or `"17.5"` (up to four decimals). - `calculation` (enum, required): How the rate is applied to the line price. One of: `exclusive`, `inclusive`. - `compound` (boolean, default: false): Charge this tax on the line amount plus the other taxes. - `preset_key` (string): Tax preset the rate is based on, when you start from a country preset. ## Response - `id` (string): Unique id of the tax rate. - `business_id` (string): The business the rate belongs to. - `name` (string): Label printed on lines and totals, e.g. `VAT 20%`. - `rate` (string): Percent as a decimal string with up to four decimals, e.g. `"20.0000"` = 20%. - `calculation` (enum): `exclusive` adds the tax on top of the line price; `inclusive` treats the price as already containing the tax. One of: `exclusive`, `inclusive`. - `compound` (boolean): When `true`, the tax is charged on the line amount plus the other taxes on that line. - `preset_key` (string | null): Key of the tax preset the rate was created from, if any. - `archived_at` (string | null): When the rate was archived. Archived rates cannot be picked for new lines but stay on old documents. - `created_at` (string): When the rate was created. - `updated_at` (string): When the rate was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` - `business_out_of_scope` Section: Businesses, tax rates and numbering Source: https://docs.freeinvoiceautomation.com/api-reference/businesses/upsert-tax-rate --- # Archive a tax rate POST https://api.freeinvoiceautomation.com/v1/tax-rates/{id}/archive Hides a tax rate from pickers. Documents that already use it are not changed. Tax rates are archived rather than deleted so history stays complete. Lines on existing documents keep their frozen name, percent and calculation. See [Taxes](/invoicing/taxes). Permission: `settings.manage`. Capability: `archive_tax_rate`. CLI: `freeinvoiceautomation tax-rate archive`. ## Path parameters - `id` (string, required): Id of the tax rate. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Unique id of the tax rate. - `business_id` (string): The business the rate belongs to. - `name` (string): Label printed on lines and totals, e.g. `VAT 20%`. - `rate` (string): Percent as a decimal string with up to four decimals, e.g. `"20.0000"` = 20%. - `calculation` (enum): `exclusive` adds the tax on top of the line price; `inclusive` treats the price as already containing the tax. One of: `exclusive`, `inclusive`. - `compound` (boolean): When `true`, the tax is charged on the line amount plus the other taxes on that line. - `preset_key` (string | null): Key of the tax preset the rate was created from, if any. - `archived_at` (string | null): When the rate was archived. Archived rates cannot be picked for new lines but stay on old documents. - `created_at` (string): When the rate was created. - `updated_at` (string): When the rate was last changed. Section: Businesses, tax rates and numbering Source: https://docs.freeinvoiceautomation.com/api-reference/businesses/archive-tax-rate --- # List numbering sequences GET https://api.freeinvoiceautomation.com/v1/numbering-sequences Returns the number patterns and counters of a business, one per document type and period. Numbers are gap-free per business and document type. A draft has no number: the next number is taken when the document is first sent (or, for a credit note, when it is created), inside the same transaction, so a failed send never leaves a gap and a number is never reused. A sequence row appears the first time a type is numbered in a period; with a yearly reset there is one row per year. See [Document numbering](/invoicing/numbering). Permission: `businesses.view`. Capability: `list_numbering_sequences`. CLI: `freeinvoiceautomation numbering list`. ## Query parameters - `business_id` (string): Only sequences of this business. May be left out when you can reach exactly one business. - `document_type` (enum): Only this document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`, `contract`. ## Response - `data` (object[]): The sequences. - `id` (string): Unique id of the sequence. - `business_id` (string): The business the sequence belongs to. - `document_type` (enum): Document type the sequence numbers. One of: `invoice`, `estimate`, `credit_note`, `receipt`, `contract`. - `pattern` (string): Number pattern, e.g. `INV-{YYYY}-{SEQ:4}` gives `INV-2027-0001`. - `period_key` (string): Period the counter belongs to: the year (e.g. `2027`) for a yearly reset, `all` when it never resets. - `next_value` (integer): The counter value the next numbered document will take. - `created_at` (string): When the sequence was first used. - `updated_at` (string): When a number was last taken or the pattern last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Businesses, tax rates and numbering Source: https://docs.freeinvoiceautomation.com/api-reference/businesses/list-numbering-sequences --- # Update a numbering pattern PATCH https://api.freeinvoiceautomation.com/v1/numbering-sequences/{id} Changes the number pattern a business uses for one document type. The new pattern applies to the next number taken. Numbers already given to documents never change, and the counter is not reset, so numbering stays gap-free. Each document type has its own pattern; the defaults are `INV-{YYYY}-{SEQ:4}`, `EST-{YYYY}-{SEQ:4}`, `CN-{YYYY}-{SEQ:4}` and `RCT-{YYYY}-{SEQ:4}`. `{YYYY}` is the year and `{SEQ:4}` the counter padded to four digits. When the counter resets (never, yearly or monthly) is a business setting. See [Document numbering](/invoicing/numbering). Permission: `settings.manage`. Capability: `update_numbering_pattern`. CLI: `freeinvoiceautomation numbering update`. ## Path parameters - `id` (string, required): Id of the numbering sequence. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `pattern` (string, required): New pattern built from fixed text, `{YYYY}` and the `{SEQ:n}` counter, e.g. `NW-{YYYY}-{SEQ:5}`. ## Response - `id` (string): Unique id of the sequence. - `business_id` (string): The business the sequence belongs to. - `document_type` (enum): Document type the sequence numbers. One of: `invoice`, `estimate`, `credit_note`, `receipt`, `contract`. - `pattern` (string): Number pattern, e.g. `INV-{YYYY}-{SEQ:4}` gives `INV-2027-0001`. - `period_key` (string): Period the counter belongs to: the year (e.g. `2027`) for a yearly reset, `all` when it never resets. - `next_value` (integer): The counter value the next numbered document will take. - `created_at` (string): When the sequence was first used. - `updated_at` (string): When a number was last taken or the pattern last changed. ## Errors - `validation_failed` - `business_out_of_scope` Section: Businesses, tax rates and numbering Source: https://docs.freeinvoiceautomation.com/api-reference/businesses/update-numbering-pattern --- # List clients GET https://api.freeinvoiceautomation.com/v1/clients Returns the clients of the businesses you can reach, newest first. Use `q` to find a client by name or email before creating a document. Each client belongs to one business; pass `business_id` when you can reach several. Results use [cursor pagination](/api-reference/pagination). See [Clients and contacts](/clients/clients). Permission: `clients.view`. Capability: `list_clients`. MCP tool: `list_clients`. CLI: `freeinvoiceautomation client list`. ## Query parameters - `business_id` (string): Only clients of this business. May be left out when you can reach exactly one business. - `q` (string): Text search over name, company name and email. - `country` (string): Only clients in this country. - `updated_after` (string): Only clients changed after this time, for syncing. - `sort` (string, default: -created_at): Sort field; a leading `-` sorts descending. One of: `name`, `-name`, `created_at`, `-created_at`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The clients on this page. - `id` (string): Unique id of the client (uuid v7). - `business_id` (string): The business the client belongs to. - `name` (string): Client name shown on documents. - `company_name` (string | null): Company name, when the client is a business. - `email` (string | null): Main email address. Documents are sent here and to contacts that receive documents. - `phone` (string | null): Phone number. - `address` (object | null): Postal address printed on documents. - `country` (string | null): ISO 3166-1 alpha-2 country code. - `tax_id` (string | null): The client's tax registration number, printed on documents. - `default_currency` (string | null): ISO 4217 code used for new documents for this client. - `language` (string | null): Language for emails and documents to this client. - `notes` (string | null): Private notes. Never shown to the client. - `reminders_opt_out` (boolean): The client opted out of payment reminders (reminders ship in January 2027). - `reminder_sequence_id` (string | null): Reminder sequence used for this client instead of the business default (January 2027). - `external_refs` (object | null): Ids of the same customer in connected stores, e.g. a Shopify or WooCommerce customer id. - `created_at` (string): When the client was created (RFC 3339, UTC). - `updated_at` (string): When the client was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Clients Source: https://docs.freeinvoiceautomation.com/api-reference/clients/list-clients --- # Get a client GET https://api.freeinvoiceautomation.com/v1/clients/{id} Returns one client. Permission: `clients.view`. Capability: `get_client`. CLI: `freeinvoiceautomation client get`. ## Path parameters - `id` (string, required): Id of the client. ## Response - `id` (string): Unique id of the client (uuid v7). - `business_id` (string): The business the client belongs to. - `name` (string): Client name shown on documents. - `company_name` (string | null): Company name, when the client is a business. - `email` (string | null): Main email address. Documents are sent here and to contacts that receive documents. - `phone` (string | null): Phone number. - `address` (object | null): Postal address printed on documents. - `country` (string | null): ISO 3166-1 alpha-2 country code. - `tax_id` (string | null): The client's tax registration number, printed on documents. - `default_currency` (string | null): ISO 4217 code used for new documents for this client. - `language` (string | null): Language for emails and documents to this client. - `notes` (string | null): Private notes. Never shown to the client. - `reminders_opt_out` (boolean): The client opted out of payment reminders (reminders ship in January 2027). - `reminder_sequence_id` (string | null): Reminder sequence used for this client instead of the business default (January 2027). - `external_refs` (object | null): Ids of the same customer in connected stores, e.g. a Shopify or WooCommerce customer id. - `created_at` (string): When the client was created (RFC 3339, UTC). - `updated_at` (string): When the client was last changed. Section: Clients Source: https://docs.freeinvoiceautomation.com/api-reference/clients/get-client --- # Create or update a client POST https://api.freeinvoiceautomation.com/v1/clients Creates a client, or updates it when the body contains the `id` of an existing client. The same capability also answers `PATCH /v1/clients/{id}` for partial updates. Creating a client needs `clients.create`; updating one needs `clients.edit`. A client's defaults (currency, language) fill new documents and can be overridden on each document. Changes never alter documents that were already sent: each sent document keeps the client details frozen in its snapshot. Clients never get a password; they open documents through single-purpose links. See [Clients and contacts](/clients/clients). Permission: `clients.create / clients.edit`. Capability: `upsert_client`. MCP tool: `upsert_client`. CLI: `freeinvoiceautomation client upsert`. - Send an `Idempotency-Key` header so a retried request never creates the client twice. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `id` (string): Id of an existing client to update. Leave out to create a new client. - `business_id` (string): Business the client belongs to. May be left out when you can reach exactly one business. - `name` (string, required): Client name shown on documents. - `company_name` (string): Company name. - `email` (string): Main email address. - `phone` (string): Phone number. - `address` (object): Postal address printed on documents. - `country` (string): ISO 3166-1 alpha-2 code. - `tax_id` (string): Tax registration number. - `default_currency` (string): ISO 4217 code for new documents. - `language` (string): Language for emails and documents. - `notes` (string): Private notes. - `external_refs` (object): Ids of the same customer in connected stores. ## Response - `id` (string): Unique id of the client (uuid v7). - `business_id` (string): The business the client belongs to. - `name` (string): Client name shown on documents. - `company_name` (string | null): Company name, when the client is a business. - `email` (string | null): Main email address. Documents are sent here and to contacts that receive documents. - `phone` (string | null): Phone number. - `address` (object | null): Postal address printed on documents. - `country` (string | null): ISO 3166-1 alpha-2 country code. - `tax_id` (string | null): The client's tax registration number, printed on documents. - `default_currency` (string | null): ISO 4217 code used for new documents for this client. - `language` (string | null): Language for emails and documents to this client. - `notes` (string | null): Private notes. Never shown to the client. - `reminders_opt_out` (boolean): The client opted out of payment reminders (reminders ship in January 2027). - `reminder_sequence_id` (string | null): Reminder sequence used for this client instead of the business default (January 2027). - `external_refs` (object | null): Ids of the same customer in connected stores, e.g. a Shopify or WooCommerce customer id. - `created_at` (string): When the client was created (RFC 3339, UTC). - `updated_at` (string): When the client was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` - `business_out_of_scope` Section: Clients Source: https://docs.freeinvoiceautomation.com/api-reference/clients/upsert-client --- # Delete a client DELETE https://api.freeinvoiceautomation.com/v1/clients/{id} Soft-deletes a client and hides it from lists and pickers. Deleting is a destructive action, so the request must include `confirm: true`; without it you get the preview and `confirmation_required`. See [Drafts and confirmations](/api-reference/confirmations). The client is soft-deleted, never removed: documents and payments for the client are kept, and sent documents keep the client details frozen in their snapshots. The delete writes an audit entry. Permission: `clients.delete`. Capability: `delete_client`. CLI: `freeinvoiceautomation client delete`. ## Path parameters - `id` (string, required): Id of the client. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true` to delete. Without it you get the preview and `confirmation_required`. ## Errors - `confirmation_required` Section: Clients Source: https://docs.freeinvoiceautomation.com/api-reference/clients/delete-client --- # List client contacts GET https://api.freeinvoiceautomation.com/v1/clients/{id}/contacts Returns the contact people of a client. Contacts with `receives_documents: true` get a copy of every document sent to the client. See [Clients and contacts](/clients/clients). Permission: `clients.view`. Capability: `list_client_contacts`. CLI: `freeinvoiceautomation client contacts`. ## Path parameters - `id` (string, required): Id of the client. ## Response - `data` (object[]): The contacts. - `id` (string): Unique id of the contact. - `client_id` (string): The client the contact belongs to. - `name` (string): Contact name. - `email` (string | null): Email address. - `phone` (string | null): Phone number. - `role` (string | null): Free text such as `Finance` or `Project lead`. - `is_primary` (boolean): The main contact for the client. - `receives_documents` (boolean): When `true`, sent documents are also emailed to this contact. - `created_at` (string): When the contact was created. - `updated_at` (string): When the contact was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. Section: Clients Source: https://docs.freeinvoiceautomation.com/api-reference/clients/list-client-contacts --- # Create or update a client contact POST https://api.freeinvoiceautomation.com/v1/clients/{id}/contacts Adds a contact person to a client. To change an existing contact, use `PATCH /v1/client-contacts/{id}` with the fields to change (same capability). Setting `is_primary: true` makes this the client's main contact. Permission: `clients.edit`. Capability: `upsert_client_contact`. CLI: `freeinvoiceautomation client contact-upsert`. ## Path parameters - `id` (string, required): Id of the client. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `name` (string, required): Contact name. - `email` (string): Email address. - `phone` (string): Phone number. - `role` (string): Free text role, e.g. `Finance`. - `is_primary` (boolean, default: false): Make this the main contact. - `receives_documents` (boolean, default: false): Also email sent documents to this contact. ## Response - `id` (string): Unique id of the contact. - `client_id` (string): The client the contact belongs to. - `name` (string): Contact name. - `email` (string | null): Email address. - `phone` (string | null): Phone number. - `role` (string | null): Free text such as `Finance` or `Project lead`. - `is_primary` (boolean): The main contact for the client. - `receives_documents` (boolean): When `true`, sent documents are also emailed to this contact. - `created_at` (string): When the contact was created. - `updated_at` (string): When the contact was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` Section: Clients Source: https://docs.freeinvoiceautomation.com/api-reference/clients/upsert-client-contact --- # Delete a client contact DELETE https://api.freeinvoiceautomation.com/v1/client-contacts/{id} Removes a contact person from a client. The contact is soft-deleted and no longer receives documents. Messages already sent to the contact stay in the send log. Permission: `clients.edit`. Capability: `delete_client_contact`. CLI: `freeinvoiceautomation client contact-delete`. ## Path parameters - `id` (string, required): Id of the contact. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). Section: Clients Source: https://docs.freeinvoiceautomation.com/api-reference/clients/delete-client-contact --- # Get client credit GET https://api.freeinvoiceautomation.com/v1/clients/{id}/credit Returns the client's credit balance per currency and the entries behind it. Client credit is created when you issue a credit note on a paid invoice and choose to keep the amount as client credit instead of refunding it. The credit is then applied to a later invoice for the same client and currency with [Apply client credit](/api-reference/documents/apply-client-credit). There is one balance per currency. Every change is an append-only entry: positive from a credit note, negative when applied or refunded. See [Client credit](/clients/client-credit). Permission: `clients.view`. Capability: `get_client_credit`. CLI: `freeinvoiceautomation client credit`. ## Path parameters - `id` (string, required): Id of the client. ## Response - `client_id` (string): The client. - `balances` (object[]): Available credit, one entry per currency. - `currency` (string): ISO 4217 code. - `balance_minor` (string): Credit available, in minor units. Never negative. - `updated_at` (string): When the balance last changed. - `entries` (object[]): Credit history, newest first. - `id` (string): Id of the entry. - `currency` (string): ISO 4217 code. - `delta_minor` (string): Signed change in minor units: positive adds credit, negative uses it. - `reason` (enum): Why the balance changed. One of: `credit_note`, `applied`, `refunded`. - `credit_note_document_id` (string | null): The credit note that created the credit. - `applied_document_id` (string | null): The invoice the credit was applied to. - `created_at` (string): When the entry was written. Section: Clients Source: https://docs.freeinvoiceautomation.com/api-reference/clients/get-client-credit --- # List items GET https://api.freeinvoiceautomation.com/v1/items Returns the saved products and services of the businesses you can reach, newest first. Use this to fill an item picker or to sync your catalogue. Archived items are left out unless you pass `archived=true`. Results use [cursor pagination](/api-reference/pagination): pass `next_cursor` from one page as `cursor` to get the next. Permission: `items.view`. Capability: `list_items`. MCP tool: `list_items`. CLI: `freeinvoiceautomation item list`. ## Query parameters - `business_id` (string): Only items of this business. May be left out when you can reach exactly one business. - `q` (string): Text search over name, description and SKU. - `currency` (string): Only items priced in this currency. - `archived` (boolean, default: false): Set to `true` to list archived items instead. - `sort` (string, default: -created_at): Sort field; a leading `-` sorts descending. One of: `name`, `-name`, `unit_price_minor`, `-unit_price_minor`, `created_at`, `-created_at`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The items on this page. - `id` (string): Unique id of the item (uuid v7). - `business_id` (string): The business the item belongs to. - `name` (string): Name shown on document lines, e.g. `Logo design`. - `description` (string | null): Longer text copied to the line description. - `sku` (string | null): Your own stock or product code. - `unit` (string | null): Unit label such as `hour`, `day` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of `currency`, as a string of an integer (e.g. `"15000"` = 150.00). - `currency` (string): ISO 4217 currency code, e.g. `USD`. - `default_tax_rate_id` (string | null): Tax rate applied when the item is added to a line. - `archived_at` (string | null): When the item was archived. Archived items are hidden from pickers but stay on old documents. - `created_at` (string): When the item was created (RFC 3339, UTC). - `updated_at` (string): When the item was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Items Source: https://docs.freeinvoiceautomation.com/api-reference/items/list-items --- # Get an item GET https://api.freeinvoiceautomation.com/v1/items/{id} Returns one saved item. Permission: `items.view`. Capability: `get_item`. CLI: `freeinvoiceautomation item get`. ## Path parameters - `id` (string, required): Id of the item. ## Response - `id` (string): Unique id of the item (uuid v7). - `business_id` (string): The business the item belongs to. - `name` (string): Name shown on document lines, e.g. `Logo design`. - `description` (string | null): Longer text copied to the line description. - `sku` (string | null): Your own stock or product code. - `unit` (string | null): Unit label such as `hour`, `day` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of `currency`, as a string of an integer (e.g. `"15000"` = 150.00). - `currency` (string): ISO 4217 currency code, e.g. `USD`. - `default_tax_rate_id` (string | null): Tax rate applied when the item is added to a line. - `archived_at` (string | null): When the item was archived. Archived items are hidden from pickers but stay on old documents. - `created_at` (string): When the item was created (RFC 3339, UTC). - `updated_at` (string): When the item was last changed. Section: Items Source: https://docs.freeinvoiceautomation.com/api-reference/items/get-item --- # Create or update an item POST https://api.freeinvoiceautomation.com/v1/items Creates a saved item, or updates it when the body contains the `id` of an existing item. The same capability also answers `PATCH /v1/items/{id}` for partial updates. Changing an item never changes documents that already use it: each document line keeps its own copy. Prices are integer minor units sent as strings. Floating-point amounts are rejected with `validation_failed`. Permission: `items.manage`. Capability: `upsert_item`. MCP tool: `upsert_item`. CLI: `freeinvoiceautomation item upsert`. - Send an `Idempotency-Key` header so a retried request never creates the item twice. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `id` (string): Id of an existing item to update. Leave out to create a new item. - `business_id` (string): Business that owns the item. May be left out when you can reach exactly one business. - `name` (string, required): Name shown on document lines (1 to 200 characters). - `description` (string): Longer text copied to the line. - `sku` (string): Your own product code. - `unit` (string): Unit label, e.g. `hour`. - `unit_price_minor` (string, required): Price per unit in minor units, e.g. `"45000"` for 450.00. - `currency` (string, required): ISO 4217 code of the price. - `default_tax_rate_id` (string): Tax rate added when the item is put on a line. Must belong to the same business. ## Response - `id` (string): Unique id of the item (uuid v7). - `business_id` (string): The business the item belongs to. - `name` (string): Name shown on document lines, e.g. `Logo design`. - `description` (string | null): Longer text copied to the line description. - `sku` (string | null): Your own stock or product code. - `unit` (string | null): Unit label such as `hour`, `day` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of `currency`, as a string of an integer (e.g. `"15000"` = 150.00). - `currency` (string): ISO 4217 currency code, e.g. `USD`. - `default_tax_rate_id` (string | null): Tax rate applied when the item is added to a line. - `archived_at` (string | null): When the item was archived. Archived items are hidden from pickers but stay on old documents. - `created_at` (string): When the item was created (RFC 3339, UTC). - `updated_at` (string): When the item was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` Section: Items Source: https://docs.freeinvoiceautomation.com/api-reference/items/upsert-item --- # Archive an item POST https://api.freeinvoiceautomation.com/v1/items/{id}/archive Hides an item from pickers and lists. Documents that already use it are not changed. Items are archived rather than deleted, so history stays complete. Update the item with `archived_at: null` through [Create or update an item](/api-reference/items/upsert-item) to bring it back. Permission: `items.manage`. Capability: `archive_item`. CLI: `freeinvoiceautomation item archive`. ## Path parameters - `id` (string, required): Id of the item. ## Response - `id` (string): Unique id of the item (uuid v7). - `business_id` (string): The business the item belongs to. - `name` (string): Name shown on document lines, e.g. `Logo design`. - `description` (string | null): Longer text copied to the line description. - `sku` (string | null): Your own stock or product code. - `unit` (string | null): Unit label such as `hour`, `day` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of `currency`, as a string of an integer (e.g. `"15000"` = 150.00). - `currency` (string): ISO 4217 currency code, e.g. `USD`. - `default_tax_rate_id` (string | null): Tax rate applied when the item is added to a line. - `archived_at` (string | null): When the item was archived. Archived items are hidden from pickers but stay on old documents. - `created_at` (string): When the item was created (RFC 3339, UTC). - `updated_at` (string): When the item was last changed. Section: Items Source: https://docs.freeinvoiceautomation.com/api-reference/items/archive-item --- # Import items from CSV POST https://api.freeinvoiceautomation.com/v1/items/import Creates or updates many items at once from a CSV file. Send the file as `multipart/form-data` in the field `file`. The first row must be a header with any of: `name`, `description`, `sku`, `unit`, `unit_price`, `currency`, `tax_rate`. `unit_price` is a decimal in the file (e.g. `450.00`) and is converted to minor units with the currency's decimals. Rows whose `sku` matches an existing item update that item; other rows create new items. The response reports every row that failed and why, and nothing from a failed row is saved. Permission: `items.manage`. Capability: `import_items`. CLI: `freeinvoiceautomation item import`. ## Body - `file` (file, required): CSV file, UTF-8, up to 5 MB. - `business_id` (string): Business to import into. ## Response - `created` (integer): Number of new items. - `updated` (integer): Number of items updated by SKU. - `failed` (object[]): Rows that were not imported. - `row` (integer): Row number in the file (header is row 1). - `message` (string): Why the row failed. ## Errors - `validation_failed` Section: Items Source: https://docs.freeinvoiceautomation.com/api-reference/items/import-items --- # Export items to CSV GET https://api.freeinvoiceautomation.com/v1/items/export Downloads every item of a business as a CSV file with the same columns the import accepts. Permission: `items.view`. Capability: `export_items`. CLI: `freeinvoiceautomation item export`. ## Query parameters - `business_id` (string): Business to export. - `archived` (boolean, default: false): Include archived items. Section: Items Source: https://docs.freeinvoiceautomation.com/api-reference/items/export-items --- # List documents GET https://api.freeinvoiceautomation.com/v1/documents Returns invoices, estimates, credit notes and receipts, newest first, with filters for type, status, client and dates. Use the filters to find what needs action: `status=sent,overdue` lists unpaid invoices, `type=estimate&status=accepted` lists estimates ready to convert. Several values are comma-separated. Connectors find a document they created earlier with `external_ref` and `source`. Each document includes its computed totals, status, number, lines and links. Results use [cursor pagination](/api-reference/pagination). See [Invoices](/invoicing/invoices) and [Document statuses](/invoicing/statuses). Permission: `documents.view`. Capability: `list_documents`. MCP tool: `list_documents`. CLI: `freeinvoiceautomation doc list`. ## Query parameters - `business_id` (string): Only documents of this business. May be left out when you can reach exactly one business. - `type` (string): Document type, or several comma-separated. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `status` (string): Status, or several comma-separated, e.g. `sent,overdue`. One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`, `changes_requested`, `accepted`, `expired`, `converted`, `issued`, `applied`, `refunded`. - `client_id` (string): Only documents for this client. - `currency` (string): Only documents in this currency. - `issued_after` (string): Issue date on or after this day. - `issued_before` (string): Issue date on or before this day. - `due_after` (string): Due date on or after this day. - `due_before` (string): Due date on or before this day. - `updated_after` (string): Only documents changed after this time, for syncing. - `external_ref` (string): Only the document with this reference. - `source` (string): Only documents from this source, e.g. `woocommerce`. - `q` (string): Text search over number and client name or email. - `sort` (string, default: -created_at): Sort field; a leading `-` sorts descending. One of: `created_at`, `-created_at`, `issue_date`, `-issue_date`, `due_date`, `-due_date`, `total_minor`, `-total_minor`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The documents on this page. - `id` (string): Unique id of the document (uuid v7). - `business_id` (string): The issuing business. - `client_id` (string): The client the document is for. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `status` (enum): Current status. Invoices: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`. Estimates: `draft`, `sent`, `changes_requested`, `accepted`, `expired`, `converted`, `void`. Credit notes: `draft`, `issued`, `applied`, `refunded`. Receipts: `issued`, `void`. See [Document statuses](/invoicing/statuses). One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`, `changes_requested`, `accepted`, `expired`, `converted`, `issued`, `applied`, `refunded`. - `number` (string | null): Human number such as `INV-2027-0017`. `null` until the document is first sent. - `issue_date` (string | null): Issue date, `YYYY-MM-DD`. - `due_date` (string | null): Due date for invoices. - `expires_at` (string | null): Expiry date for estimates. - `currency` (string): ISO 4217 code of every amount on the document. - `fx_rate` (string | null): Exchange rate to the business base currency, frozen on the document when its currency differs. - `fx_rate_source` (string | null): Where the frozen rate came from and its date. - `subtotal_minor` (string): Sum of line amounts before tax. - `discount_minor` (string): Total discount. - `tax_minor` (string): Total tax. - `total_minor` (string): Grand total. - `amount_paid_minor` (string): Payments received so far. - `amount_credited_minor` (string): Credit notes and client credit applied. - `balance_minor` (string): What is still owed: total minus paid minus credited. - `terms` (string | null): Payment terms printed on the document. - `notes` (string | null): Notes printed on the document. - `accepted_methods` (string[] | null): Payment connections offered on this document's pay page. - `pdf_template_id` (string | null): PDF template used to render the document. - `late_fees_enabled` (boolean): Late fees on or off for this invoice (January 2027). Off by default. - `reminder_sequence_id` (string | null): Reminder sequence override for this document (January 2027). - `promise_to_pay_date` (string | null): Date the client promised to pay; stops reminders (January 2027). - `recurring_schedule_id` (string | null): Recurring schedule that created the document (January 2027). - `current_version` (integer): `0` for a draft, `1` after the first send, then one more for each revision. - `first_sent_at` (string | null): When the document was first sent. - `last_sent_at` (string | null): When the document was last sent. - `accepted_at` (string | null): When the client accepted the estimate. - `paid_at` (string | null): When the invoice became fully paid. - `overdue_at` (string | null): When the invoice was marked overdue. - `voided_at` (string | null): When the document was voided. - `voided_by` (string | null): Who voided it. - `void_reason` (string | null): Reason given when voiding. - `source` (enum): Where the document was created. One of: `app`, `api`, `mcp`, `cli`, `agent`, `shopify`, `woocommerce`, `generator_sync`, `recurring`. - `external_ref` (string | null): Your own reference, e.g. a store order id. Unique per business and source, so a connector cannot create the same document twice. - `client_generated_id` (string | null): Id given by the free generator for documents synced on sign-up. - `lines` (object[]): The document lines, in order. - `id` (string): Unique id of the line. - `position` (integer): Order of the line on the document, starting at 1. - `kind` (enum): Where the line came from. `late_fee` from January 2027, `deposit` from late January 2027, `time` and `expense` from April 2027. One of: `item`, `late_fee`, `time`, `expense`, `deposit`. - `item_id` (string | null): Saved item the line was filled from. Reference only: the values are copied, so editing the item never changes this line. - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string with up to six decimals, e.g. `"1.5"`. - `unit` (string | null): Unit label such as `hour` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of the document currency. - `discount_type` (enum | null): Kind of line discount. One of: `percent`, `amount`. - `discount_value` (string | null): Discount as a percent (`"10"` = 10%) or, for `amount`, in minor units. - `tax_rate_id` (string | null): Tax rate picked for the line. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line, e.g. `"20.0000"`. - `tax_calculation` (enum | null): Frozen calculation of the tax. One of: `exclusive`, `inclusive`. - `subtotal_minor` (string): Line amount before tax, after discount. - `tax_minor` (string): Tax on the line. - `total_minor` (string): Line total including tax. - `credited_line_id` (string | null): On a credit note: the invoice line this line credits. - `links` (object[]): Linked documents: credit notes, source estimate, contract. - `type` (enum): How the documents are linked. One of: `credit_note`, `estimate`, `contract`, `revision`. - `from_document_id` (string): The linking document, e.g. the credit note or the invoice made from an estimate. - `to_document_id` (string): The linked document, e.g. the credited invoice or the source estimate. - `amount_minor` (string | null): For `credit_note` links: the amount credited. - `pay_url` (string | null): Pay page link, present when an active share link exists. - `created_at` (string): When the document was created (RFC 3339, UTC). - `updated_at` (string): When the document was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/list-documents --- # Get a document GET https://api.freeinvoiceautomation.com/v1/documents/{id} Returns one document with its lines, totals, status, links and pay link. The response shows the document as it is now. For a sent document that was revised, older versions stay readable through [List document versions](/api-reference/documents/list-document-versions). Permission: `documents.view`. Capability: `get_document`. MCP tool: `get_document`. CLI: `freeinvoiceautomation doc get`. ## Path parameters - `id` (string, required): Id of the document. ## Response - `id` (string): Unique id of the document (uuid v7). - `business_id` (string): The issuing business. - `client_id` (string): The client the document is for. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `status` (enum): Current status. Invoices: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`. Estimates: `draft`, `sent`, `changes_requested`, `accepted`, `expired`, `converted`, `void`. Credit notes: `draft`, `issued`, `applied`, `refunded`. Receipts: `issued`, `void`. See [Document statuses](/invoicing/statuses). One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`, `changes_requested`, `accepted`, `expired`, `converted`, `issued`, `applied`, `refunded`. - `number` (string | null): Human number such as `INV-2027-0017`. `null` until the document is first sent. - `issue_date` (string | null): Issue date, `YYYY-MM-DD`. - `due_date` (string | null): Due date for invoices. - `expires_at` (string | null): Expiry date for estimates. - `currency` (string): ISO 4217 code of every amount on the document. - `fx_rate` (string | null): Exchange rate to the business base currency, frozen on the document when its currency differs. - `fx_rate_source` (string | null): Where the frozen rate came from and its date. - `subtotal_minor` (string): Sum of line amounts before tax. - `discount_minor` (string): Total discount. - `tax_minor` (string): Total tax. - `total_minor` (string): Grand total. - `amount_paid_minor` (string): Payments received so far. - `amount_credited_minor` (string): Credit notes and client credit applied. - `balance_minor` (string): What is still owed: total minus paid minus credited. - `terms` (string | null): Payment terms printed on the document. - `notes` (string | null): Notes printed on the document. - `accepted_methods` (string[] | null): Payment connections offered on this document's pay page. - `pdf_template_id` (string | null): PDF template used to render the document. - `late_fees_enabled` (boolean): Late fees on or off for this invoice (January 2027). Off by default. - `reminder_sequence_id` (string | null): Reminder sequence override for this document (January 2027). - `promise_to_pay_date` (string | null): Date the client promised to pay; stops reminders (January 2027). - `recurring_schedule_id` (string | null): Recurring schedule that created the document (January 2027). - `current_version` (integer): `0` for a draft, `1` after the first send, then one more for each revision. - `first_sent_at` (string | null): When the document was first sent. - `last_sent_at` (string | null): When the document was last sent. - `accepted_at` (string | null): When the client accepted the estimate. - `paid_at` (string | null): When the invoice became fully paid. - `overdue_at` (string | null): When the invoice was marked overdue. - `voided_at` (string | null): When the document was voided. - `voided_by` (string | null): Who voided it. - `void_reason` (string | null): Reason given when voiding. - `source` (enum): Where the document was created. One of: `app`, `api`, `mcp`, `cli`, `agent`, `shopify`, `woocommerce`, `generator_sync`, `recurring`. - `external_ref` (string | null): Your own reference, e.g. a store order id. Unique per business and source, so a connector cannot create the same document twice. - `client_generated_id` (string | null): Id given by the free generator for documents synced on sign-up. - `lines` (object[]): The document lines, in order. - `id` (string): Unique id of the line. - `position` (integer): Order of the line on the document, starting at 1. - `kind` (enum): Where the line came from. `late_fee` from January 2027, `deposit` from late January 2027, `time` and `expense` from April 2027. One of: `item`, `late_fee`, `time`, `expense`, `deposit`. - `item_id` (string | null): Saved item the line was filled from. Reference only: the values are copied, so editing the item never changes this line. - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string with up to six decimals, e.g. `"1.5"`. - `unit` (string | null): Unit label such as `hour` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of the document currency. - `discount_type` (enum | null): Kind of line discount. One of: `percent`, `amount`. - `discount_value` (string | null): Discount as a percent (`"10"` = 10%) or, for `amount`, in minor units. - `tax_rate_id` (string | null): Tax rate picked for the line. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line, e.g. `"20.0000"`. - `tax_calculation` (enum | null): Frozen calculation of the tax. One of: `exclusive`, `inclusive`. - `subtotal_minor` (string): Line amount before tax, after discount. - `tax_minor` (string): Tax on the line. - `total_minor` (string): Line total including tax. - `credited_line_id` (string | null): On a credit note: the invoice line this line credits. - `links` (object[]): Linked documents: credit notes, source estimate, contract. - `type` (enum): How the documents are linked. One of: `credit_note`, `estimate`, `contract`, `revision`. - `from_document_id` (string): The linking document, e.g. the credit note or the invoice made from an estimate. - `to_document_id` (string): The linked document, e.g. the credited invoice or the source estimate. - `amount_minor` (string | null): For `credit_note` links: the amount credited. - `pay_url` (string | null): Pay page link, present when an active share link exists. - `created_at` (string): When the document was created (RFC 3339, UTC). - `updated_at` (string): When the document was last changed. Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/get-document --- # Create an invoice POST https://api.freeinvoiceautomation.com/v1/documents Creates a draft invoice with lines, taxes and discounts. Send `type: "invoice"`. Invoices, estimates and receipts are all created with `POST /v1/documents`; the `type` field picks the kind. The invoice is created as a `draft` with no number. Totals are computed on the server in integer minor units: line amounts, discounts, exclusive, inclusive and compound taxes, and the grand total. When the currency differs from the business base currency, the exchange rate is frozen on the document. Nothing is sent to the client. Integrations, MCP clients and the AI agent always create drafts; you send with [Send a document](/api-reference/documents/send-document), which also gives the invoice its number. Lines can be filled from [saved items](/invoicing/saved-items); each line keeps a copy of the values. See [Invoices](/invoicing/invoices) and [Line items](/invoicing/line-items). Permission: `documents.create`. Capability: `create_invoice`. MCP tool: `create_invoice`. CLI: `freeinvoiceautomation invoice create`. - `Idempotency-Key` is required for API-key and MCP calls, so a retry never creates two invoices. - Floating-point amounts are rejected; send minor units as strings. ## Headers - `Idempotency-Key` (string, required): Required for API-key and MCP calls on create calls. Any unique string up to 255 characters; a retry with the same key returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `type` (enum, required): Document type. Send `invoice` for this capability. One of: `invoice`, `estimate`, `receipt`. - `business_id` (string): Issuing business. May be left out when you can reach exactly one business. - `client_id` (string, required): Client of the same business. Its defaults (currency, language, terms) fill the document unless you send them. - `currency` (string): ISO 4217 code. Defaults to the client's default currency, then the business base currency. When it differs from the base currency, the exchange rate is frozen on the document. - `issue_date` (string): Issue date, `YYYY-MM-DD`. - `due_date` (string): Due date. Defaults from the payment terms setting of the client or business. - `lines` (object[], required): At least one line. Totals are computed by the server in integer minor units. - `item_id` (string): Fill the line from a saved item. Values you also send override the item's values. - `description` (string, required): Line text (taken from the item when `item_id` is given). - `quantity` (string, required): Decimal string greater than 0, e.g. `"2"` or `"1.5"`. - `unit` (string): Unit label, e.g. `hour`. - `unit_price_minor` (string, required): Price per unit in minor units, e.g. `"45000"` for 450.00 (taken from the item when `item_id` is given). - `discount_type` (enum): Line discount kind. One of: `percent`, `amount`. - `discount_value` (string): Percent, or minor units for `amount`. - `tax_rate_id` (string): Tax rate of the same business. Its name, percent and calculation are frozen on the line. - `terms` (string): Payment terms printed on the document. - `notes` (string): Notes printed on the document. - `accepted_methods` (string[]): Payment connections offered on the pay page. Defaults to every enabled payment method of the business that fits the document. - `pdf_template_id` (string): PDF template to use instead of the default. - `external_ref` (string): Your own reference, e.g. a store order id. A second create with the same reference and source is refused, so connectors never create duplicates. - `client_generated_id` (string): Id from the free generator, so a document synced on sign-up is created only once. ## Response - `id` (string): Unique id of the document (uuid v7). - `business_id` (string): The issuing business. - `client_id` (string): The client the document is for. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `status` (enum): Current status. Invoices: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`. Estimates: `draft`, `sent`, `changes_requested`, `accepted`, `expired`, `converted`, `void`. Credit notes: `draft`, `issued`, `applied`, `refunded`. Receipts: `issued`, `void`. See [Document statuses](/invoicing/statuses). One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`, `changes_requested`, `accepted`, `expired`, `converted`, `issued`, `applied`, `refunded`. - `number` (string | null): Human number such as `INV-2027-0017`. `null` until the document is first sent. - `issue_date` (string | null): Issue date, `YYYY-MM-DD`. - `due_date` (string | null): Due date for invoices. - `expires_at` (string | null): Expiry date for estimates. - `currency` (string): ISO 4217 code of every amount on the document. - `fx_rate` (string | null): Exchange rate to the business base currency, frozen on the document when its currency differs. - `fx_rate_source` (string | null): Where the frozen rate came from and its date. - `subtotal_minor` (string): Sum of line amounts before tax. - `discount_minor` (string): Total discount. - `tax_minor` (string): Total tax. - `total_minor` (string): Grand total. - `amount_paid_minor` (string): Payments received so far. - `amount_credited_minor` (string): Credit notes and client credit applied. - `balance_minor` (string): What is still owed: total minus paid minus credited. - `terms` (string | null): Payment terms printed on the document. - `notes` (string | null): Notes printed on the document. - `accepted_methods` (string[] | null): Payment connections offered on this document's pay page. - `pdf_template_id` (string | null): PDF template used to render the document. - `late_fees_enabled` (boolean): Late fees on or off for this invoice (January 2027). Off by default. - `reminder_sequence_id` (string | null): Reminder sequence override for this document (January 2027). - `promise_to_pay_date` (string | null): Date the client promised to pay; stops reminders (January 2027). - `recurring_schedule_id` (string | null): Recurring schedule that created the document (January 2027). - `current_version` (integer): `0` for a draft, `1` after the first send, then one more for each revision. - `first_sent_at` (string | null): When the document was first sent. - `last_sent_at` (string | null): When the document was last sent. - `accepted_at` (string | null): When the client accepted the estimate. - `paid_at` (string | null): When the invoice became fully paid. - `overdue_at` (string | null): When the invoice was marked overdue. - `voided_at` (string | null): When the document was voided. - `voided_by` (string | null): Who voided it. - `void_reason` (string | null): Reason given when voiding. - `source` (enum): Where the document was created. One of: `app`, `api`, `mcp`, `cli`, `agent`, `shopify`, `woocommerce`, `generator_sync`, `recurring`. - `external_ref` (string | null): Your own reference, e.g. a store order id. Unique per business and source, so a connector cannot create the same document twice. - `client_generated_id` (string | null): Id given by the free generator for documents synced on sign-up. - `lines` (object[]): The document lines, in order. - `id` (string): Unique id of the line. - `position` (integer): Order of the line on the document, starting at 1. - `kind` (enum): Where the line came from. `late_fee` from January 2027, `deposit` from late January 2027, `time` and `expense` from April 2027. One of: `item`, `late_fee`, `time`, `expense`, `deposit`. - `item_id` (string | null): Saved item the line was filled from. Reference only: the values are copied, so editing the item never changes this line. - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string with up to six decimals, e.g. `"1.5"`. - `unit` (string | null): Unit label such as `hour` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of the document currency. - `discount_type` (enum | null): Kind of line discount. One of: `percent`, `amount`. - `discount_value` (string | null): Discount as a percent (`"10"` = 10%) or, for `amount`, in minor units. - `tax_rate_id` (string | null): Tax rate picked for the line. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line, e.g. `"20.0000"`. - `tax_calculation` (enum | null): Frozen calculation of the tax. One of: `exclusive`, `inclusive`. - `subtotal_minor` (string): Line amount before tax, after discount. - `tax_minor` (string): Tax on the line. - `total_minor` (string): Line total including tax. - `credited_line_id` (string | null): On a credit note: the invoice line this line credits. - `links` (object[]): Linked documents: credit notes, source estimate, contract. - `type` (enum): How the documents are linked. One of: `credit_note`, `estimate`, `contract`, `revision`. - `from_document_id` (string): The linking document, e.g. the credit note or the invoice made from an estimate. - `to_document_id` (string): The linked document, e.g. the credited invoice or the source estimate. - `amount_minor` (string | null): For `credit_note` links: the amount credited. - `pay_url` (string | null): Pay page link, present when an active share link exists. - `created_at` (string): When the document was created (RFC 3339, UTC). - `updated_at` (string): When the document was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` - `idempotency_in_progress` - `business_out_of_scope` - `conflict` Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/create-invoice --- # Create an estimate POST https://api.freeinvoiceautomation.com/v1/documents Creates a draft estimate (quote) with the same lines and maths as an invoice. Send `type: "estimate"`. An estimate has its own type and its own number sequence (`EST-{YYYY}-{SEQ:4}` by default) and uses the same line maths as invoices. It is created as a `draft` with no number and takes its number when first sent. After sending, the client opens the estimate link to accept it or request changes. Turn an estimate into an invoice with [Convert an estimate](/api-reference/documents/convert-estimate). See [Estimates](/invoicing/estimates). Permission: `documents.create`. Capability: `create_estimate`. MCP tool: `create_estimate`. CLI: `freeinvoiceautomation estimate create`. - `Idempotency-Key` is required for API-key and MCP calls. ## Headers - `Idempotency-Key` (string, required): Required for API-key and MCP calls on create calls. Any unique string up to 255 characters; a retry with the same key returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `type` (enum, required): Document type. Send `estimate` for this capability. One of: `invoice`, `estimate`, `receipt`. - `business_id` (string): Issuing business. May be left out when you can reach exactly one business. - `client_id` (string, required): Client of the same business. Its defaults (currency, language, terms) fill the document unless you send them. - `currency` (string): ISO 4217 code. Defaults to the client's default currency, then the business base currency. When it differs from the base currency, the exchange rate is frozen on the document. - `issue_date` (string): Issue date, `YYYY-MM-DD`. - `expires_at` (string): Date the estimate expires. Defaults from the business's estimate expiry setting. - `lines` (object[], required): At least one line. Totals are computed by the server in integer minor units. - `item_id` (string): Fill the line from a saved item. Values you also send override the item's values. - `description` (string, required): Line text (taken from the item when `item_id` is given). - `quantity` (string, required): Decimal string greater than 0, e.g. `"2"` or `"1.5"`. - `unit` (string): Unit label, e.g. `hour`. - `unit_price_minor` (string, required): Price per unit in minor units, e.g. `"45000"` for 450.00 (taken from the item when `item_id` is given). - `discount_type` (enum): Line discount kind. One of: `percent`, `amount`. - `discount_value` (string): Percent, or minor units for `amount`. - `tax_rate_id` (string): Tax rate of the same business. Its name, percent and calculation are frozen on the line. - `terms` (string): Payment terms printed on the document. - `notes` (string): Notes printed on the document. - `accepted_methods` (string[]): Payment connections offered on the pay page. Defaults to every enabled payment method of the business that fits the document. - `pdf_template_id` (string): PDF template to use instead of the default. - `external_ref` (string): Your own reference, e.g. a store order id. A second create with the same reference and source is refused, so connectors never create duplicates. - `client_generated_id` (string): Id from the free generator, so a document synced on sign-up is created only once. ## Response - `id` (string): Unique id of the document (uuid v7). - `business_id` (string): The issuing business. - `client_id` (string): The client the document is for. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `status` (enum): Current status. Invoices: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`. Estimates: `draft`, `sent`, `changes_requested`, `accepted`, `expired`, `converted`, `void`. Credit notes: `draft`, `issued`, `applied`, `refunded`. Receipts: `issued`, `void`. See [Document statuses](/invoicing/statuses). One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`, `changes_requested`, `accepted`, `expired`, `converted`, `issued`, `applied`, `refunded`. - `number` (string | null): Human number such as `INV-2027-0017`. `null` until the document is first sent. - `issue_date` (string | null): Issue date, `YYYY-MM-DD`. - `due_date` (string | null): Due date for invoices. - `expires_at` (string | null): Expiry date for estimates. - `currency` (string): ISO 4217 code of every amount on the document. - `fx_rate` (string | null): Exchange rate to the business base currency, frozen on the document when its currency differs. - `fx_rate_source` (string | null): Where the frozen rate came from and its date. - `subtotal_minor` (string): Sum of line amounts before tax. - `discount_minor` (string): Total discount. - `tax_minor` (string): Total tax. - `total_minor` (string): Grand total. - `amount_paid_minor` (string): Payments received so far. - `amount_credited_minor` (string): Credit notes and client credit applied. - `balance_minor` (string): What is still owed: total minus paid minus credited. - `terms` (string | null): Payment terms printed on the document. - `notes` (string | null): Notes printed on the document. - `accepted_methods` (string[] | null): Payment connections offered on this document's pay page. - `pdf_template_id` (string | null): PDF template used to render the document. - `late_fees_enabled` (boolean): Late fees on or off for this invoice (January 2027). Off by default. - `reminder_sequence_id` (string | null): Reminder sequence override for this document (January 2027). - `promise_to_pay_date` (string | null): Date the client promised to pay; stops reminders (January 2027). - `recurring_schedule_id` (string | null): Recurring schedule that created the document (January 2027). - `current_version` (integer): `0` for a draft, `1` after the first send, then one more for each revision. - `first_sent_at` (string | null): When the document was first sent. - `last_sent_at` (string | null): When the document was last sent. - `accepted_at` (string | null): When the client accepted the estimate. - `paid_at` (string | null): When the invoice became fully paid. - `overdue_at` (string | null): When the invoice was marked overdue. - `voided_at` (string | null): When the document was voided. - `voided_by` (string | null): Who voided it. - `void_reason` (string | null): Reason given when voiding. - `source` (enum): Where the document was created. One of: `app`, `api`, `mcp`, `cli`, `agent`, `shopify`, `woocommerce`, `generator_sync`, `recurring`. - `external_ref` (string | null): Your own reference, e.g. a store order id. Unique per business and source, so a connector cannot create the same document twice. - `client_generated_id` (string | null): Id given by the free generator for documents synced on sign-up. - `lines` (object[]): The document lines, in order. - `id` (string): Unique id of the line. - `position` (integer): Order of the line on the document, starting at 1. - `kind` (enum): Where the line came from. `late_fee` from January 2027, `deposit` from late January 2027, `time` and `expense` from April 2027. One of: `item`, `late_fee`, `time`, `expense`, `deposit`. - `item_id` (string | null): Saved item the line was filled from. Reference only: the values are copied, so editing the item never changes this line. - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string with up to six decimals, e.g. `"1.5"`. - `unit` (string | null): Unit label such as `hour` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of the document currency. - `discount_type` (enum | null): Kind of line discount. One of: `percent`, `amount`. - `discount_value` (string | null): Discount as a percent (`"10"` = 10%) or, for `amount`, in minor units. - `tax_rate_id` (string | null): Tax rate picked for the line. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line, e.g. `"20.0000"`. - `tax_calculation` (enum | null): Frozen calculation of the tax. One of: `exclusive`, `inclusive`. - `subtotal_minor` (string): Line amount before tax, after discount. - `tax_minor` (string): Tax on the line. - `total_minor` (string): Line total including tax. - `credited_line_id` (string | null): On a credit note: the invoice line this line credits. - `links` (object[]): Linked documents: credit notes, source estimate, contract. - `type` (enum): How the documents are linked. One of: `credit_note`, `estimate`, `contract`, `revision`. - `from_document_id` (string): The linking document, e.g. the credit note or the invoice made from an estimate. - `to_document_id` (string): The linked document, e.g. the credited invoice or the source estimate. - `amount_minor` (string | null): For `credit_note` links: the amount credited. - `pay_url` (string | null): Pay page link, present when an active share link exists. - `created_at` (string): When the document was created (RFC 3339, UTC). - `updated_at` (string): When the document was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` - `idempotency_in_progress` - `business_out_of_scope` - `conflict` Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/create-estimate --- # Create a receipt POST https://api.freeinvoiceautomation.com/v1/documents Creates a receipt for a sale that was already paid, e.g. a store order paid at checkout. Send `type: "receipt"`. A receipt confirms a payment that already happened. It has no draft status: it is created as `issued` with the next number of the business's receipt sequence (`RCT-{YYYY}-{SEQ:4}` by default). A receipt can later be voided, never deleted. Store connectors create receipts or invoices for orders depending on the install settings, using `external_ref` with the order id so the same order never creates two documents. See [Receipts](/invoicing/receipts). Permission: `documents.create`. Capability: `create_receipt`. CLI: `freeinvoiceautomation receipt create`. - `Idempotency-Key` is required for API-key and MCP calls. ## Headers - `Idempotency-Key` (string, required): Required for API-key and MCP calls on create calls. Any unique string up to 255 characters; a retry with the same key returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `type` (enum, required): Document type. Send `receipt` for this capability. One of: `invoice`, `estimate`, `receipt`. - `business_id` (string): Issuing business. May be left out when you can reach exactly one business. - `client_id` (string, required): Client of the same business. Its defaults (currency, language, terms) fill the document unless you send them. - `currency` (string): ISO 4217 code. Defaults to the client's default currency, then the business base currency. When it differs from the base currency, the exchange rate is frozen on the document. - `issue_date` (string): Issue date, `YYYY-MM-DD`. - `lines` (object[], required): At least one line. Totals are computed by the server in integer minor units. - `item_id` (string): Fill the line from a saved item. Values you also send override the item's values. - `description` (string, required): Line text (taken from the item when `item_id` is given). - `quantity` (string, required): Decimal string greater than 0, e.g. `"2"` or `"1.5"`. - `unit` (string): Unit label, e.g. `hour`. - `unit_price_minor` (string, required): Price per unit in minor units, e.g. `"45000"` for 450.00 (taken from the item when `item_id` is given). - `discount_type` (enum): Line discount kind. One of: `percent`, `amount`. - `discount_value` (string): Percent, or minor units for `amount`. - `tax_rate_id` (string): Tax rate of the same business. Its name, percent and calculation are frozen on the line. - `terms` (string): Payment terms printed on the document. - `notes` (string): Notes printed on the document. - `pdf_template_id` (string): PDF template to use instead of the default. - `external_ref` (string): Your own reference, e.g. a store order id. A second create with the same reference and source is refused, so connectors never create duplicates. - `client_generated_id` (string): Id from the free generator, so a document synced on sign-up is created only once. ## Response - `id` (string): Unique id of the document (uuid v7). - `business_id` (string): The issuing business. - `client_id` (string): The client the document is for. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `status` (enum): Current status. Invoices: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`. Estimates: `draft`, `sent`, `changes_requested`, `accepted`, `expired`, `converted`, `void`. Credit notes: `draft`, `issued`, `applied`, `refunded`. Receipts: `issued`, `void`. See [Document statuses](/invoicing/statuses). One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`, `changes_requested`, `accepted`, `expired`, `converted`, `issued`, `applied`, `refunded`. - `number` (string | null): Human number such as `INV-2027-0017`. `null` until the document is first sent. - `issue_date` (string | null): Issue date, `YYYY-MM-DD`. - `due_date` (string | null): Due date for invoices. - `expires_at` (string | null): Expiry date for estimates. - `currency` (string): ISO 4217 code of every amount on the document. - `fx_rate` (string | null): Exchange rate to the business base currency, frozen on the document when its currency differs. - `fx_rate_source` (string | null): Where the frozen rate came from and its date. - `subtotal_minor` (string): Sum of line amounts before tax. - `discount_minor` (string): Total discount. - `tax_minor` (string): Total tax. - `total_minor` (string): Grand total. - `amount_paid_minor` (string): Payments received so far. - `amount_credited_minor` (string): Credit notes and client credit applied. - `balance_minor` (string): What is still owed: total minus paid minus credited. - `terms` (string | null): Payment terms printed on the document. - `notes` (string | null): Notes printed on the document. - `accepted_methods` (string[] | null): Payment connections offered on this document's pay page. - `pdf_template_id` (string | null): PDF template used to render the document. - `late_fees_enabled` (boolean): Late fees on or off for this invoice (January 2027). Off by default. - `reminder_sequence_id` (string | null): Reminder sequence override for this document (January 2027). - `promise_to_pay_date` (string | null): Date the client promised to pay; stops reminders (January 2027). - `recurring_schedule_id` (string | null): Recurring schedule that created the document (January 2027). - `current_version` (integer): `0` for a draft, `1` after the first send, then one more for each revision. - `first_sent_at` (string | null): When the document was first sent. - `last_sent_at` (string | null): When the document was last sent. - `accepted_at` (string | null): When the client accepted the estimate. - `paid_at` (string | null): When the invoice became fully paid. - `overdue_at` (string | null): When the invoice was marked overdue. - `voided_at` (string | null): When the document was voided. - `voided_by` (string | null): Who voided it. - `void_reason` (string | null): Reason given when voiding. - `source` (enum): Where the document was created. One of: `app`, `api`, `mcp`, `cli`, `agent`, `shopify`, `woocommerce`, `generator_sync`, `recurring`. - `external_ref` (string | null): Your own reference, e.g. a store order id. Unique per business and source, so a connector cannot create the same document twice. - `client_generated_id` (string | null): Id given by the free generator for documents synced on sign-up. - `lines` (object[]): The document lines, in order. - `id` (string): Unique id of the line. - `position` (integer): Order of the line on the document, starting at 1. - `kind` (enum): Where the line came from. `late_fee` from January 2027, `deposit` from late January 2027, `time` and `expense` from April 2027. One of: `item`, `late_fee`, `time`, `expense`, `deposit`. - `item_id` (string | null): Saved item the line was filled from. Reference only: the values are copied, so editing the item never changes this line. - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string with up to six decimals, e.g. `"1.5"`. - `unit` (string | null): Unit label such as `hour` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of the document currency. - `discount_type` (enum | null): Kind of line discount. One of: `percent`, `amount`. - `discount_value` (string | null): Discount as a percent (`"10"` = 10%) or, for `amount`, in minor units. - `tax_rate_id` (string | null): Tax rate picked for the line. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line, e.g. `"20.0000"`. - `tax_calculation` (enum | null): Frozen calculation of the tax. One of: `exclusive`, `inclusive`. - `subtotal_minor` (string): Line amount before tax, after discount. - `tax_minor` (string): Tax on the line. - `total_minor` (string): Line total including tax. - `credited_line_id` (string | null): On a credit note: the invoice line this line credits. - `links` (object[]): Linked documents: credit notes, source estimate, contract. - `type` (enum): How the documents are linked. One of: `credit_note`, `estimate`, `contract`, `revision`. - `from_document_id` (string): The linking document, e.g. the credit note or the invoice made from an estimate. - `to_document_id` (string): The linked document, e.g. the credited invoice or the source estimate. - `amount_minor` (string | null): For `credit_note` links: the amount credited. - `pay_url` (string | null): Pay page link, present when an active share link exists. - `created_at` (string): When the document was created (RFC 3339, UTC). - `updated_at` (string): When the document was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` - `idempotency_in_progress` - `business_out_of_scope` - `conflict` Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/create-receipt --- # Update an invoice PATCH https://api.freeinvoiceautomation.com/v1/documents/{id} Changes a draft freely; changing a sent document creates a new revision. Updates an invoice or estimate through `PATCH /v1/documents/{id}`. A partial update (JSON merge): send only the fields to change. `lines`, when sent, replaces the whole list of lines. Totals are recomputed. While the document is a draft, it simply changes. Once it has been sent, it changes only by **revision**: the update creates a new version with its own snapshot, `current_version` goes up by one, and the earlier version stays readable in [List document versions](/api-reference/documents/list-document-versions). The number never changes. Payment status and timestamps are not part of a revision. Voided documents cannot be changed (`document_voided`). See [Revisions](/invoicing/revisions). Permission: `documents.edit`. Capability: `update_invoice`. MCP tool: `update_invoice`. CLI: `freeinvoiceautomation invoice update`. ## Path parameters - `id` (string, required): Id of the document. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `client_id` (string): Another client of the same business. - `currency` (string): ISO 4217 code. - `issue_date` (string): Issue date. - `due_date` (string): Due date (invoices). - `expires_at` (string): Expiry date (estimates). - `lines` (object[]): The full new list of lines; replaces the current lines. - `item_id` (string): Fill the line from a saved item. Values you also send override the item's values. - `description` (string, required): Line text (taken from the item when `item_id` is given). - `quantity` (string, required): Decimal string greater than 0, e.g. `"2"` or `"1.5"`. - `unit` (string): Unit label, e.g. `hour`. - `unit_price_minor` (string, required): Price per unit in minor units, e.g. `"45000"` for 450.00 (taken from the item when `item_id` is given). - `discount_type` (enum): Line discount kind. One of: `percent`, `amount`. - `discount_value` (string): Percent, or minor units for `amount`. - `tax_rate_id` (string): Tax rate of the same business. Its name, percent and calculation are frozen on the line. - `terms` (string): Payment terms. - `notes` (string): Notes. - `accepted_methods` (string[]): Payment connections offered on the pay page. - `pdf_template_id` (string): PDF template. ## Response - `id` (string): Unique id of the document (uuid v7). - `business_id` (string): The issuing business. - `client_id` (string): The client the document is for. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `status` (enum): Current status. Invoices: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`. Estimates: `draft`, `sent`, `changes_requested`, `accepted`, `expired`, `converted`, `void`. Credit notes: `draft`, `issued`, `applied`, `refunded`. Receipts: `issued`, `void`. See [Document statuses](/invoicing/statuses). One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`, `changes_requested`, `accepted`, `expired`, `converted`, `issued`, `applied`, `refunded`. - `number` (string | null): Human number such as `INV-2027-0017`. `null` until the document is first sent. - `issue_date` (string | null): Issue date, `YYYY-MM-DD`. - `due_date` (string | null): Due date for invoices. - `expires_at` (string | null): Expiry date for estimates. - `currency` (string): ISO 4217 code of every amount on the document. - `fx_rate` (string | null): Exchange rate to the business base currency, frozen on the document when its currency differs. - `fx_rate_source` (string | null): Where the frozen rate came from and its date. - `subtotal_minor` (string): Sum of line amounts before tax. - `discount_minor` (string): Total discount. - `tax_minor` (string): Total tax. - `total_minor` (string): Grand total. - `amount_paid_minor` (string): Payments received so far. - `amount_credited_minor` (string): Credit notes and client credit applied. - `balance_minor` (string): What is still owed: total minus paid minus credited. - `terms` (string | null): Payment terms printed on the document. - `notes` (string | null): Notes printed on the document. - `accepted_methods` (string[] | null): Payment connections offered on this document's pay page. - `pdf_template_id` (string | null): PDF template used to render the document. - `late_fees_enabled` (boolean): Late fees on or off for this invoice (January 2027). Off by default. - `reminder_sequence_id` (string | null): Reminder sequence override for this document (January 2027). - `promise_to_pay_date` (string | null): Date the client promised to pay; stops reminders (January 2027). - `recurring_schedule_id` (string | null): Recurring schedule that created the document (January 2027). - `current_version` (integer): `0` for a draft, `1` after the first send, then one more for each revision. - `first_sent_at` (string | null): When the document was first sent. - `last_sent_at` (string | null): When the document was last sent. - `accepted_at` (string | null): When the client accepted the estimate. - `paid_at` (string | null): When the invoice became fully paid. - `overdue_at` (string | null): When the invoice was marked overdue. - `voided_at` (string | null): When the document was voided. - `voided_by` (string | null): Who voided it. - `void_reason` (string | null): Reason given when voiding. - `source` (enum): Where the document was created. One of: `app`, `api`, `mcp`, `cli`, `agent`, `shopify`, `woocommerce`, `generator_sync`, `recurring`. - `external_ref` (string | null): Your own reference, e.g. a store order id. Unique per business and source, so a connector cannot create the same document twice. - `client_generated_id` (string | null): Id given by the free generator for documents synced on sign-up. - `lines` (object[]): The document lines, in order. - `id` (string): Unique id of the line. - `position` (integer): Order of the line on the document, starting at 1. - `kind` (enum): Where the line came from. `late_fee` from January 2027, `deposit` from late January 2027, `time` and `expense` from April 2027. One of: `item`, `late_fee`, `time`, `expense`, `deposit`. - `item_id` (string | null): Saved item the line was filled from. Reference only: the values are copied, so editing the item never changes this line. - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string with up to six decimals, e.g. `"1.5"`. - `unit` (string | null): Unit label such as `hour` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of the document currency. - `discount_type` (enum | null): Kind of line discount. One of: `percent`, `amount`. - `discount_value` (string | null): Discount as a percent (`"10"` = 10%) or, for `amount`, in minor units. - `tax_rate_id` (string | null): Tax rate picked for the line. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line, e.g. `"20.0000"`. - `tax_calculation` (enum | null): Frozen calculation of the tax. One of: `exclusive`, `inclusive`. - `subtotal_minor` (string): Line amount before tax, after discount. - `tax_minor` (string): Tax on the line. - `total_minor` (string): Line total including tax. - `credited_line_id` (string | null): On a credit note: the invoice line this line credits. - `links` (object[]): Linked documents: credit notes, source estimate, contract. - `type` (enum): How the documents are linked. One of: `credit_note`, `estimate`, `contract`, `revision`. - `from_document_id` (string): The linking document, e.g. the credit note or the invoice made from an estimate. - `to_document_id` (string): The linked document, e.g. the credited invoice or the source estimate. - `amount_minor` (string | null): For `credit_note` links: the amount credited. - `pay_url` (string | null): Pay page link, present when an active share link exists. - `created_at` (string): When the document was created (RFC 3339, UTC). - `updated_at` (string): When the document was last changed. ## Errors - `validation_failed` - `document_voided` - `document_not_editable` - `idempotency_key_reused` Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/update-invoice --- # Delete a draft DELETE https://api.freeinvoiceautomation.com/v1/documents/{id} Soft-deletes a draft. Documents that were sent cannot be deleted. Only a draft can be deleted, because it has no number yet, so no number is lost. The draft is soft-deleted and an audit entry is written. The request must include `confirm: true`. A document that has been sent keeps its number for ever and is never deleted: void it with [Void a document](/api-reference/documents/void-document), or correct it with [Create a credit note](/api-reference/documents/create-credit-note). See [Void and delete](/invoicing/void). Permission: `documents.edit`. Capability: `delete_draft`. CLI: `freeinvoiceautomation doc delete`. ## Path parameters - `id` (string, required): Id of the document. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true` to delete the draft. Without it nothing changes: you get the preview and `confirmation_required`. See [Drafts and confirmations](/api-reference/confirmations). ## Errors - `confirmation_required` - `document_not_editable` Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/delete-draft --- # Convert an estimate POST https://api.freeinvoiceautomation.com/v1/documents/{id}/convert Creates a draft invoice from an estimate, with the same client, currency and lines. The new invoice is a `draft` with no number, ready to review and send. It is linked to the estimate (a link of type `estimate`), and the estimate's status becomes `converted`. The estimate itself does not change otherwise. A voided estimate cannot be converted. See [Estimates](/invoicing/estimates). Permission: `documents.create`. Capability: `convert_estimate`. CLI: `freeinvoiceautomation estimate convert`. ## Path parameters - `id` (string, required): Id of the estimate. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Unique id of the document (uuid v7). - `business_id` (string): The issuing business. - `client_id` (string): The client the document is for. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `status` (enum): Current status. Invoices: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`. Estimates: `draft`, `sent`, `changes_requested`, `accepted`, `expired`, `converted`, `void`. Credit notes: `draft`, `issued`, `applied`, `refunded`. Receipts: `issued`, `void`. See [Document statuses](/invoicing/statuses). One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`, `changes_requested`, `accepted`, `expired`, `converted`, `issued`, `applied`, `refunded`. - `number` (string | null): Human number such as `INV-2027-0017`. `null` until the document is first sent. - `issue_date` (string | null): Issue date, `YYYY-MM-DD`. - `due_date` (string | null): Due date for invoices. - `expires_at` (string | null): Expiry date for estimates. - `currency` (string): ISO 4217 code of every amount on the document. - `fx_rate` (string | null): Exchange rate to the business base currency, frozen on the document when its currency differs. - `fx_rate_source` (string | null): Where the frozen rate came from and its date. - `subtotal_minor` (string): Sum of line amounts before tax. - `discount_minor` (string): Total discount. - `tax_minor` (string): Total tax. - `total_minor` (string): Grand total. - `amount_paid_minor` (string): Payments received so far. - `amount_credited_minor` (string): Credit notes and client credit applied. - `balance_minor` (string): What is still owed: total minus paid minus credited. - `terms` (string | null): Payment terms printed on the document. - `notes` (string | null): Notes printed on the document. - `accepted_methods` (string[] | null): Payment connections offered on this document's pay page. - `pdf_template_id` (string | null): PDF template used to render the document. - `late_fees_enabled` (boolean): Late fees on or off for this invoice (January 2027). Off by default. - `reminder_sequence_id` (string | null): Reminder sequence override for this document (January 2027). - `promise_to_pay_date` (string | null): Date the client promised to pay; stops reminders (January 2027). - `recurring_schedule_id` (string | null): Recurring schedule that created the document (January 2027). - `current_version` (integer): `0` for a draft, `1` after the first send, then one more for each revision. - `first_sent_at` (string | null): When the document was first sent. - `last_sent_at` (string | null): When the document was last sent. - `accepted_at` (string | null): When the client accepted the estimate. - `paid_at` (string | null): When the invoice became fully paid. - `overdue_at` (string | null): When the invoice was marked overdue. - `voided_at` (string | null): When the document was voided. - `voided_by` (string | null): Who voided it. - `void_reason` (string | null): Reason given when voiding. - `source` (enum): Where the document was created. One of: `app`, `api`, `mcp`, `cli`, `agent`, `shopify`, `woocommerce`, `generator_sync`, `recurring`. - `external_ref` (string | null): Your own reference, e.g. a store order id. Unique per business and source, so a connector cannot create the same document twice. - `client_generated_id` (string | null): Id given by the free generator for documents synced on sign-up. - `lines` (object[]): The document lines, in order. - `id` (string): Unique id of the line. - `position` (integer): Order of the line on the document, starting at 1. - `kind` (enum): Where the line came from. `late_fee` from January 2027, `deposit` from late January 2027, `time` and `expense` from April 2027. One of: `item`, `late_fee`, `time`, `expense`, `deposit`. - `item_id` (string | null): Saved item the line was filled from. Reference only: the values are copied, so editing the item never changes this line. - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string with up to six decimals, e.g. `"1.5"`. - `unit` (string | null): Unit label such as `hour` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of the document currency. - `discount_type` (enum | null): Kind of line discount. One of: `percent`, `amount`. - `discount_value` (string | null): Discount as a percent (`"10"` = 10%) or, for `amount`, in minor units. - `tax_rate_id` (string | null): Tax rate picked for the line. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line, e.g. `"20.0000"`. - `tax_calculation` (enum | null): Frozen calculation of the tax. One of: `exclusive`, `inclusive`. - `subtotal_minor` (string): Line amount before tax, after discount. - `tax_minor` (string): Tax on the line. - `total_minor` (string): Line total including tax. - `credited_line_id` (string | null): On a credit note: the invoice line this line credits. - `links` (object[]): Linked documents: credit notes, source estimate, contract. - `type` (enum): How the documents are linked. One of: `credit_note`, `estimate`, `contract`, `revision`. - `from_document_id` (string): The linking document, e.g. the credit note or the invoice made from an estimate. - `to_document_id` (string): The linked document, e.g. the credited invoice or the source estimate. - `amount_minor` (string | null): For `credit_note` links: the amount credited. - `pay_url` (string | null): Pay page link, present when an active share link exists. - `created_at` (string): When the document was created (RFC 3339, UTC). - `updated_at` (string): When the document was last changed. ## Errors - `document_voided` - `conflict` - `idempotency_key_reused` Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/convert-estimate --- # List document versions GET https://api.freeinvoiceautomation.com/v1/documents/{id}/versions Returns the versions of a sent document: the first send and every revision, with the reason for each change. A snapshot of the business, client, lines and totals is frozen on first send and on every revision. Versions are append-only and never change, so you can always show what the client received at any point. Download the PDF of a version with [Render a PDF](/api-reference/documents/render-pdf). See [Revisions](/invoicing/revisions). Permission: `documents.view`. Capability: `list_document_versions`. CLI: `freeinvoiceautomation doc versions`. ## Path parameters - `id` (string, required): Id of the document. ## Response - `data` (object[]): The versions on this page. - `id` (string): Id of the version record. - `version` (integer): Version number; `1` is the first send. - `snapshot_id` (string): The frozen snapshot of this version. - `reason` (enum): Why the document changed. Late fees from January 2027, payment schedules from late January 2027. One of: `edit`, `late_fee`, `late_fee_waived`, `payment_schedule`. - `change_summary` (object | null): What changed compared with the previous version. - `created_by` (string | null): Who made the change. - `created_at` (string): When the version was created. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/list-document-versions --- # List document activity GET https://api.freeinvoiceautomation.com/v1/documents/{id}/activity Returns the timeline of a document: created, sent, viewed, payments, reminders, void and credit notes. Each entry records what happened and who did it: a member, an API key, an MCP token, the AI agent, a store connector, the client or the system. Use it to answer "did the client open the invoice?" or "who voided this?". Permission: `documents.view`. Capability: `list_document_activity`. CLI: `freeinvoiceautomation doc activity`. ## Path parameters - `id` (string, required): Id of the document. ## Query parameters - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The activity entries on this page. - `id` (string): Id of the entry. - `event` (string): What happened, e.g. the document was sent, viewed or paid. - `actor_type` (enum): Kind of actor. One of: `user`, `api_key`, `mcp_token`, `agent`, `connector`, `client`, `system`, `platform_support`. - `actor_id` (string | null): Id of the actor, when there is one. - `data` (object | null): Details of the event, e.g. the amount of a payment. - `created_at` (string): When it happened. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/list-document-activity --- # List overdue invoices GET https://api.freeinvoiceautomation.com/v1/documents/overdue Returns invoices past their due date that are not fully paid, oldest due date first. An invoice is marked `overdue` automatically by a scheduled job once its due date has passed and a balance is still open. Partly paid invoices stay in the list until the balance is zero. Use this list to chase payments or to drive your own reminders. See [Due dates and payment terms](/invoicing/due-dates). Permission: `documents.view`. Capability: `list_overdue`. MCP tool: `list_overdue`. CLI: `freeinvoiceautomation doc overdue`. ## Query parameters - `business_id` (string): Only invoices of this business. - `client_id` (string): Only invoices for this client. - `currency` (string): Only invoices in this currency. - `sort` (string, default: due_date): Sort field; a leading `-` sorts descending. One of: `due_date`, `-due_date`, `balance_minor`, `-balance_minor`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The overdue invoices on this page. - `id` (string): Unique id of the document (uuid v7). - `business_id` (string): The issuing business. - `client_id` (string): The client the document is for. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `status` (enum): Current status. Invoices: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`. Estimates: `draft`, `sent`, `changes_requested`, `accepted`, `expired`, `converted`, `void`. Credit notes: `draft`, `issued`, `applied`, `refunded`. Receipts: `issued`, `void`. See [Document statuses](/invoicing/statuses). One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`, `changes_requested`, `accepted`, `expired`, `converted`, `issued`, `applied`, `refunded`. - `number` (string | null): Human number such as `INV-2027-0017`. `null` until the document is first sent. - `issue_date` (string | null): Issue date, `YYYY-MM-DD`. - `due_date` (string | null): Due date for invoices. - `expires_at` (string | null): Expiry date for estimates. - `currency` (string): ISO 4217 code of every amount on the document. - `fx_rate` (string | null): Exchange rate to the business base currency, frozen on the document when its currency differs. - `fx_rate_source` (string | null): Where the frozen rate came from and its date. - `subtotal_minor` (string): Sum of line amounts before tax. - `discount_minor` (string): Total discount. - `tax_minor` (string): Total tax. - `total_minor` (string): Grand total. - `amount_paid_minor` (string): Payments received so far. - `amount_credited_minor` (string): Credit notes and client credit applied. - `balance_minor` (string): What is still owed: total minus paid minus credited. - `terms` (string | null): Payment terms printed on the document. - `notes` (string | null): Notes printed on the document. - `accepted_methods` (string[] | null): Payment connections offered on this document's pay page. - `pdf_template_id` (string | null): PDF template used to render the document. - `late_fees_enabled` (boolean): Late fees on or off for this invoice (January 2027). Off by default. - `reminder_sequence_id` (string | null): Reminder sequence override for this document (January 2027). - `promise_to_pay_date` (string | null): Date the client promised to pay; stops reminders (January 2027). - `recurring_schedule_id` (string | null): Recurring schedule that created the document (January 2027). - `current_version` (integer): `0` for a draft, `1` after the first send, then one more for each revision. - `first_sent_at` (string | null): When the document was first sent. - `last_sent_at` (string | null): When the document was last sent. - `accepted_at` (string | null): When the client accepted the estimate. - `paid_at` (string | null): When the invoice became fully paid. - `overdue_at` (string | null): When the invoice was marked overdue. - `voided_at` (string | null): When the document was voided. - `voided_by` (string | null): Who voided it. - `void_reason` (string | null): Reason given when voiding. - `source` (enum): Where the document was created. One of: `app`, `api`, `mcp`, `cli`, `agent`, `shopify`, `woocommerce`, `generator_sync`, `recurring`. - `external_ref` (string | null): Your own reference, e.g. a store order id. Unique per business and source, so a connector cannot create the same document twice. - `client_generated_id` (string | null): Id given by the free generator for documents synced on sign-up. - `lines` (object[]): The document lines, in order. - `id` (string): Unique id of the line. - `position` (integer): Order of the line on the document, starting at 1. - `kind` (enum): Where the line came from. `late_fee` from January 2027, `deposit` from late January 2027, `time` and `expense` from April 2027. One of: `item`, `late_fee`, `time`, `expense`, `deposit`. - `item_id` (string | null): Saved item the line was filled from. Reference only: the values are copied, so editing the item never changes this line. - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string with up to six decimals, e.g. `"1.5"`. - `unit` (string | null): Unit label such as `hour` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of the document currency. - `discount_type` (enum | null): Kind of line discount. One of: `percent`, `amount`. - `discount_value` (string | null): Discount as a percent (`"10"` = 10%) or, for `amount`, in minor units. - `tax_rate_id` (string | null): Tax rate picked for the line. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line, e.g. `"20.0000"`. - `tax_calculation` (enum | null): Frozen calculation of the tax. One of: `exclusive`, `inclusive`. - `subtotal_minor` (string): Line amount before tax, after discount. - `tax_minor` (string): Tax on the line. - `total_minor` (string): Line total including tax. - `credited_line_id` (string | null): On a credit note: the invoice line this line credits. - `links` (object[]): Linked documents: credit notes, source estimate, contract. - `type` (enum): How the documents are linked. One of: `credit_note`, `estimate`, `contract`, `revision`. - `from_document_id` (string): The linking document, e.g. the credit note or the invoice made from an estimate. - `to_document_id` (string): The linked document, e.g. the credited invoice or the source estimate. - `amount_minor` (string | null): For `credit_note` links: the amount credited. - `pay_url` (string | null): Pay page link, present when an active share link exists. - `created_at` (string): When the document was created (RFC 3339, UTC). - `updated_at` (string): When the document was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/list-overdue --- # Preview a send POST https://api.freeinvoiceautomation.com/v1/documents/{id}/send-preview Shows exactly what sending would do: sender, recipients, subject, message and quota use. Nothing is sent. Call this before [Send a document](/api-reference/documents/send-document), and show the result to a person when an AI or integration prepared the send. The sender is chosen in this order: document override, client preference, business default, then the platform mailer, which is used only when the account still has platform emails left this month. The preview reports problems the send would hit, such as an unverified email address or a used-up allowance, so you can fix them first. It changes nothing and uses no quota. See [Send a document](/invoicing/sending). Permission: `documents.view`. Capability: `preview_send`. MCP tool: `preview_send`. CLI: `freeinvoiceautomation doc send --preview`. ## Path parameters - `id` (string, required): Id of the document. ## Body - `channel` (enum, default: email): Channel to preview. `email` at launch. One of: `email`. ## Response - `document_id` (string): The document that would be sent. - `number` (string | null): The document number, or `null` for a draft: a draft takes the next number of its sequence when it is actually sent. - `channel` (enum): Channel that would be used. `email` at launch. One of: `email`. - `sender` (enum): Sender that would be used: your own SMTP, or the platform mailer when there is no SMTP and quota is left. One of: `smtp`, `platform_mail`. - `connection_id` (string | null): The SMTP connection, or `null` for the platform mailer. - `to` (string[]): Recipients: the client's email and every contact that receives documents. - `reply_to` (string | null): Reply-To address (the business email on platform sends). - `subject` (string): Email subject from the message template. - `body` (string): Email text from the message template, with variables filled in. - `attach_pdf` (boolean): Whether the PDF is attached. - `counts_against_quota` (boolean): `true` when the platform mailer would be used, so the send uses one platform email of your monthly allowance. - `warnings` (string[]): Plain-English warnings, e.g. that 80% of the platform email allowance is used. ## Errors - `document_voided` - `email_not_verified` - `quota_exceeded` - `platform_mail_paused` Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/preview-send --- # Send a document POST https://api.freeinvoiceautomation.com/v1/documents/{id}/send Emails the document to the client with its pay link. A draft gets its number and first snapshot in the same step. On first send, the draft takes the next gap-free number of its business and type, a snapshot of the business, client, lines and totals is frozen as version 1, and the status becomes `sent`. If anything fails, nothing is kept and no number is used. Sending again later re-sends the current version. Sends are drafts-first: the request must include `confirm: true` unless the API key or MCP token was created with `mode: auto_send`. Without it, nothing is sent and you get the preview with `confirmation_required`. See [Drafts and confirmations](/api-reference/confirmations). The sender is resolved as in [Preview a send](/api-reference/documents/preview-send). Platform emails count against the monthly allowance (Free 20, Team 100); with your own SMTP there is no limit. A send is idempotent per document, event and channel, so a retried request never emails twice. See [Send a document](/invoicing/sending), [Platform email](/messaging/platform-email) and [Your own SMTP](/messaging/smtp). Permission: `documents.send`. Capability: `send_document`. MCP tool: `send_document`. CLI: `freeinvoiceautomation doc send`. - Needs `confirm: true` unless the key or token has `mode: auto_send`. - `Idempotency-Key` is required for API-key and MCP calls. - The platform mailer only works after your email address is verified. ## Path parameters - `id` (string, required): Id of the document. ## Headers - `Idempotency-Key` (string, required): Required for API-key and MCP calls on send calls. Any unique string up to 255 characters; a retry with the same key returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean): Set to `true` to send. Required unless the API key or MCP token has `mode: auto_send`; without it you get the preview and `confirmation_required`. - `channel` (enum, default: email): Delivery channel. `email` at launch; WhatsApp, Telegram and SMS come later. One of: `email`. ## Response - `document` (object): The document after sending, with its number and pay link. - `id` (string): Unique id of the document (uuid v7). - `business_id` (string): The issuing business. - `client_id` (string): The client the document is for. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `status` (enum): Current status. Invoices: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`. Estimates: `draft`, `sent`, `changes_requested`, `accepted`, `expired`, `converted`, `void`. Credit notes: `draft`, `issued`, `applied`, `refunded`. Receipts: `issued`, `void`. See [Document statuses](/invoicing/statuses). One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`, `changes_requested`, `accepted`, `expired`, `converted`, `issued`, `applied`, `refunded`. - `number` (string | null): Human number such as `INV-2027-0017`. `null` until the document is first sent. - `issue_date` (string | null): Issue date, `YYYY-MM-DD`. - `due_date` (string | null): Due date for invoices. - `expires_at` (string | null): Expiry date for estimates. - `currency` (string): ISO 4217 code of every amount on the document. - `fx_rate` (string | null): Exchange rate to the business base currency, frozen on the document when its currency differs. - `fx_rate_source` (string | null): Where the frozen rate came from and its date. - `subtotal_minor` (string): Sum of line amounts before tax. - `discount_minor` (string): Total discount. - `tax_minor` (string): Total tax. - `total_minor` (string): Grand total. - `amount_paid_minor` (string): Payments received so far. - `amount_credited_minor` (string): Credit notes and client credit applied. - `balance_minor` (string): What is still owed: total minus paid minus credited. - `terms` (string | null): Payment terms printed on the document. - `notes` (string | null): Notes printed on the document. - `accepted_methods` (string[] | null): Payment connections offered on this document's pay page. - `pdf_template_id` (string | null): PDF template used to render the document. - `late_fees_enabled` (boolean): Late fees on or off for this invoice (January 2027). Off by default. - `reminder_sequence_id` (string | null): Reminder sequence override for this document (January 2027). - `promise_to_pay_date` (string | null): Date the client promised to pay; stops reminders (January 2027). - `recurring_schedule_id` (string | null): Recurring schedule that created the document (January 2027). - `current_version` (integer): `0` for a draft, `1` after the first send, then one more for each revision. - `first_sent_at` (string | null): When the document was first sent. - `last_sent_at` (string | null): When the document was last sent. - `accepted_at` (string | null): When the client accepted the estimate. - `paid_at` (string | null): When the invoice became fully paid. - `overdue_at` (string | null): When the invoice was marked overdue. - `voided_at` (string | null): When the document was voided. - `voided_by` (string | null): Who voided it. - `void_reason` (string | null): Reason given when voiding. - `source` (enum): Where the document was created. One of: `app`, `api`, `mcp`, `cli`, `agent`, `shopify`, `woocommerce`, `generator_sync`, `recurring`. - `external_ref` (string | null): Your own reference, e.g. a store order id. Unique per business and source, so a connector cannot create the same document twice. - `client_generated_id` (string | null): Id given by the free generator for documents synced on sign-up. - `lines` (object[]): The document lines, in order. - `id` (string): Unique id of the line. - `position` (integer): Order of the line on the document, starting at 1. - `kind` (enum): Where the line came from. `late_fee` from January 2027, `deposit` from late January 2027, `time` and `expense` from April 2027. One of: `item`, `late_fee`, `time`, `expense`, `deposit`. - `item_id` (string | null): Saved item the line was filled from. Reference only: the values are copied, so editing the item never changes this line. - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string with up to six decimals, e.g. `"1.5"`. - `unit` (string | null): Unit label such as `hour` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of the document currency. - `discount_type` (enum | null): Kind of line discount. One of: `percent`, `amount`. - `discount_value` (string | null): Discount as a percent (`"10"` = 10%) or, for `amount`, in minor units. - `tax_rate_id` (string | null): Tax rate picked for the line. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line, e.g. `"20.0000"`. - `tax_calculation` (enum | null): Frozen calculation of the tax. One of: `exclusive`, `inclusive`. - `subtotal_minor` (string): Line amount before tax, after discount. - `tax_minor` (string): Tax on the line. - `total_minor` (string): Line total including tax. - `credited_line_id` (string | null): On a credit note: the invoice line this line credits. - `links` (object[]): Linked documents: credit notes, source estimate, contract. - `type` (enum): How the documents are linked. One of: `credit_note`, `estimate`, `contract`, `revision`. - `from_document_id` (string): The linking document, e.g. the credit note or the invoice made from an estimate. - `to_document_id` (string): The linked document, e.g. the credited invoice or the source estimate. - `amount_minor` (string | null): For `credit_note` links: the amount credited. - `pay_url` (string | null): Pay page link, present when an active share link exists. - `created_at` (string): When the document was created (RFC 3339, UTC). - `updated_at` (string): When the document was last changed. - `message` (object): The send record. - `id` (string): Id of the send record. - `channel` (string): Channel used. - `sender` (string): `smtp` or `platform_mail`. - `to_address` (string): Recipient addresses. - `status` (enum): Delivery status. Sending happens in the background, so it starts as `queued`. One of: `queued`, `sent`, `failed`, `bounced`. - `counted_against_quota` (boolean): Whether the send used a platform email. - `queued_at` (string): When the send was queued. ## Errors - `confirmation_required` - `document_voided` - `email_not_verified` - `quota_exceeded` - `platform_mail_paused` - `idempotency_key_reused` - `idempotency_in_progress` - `upstream_error` Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/send-document --- # Render a PDF GET https://api.freeinvoiceautomation.com/v1/documents/{id}/pdf Returns the document as a PDF file. The response is `application/pdf` (over MCP the PDF is returned base64-encoded). The PDF uses the document's template and carries a verification QR that opens `/v/[code]`, where anyone can check the issuer, amount and status. A voided invoice shows a VOID stamp; a credit note uses the CREDIT NOTE layout and names the original invoice. On Free, the PDF shows a small "Powered by Free Invoice Automation" line; on Team it does not. For a sent document you can pick an earlier version; the default is the current one. See [PDFs and templates](/invoicing/pdf) and [Verification codes](/invoicing/verification). Permission: `documents.view`. Capability: `render_pdf`. MCP tool: `render_pdf`. CLI: `freeinvoiceautomation doc pdf`. ## Path parameters - `id` (string, required): Id of the document. ## Query parameters - `version` (integer): Version of a sent document to render. Defaults to the current version. Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/render-pdf --- # Create a share link POST https://api.freeinvoiceautomation.com/v1/documents/{id}/share-links Creates a public link to the document: the pay page for an invoice, the estimate page for an estimate. Share the link through any channel you like. The token in the link is 256-bit random, stored only as a hash, used for this one purpose, and expires (by default 90 days after it is created); the full URL is returned once, in this response. You can revoke it at any time with [Revoke a share link](/api-reference/documents/revoke-share-link). The pay page shows the invoice, the balance and every payment method accepted on it. It has no analytics and no ads. Voiding the invoice revokes its pay links. See [Share links](/invoicing/share-links) and [The pay page](/invoicing/pay-page). Permission: `documents.send`. Capability: `create_share_link`. CLI: `freeinvoiceautomation doc share`. ## Path parameters - `id` (string, required): Id of the document. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Id of the share link. Use it to revoke the link. - `document_id` (string): The shared document. - `purpose` (enum): `pay` for invoices (opens `/pay/[token]`), `estimate` for estimates (opens `/estimate/[token]`). One of: `pay`, `estimate`. - `url` (string): The full public link. Shown only in this response: the token is stored hashed and cannot be read again. - `expires_at` (string): When the link stops working. - `revoked_at` (string | null): When the link was revoked. - `created_at` (string): When the link was created. ## Errors - `document_voided` - `idempotency_key_reused` Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/create-share-link --- # Revoke a share link DELETE https://api.freeinvoiceautomation.com/v1/share-links/{id} Turns a share link off at once. Anyone opening it sees that the link is no longer valid. After revoking, the old link shows "link no longer valid" and no document data. The document itself does not change; create a new link when you want to share it again. See [Share links](/invoicing/share-links). Permission: `documents.send`. Capability: `revoke_share_link`. CLI: `freeinvoiceautomation doc unshare`. ## Path parameters - `id` (string, required): Id of the share link. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/revoke-share-link --- # Void a document POST https://api.freeinvoiceautomation.com/v1/documents/{id}/void Cancels a sent invoice that has no payments. It keeps its number and is stamped VOID. Voiding is destructive and needs `confirm: true` and a `void_reason`. A voided invoice keeps its number (so numbering stays complete), shows a VOID stamp on the PDF, pay page and portal, stops reminders and late fees, and revokes its open pay links. It is never deleted. An invoice with recorded payments cannot be voided: the request is refused with `document_has_payments`. Issue a [credit note](/api-reference/documents/create-credit-note) instead. A draft has nothing to void; delete it with [Delete a draft](/api-reference/documents/delete-draft). Every void writes an audit entry with the reason. When a store connector mirrors a cancelled order, the void runs automatically and is audited with the connector as actor. See [Void and delete](/invoicing/void). Permission: `documents.void`. Capability: `void_document`. MCP tool: `void_document`. CLI: `freeinvoiceautomation doc void`. - Without `confirm: true` you get the preview and `confirmation_required`. - `Idempotency-Key` is required for API-key and MCP calls. ## Path parameters - `id` (string, required): Id of the document. ## Headers - `Idempotency-Key` (string, required): Required for API-key and MCP calls on void calls. Any unique string up to 255 characters; a retry with the same key returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `void_reason` (string, required): Why the document is voided. Stored on the document and in the audit log. - `confirm` (boolean, required): Must be `true` to void the document. Without it nothing changes: you get the preview and `confirmation_required`. See [Drafts and confirmations](/api-reference/confirmations). ## Response - `id` (string): Unique id of the document (uuid v7). - `business_id` (string): The issuing business. - `client_id` (string): The client the document is for. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `status` (enum): Current status. Invoices: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`. Estimates: `draft`, `sent`, `changes_requested`, `accepted`, `expired`, `converted`, `void`. Credit notes: `draft`, `issued`, `applied`, `refunded`. Receipts: `issued`, `void`. See [Document statuses](/invoicing/statuses). One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`, `changes_requested`, `accepted`, `expired`, `converted`, `issued`, `applied`, `refunded`. - `number` (string | null): Human number such as `INV-2027-0017`. `null` until the document is first sent. - `issue_date` (string | null): Issue date, `YYYY-MM-DD`. - `due_date` (string | null): Due date for invoices. - `expires_at` (string | null): Expiry date for estimates. - `currency` (string): ISO 4217 code of every amount on the document. - `fx_rate` (string | null): Exchange rate to the business base currency, frozen on the document when its currency differs. - `fx_rate_source` (string | null): Where the frozen rate came from and its date. - `subtotal_minor` (string): Sum of line amounts before tax. - `discount_minor` (string): Total discount. - `tax_minor` (string): Total tax. - `total_minor` (string): Grand total. - `amount_paid_minor` (string): Payments received so far. - `amount_credited_minor` (string): Credit notes and client credit applied. - `balance_minor` (string): What is still owed: total minus paid minus credited. - `terms` (string | null): Payment terms printed on the document. - `notes` (string | null): Notes printed on the document. - `accepted_methods` (string[] | null): Payment connections offered on this document's pay page. - `pdf_template_id` (string | null): PDF template used to render the document. - `late_fees_enabled` (boolean): Late fees on or off for this invoice (January 2027). Off by default. - `reminder_sequence_id` (string | null): Reminder sequence override for this document (January 2027). - `promise_to_pay_date` (string | null): Date the client promised to pay; stops reminders (January 2027). - `recurring_schedule_id` (string | null): Recurring schedule that created the document (January 2027). - `current_version` (integer): `0` for a draft, `1` after the first send, then one more for each revision. - `first_sent_at` (string | null): When the document was first sent. - `last_sent_at` (string | null): When the document was last sent. - `accepted_at` (string | null): When the client accepted the estimate. - `paid_at` (string | null): When the invoice became fully paid. - `overdue_at` (string | null): When the invoice was marked overdue. - `voided_at` (string | null): When the document was voided. - `voided_by` (string | null): Who voided it. - `void_reason` (string | null): Reason given when voiding. - `source` (enum): Where the document was created. One of: `app`, `api`, `mcp`, `cli`, `agent`, `shopify`, `woocommerce`, `generator_sync`, `recurring`. - `external_ref` (string | null): Your own reference, e.g. a store order id. Unique per business and source, so a connector cannot create the same document twice. - `client_generated_id` (string | null): Id given by the free generator for documents synced on sign-up. - `lines` (object[]): The document lines, in order. - `id` (string): Unique id of the line. - `position` (integer): Order of the line on the document, starting at 1. - `kind` (enum): Where the line came from. `late_fee` from January 2027, `deposit` from late January 2027, `time` and `expense` from April 2027. One of: `item`, `late_fee`, `time`, `expense`, `deposit`. - `item_id` (string | null): Saved item the line was filled from. Reference only: the values are copied, so editing the item never changes this line. - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string with up to six decimals, e.g. `"1.5"`. - `unit` (string | null): Unit label such as `hour` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of the document currency. - `discount_type` (enum | null): Kind of line discount. One of: `percent`, `amount`. - `discount_value` (string | null): Discount as a percent (`"10"` = 10%) or, for `amount`, in minor units. - `tax_rate_id` (string | null): Tax rate picked for the line. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line, e.g. `"20.0000"`. - `tax_calculation` (enum | null): Frozen calculation of the tax. One of: `exclusive`, `inclusive`. - `subtotal_minor` (string): Line amount before tax, after discount. - `tax_minor` (string): Tax on the line. - `total_minor` (string): Line total including tax. - `credited_line_id` (string | null): On a credit note: the invoice line this line credits. - `links` (object[]): Linked documents: credit notes, source estimate, contract. - `type` (enum): How the documents are linked. One of: `credit_note`, `estimate`, `contract`, `revision`. - `from_document_id` (string): The linking document, e.g. the credit note or the invoice made from an estimate. - `to_document_id` (string): The linked document, e.g. the credited invoice or the source estimate. - `amount_minor` (string | null): For `credit_note` links: the amount credited. - `pay_url` (string | null): Pay page link, present when an active share link exists. - `created_at` (string): When the document was created (RFC 3339, UTC). - `updated_at` (string): When the document was last changed. ## Errors - `confirmation_required` - `document_has_payments` - `document_voided` - `conflict` - `idempotency_key_reused` Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/void-document --- # Create a credit note POST https://api.freeinvoiceautomation.com/v1/documents/{id}/credit-notes Issues a full or partial credit note against an invoice and, for a paid invoice, refunds the amount or keeps it as client credit. A credit note is its own document type with its own gap-free sequence (`CN-2027-0001`). It is linked to the invoice and reduces the invoice's balance by the credited amount. Credit the whole invoice, pick lines (with quantities), or enter an amount. The credit can never be larger than what is left to credit (`credit_exceeds_balance`). When the invoice was already paid, choose a `settlement`: `refund` sends the money back through the payment rail when it supports refunds (Stripe), otherwise the refund is recorded as made by you outside the system; `client_credit` keeps the amount as [client credit](/clients/client-credit) for a later invoice. A Stripe refund is recorded when Stripe confirms it. This is a destructive, money-moving action: it needs `confirm: true` and `documents.credit`, and writes an audit entry. Store connectors create credit notes automatically when an order is refunded. See [Credit notes](/invoicing/credit-notes) and [Refunds](/payments/refunds). Permission: `documents.credit`. Capability: `create_credit_note`. MCP tool: `create_credit_note`. CLI: `freeinvoiceautomation credit-note create`. - Without `confirm: true` you get the preview and `confirmation_required`. - `Idempotency-Key` is required for API-key and MCP calls. ## Path parameters - `id` (string, required): Id of the invoice to credit. ## Headers - `Idempotency-Key` (string, required): Required for API-key and MCP calls on credit-note calls. Any unique string up to 255 characters; a retry with the same key returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `lines` (object[]): Partial credit by line. Leave out `lines` and `amount_minor` to credit the whole invoice. - `credited_line_id` (string, required): The invoice line to credit. - `quantity` (string): Quantity to credit. Defaults to the full quantity of the line. - `amount_minor` (string): Partial credit by amount, in minor units of the invoice currency. Use instead of `lines`. - `settlement` (enum): Required when the invoice is already paid: refund the client or keep the amount as client credit. One of: `refund`, `client_credit`. - `notes` (string): Reason shown on the credit note. - `confirm` (boolean, required): Must be `true` to issue the credit note. Without it nothing changes: you get the preview and `confirmation_required`. See [Drafts and confirmations](/api-reference/confirmations). ## Response - `id` (string): Unique id of the document (uuid v7). - `business_id` (string): The issuing business. - `client_id` (string): The client the document is for. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `status` (enum): Current status. Invoices: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`. Estimates: `draft`, `sent`, `changes_requested`, `accepted`, `expired`, `converted`, `void`. Credit notes: `draft`, `issued`, `applied`, `refunded`. Receipts: `issued`, `void`. See [Document statuses](/invoicing/statuses). One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`, `changes_requested`, `accepted`, `expired`, `converted`, `issued`, `applied`, `refunded`. - `number` (string | null): Human number such as `INV-2027-0017`. `null` until the document is first sent. - `issue_date` (string | null): Issue date, `YYYY-MM-DD`. - `due_date` (string | null): Due date for invoices. - `expires_at` (string | null): Expiry date for estimates. - `currency` (string): ISO 4217 code of every amount on the document. - `fx_rate` (string | null): Exchange rate to the business base currency, frozen on the document when its currency differs. - `fx_rate_source` (string | null): Where the frozen rate came from and its date. - `subtotal_minor` (string): Sum of line amounts before tax. - `discount_minor` (string): Total discount. - `tax_minor` (string): Total tax. - `total_minor` (string): Grand total. - `amount_paid_minor` (string): Payments received so far. - `amount_credited_minor` (string): Credit notes and client credit applied. - `balance_minor` (string): What is still owed: total minus paid minus credited. - `terms` (string | null): Payment terms printed on the document. - `notes` (string | null): Notes printed on the document. - `accepted_methods` (string[] | null): Payment connections offered on this document's pay page. - `pdf_template_id` (string | null): PDF template used to render the document. - `late_fees_enabled` (boolean): Late fees on or off for this invoice (January 2027). Off by default. - `reminder_sequence_id` (string | null): Reminder sequence override for this document (January 2027). - `promise_to_pay_date` (string | null): Date the client promised to pay; stops reminders (January 2027). - `recurring_schedule_id` (string | null): Recurring schedule that created the document (January 2027). - `current_version` (integer): `0` for a draft, `1` after the first send, then one more for each revision. - `first_sent_at` (string | null): When the document was first sent. - `last_sent_at` (string | null): When the document was last sent. - `accepted_at` (string | null): When the client accepted the estimate. - `paid_at` (string | null): When the invoice became fully paid. - `overdue_at` (string | null): When the invoice was marked overdue. - `voided_at` (string | null): When the document was voided. - `voided_by` (string | null): Who voided it. - `void_reason` (string | null): Reason given when voiding. - `source` (enum): Where the document was created. One of: `app`, `api`, `mcp`, `cli`, `agent`, `shopify`, `woocommerce`, `generator_sync`, `recurring`. - `external_ref` (string | null): Your own reference, e.g. a store order id. Unique per business and source, so a connector cannot create the same document twice. - `client_generated_id` (string | null): Id given by the free generator for documents synced on sign-up. - `lines` (object[]): The document lines, in order. - `id` (string): Unique id of the line. - `position` (integer): Order of the line on the document, starting at 1. - `kind` (enum): Where the line came from. `late_fee` from January 2027, `deposit` from late January 2027, `time` and `expense` from April 2027. One of: `item`, `late_fee`, `time`, `expense`, `deposit`. - `item_id` (string | null): Saved item the line was filled from. Reference only: the values are copied, so editing the item never changes this line. - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string with up to six decimals, e.g. `"1.5"`. - `unit` (string | null): Unit label such as `hour` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of the document currency. - `discount_type` (enum | null): Kind of line discount. One of: `percent`, `amount`. - `discount_value` (string | null): Discount as a percent (`"10"` = 10%) or, for `amount`, in minor units. - `tax_rate_id` (string | null): Tax rate picked for the line. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line, e.g. `"20.0000"`. - `tax_calculation` (enum | null): Frozen calculation of the tax. One of: `exclusive`, `inclusive`. - `subtotal_minor` (string): Line amount before tax, after discount. - `tax_minor` (string): Tax on the line. - `total_minor` (string): Line total including tax. - `credited_line_id` (string | null): On a credit note: the invoice line this line credits. - `links` (object[]): Linked documents: credit notes, source estimate, contract. - `type` (enum): How the documents are linked. One of: `credit_note`, `estimate`, `contract`, `revision`. - `from_document_id` (string): The linking document, e.g. the credit note or the invoice made from an estimate. - `to_document_id` (string): The linked document, e.g. the credited invoice or the source estimate. - `amount_minor` (string | null): For `credit_note` links: the amount credited. - `pay_url` (string | null): Pay page link, present when an active share link exists. - `created_at` (string): When the document was created (RFC 3339, UTC). - `updated_at` (string): When the document was last changed. ## Errors - `confirmation_required` - `credit_exceeds_balance` - `document_voided` - `validation_failed` - `upstream_error` - `idempotency_key_reused` Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/create-credit-note --- # Apply client credit POST https://api.freeinvoiceautomation.com/v1/documents/{id}/apply-credit Uses the client's credit balance to reduce what is owed on an invoice. Client credit comes from credit notes on paid invoices where you chose to keep the amount as credit. Applying it reduces the invoice balance and the client's credit balance in the same step, and both changes are recorded (append-only credit history plus an audit entry). The credit must be in the invoice currency. Leave out `amount_minor` to apply as much as possible: the smaller of the client's credit and the invoice balance. See [Client credit](/clients/client-credit) and [Get client credit](/api-reference/clients/get-client-credit). Permission: `documents.edit`. Capability: `apply_client_credit`. CLI: `freeinvoiceautomation doc apply-credit`. ## Path parameters - `id` (string, required): Id of the invoice. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `amount_minor` (string): Amount of credit to apply, in minor units. Defaults to the most that can be applied. ## Response - `id` (string): Unique id of the document (uuid v7). - `business_id` (string): The issuing business. - `client_id` (string): The client the document is for. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `status` (enum): Current status. Invoices: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`. Estimates: `draft`, `sent`, `changes_requested`, `accepted`, `expired`, `converted`, `void`. Credit notes: `draft`, `issued`, `applied`, `refunded`. Receipts: `issued`, `void`. See [Document statuses](/invoicing/statuses). One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`, `changes_requested`, `accepted`, `expired`, `converted`, `issued`, `applied`, `refunded`. - `number` (string | null): Human number such as `INV-2027-0017`. `null` until the document is first sent. - `issue_date` (string | null): Issue date, `YYYY-MM-DD`. - `due_date` (string | null): Due date for invoices. - `expires_at` (string | null): Expiry date for estimates. - `currency` (string): ISO 4217 code of every amount on the document. - `fx_rate` (string | null): Exchange rate to the business base currency, frozen on the document when its currency differs. - `fx_rate_source` (string | null): Where the frozen rate came from and its date. - `subtotal_minor` (string): Sum of line amounts before tax. - `discount_minor` (string): Total discount. - `tax_minor` (string): Total tax. - `total_minor` (string): Grand total. - `amount_paid_minor` (string): Payments received so far. - `amount_credited_minor` (string): Credit notes and client credit applied. - `balance_minor` (string): What is still owed: total minus paid minus credited. - `terms` (string | null): Payment terms printed on the document. - `notes` (string | null): Notes printed on the document. - `accepted_methods` (string[] | null): Payment connections offered on this document's pay page. - `pdf_template_id` (string | null): PDF template used to render the document. - `late_fees_enabled` (boolean): Late fees on or off for this invoice (January 2027). Off by default. - `reminder_sequence_id` (string | null): Reminder sequence override for this document (January 2027). - `promise_to_pay_date` (string | null): Date the client promised to pay; stops reminders (January 2027). - `recurring_schedule_id` (string | null): Recurring schedule that created the document (January 2027). - `current_version` (integer): `0` for a draft, `1` after the first send, then one more for each revision. - `first_sent_at` (string | null): When the document was first sent. - `last_sent_at` (string | null): When the document was last sent. - `accepted_at` (string | null): When the client accepted the estimate. - `paid_at` (string | null): When the invoice became fully paid. - `overdue_at` (string | null): When the invoice was marked overdue. - `voided_at` (string | null): When the document was voided. - `voided_by` (string | null): Who voided it. - `void_reason` (string | null): Reason given when voiding. - `source` (enum): Where the document was created. One of: `app`, `api`, `mcp`, `cli`, `agent`, `shopify`, `woocommerce`, `generator_sync`, `recurring`. - `external_ref` (string | null): Your own reference, e.g. a store order id. Unique per business and source, so a connector cannot create the same document twice. - `client_generated_id` (string | null): Id given by the free generator for documents synced on sign-up. - `lines` (object[]): The document lines, in order. - `id` (string): Unique id of the line. - `position` (integer): Order of the line on the document, starting at 1. - `kind` (enum): Where the line came from. `late_fee` from January 2027, `deposit` from late January 2027, `time` and `expense` from April 2027. One of: `item`, `late_fee`, `time`, `expense`, `deposit`. - `item_id` (string | null): Saved item the line was filled from. Reference only: the values are copied, so editing the item never changes this line. - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string with up to six decimals, e.g. `"1.5"`. - `unit` (string | null): Unit label such as `hour` or `piece`. - `unit_price_minor` (string): Price per unit in minor units of the document currency. - `discount_type` (enum | null): Kind of line discount. One of: `percent`, `amount`. - `discount_value` (string | null): Discount as a percent (`"10"` = 10%) or, for `amount`, in minor units. - `tax_rate_id` (string | null): Tax rate picked for the line. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line, e.g. `"20.0000"`. - `tax_calculation` (enum | null): Frozen calculation of the tax. One of: `exclusive`, `inclusive`. - `subtotal_minor` (string): Line amount before tax, after discount. - `tax_minor` (string): Tax on the line. - `total_minor` (string): Line total including tax. - `credited_line_id` (string | null): On a credit note: the invoice line this line credits. - `links` (object[]): Linked documents: credit notes, source estimate, contract. - `type` (enum): How the documents are linked. One of: `credit_note`, `estimate`, `contract`, `revision`. - `from_document_id` (string): The linking document, e.g. the credit note or the invoice made from an estimate. - `to_document_id` (string): The linked document, e.g. the credited invoice or the source estimate. - `amount_minor` (string | null): For `credit_note` links: the amount credited. - `pay_url` (string | null): Pay page link, present when an active share link exists. - `created_at` (string): When the document was created (RFC 3339, UTC). - `updated_at` (string): When the document was last changed. ## Errors - `currency_mismatch` - `credit_exceeds_balance` - `document_voided` - `idempotency_key_reused` Section: Documents Source: https://docs.freeinvoiceautomation.com/api-reference/documents/apply-client-credit --- # Record a payment POST https://api.freeinvoiceautomation.com/v1/payments Records money you received outside the system, such as a bank transfer, and updates the invoice balance. Use this for payments by bank transfer, local QR rails, cash or any other way that does not confirm itself. Recording a payment is an audited human confirmation: the record stores who recorded it, and the invoice moves to `partially_paid` or `paid` with an audit entry. Card payments through Stripe are recorded automatically from Stripe's verified webhook; do not record them by hand. The amount is added to the invoice's paid amount and its balance goes down. When the balance reaches zero, the invoice is `paid` and reminders stop. A browser redirect from a checkout never marks anything paid. Store connectors (Shopify, WooCommerce) use the same call for orders paid in the store; when the call arrives through the install's verified channel it is stored with `source: connector`. See [Record a payment](/payments/recording). Permission: `payments.record`. Capability: `record_payment`. MCP tool: `record_payment`. CLI: `freeinvoiceautomation payment record`. - `Idempotency-Key` is required for API-key and MCP calls. - Floating-point amounts are rejected; send minor units as strings. ## Headers - `Idempotency-Key` (string, required): Required for API-key and MCP calls on payment calls, so a retry never records money twice. Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `document_id` (string, required): The invoice that was paid. - `amount_minor` (string, required): Amount received in minor units, greater than 0, e.g. `"50000"` for 500.00. - `currency` (string, required): ISO 4217 code. Must match the invoice currency. - `received_at` (string, required): When the money arrived. A date at midnight UTC is fine. - `connection_id` (string): The payment method it came through, e.g. your bank transfer connection. Leave out for a payment with no connection. - `reference` (string): Bank or transfer reference. - `proof_id` (string): The client's payment proof this payment matches. Use [Accept a payment proof](/api-reference/payments/accept-payment-proof) to do both in one step. - `notes` (string): Private notes. ## Response - `id` (string): Unique id of the payment (uuid v7). - `business_id` (string): The business that received the payment. - `document_id` (string): The invoice the payment is for. - `client_id` (string): The client who paid. - `connection_id` (string | null): The payment connection used, or `null` for a manual record with no connection. - `rail` (string): Payment rail key such as `stripe` or `bank_transfer`, or `manual`. - `kind` (enum): `payment` for money received, `refund` for money sent back. One of: `payment`, `refund`. - `refund_of_payment_id` (string | null): For a refund: the payment it refunds. - `amount_minor` (string): Amount in minor units of `currency`. Always greater than 0. - `currency` (string): ISO 4217 code. - `fee_minor` (string | null): Fee the rail charged, e.g. the Stripe fee. We never take a cut of payments. - `received_at` (string): When the money was received. - `reference` (string | null): Bank or transfer reference. - `external_id` (string | null): Id at the rail, e.g. a Stripe PaymentIntent or charge id. - `source` (enum): How the payment was confirmed: a verified rail webhook, a reconcile run, a person recording it, or a verified store connector event. One of: `webhook`, `reconcile`, `manual`, `connector`. - `recorded_by` (string | null): Who recorded it. Always set for `manual` payments. - `proof_id` (string | null): The client's payment proof this record came from, if any. - `status` (enum): Refund state of the payment. One of: `succeeded`, `refunded`, `partially_refunded`. - `refunded_amount_minor` (string): How much of this payment has been refunded. - `notes` (string | null): Private notes. - `created_at` (string): When the record was created (RFC 3339, UTC). - `updated_at` (string): When the record last changed. ## Errors - `validation_failed` - `currency_mismatch` - `document_voided` - `idempotency_key_reused` - `idempotency_in_progress` Section: Payments Source: https://docs.freeinvoiceautomation.com/api-reference/payments/record-payment --- # Get payment status GET https://api.freeinvoiceautomation.com/v1/documents/{id}/payment-status Returns how much of an invoice is paid, credited and still owed, with the payments behind it. A quick answer to "has this invoice been paid?" for integrations and AI assistants. The status reflects only confirmed money: verified rail webhooks, reconcile runs and payments recorded by a person. A client clicking "I have paid" does not change it until you accept the proof. See [How payments work](/payments/overview). Permission: `payments.view`. Capability: `get_payment_status`. MCP tool: `get_payment_status`. CLI: `freeinvoiceautomation payment status`. ## Path parameters - `id` (string, required): Id of the invoice. ## Response - `document_id` (string): The invoice. - `number` (string | null): Invoice number. - `status` (enum): Invoice status. One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`. - `currency` (string): ISO 4217 code. - `total_minor` (string): Invoice total. - `amount_paid_minor` (string): Paid so far. - `amount_credited_minor` (string): Credited by credit notes or client credit. - `balance_minor` (string): Still owed. - `due_date` (string | null): Due date. - `paid_at` (string | null): When it became fully paid. - `payments` (object[]): Payments and refunds on the invoice, newest first. - `id` (string): Unique id of the payment (uuid v7). - `business_id` (string): The business that received the payment. - `document_id` (string): The invoice the payment is for. - `client_id` (string): The client who paid. - `connection_id` (string | null): The payment connection used, or `null` for a manual record with no connection. - `rail` (string): Payment rail key such as `stripe` or `bank_transfer`, or `manual`. - `kind` (enum): `payment` for money received, `refund` for money sent back. One of: `payment`, `refund`. - `refund_of_payment_id` (string | null): For a refund: the payment it refunds. - `amount_minor` (string): Amount in minor units of `currency`. Always greater than 0. - `currency` (string): ISO 4217 code. - `fee_minor` (string | null): Fee the rail charged, e.g. the Stripe fee. We never take a cut of payments. - `received_at` (string): When the money was received. - `reference` (string | null): Bank or transfer reference. - `external_id` (string | null): Id at the rail, e.g. a Stripe PaymentIntent or charge id. - `source` (enum): How the payment was confirmed: a verified rail webhook, a reconcile run, a person recording it, or a verified store connector event. One of: `webhook`, `reconcile`, `manual`, `connector`. - `recorded_by` (string | null): Who recorded it. Always set for `manual` payments. - `proof_id` (string | null): The client's payment proof this record came from, if any. - `status` (enum): Refund state of the payment. One of: `succeeded`, `refunded`, `partially_refunded`. - `refunded_amount_minor` (string): How much of this payment has been refunded. - `notes` (string | null): Private notes. - `created_at` (string): When the record was created (RFC 3339, UTC). - `updated_at` (string): When the record last changed. Section: Payments Source: https://docs.freeinvoiceautomation.com/api-reference/payments/get-payment-status --- # List payments GET https://api.freeinvoiceautomation.com/v1/payments Returns payments and refunds of the businesses you can reach, newest first. Includes payments from every source: Stripe webhooks, reconcile runs, manual records and store connectors. Refunds appear as their own rows with `kind: refund`. Deleted manual records are left out. Results use [cursor pagination](/api-reference/pagination). Permission: `payments.view`. Capability: `list_payments`. CLI: `freeinvoiceautomation payment list`. ## Query parameters - `business_id` (string): Only payments of this business. - `document_id` (string): Only payments for this document. - `client_id` (string): Only payments from this client. - `rail` (string): Only this rail, e.g. `stripe` or `manual`. - `kind` (enum): Only payments or only refunds. One of: `payment`, `refund`. - `source` (string): Only this source; several comma-separated. One of: `webhook`, `reconcile`, `manual`, `connector`. - `currency` (string): Only this currency. - `received_after` (string): Received at or after this time. - `received_before` (string): Received before this time. - `sort` (string, default: -received_at): Sort field; a leading `-` sorts descending. One of: `received_at`, `-received_at`, `amount_minor`, `-amount_minor`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The payments on this page. - `id` (string): Unique id of the payment (uuid v7). - `business_id` (string): The business that received the payment. - `document_id` (string): The invoice the payment is for. - `client_id` (string): The client who paid. - `connection_id` (string | null): The payment connection used, or `null` for a manual record with no connection. - `rail` (string): Payment rail key such as `stripe` or `bank_transfer`, or `manual`. - `kind` (enum): `payment` for money received, `refund` for money sent back. One of: `payment`, `refund`. - `refund_of_payment_id` (string | null): For a refund: the payment it refunds. - `amount_minor` (string): Amount in minor units of `currency`. Always greater than 0. - `currency` (string): ISO 4217 code. - `fee_minor` (string | null): Fee the rail charged, e.g. the Stripe fee. We never take a cut of payments. - `received_at` (string): When the money was received. - `reference` (string | null): Bank or transfer reference. - `external_id` (string | null): Id at the rail, e.g. a Stripe PaymentIntent or charge id. - `source` (enum): How the payment was confirmed: a verified rail webhook, a reconcile run, a person recording it, or a verified store connector event. One of: `webhook`, `reconcile`, `manual`, `connector`. - `recorded_by` (string | null): Who recorded it. Always set for `manual` payments. - `proof_id` (string | null): The client's payment proof this record came from, if any. - `status` (enum): Refund state of the payment. One of: `succeeded`, `refunded`, `partially_refunded`. - `refunded_amount_minor` (string): How much of this payment has been refunded. - `notes` (string | null): Private notes. - `created_at` (string): When the record was created (RFC 3339, UTC). - `updated_at` (string): When the record last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Payments Source: https://docs.freeinvoiceautomation.com/api-reference/payments/list-payments --- # Get a payment GET https://api.freeinvoiceautomation.com/v1/payments/{id} Returns one payment or refund. Permission: `payments.view`. Capability: `get_payment`. CLI: `freeinvoiceautomation payment get`. ## Path parameters - `id` (string, required): Id of the payment. ## Response - `id` (string): Unique id of the payment (uuid v7). - `business_id` (string): The business that received the payment. - `document_id` (string): The invoice the payment is for. - `client_id` (string): The client who paid. - `connection_id` (string | null): The payment connection used, or `null` for a manual record with no connection. - `rail` (string): Payment rail key such as `stripe` or `bank_transfer`, or `manual`. - `kind` (enum): `payment` for money received, `refund` for money sent back. One of: `payment`, `refund`. - `refund_of_payment_id` (string | null): For a refund: the payment it refunds. - `amount_minor` (string): Amount in minor units of `currency`. Always greater than 0. - `currency` (string): ISO 4217 code. - `fee_minor` (string | null): Fee the rail charged, e.g. the Stripe fee. We never take a cut of payments. - `received_at` (string): When the money was received. - `reference` (string | null): Bank or transfer reference. - `external_id` (string | null): Id at the rail, e.g. a Stripe PaymentIntent or charge id. - `source` (enum): How the payment was confirmed: a verified rail webhook, a reconcile run, a person recording it, or a verified store connector event. One of: `webhook`, `reconcile`, `manual`, `connector`. - `recorded_by` (string | null): Who recorded it. Always set for `manual` payments. - `proof_id` (string | null): The client's payment proof this record came from, if any. - `status` (enum): Refund state of the payment. One of: `succeeded`, `refunded`, `partially_refunded`. - `refunded_amount_minor` (string): How much of this payment has been refunded. - `notes` (string | null): Private notes. - `created_at` (string): When the record was created (RFC 3339, UTC). - `updated_at` (string): When the record last changed. Section: Payments Source: https://docs.freeinvoiceautomation.com/api-reference/payments/get-payment --- # Delete a payment DELETE https://api.freeinvoiceautomation.com/v1/payments/{id} Removes a payment that was recorded by mistake. Only manual records can be deleted. Use this to undo a manual record, for example a payment entered on the wrong invoice. The payment is soft-deleted (kept for the audit trail, hidden from lists), the invoice's paid amount and balance are recalculated, and an audit entry is written. The request must include `confirm: true`. Payments confirmed by a rail webhook, a reconcile run or a store connector cannot be deleted; refund them instead. See [Record a payment](/payments/recording). Permission: `payments.record`. Capability: `delete_payment`. CLI: `freeinvoiceautomation payment delete`. ## Path parameters - `id` (string, required): Id of the payment. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true` to delete. Without it you get the preview and `confirmation_required`. See [Drafts and confirmations](/api-reference/confirmations). ## Errors - `confirmation_required` - `conflict` Section: Payments Source: https://docs.freeinvoiceautomation.com/api-reference/payments/delete-payment --- # Refund a payment POST https://api.freeinvoiceautomation.com/v1/payments/{id}/refunds Refunds all or part of a payment, through the rail when it supports refunds, otherwise as a manual record. For a Stripe payment, the refund is created on your connected Stripe account and recorded when Stripe confirms it through its webhook, so the response is `202` and the refund row appears shortly after. For a payment received by bank transfer or another method we cannot move money on, the refund is recorded as one you made yourself, with an audit entry. Refunding moves money, so it needs `confirm: true` and the `payments.refund` permission. When you are correcting an invoice, use [Create a credit note](/api-reference/documents/create-credit-note) with `settlement: refund` instead: it issues the credit note and makes the refund in one step. See [Refunds](/payments/refunds). Permission: `payments.refund`. Capability: `refund_payment`. CLI: `freeinvoiceautomation payment refund`. - Without `confirm: true` you get the preview and `confirmation_required`. - `Idempotency-Key` is required for API-key and MCP calls. ## Path parameters - `id` (string, required): Id of the payment to refund. ## Headers - `Idempotency-Key` (string, required): Required for API-key and MCP calls on payment calls, so a retry never records money twice. Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `amount_minor` (string): Amount to refund in minor units. Defaults to everything not yet refunded. - `notes` (string): Reason for the refund, kept privately. - `confirm` (boolean, required): Must be `true` to refund. Without it you get the preview and `confirmation_required`. See [Drafts and confirmations](/api-reference/confirmations). ## Response - `id` (string): Unique id of the payment (uuid v7). - `business_id` (string): The business that received the payment. - `document_id` (string): The invoice the payment is for. - `client_id` (string): The client who paid. - `connection_id` (string | null): The payment connection used, or `null` for a manual record with no connection. - `rail` (string): Payment rail key such as `stripe` or `bank_transfer`, or `manual`. - `kind` (enum): `payment` for money received, `refund` for money sent back. One of: `payment`, `refund`. - `refund_of_payment_id` (string | null): For a refund: the payment it refunds. - `amount_minor` (string): Amount in minor units of `currency`. Always greater than 0. - `currency` (string): ISO 4217 code. - `fee_minor` (string | null): Fee the rail charged, e.g. the Stripe fee. We never take a cut of payments. - `received_at` (string): When the money was received. - `reference` (string | null): Bank or transfer reference. - `external_id` (string | null): Id at the rail, e.g. a Stripe PaymentIntent or charge id. - `source` (enum): How the payment was confirmed: a verified rail webhook, a reconcile run, a person recording it, or a verified store connector event. One of: `webhook`, `reconcile`, `manual`, `connector`. - `recorded_by` (string | null): Who recorded it. Always set for `manual` payments. - `proof_id` (string | null): The client's payment proof this record came from, if any. - `status` (enum): Refund state of the payment. One of: `succeeded`, `refunded`, `partially_refunded`. - `refunded_amount_minor` (string): How much of this payment has been refunded. - `notes` (string | null): Private notes. - `created_at` (string): When the record was created (RFC 3339, UTC). - `updated_at` (string): When the record last changed. ## Errors - `confirmation_required` - `rail_capability_missing` - `rail_not_connected` - `upstream_error` - `validation_failed` - `idempotency_key_reused` Section: Payments Source: https://docs.freeinvoiceautomation.com/api-reference/payments/refund-payment --- # List payment proofs GET https://api.freeinvoiceautomation.com/v1/payment-proofs Returns the "I have paid" messages clients sent from the pay page, with any uploaded proof. When a client pays by bank transfer or a local QR code, they click "I have paid" on the pay page and may upload a proof file. The business is notified and the proof waits here as `pending`. A proof never marks an invoice paid by itself: accept it to record the payment, or reject it. See [Payment proofs](/payments/payment-proofs). Permission: `payments.view`. Capability: `list_payment_proofs`. CLI: `freeinvoiceautomation proof list`. ## Query parameters - `business_id` (string): Only proofs for this business. - `document_id` (string): Only proofs for this invoice. - `client_id` (string): Only proofs from this client. - `status` (string, default: pending): Review state; several comma-separated. One of: `pending`, `accepted`, `rejected`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The payment proofs on this page. - `id` (string): Unique id of the proof. - `business_id` (string): The business the proof was sent to. - `document_id` (string): The invoice the client says they paid. - `client_id` (string): The client. - `attachment_id` (string | null): Uploaded proof file, e.g. a transfer screenshot. Stored privately. - `claimed_amount_minor` (string | null): Amount the client says they paid, in minor units. - `currency` (string | null): ISO 4217 code of the claimed amount. - `claimed_paid_on` (string | null): Date the client says they paid. - `client_note` (string | null): Message from the client. - `status` (enum): Review state. One of: `pending`, `accepted`, `rejected`. - `reviewed_by` (string | null): Who accepted or rejected it. - `reviewed_at` (string | null): When it was reviewed. - `payment_id` (string | null): The payment recorded when the proof was accepted. - `created_at` (string): When the client sent it. - `updated_at` (string): When it last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Payments Source: https://docs.freeinvoiceautomation.com/api-reference/payments/list-payment-proofs --- # Accept a payment proof POST https://api.freeinvoiceautomation.com/v1/payment-proofs/{id}/accept Confirms a client's "I have paid" and records the payment on the invoice. Accepting is the audited human confirmation that the money arrived. It records a manual payment linked to the proof, using the client's claimed amount and date unless you send what you actually received, and marks the proof `accepted`. The invoice balance and status update as with [Record a payment](/api-reference/payments/record-payment). Check your bank account before accepting: the proof is the client's claim, not a confirmation from the bank. See [Payment proofs](/payments/payment-proofs). Permission: `payments.record`. Capability: `accept_payment_proof`. CLI: `freeinvoiceautomation proof accept`. - `Idempotency-Key` is required for API-key and MCP calls, as for every payment call. ## Path parameters - `id` (string, required): Id of the payment proof. ## Headers - `Idempotency-Key` (string, required): Required for API-key and MCP calls on payment calls, so a retry never records money twice. Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `amount_minor` (string): Amount actually received. Defaults to the claimed amount; required when the client did not give one. - `received_at` (string): When the money arrived. Defaults to the claimed date. - `connection_id` (string): The payment method the money came through. - `reference` (string): Bank or transfer reference. - `notes` (string): Private notes. ## Response - `proof` (object): The proof, now `accepted`. - `id` (string): Unique id of the proof. - `business_id` (string): The business the proof was sent to. - `document_id` (string): The invoice the client says they paid. - `client_id` (string): The client. - `attachment_id` (string | null): Uploaded proof file, e.g. a transfer screenshot. Stored privately. - `claimed_amount_minor` (string | null): Amount the client says they paid, in minor units. - `currency` (string | null): ISO 4217 code of the claimed amount. - `claimed_paid_on` (string | null): Date the client says they paid. - `client_note` (string | null): Message from the client. - `status` (enum): Review state. One of: `pending`, `accepted`, `rejected`. - `reviewed_by` (string | null): Who accepted or rejected it. - `reviewed_at` (string | null): When it was reviewed. - `payment_id` (string | null): The payment recorded when the proof was accepted. - `created_at` (string): When the client sent it. - `updated_at` (string): When it last changed. - `payment` (object): The payment that was recorded. - `id` (string): Unique id of the payment (uuid v7). - `business_id` (string): The business that received the payment. - `document_id` (string): The invoice the payment is for. - `client_id` (string): The client who paid. - `connection_id` (string | null): The payment connection used, or `null` for a manual record with no connection. - `rail` (string): Payment rail key such as `stripe` or `bank_transfer`, or `manual`. - `kind` (enum): `payment` for money received, `refund` for money sent back. One of: `payment`, `refund`. - `refund_of_payment_id` (string | null): For a refund: the payment it refunds. - `amount_minor` (string): Amount in minor units of `currency`. Always greater than 0. - `currency` (string): ISO 4217 code. - `fee_minor` (string | null): Fee the rail charged, e.g. the Stripe fee. We never take a cut of payments. - `received_at` (string): When the money was received. - `reference` (string | null): Bank or transfer reference. - `external_id` (string | null): Id at the rail, e.g. a Stripe PaymentIntent or charge id. - `source` (enum): How the payment was confirmed: a verified rail webhook, a reconcile run, a person recording it, or a verified store connector event. One of: `webhook`, `reconcile`, `manual`, `connector`. - `recorded_by` (string | null): Who recorded it. Always set for `manual` payments. - `proof_id` (string | null): The client's payment proof this record came from, if any. - `status` (enum): Refund state of the payment. One of: `succeeded`, `refunded`, `partially_refunded`. - `refunded_amount_minor` (string): How much of this payment has been refunded. - `notes` (string | null): Private notes. - `created_at` (string): When the record was created (RFC 3339, UTC). - `updated_at` (string): When the record last changed. ## Errors - `conflict` - `document_voided` - `validation_failed` - `idempotency_key_reused` Section: Payments Source: https://docs.freeinvoiceautomation.com/api-reference/payments/accept-payment-proof --- # Reject a payment proof POST https://api.freeinvoiceautomation.com/v1/payment-proofs/{id}/reject Marks a client's "I have paid" as not matching a payment. Nothing is recorded. Use this when the money did not arrive or the proof does not match. The proof becomes `rejected` with who reviewed it and when; the invoice is not changed. The action is audited. See [Payment proofs](/payments/payment-proofs). Permission: `payments.record`. Capability: `reject_payment_proof`. CLI: `freeinvoiceautomation proof reject`. ## Path parameters - `id` (string, required): Id of the payment proof. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key and body returns the first response. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Unique id of the proof. - `business_id` (string): The business the proof was sent to. - `document_id` (string): The invoice the client says they paid. - `client_id` (string): The client. - `attachment_id` (string | null): Uploaded proof file, e.g. a transfer screenshot. Stored privately. - `claimed_amount_minor` (string | null): Amount the client says they paid, in minor units. - `currency` (string | null): ISO 4217 code of the claimed amount. - `claimed_paid_on` (string | null): Date the client says they paid. - `client_note` (string | null): Message from the client. - `status` (enum): Review state. One of: `pending`, `accepted`, `rejected`. - `reviewed_by` (string | null): Who accepted or rejected it. - `reviewed_at` (string | null): When it was reviewed. - `payment_id` (string | null): The payment recorded when the proof was accepted. - `created_at` (string): When the client sent it. - `updated_at` (string): When it last changed. ## Errors - `conflict` Section: Payments Source: https://docs.freeinvoiceautomation.com/api-reference/payments/reject-payment-proof --- # List payment rails GET https://api.freeinvoiceautomation.com/v1/rails Returns every payment rail in the rails registry, with what it can do and whether it can be connected now. A rail is a way for your clients to pay: a gateway such as [Stripe](/payments/stripe) that takes the payment and confirms it by webhook, or an instruction rail such as [bank transfer](/payments/bank-transfer) or a [local QR rail](/payments/qr-rails) where the client pays outside the app and you record the payment. Use `connection_schema` to build a connect form and `status` to show which rails are available. At launch Stripe and bank transfer are `available`; UPI, Raast, JazzCash and Easypaisa ship in late January 2027 and [Whop](/payments/whop) in February 2027. We never take a cut of the payments your clients make. Permission: `rails.manage`. Capability: `list_rails`. CLI: `freeinvoiceautomation rail list`. ## Query parameters - `status` (enum): Only rails with this status. Comma-separate several values. One of: `available`, `coming_soon`, `disabled`, `dev_only`. - `currency` (string): Only rails that accept this invoice currency. ## Response - `data` (object[]): The rails. - `key` (string): Stable rail key, used as `provider` when you connect it, e.g. `stripe`, `bank_transfer`, `upi`. - `name` (string): Display name. - `kind` (enum): `gateway` takes the payment itself and confirms it by webhook (Stripe, Whop). `instruction` shows payment details and a QR code, and the business records the payment by hand (bank transfer, UPI, Raast, JazzCash, Easypaisa). One of: `gateway`, `instruction`, `link`. - `capabilities` (object): What the rail can do. - `checkout` (boolean): The rail offers a hosted checkout (card or wallet payment on the provider's page). - `partial` (boolean): The client may pay part of the balance. - `refunds` (boolean): Refunds can be started from the app. Without it, record refunds manually. - `qr` (enum): Whether the pay page shows a QR code, and whether it is generated per invoice or an uploaded merchant QR. One of: `none`, `generated`, `uploaded`. - `webhooks` (boolean): The provider sends signed webhooks that mark invoices paid. - `auto_charge` (boolean): Saved cards can be charged automatically (Stripe, from March 2027). - `currencies` (string[] | "any"): Invoice currencies the rail accepts. QR rails accept one currency (`["PKR"]` or `["INR"]`). - `seller_countries` (string[] | "any"): Business countries the rail is offered to. - `connection_schema` (object): JSON Schema of the fields [Create a connection](/api-reference/connections/create-connection) accepts for this rail, split into public and secret fields. Connect forms are generated from it. - `release` (string): When the rail ships: `L` (launch) or a release code such as `R2`. - `status` (enum): Whether the rail can be connected now. One of: `available`, `coming_soon`, `disabled`, `dev_only`. - `docs_url` (string | null): Guide page for the rail. Section: Payment rails and connections Source: https://docs.freeinvoiceautomation.com/api-reference/connections/list-rails --- # List connections GET https://api.freeinvoiceautomation.com/v1/connections Returns the connected payment rails, email senders and AI keys you can reach. What you see depends on your permissions: rail connections need `rails.manage`, sender connections (your own SMTP) need `senders.manage`, and AI key connections need `ai.manage`. Kinds you have no permission for are left out, so filter by `kind` when you only need one. Secrets are never returned. `secret_last4` shows the last 4 characters so you can tell keys apart. Results use [cursor pagination](/api-reference/pagination). Permission: `rails.manage / senders.manage / ai.manage (by kind)`. Capability: `list_connections`. CLI: `freeinvoiceautomation connection list`. ## Query parameters - `kind` (enum): Only connections of this kind. One of: `rail`, `sender`, `ai`. - `business_id` (string): Only connections of this business. - `provider` (string): Only connections of this provider, e.g. `stripe`. - `status` (enum): Only connections with this status. Comma-separate several values. One of: `pending`, `active`, `failing`, `disconnected`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The connections on this page. - `id` (string): Unique id of the connection (uuid v7). - `account_id` (string): The account that owns the connection. - `business_id` (string | null): The business the connection belongs to. Required for rails and senders; `null` for an account-level AI key. - `kind` (enum): What the connection is: a payment rail, an email sender (your own SMTP), or your own AI key. One of: `rail`, `sender`, `ai`. - `provider` (string): Registry key, e.g. `stripe`, `whop`, `bank_transfer`, `upi`, `raast`, `jazzcash`, `easypaisa`, `smtp`, `anthropic`, `openai`, `google`, `openai_compatible`. - `label` (string): Your name for the connection, e.g. `Main bank account`. - `public_config` (object | null): Non-secret settings shown on pay pages: bank details (IBAN, SWIFT, local fields), UPI ID and payee name, Raast ID, till numbers, account titles, the Stripe connected account id, the Whop company id. The fields depend on the provider's connection schema. - `secret_last4` (string | null): Last 4 characters of the stored secret, for display. The secret itself is never returned. - `qr_attachment_id` (string | null): Uploaded merchant QR image (Raast, JazzCash, Easypaisa). Upload it first with [Upload a file](/api-reference/account/upload-file). - `status` (enum): `pending` until the connection is verified or onboarding finishes, `active` when it works, `failing` when the last check failed, `disconnected` after it was deleted or revoked at the provider. One of: `pending`, `active`, `failing`, `disconnected`. - `capabilities` (object): The rail's capabilities, copied from the rails registry when the connection was made. - `checkout` (boolean): The rail offers a hosted checkout (card or wallet payment on the provider's page). - `partial` (boolean): The client may pay part of the balance. - `refunds` (boolean): Refunds can be started from the app. Without it, record refunds manually. - `qr` (enum): Whether the pay page shows a QR code, and whether it is generated per invoice or an uploaded merchant QR. One of: `none`, `generated`, `uploaded`. - `webhooks` (boolean): The provider sends signed webhooks that mark invoices paid. - `auto_charge` (boolean): Saved cards can be charged automatically (Stripe, from March 2027). - `last_verified_at` (string | null): When the connection last passed a check. Connections are also checked every night. - `last_error` (string | null): Plain-language reason of the last failed check. - `created_at` (string): When the connection was created (RFC 3339, UTC). - `updated_at` (string): When the connection was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Payment rails and connections Source: https://docs.freeinvoiceautomation.com/api-reference/connections/list-connections --- # Create a connection POST https://api.freeinvoiceautomation.com/v1/connections Connects a payment rail, your own SMTP mailbox or your own AI key. One endpoint connects every kind: bank transfer (launch), [Whop](/payments/whop) (February 2027), UPI, Raast, JazzCash and Easypaisa (late January 2027) as `kind: rail`; [your own SMTP](/messaging/smtp) as `kind: sender`; and [your own AI key](/ai/agent/own-key) (Anthropic, OpenAI, Google or OpenAI-compatible) as `kind: ai`. The fields inside `public_config` and `secrets` come from the provider's `connection_schema` in [List payment rails](/api-reference/connections/list-rails). Stripe is connected through [Start Stripe Connect](/api-reference/connections/start-stripe-connect) instead. For Raast, JazzCash and Easypaisa, upload the merchant QR image first with [Upload a file](/api-reference/account/upload-file) and pass its id as `qr_attachment_id`. QR rails are shown to clients only when the invoice currency matches (PKR or INR). Secrets (Whop API key and webhook secret, SMTP password, AI key) are sealed at rest and are write-only: they are never returned, logged or shown again. SMTP hosts pass an SSRF guard and a handshake check; AI keys are verified by a test call. Connecting a rail or adding an AI key needs a **fresh sign-in**. Creating a connection never moves money and never changes an invoice. Permission: `rails.manage / senders.manage / ai.manage`. Capability: `create_connection`. CLI: `freeinvoiceautomation connection create`. - Needs a fresh sign-in for rails and AI keys: a web session that is not fresh gets `401 fresh_signin_required`. API keys cannot perform fresh-sign-in actions, so connect rails from the app. - Send an `Idempotency-Key` header so a retried request never creates the connection twice. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `business_id` (string): Business the rail or sender belongs to. Required for rails and senders when you can reach more than one business; leave out for an account-level AI key. - `kind` (enum, required): What you are connecting. One of: `rail`, `sender`, `ai`. - `provider` (string, required): Registry key, e.g. `bank_transfer`, `upi`, `raast`, `jazzcash`, `easypaisa`, `whop`, `smtp`, `anthropic`. - `label` (string, required): Your name for the connection (1 to 100 characters). - `public_config` (object): Non-secret fields from the provider's connection schema, e.g. IBAN, SWIFT, account title, UPI ID and payee name, Raast ID, till number, SMTP host and port. - `secrets` (object): Secret fields from the connection schema, e.g. Whop API key and webhook secret, SMTP password, AI key. Write-only. - `qr_attachment_id` (string): Uploaded merchant QR image for Raast, JazzCash or Easypaisa. ## Response - `id` (string): Unique id of the connection (uuid v7). - `account_id` (string): The account that owns the connection. - `business_id` (string | null): The business the connection belongs to. Required for rails and senders; `null` for an account-level AI key. - `kind` (enum): What the connection is: a payment rail, an email sender (your own SMTP), or your own AI key. One of: `rail`, `sender`, `ai`. - `provider` (string): Registry key, e.g. `stripe`, `whop`, `bank_transfer`, `upi`, `raast`, `jazzcash`, `easypaisa`, `smtp`, `anthropic`, `openai`, `google`, `openai_compatible`. - `label` (string): Your name for the connection, e.g. `Main bank account`. - `public_config` (object | null): Non-secret settings shown on pay pages: bank details (IBAN, SWIFT, local fields), UPI ID and payee name, Raast ID, till numbers, account titles, the Stripe connected account id, the Whop company id. The fields depend on the provider's connection schema. - `secret_last4` (string | null): Last 4 characters of the stored secret, for display. The secret itself is never returned. - `qr_attachment_id` (string | null): Uploaded merchant QR image (Raast, JazzCash, Easypaisa). Upload it first with [Upload a file](/api-reference/account/upload-file). - `status` (enum): `pending` until the connection is verified or onboarding finishes, `active` when it works, `failing` when the last check failed, `disconnected` after it was deleted or revoked at the provider. One of: `pending`, `active`, `failing`, `disconnected`. - `capabilities` (object): The rail's capabilities, copied from the rails registry when the connection was made. - `checkout` (boolean): The rail offers a hosted checkout (card or wallet payment on the provider's page). - `partial` (boolean): The client may pay part of the balance. - `refunds` (boolean): Refunds can be started from the app. Without it, record refunds manually. - `qr` (enum): Whether the pay page shows a QR code, and whether it is generated per invoice or an uploaded merchant QR. One of: `none`, `generated`, `uploaded`. - `webhooks` (boolean): The provider sends signed webhooks that mark invoices paid. - `auto_charge` (boolean): Saved cards can be charged automatically (Stripe, from March 2027). - `last_verified_at` (string | null): When the connection last passed a check. Connections are also checked every night. - `last_error` (string | null): Plain-language reason of the last failed check. - `created_at` (string): When the connection was created (RFC 3339, UTC). - `updated_at` (string): When the connection was last changed. ## Errors - `fresh_signin_required` - `validation_failed` - `connection_failed` - `business_out_of_scope` - `idempotency_key_reused` Section: Payment rails and connections Source: https://docs.freeinvoiceautomation.com/api-reference/connections/create-connection --- # Start Stripe Connect POST https://api.freeinvoiceautomation.com/v1/connections/stripe/onboarding Starts Stripe Connect onboarding for a business and returns the Stripe link to open. Businesses connect their **own** Stripe account (Stripe Connect Standard). This call creates a `pending` Stripe connection and returns a Stripe-hosted onboarding link. Send the user to that link; when they finish, Stripe tells us through its signed webhook and the connection becomes `active`. A browser returning from Stripe never activates anything by itself. Payments go straight to the business's Stripe account. Clients pay on Stripe's hosted Checkout, so card data never touches our servers, and we never take a cut. See [Stripe](/payments/stripe). Permission: `rails.manage`. Capability: `start_stripe_connect`. CLI: `freeinvoiceautomation connection stripe`. - Needs a fresh sign-in. API keys cannot perform fresh-sign-in actions. - Returns `503 not_configured` if Stripe has not been set up on the platform yet. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `business_id` (string): Business to connect. May be left out when you can reach exactly one business. - `label` (string, default: Stripe): Your name for the connection. ## Response - `connection` (object): The new connection, `status: pending` until onboarding finishes. - `id` (string): Unique id of the connection (uuid v7). - `account_id` (string): The account that owns the connection. - `business_id` (string | null): The business the connection belongs to. Required for rails and senders; `null` for an account-level AI key. - `kind` (enum): What the connection is: a payment rail, an email sender (your own SMTP), or your own AI key. One of: `rail`, `sender`, `ai`. - `provider` (string): Registry key, e.g. `stripe`, `whop`, `bank_transfer`, `upi`, `raast`, `jazzcash`, `easypaisa`, `smtp`, `anthropic`, `openai`, `google`, `openai_compatible`. - `label` (string): Your name for the connection, e.g. `Main bank account`. - `public_config` (object | null): Non-secret settings shown on pay pages: bank details (IBAN, SWIFT, local fields), UPI ID and payee name, Raast ID, till numbers, account titles, the Stripe connected account id, the Whop company id. The fields depend on the provider's connection schema. - `secret_last4` (string | null): Last 4 characters of the stored secret, for display. The secret itself is never returned. - `qr_attachment_id` (string | null): Uploaded merchant QR image (Raast, JazzCash, Easypaisa). Upload it first with [Upload a file](/api-reference/account/upload-file). - `status` (enum): `pending` until the connection is verified or onboarding finishes, `active` when it works, `failing` when the last check failed, `disconnected` after it was deleted or revoked at the provider. One of: `pending`, `active`, `failing`, `disconnected`. - `capabilities` (object): The rail's capabilities, copied from the rails registry when the connection was made. - `checkout` (boolean): The rail offers a hosted checkout (card or wallet payment on the provider's page). - `partial` (boolean): The client may pay part of the balance. - `refunds` (boolean): Refunds can be started from the app. Without it, record refunds manually. - `qr` (enum): Whether the pay page shows a QR code, and whether it is generated per invoice or an uploaded merchant QR. One of: `none`, `generated`, `uploaded`. - `webhooks` (boolean): The provider sends signed webhooks that mark invoices paid. - `auto_charge` (boolean): Saved cards can be charged automatically (Stripe, from March 2027). - `last_verified_at` (string | null): When the connection last passed a check. Connections are also checked every night. - `last_error` (string | null): Plain-language reason of the last failed check. - `created_at` (string): When the connection was created (RFC 3339, UTC). - `updated_at` (string): When the connection was last changed. - `onboarding_url` (string): Stripe-hosted onboarding link. Open it in the user's browser; it expires after a short time. ## Errors - `fresh_signin_required` - `not_configured` - `upstream_error` - `business_out_of_scope` Section: Payment rails and connections Source: https://docs.freeinvoiceautomation.com/api-reference/connections/start-stripe-connect --- # Update a connection PATCH https://api.freeinvoiceautomation.com/v1/connections/{id} Changes a connection's label, public details, secrets or QR image. Partial update: send only the fields you want to change. Sending `secrets` replaces the stored secret; there is no way to read the old one. Changing details such as bank account numbers takes effect on pay pages at once, including for invoices already sent. Changing a rail connection needs a **fresh sign-in**, the same as connecting one. The permission follows the connection's kind: `rails.manage`, `senders.manage` or `ai.manage`. Permission: `rails.manage / senders.manage / ai.manage`. Capability: `update_connection`. CLI: `freeinvoiceautomation connection update`. - Needs a fresh sign-in for rail connections and AI keys. API keys cannot perform fresh-sign-in actions. ## Path parameters - `id` (string, required): Id of the connection. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `label` (string): New name. - `public_config` (object): New non-secret details. Replaces the whole object; validated against the provider's connection schema. - `secrets` (object): New secret values. Write-only. - `qr_attachment_id` (string | null): New merchant QR image, or `null` to remove it. ## Response - `id` (string): Unique id of the connection (uuid v7). - `account_id` (string): The account that owns the connection. - `business_id` (string | null): The business the connection belongs to. Required for rails and senders; `null` for an account-level AI key. - `kind` (enum): What the connection is: a payment rail, an email sender (your own SMTP), or your own AI key. One of: `rail`, `sender`, `ai`. - `provider` (string): Registry key, e.g. `stripe`, `whop`, `bank_transfer`, `upi`, `raast`, `jazzcash`, `easypaisa`, `smtp`, `anthropic`, `openai`, `google`, `openai_compatible`. - `label` (string): Your name for the connection, e.g. `Main bank account`. - `public_config` (object | null): Non-secret settings shown on pay pages: bank details (IBAN, SWIFT, local fields), UPI ID and payee name, Raast ID, till numbers, account titles, the Stripe connected account id, the Whop company id. The fields depend on the provider's connection schema. - `secret_last4` (string | null): Last 4 characters of the stored secret, for display. The secret itself is never returned. - `qr_attachment_id` (string | null): Uploaded merchant QR image (Raast, JazzCash, Easypaisa). Upload it first with [Upload a file](/api-reference/account/upload-file). - `status` (enum): `pending` until the connection is verified or onboarding finishes, `active` when it works, `failing` when the last check failed, `disconnected` after it was deleted or revoked at the provider. One of: `pending`, `active`, `failing`, `disconnected`. - `capabilities` (object): The rail's capabilities, copied from the rails registry when the connection was made. - `checkout` (boolean): The rail offers a hosted checkout (card or wallet payment on the provider's page). - `partial` (boolean): The client may pay part of the balance. - `refunds` (boolean): Refunds can be started from the app. Without it, record refunds manually. - `qr` (enum): Whether the pay page shows a QR code, and whether it is generated per invoice or an uploaded merchant QR. One of: `none`, `generated`, `uploaded`. - `webhooks` (boolean): The provider sends signed webhooks that mark invoices paid. - `auto_charge` (boolean): Saved cards can be charged automatically (Stripe, from March 2027). - `last_verified_at` (string | null): When the connection last passed a check. Connections are also checked every night. - `last_error` (string | null): Plain-language reason of the last failed check. - `created_at` (string): When the connection was created (RFC 3339, UTC). - `updated_at` (string): When the connection was last changed. ## Errors - `fresh_signin_required` - `validation_failed` - `not_found` Section: Payment rails and connections Source: https://docs.freeinvoiceautomation.com/api-reference/connections/update-connection --- # Verify a connection POST https://api.freeinvoiceautomation.com/v1/connections/{id}/verify Checks that a connection still works and updates its status. Runs the provider's check: the Stripe account status, an SMTP handshake, or a test call with your AI key. On success the connection is `active` and `last_verified_at` is set; on failure it becomes `failing` with a plain-language `last_error`. Nothing is sent to clients and no money moves. Every connection is also checked automatically each night, so you usually only need this after fixing something at the provider. Permission: `rails.manage / senders.manage / ai.manage`. Capability: `verify_connection`. CLI: `freeinvoiceautomation connection verify`. ## Path parameters - `id` (string, required): Id of the connection. ## Response - `id` (string): Unique id of the connection (uuid v7). - `account_id` (string): The account that owns the connection. - `business_id` (string | null): The business the connection belongs to. Required for rails and senders; `null` for an account-level AI key. - `kind` (enum): What the connection is: a payment rail, an email sender (your own SMTP), or your own AI key. One of: `rail`, `sender`, `ai`. - `provider` (string): Registry key, e.g. `stripe`, `whop`, `bank_transfer`, `upi`, `raast`, `jazzcash`, `easypaisa`, `smtp`, `anthropic`, `openai`, `google`, `openai_compatible`. - `label` (string): Your name for the connection, e.g. `Main bank account`. - `public_config` (object | null): Non-secret settings shown on pay pages: bank details (IBAN, SWIFT, local fields), UPI ID and payee name, Raast ID, till numbers, account titles, the Stripe connected account id, the Whop company id. The fields depend on the provider's connection schema. - `secret_last4` (string | null): Last 4 characters of the stored secret, for display. The secret itself is never returned. - `qr_attachment_id` (string | null): Uploaded merchant QR image (Raast, JazzCash, Easypaisa). Upload it first with [Upload a file](/api-reference/account/upload-file). - `status` (enum): `pending` until the connection is verified or onboarding finishes, `active` when it works, `failing` when the last check failed, `disconnected` after it was deleted or revoked at the provider. One of: `pending`, `active`, `failing`, `disconnected`. - `capabilities` (object): The rail's capabilities, copied from the rails registry when the connection was made. - `checkout` (boolean): The rail offers a hosted checkout (card or wallet payment on the provider's page). - `partial` (boolean): The client may pay part of the balance. - `refunds` (boolean): Refunds can be started from the app. Without it, record refunds manually. - `qr` (enum): Whether the pay page shows a QR code, and whether it is generated per invoice or an uploaded merchant QR. One of: `none`, `generated`, `uploaded`. - `webhooks` (boolean): The provider sends signed webhooks that mark invoices paid. - `auto_charge` (boolean): Saved cards can be charged automatically (Stripe, from March 2027). - `last_verified_at` (string | null): When the connection last passed a check. Connections are also checked every night. - `last_error` (string | null): Plain-language reason of the last failed check. - `created_at` (string): When the connection was created (RFC 3339, UTC). - `updated_at` (string): When the connection was last changed. ## Errors - `connection_failed` - `upstream_error` - `not_found` Section: Payment rails and connections Source: https://docs.freeinvoiceautomation.com/api-reference/connections/verify-connection --- # Delete a connection DELETE https://api.freeinvoiceautomation.com/v1/connections/{id} Disconnects a rail, sender or AI key. Past payments and messages are kept. The connection is soft-deleted and its status becomes `disconnected`. Stored secrets stop being used. A disconnected rail is no longer offered to clients on pay pages; payments already recorded through it stay on their invoices. A disconnected SMTP sender means sends fall back to the platform mailer while quota remains. A disconnected AI key means the agent uses credits again. This is a destructive action, so the body must contain `"confirm": true`. Without it you get `428 confirmation_required` and a preview of what would change. Permission: `rails.manage / senders.manage / ai.manage`. Capability: `delete_connection`. CLI: `freeinvoiceautomation connection delete`. ## Path parameters - `id` (string, required): Id of the connection. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true`. Without it the call changes nothing and returns `428 confirmation_required` with a preview of what would happen. See [Drafts and confirmations](/api-reference/confirmations). ## Errors - `confirmation_required` - `not_found` Section: Payment rails and connections Source: https://docs.freeinvoiceautomation.com/api-reference/connections/delete-connection --- # Set a business's payment methods PUT https://api.freeinvoiceautomation.com/v1/businesses/{id}/payment-methods Chooses which connected rails a business offers to clients, in which order, and under which rules. Replaces the business's whole list of payment methods. Each entry points to a rail connection of the same business. Methods are shown on the [pay page](/invoicing/pay-page) in `position` order, and only when their `rules` match the invoice (currency, amount or buyer country). A QR rail is shown only when the invoice currency matches the rail (PKR or INR). Connections left out of the list are not deleted; they are simply not offered. Permission: `rails.manage`. Capability: `set_payment_methods`. CLI: `freeinvoiceautomation business methods`. ## Path parameters - `id` (string, required): Id of the business. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `methods` (object[], required): The full list of methods to offer. - `connection_id` (string, required): A rail connection of this business. - `enabled` (boolean, default: true): Offer the method now. - `position` (integer, required): Order on the pay page, lowest first. - `rules` (object): Offer the method only for some invoice currencies, amounts or buyer countries. - `show_on_pay_me` (boolean, default: false): Also show it on the business's pay-me page (from February 2027). ## Response - `data` (object[]): The business's payment methods after the change. - `id` (string): Id of the payment method entry. - `business_id` (string): The business. - `connection_id` (string): The rail connection offered to clients. - `enabled` (boolean): Whether the method is shown on pay pages. - `position` (integer): Order on the pay page, lowest first. - `rules` (object | null): When to offer the method: by invoice currency, amount or buyer country. - `show_on_pay_me` (boolean): Show the method on the business's pay-me page (from February 2027). - `created_at` (string): When the entry was created. - `updated_at` (string): When the entry was last changed. ## Errors - `rail_not_connected` - `validation_failed` - `business_out_of_scope` Section: Payment rails and connections Source: https://docs.freeinvoiceautomation.com/api-reference/connections/set-payment-methods --- # List messages GET https://api.freeinvoiceautomation.com/v1/messages Returns the log of messages sent to clients, newest first. Every send is logged: documents, receipts and (from mid-January 2027) reminders. Use this to check that an email went out, which sender was used, and whether it used one of your [platform emails](/messaging/platform-email). A send is recorded once per document, event and channel, so a retried send never emails the client twice. Results use [cursor pagination](/api-reference/pagination). Permission: `documents.view`. Capability: `list_messages`. CLI: `freeinvoiceautomation message list`. ## Query parameters - `business_id` (string): Only messages of this business. - `document_id` (string): Only messages about this document. - `client_id` (string): Only messages to this client. - `status` (enum): Only messages with this status. Comma-separate several values. One of: `queued`, `sent`, `failed`, `bounced`. - `channel` (enum): Only messages on this channel. One of: `email`, `whatsapp`, `telegram`, `sms`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The messages on this page. - `id` (string): Unique id of the message. - `business_id` (string): The business that sent it. - `document_id` (string | null): The document the message was about, if any. - `client_id` (string | null): The client it was sent to. - `event` (string): Why it was sent, e.g. `document_sent`, `receipt`, or a reminder step such as `reminder:`. - `channel` (enum): Delivery channel. Email at launch; WhatsApp, Telegram and SMS from April 2027. One of: `email`, `whatsapp`, `telegram`, `sms`. - `sender` (string): Sender registry key: `platform_mail` (our mailer, counted against your monthly quota) or `smtp` (your own mailbox, unlimited). - `connection_id` (string | null): The SMTP connection used, or `null` for the platform mailer. - `to_address` (string): Recipient address. - `subject` (string | null): Subject line as sent. - `status` (enum): Delivery state. One of: `queued`, `sent`, `failed`, `bounced`. - `provider_message_id` (string | null): Message id from the mail server, for tracing. - `error` (string | null): Why the send failed or bounced. - `counted_against_quota` (boolean): `true` when it was sent through the platform mailer and used one of your monthly platform emails. - `queued_at` (string): When the send was queued. - `sent_at` (string | null): When the mail server accepted it. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Messages, templates and usage Source: https://docs.freeinvoiceautomation.com/api-reference/messaging/list-messages --- # List message templates GET https://api.freeinvoiceautomation.com/v1/message-templates Returns a business's email templates per event, channel and language. Templates set the subject and text of the messages your clients receive. Each business has its own templates; see [Message templates](/messaging/templates). Results use [cursor pagination](/api-reference/pagination). Permission: `templates.manage`. Capability: `list_message_templates`. CLI: `freeinvoiceautomation template messages`. ## Query parameters - `business_id` (string): Templates of this business. May be left out when you can reach exactly one business. - `event` (string): Only templates for this event, e.g. `document_sent`. - `channel` (enum): Only templates for this channel. One of: `email`, `whatsapp`, `telegram`, `sms`. - `language` (string): Only templates in this language. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The templates on this page. - `id` (string): Unique id of the template. - `business_id` (string): The business the template belongs to. - `event` (string): Which message it is for, e.g. `document_sent`, `reminder`, `receipt`, `credit_note_issued`. - `channel` (enum): Channel the template is written for. One of: `email`, `whatsapp`, `telegram`, `sms`. - `language` (string): Language of the text, e.g. `en`. - `subject` (string | null): Subject line (email only). - `body` (string): Message text. - `is_default` (boolean): Used for this event, channel and language unless a send picks another template. - `created_at` (string): When the template was created. - `updated_at` (string): When the template was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Messages, templates and usage Source: https://docs.freeinvoiceautomation.com/api-reference/messaging/list-message-templates --- # Create or update a message template POST https://api.freeinvoiceautomation.com/v1/message-templates Creates a message template, or updates it when the body contains the `id` of an existing one. The same capability also answers `PATCH /v1/message-templates/{id}` for partial updates. Changes apply to messages sent after the change; messages already sent keep their text in the [message log](/api-reference/messaging/list-messages). Setting `is_default: true` makes this the template used for its event, channel and language, and clears the flag on the previous default. Platform emails always go out as "{Business} via Free Invoice Automation" with your business email as Reply-To, whatever the template says. Permission: `templates.manage`. Capability: `upsert_message_template`. CLI: `freeinvoiceautomation template message-upsert`. - Send an `Idempotency-Key` header so a retried request never creates the template twice. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `id` (string): Id of an existing template to update. Leave out to create one. - `business_id` (string): Business that owns the template. May be left out when you can reach exactly one business. - `event` (string, required): Which message it is for, e.g. `document_sent`, `reminder`, `receipt`, `credit_note_issued`. - `channel` (enum, default: email): Channel. Only `email` can be sent at launch; the others from April 2027. One of: `email`, `whatsapp`, `telegram`, `sms`. - `language` (string, required): Language of the text, e.g. `en`. - `subject` (string): Subject line (email). - `body` (string, required): Message text. - `is_default` (boolean, default: false): Use this template by default for its event, channel and language. ## Response - `id` (string): Unique id of the template. - `business_id` (string): The business the template belongs to. - `event` (string): Which message it is for, e.g. `document_sent`, `reminder`, `receipt`, `credit_note_issued`. - `channel` (enum): Channel the template is written for. One of: `email`, `whatsapp`, `telegram`, `sms`. - `language` (string): Language of the text, e.g. `en`. - `subject` (string | null): Subject line (email only). - `body` (string): Message text. - `is_default` (boolean): Used for this event, channel and language unless a send picks another template. - `created_at` (string): When the template was created. - `updated_at` (string): When the template was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` - `business_out_of_scope` Section: Messages, templates and usage Source: https://docs.freeinvoiceautomation.com/api-reference/messaging/upsert-message-template --- # List PDF templates GET https://api.freeinvoiceautomation.com/v1/pdf-templates Returns a business's PDF templates. PDF templates are saved settings on top of a built-in layout: colours, fonts, labels, footer, QR placement and paper size. See [PDFs and templates](/invoicing/pdf). Results use [cursor pagination](/api-reference/pagination). Permission: `documents.view`. Capability: `list_pdf_templates`. CLI: `freeinvoiceautomation template pdfs`. ## Query parameters - `business_id` (string): Templates of this business. May be left out when you can reach exactly one business. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The templates on this page. - `id` (string): Unique id of the template. - `business_id` (string): The business the template belongs to. - `base_template_key` (string): The built-in layout it is based on, e.g. `standard`. - `name` (string): Your name for the template. - `options` (object): Layout options, colours, fonts, labels, footer, QR placement and paper size (`A4` or `Letter`). Allowed options depend on the base template. - `is_default` (boolean): Used for new documents of this business. - `created_at` (string): When the template was created. - `updated_at` (string): When the template was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Messages, templates and usage Source: https://docs.freeinvoiceautomation.com/api-reference/messaging/list-pdf-templates --- # Create or update a PDF template POST https://api.freeinvoiceautomation.com/v1/pdf-templates Creates a PDF template, or updates it when the body contains the `id` of an existing one. The same capability also answers `PATCH /v1/pdf-templates/{id}` for partial updates. Templates are plain data validated against the base template's options; they cannot contain code. Every PDF keeps its verification QR code. On Free, PDFs also show "Powered by Free Invoice Automation"; on Team they do not. Documents already sent keep the PDF they were sent with; a change applies to PDFs rendered afterwards. Permission: `templates.manage`. Capability: `upsert_pdf_template`. CLI: `freeinvoiceautomation template pdf-upsert`. - Send an `Idempotency-Key` header so a retried request never creates the template twice. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `id` (string): Id of an existing template to update. Leave out to create one. - `business_id` (string): Business that owns the template. - `base_template_key` (string, required): Built-in layout to start from, e.g. `standard`. - `name` (string, required): Your name for the template. - `options` (object): Colours, fonts, labels, footer, QR placement and paper size (`A4` or `Letter`). - `is_default` (boolean, default: false): Use for new documents of this business. ## Response - `id` (string): Unique id of the template. - `business_id` (string): The business the template belongs to. - `base_template_key` (string): The built-in layout it is based on, e.g. `standard`. - `name` (string): Your name for the template. - `options` (object): Layout options, colours, fonts, labels, footer, QR placement and paper size (`A4` or `Letter`). Allowed options depend on the base template. - `is_default` (boolean): Used for new documents of this business. - `created_at` (string): When the template was created. - `updated_at` (string): When the template was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` - `business_out_of_scope` Section: Messages, templates and usage Source: https://docs.freeinvoiceautomation.com/api-reference/messaging/upsert-pdf-template --- # Get usage GET https://api.freeinvoiceautomation.com/v1/usage Returns this month's platform emails, AI credits, API calls and storage against your plan's limits. The same numbers as **Settings > Usage**. Platform emails: Free 20 a month, Team 100; your own SMTP is not counted. AI credits: Team gets 1,000 a month (2,000 in the first paid month); Free gets 250 welcome credits in the first month after adding a [billing card](/billing/billing-card). Free credits expire at the end of the month they were given; [credit pack](/billing/credit-packs) credits expire 12 months after purchase, and free credits are spent first. Each meter has a `warning` flag that turns `true` at 80%. API calls and storage are [fair-use limits](/billing/fair-use). Monthly counters reset on the first of each month and do not roll over. See [AI credits](/billing/ai-credits). Permission: `settings.view`. Capability: `get_usage`. CLI: `freeinvoiceautomation usage`. ## Response - `period` (string): First day of the current month. Monthly counters reset on the first of each month and do not roll over. - `plan` (enum): The account's current plan. One of: `free`, `team`, `enterprise`. - `platform_emails` (object): Emails sent through the platform mailer this month. Free includes 20 a month, Team 100. Your own SMTP is unlimited and not counted. - `used` (integer): Platform emails sent this month. - `quota` (integer): Monthly quota of your plan. - `warning` (boolean): `true` once 80% of the quota is used. - `ai_credits` (object): AI credits for the invoice agent. Free credits are spent first, then the oldest pack. - `balance` (integer): Total credits you can spend now. - `free_credits` (object): Monthly credits (Team) or welcome credits (Free, and Team's first-month bonus). They expire at the end of the month they were given. - `kind` (enum): Where the credits came from. One of: `monthly`, `welcome`. - `balance` (integer): Credits left. - `expires_at` (string | null): When they expire. - `pack_credits` (object[]): Credit packs you bought, oldest first. Each expires 12 months after purchase. - `credit_pack_purchase_id` (string): The purchase. - `credits_remaining` (integer): Credits left in the pack. - `expires_at` (string): When the pack's credits expire. - `warning` (boolean): `true` once 80% of this month's credits are used. - `api_calls` (object): API calls this month against the fair-use limit. - `used` (integer): API calls used this month. - `limit` (integer): Your plan's monthly api calls limit. - `warning` (boolean): `true` once 80% of the limit is used. - `storage_bytes` (object): File storage against the fair-use limit, in bytes. - `used` (integer): Storage used this month. - `limit` (integer): Your plan's monthly storage limit. - `warning` (boolean): `true` once 80% of the limit is used. Section: Messages, templates and usage Source: https://docs.freeinvoiceautomation.com/api-reference/messaging/get-usage --- # Get settings GET https://api.freeinvoiceautomation.com/v1/settings Returns the settings that apply at one scope, with where each value came from. Settings are resolved in order: document override, client, business, account, plan, platform, then the built-in default. The first scope that has a value wins, so asking for a business returns that business's own values plus everything it inherits. `source` tells you which scope a value came from. Private settings are returned only to members with `settings.manage`. User-scope settings (such as theme and language) are your own and do not take part in document resolution. See [Settings](/account/settings). Permission: `settings.view`. Capability: `get_settings`. CLI: `freeinvoiceautomation settings get`. ## Query parameters - `scope` (enum, required): The scope to resolve. One of: `account`, `business`, `client`, `user`. - `scope_id` (string): Id of the business or client. Leave out for `account` (the current account) and `user` (yourself). - `keys` (string): Only these keys, comma-separated, e.g. `numbering.pattern.invoice,numbering.reset_policy`. ## Response - `scope` (string): The scope you asked for. - `scope_id` (string | null): The id you asked for. - `settings` (object[]): The resolved settings. - `key` (string): Setting key from the settings registry, e.g. `numbering.pattern.invoice`. - `value` (any): The value that applies at the requested scope after resolution. - `source` (enum): The scope the value came from. The first scope with a value wins, in the order document, client, business, account, plan, platform, default. One of: `document`, `client`, `business`, `account`, `plan`, `platform`, `default`. - `editable` (boolean): Whether you may set this key at the requested scope with [Update settings](/api-reference/account/update-settings). ## Errors - `business_out_of_scope` - `not_found` Section: Account, settings and files Source: https://docs.freeinvoiceautomation.com/api-reference/account/get-settings --- # Update settings PATCH https://api.freeinvoiceautomation.com/v1/settings Sets one or more settings at the account, business, client or user scope. Each key may only be set at the scopes the settings registry lists for it, and each value is validated against the key's type. Plan and platform values (prices, quotas, plan limits) cannot be changed through this endpoint. Account and business settings need `settings.manage` (Owner and Admin by default). Client-scope settings are changed by members with `clients.edit`. User-scope settings (theme, language) can always be changed by the signed-in user for themself. All keys in one request are saved together or not at all, and the change is audited. Permission: `settings.manage (user scope: self)`. Capability: `update_settings`. CLI: `freeinvoiceautomation settings set`. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `scope` (enum, required): Where to store the values. One of: `account`, `business`, `client`, `user`. - `scope_id` (string): Id of the business or client. Leave out for `account` and `user`. - `values` (object, required): Map of setting key to new value, e.g. `{ "numbering.reset_policy": "never" }`. ## Response - `scope` (string): The scope written. - `scope_id` (string | null): The id written. - `settings` (object[]): The changed keys as they now resolve. - `key` (string): Setting key from the settings registry, e.g. `numbering.pattern.invoice`. - `value` (any): The value that applies at the requested scope after resolution. - `source` (enum): The scope the value came from. The first scope with a value wins, in the order document, client, business, account, plan, platform, default. One of: `document`, `client`, `business`, `account`, `plan`, `platform`, `default`. - `editable` (boolean): Whether you may set this key at the requested scope with [Update settings](/api-reference/account/update-settings). ## Errors - `validation_failed` - `permission_denied` - `business_out_of_scope` Section: Account, settings and files Source: https://docs.freeinvoiceautomation.com/api-reference/account/update-settings --- # Get the account GET https://api.freeinvoiceautomation.com/v1/account Returns the account the request acts for. An API key or MCP token is bound to one account. A signed-in user with several accounts acts for the account chosen in the session, or the one named in the `FIA-Account-Id` header. See [Authentication](/api-reference/authentication). Permission: `settings.view`. Capability: `get_account`. CLI: `freeinvoiceautomation account get`. ## Response - `id` (string): Unique id of the account. - `name` (string): Account name, shown in the account switcher. - `plan` (enum): The plan in effect now. An account whose Team payment lapsed shows `free` after the grace period. One of: `free`, `team`, `enterprise`. - `owner_user_id` (string): The user who owns the account. - `status` (enum): Account state. One of: `active`, `suspended`, `closed`. - `country` (string | null): ISO 3166 country code used for country defaults. - `created_at` (string): When the account was created. - `updated_at` (string): When the account was last changed. Section: Account, settings and files Source: https://docs.freeinvoiceautomation.com/api-reference/account/get-account --- # Update the account PATCH https://api.freeinvoiceautomation.com/v1/account Changes the account's name or country. Partial update. The plan cannot be changed here: use [Start Team checkout](/api-reference/billing/start-team-checkout). The owner changes only through [Transfer ownership](/api-reference/account/transfer-ownership). Permission: `settings.manage`. Capability: `update_account`. CLI: `freeinvoiceautomation account update`. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `name` (string): New account name (1 to 200 characters). - `country` (string): ISO 3166 country code used for country defaults. ## Response - `id` (string): Unique id of the account. - `name` (string): Account name, shown in the account switcher. - `plan` (enum): The plan in effect now. An account whose Team payment lapsed shows `free` after the grace period. One of: `free`, `team`, `enterprise`. - `owner_user_id` (string): The user who owns the account. - `status` (enum): Account state. One of: `active`, `suspended`, `closed`. - `country` (string | null): ISO 3166 country code used for country defaults. - `created_at` (string): When the account was created. - `updated_at` (string): When the account was last changed. ## Errors - `validation_failed` - `permission_denied` Section: Account, settings and files Source: https://docs.freeinvoiceautomation.com/api-reference/account/update-account --- # Export the account POST https://api.freeinvoiceautomation.com/v1/account/exports Starts an export of everything in the account: JSON data, document PDFs and CSV files. The export runs in the background, so this call returns `202` with an export to poll at `GET /v1/account/exports/{id}`. When `status` is `ready`, `download_url` is a short-lived signed link to the archive. The archive is kept for a few days, then removed (`expired`); you can start a new export at any time. Only the Owner can export (`account.export` is an Owner-only key). Export before you [delete the account](/api-reference/account/delete-account): you keep your own tax records. See [Export your data](/account/export). Permission: `account.export`. Capability: `export_account`. CLI: `freeinvoiceautomation account export`. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Id of the export. - `requested_by` (string): The user who asked for it. - `status` (enum): Progress. Poll until `ready`. One of: `queued`, `running`, `ready`, `failed`, `expired`. - `download_url` (string | null): Short-lived signed link to the archive, present when `status` is `ready`. Call the endpoint again for a fresh link. - `expires_at` (string | null): When the archive is deleted. Ask for a new export after that. - `completed_at` (string | null): When the archive was ready. - `created_at` (string): When the export was requested. ## Errors - `permission_denied` - `rate_limited` Section: Account, settings and files Source: https://docs.freeinvoiceautomation.com/api-reference/account/export-account --- # Delete the account DELETE https://api.freeinvoiceautomation.com/v1/account Deletes the whole account: hidden at once, erased permanently after 30 days. Only the Owner can delete the account; `account.delete` cannot be given to any other role. It needs a **fresh sign-in** and `"confirm": true`. The account is soft-deleted and hidden at once: nobody can open it any more. It is erased permanently after 30 days. Connections to Stripe, QuickBooks and Xero are revoked. Deleted data can remain in encrypted backups until they age out (about 12 months) and is never restored into the live service. This deletes your invoices, clients and payment records too. Run [Export the account](/api-reference/account/export-account) first if you need them. See [Delete your account](/account/delete). Permission: `account.delete`. Capability: `delete_account`. CLI: `freeinvoiceautomation account delete`. - Needs a fresh sign-in: a web session that is not fresh gets `401 fresh_signin_required`. API keys cannot perform fresh-sign-in actions, so this is done from the app. - Owner only. Any other member gets `403 permission_denied`. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true`. Without it the call changes nothing and returns `428 confirmation_required` with a preview of what would happen. See [Drafts and confirmations](/api-reference/confirmations). ## Errors - `fresh_signin_required` - `confirmation_required` - `permission_denied` Section: Account, settings and files Source: https://docs.freeinvoiceautomation.com/api-reference/account/delete-account --- # Upload a file POST https://api.freeinvoiceautomation.com/v1/files Uploads a logo, merchant QR image, receipt, import file or proof document and returns its id. Send the file as `multipart/form-data` with the fields below. The returned id is what other endpoints take, for example `qr_attachment_id` in [Create a connection](/api-reference/connections/create-connection) or `proof_attachment_id` in [Apply for Free Team](/api-reference/billing/apply-free-team). The permission depends on what the file is for: a business logo needs `businesses.manage`, an expense receipt `expenses.manage`. Clients upload payment proofs through the pay page's own token route, not this endpoint. Files are checked by their content, not just their name. Images are re-encoded and their metadata stripped; SVG is not accepted. Logos and QR images: PNG, JPEG or WebP up to 2 MB. Receipts and proofs: PDF, PNG or JPEG (receipts also HEIC) up to 10 MB. CSV imports up to 5 MB. Files are private and served only through short-lived signed links. Permission: `the target's permission (logo: businesses.manage, receipt: expenses.manage)`. Capability: `upload_file`. CLI: `freeinvoiceautomation file upload`. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `file` (file, required): The file. - `purpose` (enum, required): What the file is for. Decides the allowed types, the size limit and the permission checked. One of: `business_logo`, `expense_receipt`, `rail_qr`, `import`, `free_team_proof`. - `business_id` (string): Business the file belongs to. Required for logos, QR images and receipts when you can reach more than one business. ## Response - `id` (string): Id of the file. Pass it where a field asks for an attachment id, e.g. `qr_attachment_id`. - `business_id` (string | null): The business the file belongs to. - `owner_type` (enum): What the file is for. One of: `business_logo`, `expense_receipt`, `rail_qr`, `import`, `free_team_proof`. - `content_type` (string): MIME type after processing. Images are re-encoded. - `size_bytes` (integer): Size after processing. - `original_name` (string | null): File name as uploaded. - `created_at` (string): When the file was uploaded. ## Errors - `validation_failed` - `permission_denied` - `plan_limit_reached` - `business_out_of_scope` Section: Account, settings and files Source: https://docs.freeinvoiceautomation.com/api-reference/account/upload-file --- # Transfer ownership POST https://api.freeinvoiceautomation.com/v1/account/transfer-ownership Makes another member of the account its Owner. Only the current Owner can transfer ownership. It is not a permission key that can be ticked on another role: the check is that the caller is on the Owner role itself. It needs a **fresh sign-in** and `"confirm": true`, and it is audited. The member you name moves onto the Owner role, which holds every permission, including billing, export and account deletion. An account always keeps at least one Owner. The new Owner must be an active member of this account. See [Transfer ownership](/account/ownership). Permission: `Owner role only`. Capability: `transfer_ownership`. CLI: `freeinvoiceautomation account transfer`. - Needs a fresh sign-in: a web session that is not fresh gets `401 fresh_signin_required`. API keys cannot perform fresh-sign-in actions, so this is done from the app. - This capability is proposed in the specification; the endpoint and body may still change before launch. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `member_id` (string, required): Id of the active membership that becomes the Owner (from [List members](/api-reference/team/list-members)). - `confirm` (boolean, required): Must be `true`. Without it the call changes nothing and returns `428 confirmation_required` with a preview of what would happen. See [Drafts and confirmations](/api-reference/confirmations). ## Response - `id` (string): Unique id of the account. - `name` (string): Account name, shown in the account switcher. - `plan` (enum): The plan in effect now. An account whose Team payment lapsed shows `free` after the grace period. One of: `free`, `team`, `enterprise`. - `owner_user_id` (string): The user who owns the account. - `status` (enum): Account state. One of: `active`, `suspended`, `closed`. - `country` (string | null): ISO 3166 country code used for country defaults. - `created_at` (string): When the account was created. - `updated_at` (string): When the account was last changed. ## Errors - `fresh_signin_required` - `confirmation_required` - `permission_denied` - `member_deactivated` - `last_owner` - `not_found` Section: Account, settings and files Source: https://docs.freeinvoiceautomation.com/api-reference/account/transfer-ownership --- # List members GET https://api.freeinvoiceautomation.com/v1/members Returns the people in the account with their role, business scope and status. Deactivated and removed members are included so you can see who used to have access; filter with `status=active` for current members only. One person can be a member of many accounts; this lists only the account the request acts for. See [Members and invitations](/team/members). Results use [cursor pagination](/api-reference/pagination). Permission: `settings.view`. Capability: `list_members`. CLI: `freeinvoiceautomation member list`. ## Query parameters - `status` (enum): Only members with this status. Comma-separate several values. One of: `active`, `deactivated`, `removed`. - `role_id` (string): Only members on this role. - `q` (string): Search by name or email. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The members on this page. - `id` (string): Id of the membership (not the user). - `account_id` (string): The account. - `user` (object): The person. One login can be a member of many accounts. - `id` (string): User id. - `name` (string): Name. - `email` (string): Sign-in email. - `role_id` (string): The member's role. Its permission keys apply on every request. - `business_scope` (enum): `all` reaches every business of the account, including ones added later; `list` reaches only `business_ids`. One of: `all`, `list`. - `business_ids` (string[] | null): The businesses reached when `business_scope` is `list`. - `is_free_accountant_seat` (boolean): `true` for the one view-only Accountant seat on the Free plan. - `status` (enum): `deactivated`: access ended, work kept, can be reactivated. `removed`: membership ended, work kept and still attributed. One of: `active`, `deactivated`, `removed`. - `deactivated_at` (string | null): When the member was deactivated. - `deactivated_reason` (enum | null): `manual` when the Owner or a member with `team.manage` did it; `downgrade` when the account moved to Free. One of: `manual`, `downgrade`. - `removed_at` (string | null): When the member was removed. - `created_at` (string): When the membership started. - `updated_at` (string): When the membership last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. Section: Members, roles and audit log Source: https://docs.freeinvoiceautomation.com/api-reference/team/list-members --- # Invite a member POST https://api.freeinvoiceautomation.com/v1/invitations Emails an invitation to join the account with a role and a business scope. The person gets an email with a single-use link. When they accept (signing in or creating a login), they become a member with the role and businesses you chose. They keep any other accounts they belong to. On **Free** the account has the Owner plus one view-only Accountant, so you can invite one person on the Accountant role; anything else needs Team (`feature_locked`). On **Team**, 5 members are included; each member above that adds $2 a month ($20 a year; regional $1 or $10) to the subscription, prorated by Stripe. See [Member seats](/billing/members). Permission: `team.manage`. Capability: `invite_member`. CLI: `freeinvoiceautomation member invite`. - Send an `Idempotency-Key` header so a retried request never sends two invitations. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `email` (string, required): Address to invite. - `role_id` (string, required): Role to give. Find ids with [List roles](/api-reference/team/list-roles). - `business_scope` (enum, default: all): Reach every business, or only `business_ids`. One of: `all`, `list`. - `business_ids` (string[]): Businesses to reach when `business_scope` is `list`. ## Response - `id` (string): Id of the invitation. - `email` (string): Address the invitation was sent to. - `role_id` (string): Role the person gets on accepting. - `business_scope` (enum): `all` reaches every business of the account, including ones added later; `list` reaches only `business_ids`. One of: `all`, `list`. - `business_ids` (string[] | null): The businesses reached when `business_scope` is `list`. - `invited_by` (string): User who sent it. - `expires_at` (string): After this the link no longer works. - `accepted_at` (string | null): When it was accepted. - `revoked_at` (string | null): When it was withdrawn. - `created_at` (string): When it was sent. ## Errors - `feature_locked` - `plan_limit_reached` - `validation_failed` - `business_out_of_scope` - `idempotency_key_reused` Section: Members, roles and audit log Source: https://docs.freeinvoiceautomation.com/api-reference/team/invite-member --- # Revoke an invitation DELETE https://api.freeinvoiceautomation.com/v1/invitations/{id} Withdraws an invitation that has not been accepted yet. The link in the email stops working at once. An invitation that was already accepted cannot be revoked: [deactivate](/api-reference/team/deactivate-member) or [remove](/api-reference/team/remove-member) the member instead. Permission: `team.manage`. Capability: `revoke_invitation`. CLI: `freeinvoiceautomation member uninvite`. ## Path parameters - `id` (string, required): Id of the invitation. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Errors - `conflict` - `not_found` Section: Members, roles and audit log Source: https://docs.freeinvoiceautomation.com/api-reference/team/revoke-invitation --- # Update a member PATCH https://api.freeinvoiceautomation.com/v1/members/{id} Changes a member's role or the businesses they can reach. Partial update. The new role and scope apply from the member's next request; permissions are read on every request, not stored in the session. API keys and MCP tokens the member created are limited to their new permissions at once. The Owner's membership cannot be moved off the Owner role here (an account always keeps one Owner); use [Transfer ownership](/api-reference/account/transfer-ownership). On Free, roles are fixed, so changing a role needs Team. The change is audited. See [Roles and permissions](/team/roles). Permission: `team.manage`. Capability: `update_member`. CLI: `freeinvoiceautomation member update`. ## Path parameters - `id` (string, required): Id of the membership. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `role_id` (string): New role. - `business_scope` (enum): New business scope. One of: `all`, `list`. - `business_ids` (string[]): Businesses to reach when `business_scope` is `list`. ## Response - `id` (string): Id of the membership (not the user). - `account_id` (string): The account. - `user` (object): The person. One login can be a member of many accounts. - `id` (string): User id. - `name` (string): Name. - `email` (string): Sign-in email. - `role_id` (string): The member's role. Its permission keys apply on every request. - `business_scope` (enum): `all` reaches every business of the account, including ones added later; `list` reaches only `business_ids`. One of: `all`, `list`. - `business_ids` (string[] | null): The businesses reached when `business_scope` is `list`. - `is_free_accountant_seat` (boolean): `true` for the one view-only Accountant seat on the Free plan. - `status` (enum): `deactivated`: access ended, work kept, can be reactivated. `removed`: membership ended, work kept and still attributed. One of: `active`, `deactivated`, `removed`. - `deactivated_at` (string | null): When the member was deactivated. - `deactivated_reason` (enum | null): `manual` when the Owner or a member with `team.manage` did it; `downgrade` when the account moved to Free. One of: `manual`, `downgrade`. - `removed_at` (string | null): When the member was removed. - `created_at` (string): When the membership started. - `updated_at` (string): When the membership last changed. ## Errors - `last_owner` - `feature_locked` - `validation_failed` - `not_found` Section: Members, roles and audit log Source: https://docs.freeinvoiceautomation.com/api-reference/team/update-member --- # Deactivate a member POST https://api.freeinvoiceautomation.com/v1/members/{id}/deactivate Ends a member's access to the account at once, keeping their work. Access is terminated immediately: the member's sessions in this account are signed out, and every API key and MCP token they hold for it is revoked. They can no longer open the account; any call they or their old keys make gets `member_deactivated` or `unauthenticated`. Their documents, comments and audit history stay. Their login still works for their other accounts. You can bring them back with [Reactivate a member](/api-reference/team/reactivate-member); their old keys stay revoked. The Owner can never be deactivated. Works on every plan. The member gets an email and the action is audited. Because access ends at once, the body must contain `"confirm": true`. See [Deactivate and remove](/team/deactivate). Permission: `team.manage`. Capability: `deactivate_member`. CLI: `freeinvoiceautomation member deactivate`. ## Path parameters - `id` (string, required): Id of the membership. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true`. Without it the call changes nothing and returns `428 confirmation_required` with a preview of what would happen. See [Drafts and confirmations](/api-reference/confirmations). ## Response - `id` (string): Id of the membership (not the user). - `account_id` (string): The account. - `user` (object): The person. One login can be a member of many accounts. - `id` (string): User id. - `name` (string): Name. - `email` (string): Sign-in email. - `role_id` (string): The member's role. Its permission keys apply on every request. - `business_scope` (enum): `all` reaches every business of the account, including ones added later; `list` reaches only `business_ids`. One of: `all`, `list`. - `business_ids` (string[] | null): The businesses reached when `business_scope` is `list`. - `is_free_accountant_seat` (boolean): `true` for the one view-only Accountant seat on the Free plan. - `status` (enum): `deactivated`: access ended, work kept, can be reactivated. `removed`: membership ended, work kept and still attributed. One of: `active`, `deactivated`, `removed`. - `deactivated_at` (string | null): When the member was deactivated. - `deactivated_reason` (enum | null): `manual` when the Owner or a member with `team.manage` did it; `downgrade` when the account moved to Free. One of: `manual`, `downgrade`. - `removed_at` (string | null): When the member was removed. - `created_at` (string): When the membership started. - `updated_at` (string): When the membership last changed. ## Errors - `confirmation_required` - `not_found` Section: Members, roles and audit log Source: https://docs.freeinvoiceautomation.com/api-reference/team/deactivate-member --- # Reactivate a member POST https://api.freeinvoiceautomation.com/v1/members/{id}/reactivate Gives a deactivated member access to the account again. The member can sign in to the account again with their role. API keys and MCP tokens revoked at deactivation stay revoked, so they create new ones. On **Free** a member can come back only into the one view-only Accountant seat, so after a downgrade the Owner picks one member to keep as Accountant. After re-upgrading to Team, deactivated members are not restored automatically: reactivate each one here (each member above the 5 included is billed as an extra seat). The member gets an email and the action is audited. Permission: `team.manage`. Capability: `reactivate_member`. CLI: `freeinvoiceautomation member reactivate`. ## Path parameters - `id` (string, required): Id of the membership. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Id of the membership (not the user). - `account_id` (string): The account. - `user` (object): The person. One login can be a member of many accounts. - `id` (string): User id. - `name` (string): Name. - `email` (string): Sign-in email. - `role_id` (string): The member's role. Its permission keys apply on every request. - `business_scope` (enum): `all` reaches every business of the account, including ones added later; `list` reaches only `business_ids`. One of: `all`, `list`. - `business_ids` (string[] | null): The businesses reached when `business_scope` is `list`. - `is_free_accountant_seat` (boolean): `true` for the one view-only Accountant seat on the Free plan. - `status` (enum): `deactivated`: access ended, work kept, can be reactivated. `removed`: membership ended, work kept and still attributed. One of: `active`, `deactivated`, `removed`. - `deactivated_at` (string | null): When the member was deactivated. - `deactivated_reason` (enum | null): `manual` when the Owner or a member with `team.manage` did it; `downgrade` when the account moved to Free. One of: `manual`, `downgrade`. - `removed_at` (string | null): When the member was removed. - `created_at` (string): When the membership started. - `updated_at` (string): When the membership last changed. ## Errors - `plan_limit_reached` - `conflict` - `not_found` Section: Members, roles and audit log Source: https://docs.freeinvoiceautomation.com/api-reference/team/reactivate-member --- # Remove a member DELETE https://api.freeinvoiceautomation.com/v1/members/{id} Ends a person's membership of the account. Their work stays and is still attributed to them. Use this when someone has left for good. Their access ends, their sessions in this account are signed out and their API keys and MCP tokens for it stop working. Documents and audit entries keep showing who did what. To give access back later, send a new [invitation](/api-reference/team/invite-member). The Owner can never be removed. The member gets an email and the action is audited. The body must contain `"confirm": true`. Permission: `team.manage`. Capability: `remove_member`. CLI: `freeinvoiceautomation member remove`. ## Path parameters - `id` (string, required): Id of the membership. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true`. Without it the call changes nothing and returns `428 confirmation_required` with a preview of what would happen. See [Drafts and confirmations](/api-reference/confirmations). ## Errors - `confirmation_required` - `not_found` Section: Members, roles and audit log Source: https://docs.freeinvoiceautomation.com/api-reference/team/remove-member --- # List roles GET https://api.freeinvoiceautomation.com/v1/roles Returns the account's roles with their permission keys. Every account has the Owner role and the presets Admin, Editor, Viewer and Accountant. Team accounts can also have custom roles. Because presets can be edited on Team, the keys returned here, not the defaults, are what applies. See [Roles and permissions](/team/roles) and [Permission keys](/api-reference/permissions). Permission: `team.manage`. Capability: `list_roles`. CLI: `freeinvoiceautomation role list`. ## Query parameters - `is_active` (boolean): Only active (or only inactive) roles. ## Response - `data` (object[]): The roles. - `id` (string): Id of the role. - `name` (string): Role name, unique in the account (case-insensitive). - `preset_key` (enum | null): Which preset the role started as, or `null` for a custom role. Presets can be reset to default. One of: `owner`, `admin`, `editor`, `viewer`, `accountant`. - `permissions` (string[]): Permission keys (`module.action`) the role holds. See [Permission keys](/api-reference/permissions). - `is_owner` (boolean): `true` only for the Owner role, which holds every key and cannot be edited or deleted. - `is_active` (boolean): `false` for custom and edited roles while the account is on Free after a downgrade. They are kept and come back on re-upgrade. - `created_at` (string): When the role was created. - `updated_at` (string): When the role last changed. Section: Members, roles and audit log Source: https://docs.freeinvoiceautomation.com/api-reference/team/list-roles --- # Create a role POST https://api.freeinvoiceautomation.com/v1/roles Creates a custom role from scratch or by duplicating an existing role. Send `permissions` to build a role from scratch, or `duplicate_of` to copy another role's keys (you can add `permissions` to replace them). Custom roles are a **Team** feature; on Free this returns `feature_locked`. Nobody can grant a key they do not hold: every key on the new role must be one you have yourself, or the call fails with `permission_not_grantable`. `account.delete` and ownership transfer belong to the Owner role only and can never be added. The change is audited. See [Custom roles](/team/custom-roles). Permission: `team.manage`. Capability: `create_role`. CLI: `freeinvoiceautomation role create`. - Send an `Idempotency-Key` header so a retried request never creates the role twice. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `name` (string, required): Role name, unique in the account (case-insensitive). - `permissions` (string[]): Permission keys for the role. Required unless `duplicate_of` is given. - `duplicate_of` (string): Copy the keys of this role. ## Response - `id` (string): Id of the role. - `name` (string): Role name, unique in the account (case-insensitive). - `preset_key` (enum | null): Which preset the role started as, or `null` for a custom role. Presets can be reset to default. One of: `owner`, `admin`, `editor`, `viewer`, `accountant`. - `permissions` (string[]): Permission keys (`module.action`) the role holds. See [Permission keys](/api-reference/permissions). - `is_owner` (boolean): `true` only for the Owner role, which holds every key and cannot be edited or deleted. - `is_active` (boolean): `false` for custom and edited roles while the account is on Free after a downgrade. They are kept and come back on re-upgrade. - `created_at` (string): When the role was created. - `updated_at` (string): When the role last changed. ## Errors - `permission_not_grantable` - `feature_locked` - `validation_failed` - `conflict` - `idempotency_key_reused` Section: Members, roles and audit log Source: https://docs.freeinvoiceautomation.com/api-reference/team/create-role --- # Update a role PATCH https://api.freeinvoiceautomation.com/v1/roles/{id} Renames a role or changes its permission keys. Sending `permissions` replaces the role's whole key list. Every member on the role, and every API key and MCP token they created, gets the new permissions on their next request. The audit entry records the old and new key lists. The Owner role cannot be edited (`role_locked`). You cannot add a key you do not hold yourself, and you cannot add `account.delete` or ownership transfer to any role (`permission_not_grantable`). Editing presets and custom roles is a **Team** feature. Permission: `team.manage`. Capability: `update_role`. CLI: `freeinvoiceautomation role update`. ## Path parameters - `id` (string, required): Id of the role. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `name` (string): New name. - `permissions` (string[]): The full new list of permission keys. ## Response - `id` (string): Id of the role. - `name` (string): Role name, unique in the account (case-insensitive). - `preset_key` (enum | null): Which preset the role started as, or `null` for a custom role. Presets can be reset to default. One of: `owner`, `admin`, `editor`, `viewer`, `accountant`. - `permissions` (string[]): Permission keys (`module.action`) the role holds. See [Permission keys](/api-reference/permissions). - `is_owner` (boolean): `true` only for the Owner role, which holds every key and cannot be edited or deleted. - `is_active` (boolean): `false` for custom and edited roles while the account is on Free after a downgrade. They are kept and come back on re-upgrade. - `created_at` (string): When the role was created. - `updated_at` (string): When the role last changed. ## Errors - `role_locked` - `permission_not_grantable` - `feature_locked` - `validation_failed` - `not_found` Section: Members, roles and audit log Source: https://docs.freeinvoiceautomation.com/api-reference/team/update-role --- # Delete a role DELETE https://api.freeinvoiceautomation.com/v1/roles/{id} Deletes a role that no member is on. Move every member on the role to another role first with [Update a member](/api-reference/team/update-member); while members are on it the call fails with `role_in_use`. The Owner role can never be deleted (`role_locked`). Presets can be deleted too, but if you only want their default keys back, use [Reset a role](/api-reference/team/reset-role). A **Team** feature. The body must contain `"confirm": true`, and the deletion is audited. Permission: `team.manage`. Capability: `delete_role`. CLI: `freeinvoiceautomation role delete`. ## Path parameters - `id` (string, required): Id of the role. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true`. Without it the call changes nothing and returns `428 confirmation_required` with a preview of what would happen. See [Drafts and confirmations](/api-reference/confirmations). ## Errors - `role_in_use` - `role_locked` - `confirmation_required` - `feature_locked` - `not_found` Section: Members, roles and audit log Source: https://docs.freeinvoiceautomation.com/api-reference/team/delete-role --- # Reset a role to default POST https://api.freeinvoiceautomation.com/v1/roles/{id}/reset Puts a preset role (Admin, Editor, Viewer, Accountant) back to its default permission keys. Only presets can be reset; custom roles have no default. The Owner role is fixed and cannot be reset (`role_locked`). The same no-escalation check applies: if the defaults include a key you do not hold, the reset fails with `permission_not_grantable`. A **Team** feature. The new keys apply on the next request of every member on the role, and the audit entry records the old and new lists. Permission: `team.manage`. Capability: `reset_role`. CLI: `freeinvoiceautomation role reset`. ## Path parameters - `id` (string, required): Id of the role. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Id of the role. - `name` (string): Role name, unique in the account (case-insensitive). - `preset_key` (enum | null): Which preset the role started as, or `null` for a custom role. Presets can be reset to default. One of: `owner`, `admin`, `editor`, `viewer`, `accountant`. - `permissions` (string[]): Permission keys (`module.action`) the role holds. See [Permission keys](/api-reference/permissions). - `is_owner` (boolean): `true` only for the Owner role, which holds every key and cannot be edited or deleted. - `is_active` (boolean): `false` for custom and edited roles while the account is on Free after a downgrade. They are kept and come back on re-upgrade. - `created_at` (string): When the role was created. - `updated_at` (string): When the role last changed. ## Errors - `role_locked` - `permission_not_grantable` - `feature_locked` - `conflict` - `not_found` Section: Members, roles and audit log Source: https://docs.freeinvoiceautomation.com/api-reference/team/reset-role --- # List the audit log GET https://api.freeinvoiceautomation.com/v1/audit-log Returns who did what in the account, newest first. Every change on every plan writes an audit entry: by members, API keys, MCP clients, the AI agent, store connectors, clients on pay pages, and automatic jobs. Entries cannot be edited or deleted. Viewing them is a **Team** feature; on Free this returns `feature_locked`, but entries are still recorded and appear if you upgrade. Results use [cursor pagination](/api-reference/pagination). See [Audit log](/team/audit-log). Permission: `audit.view`. Capability: `list_audit_log`. CLI: `freeinvoiceautomation audit list`. ## Query parameters - `business_id` (string): Only entries about this business. - `actor_type` (enum): Only entries by this kind of actor. Comma-separate several values. One of: `user`, `api_key`, `mcp_token`, `agent`, `connector`, `client`, `system`, `platform_support`. - `actor_id` (string): Only entries by this user, key, token or install. - `action` (string): Only this capability or job, e.g. `void_document`. - `target_type` (string): Only entries about this kind of object. - `target_id` (string): Only entries about this object. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The entries on this page. - `id` (string): Id of the entry. - `business_id` (string | null): The business affected, if any. - `actor_type` (enum): Who acted: a member, an API key, an MCP client, the AI agent, a store connector, a client on a pay page or portal, an automatic job, or our support team with your permission. One of: `user`, `api_key`, `mcp_token`, `agent`, `connector`, `client`, `system`, `platform_support`. - `actor_id` (string | null): Id of the user, key, token, agent run or install. - `action` (string): Capability or job name, e.g. `void_document`, `update_role`. - `target_type` (string | null): Kind of object changed, e.g. `document`. - `target_id` (string | null): Id of the object changed. - `request_id` (string | null): Request id, to match with API responses. - `ip` (string | null): IP address of the request. - `user_agent` (string | null): User agent of the request. - `changes` (object | null): Before and after of changed fields. Secrets are never included; a changed secret shows as changed only. - `reason` (string | null): Reason given, e.g. a void reason. - `created_at` (string): When it happened. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `feature_locked` - `business_out_of_scope` Section: Members, roles and audit log Source: https://docs.freeinvoiceautomation.com/api-reference/team/list-audit-log --- # List API keys GET https://api.freeinvoiceautomation.com/v1/api-keys Returns the account's API keys without their secret values. Includes keys issued to store installs (`source: connector_install`) and revoked keys, so you can review access. The key itself is never returned after creation; use `prefix` and `last_used_at` to recognise it. Results use [cursor pagination](/api-reference/pagination). See [Authentication](/api-reference/authentication). Permission: `integrations.manage`. Capability: `list_api_keys`. CLI: `freeinvoiceautomation key list`. ## Query parameters - `revoked` (boolean, default: false): Set to `true` to include revoked keys. - `mode` (enum): Only keys with this mode. One of: `draft_only`, `auto_send`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The keys on this page. - `id` (string): Id of the key. - `name` (string): Your name for the key, e.g. `Accounting export script`. - `prefix` (string): The first characters of the key, for recognising it. The full key is shown only once, when created. - `scopes` (string[]): Permission keys the key may use. At call time the key can do only what both its scopes and its creator's current permissions allow. - `mode` (enum): `draft_only` (default): sends need `"confirm": true`. `auto_send`: the key may send documents without confirmation. One of: `draft_only`, `auto_send`. - `business_ids` (string[] | null): Businesses the key is limited to, or `null` for every business its creator can reach. - `rate_limit_per_minute` (integer | null): This key's own request limit per minute, or `null` for the default. - `source` (enum): `user` for keys you created; `connector_install` for keys issued to a Shopify or WordPress install. One of: `user`, `connector_install`. - `last_used_at` (string | null): When the key last made a request. - `expires_at` (string | null): When the key stops working, or `null` for no expiry. - `revoked_at` (string | null): When the key was revoked. - `created_by` (string): The member who created the key. The key never has more permissions than this member has now. - `created_at` (string): When the key was created. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. Section: API keys, webhooks and installs Source: https://docs.freeinvoiceautomation.com/api-reference/developers/list-api-keys --- # Create an API key POST https://api.freeinvoiceautomation.com/v1/api-keys Creates an API key with scopes and a mode, and returns the key once. The response contains the full key in `key`. It is shown **only once**: we store only a hash. Send it as `Authorization: Bearer `. A key is bound to this account. `scopes` are permission keys, and a key can never hold a scope you do not have yourself. At call time the key is limited further to your **current** permissions: if your role loses a key or you leave the account, the API key loses it on its next request. `mode` decides whether the key can send. With `draft_only` (default), sends need `"confirm": true`. With `auto_send`, the key may send documents to clients without confirmation; creating an `auto_send` key needs a **fresh sign-in**. Keys are made in **Settings > API keys**. See [Authentication](/api-reference/authentication) and [Drafts and confirmations](/api-reference/confirmations). Permission: `integrations.manage`. Capability: `create_api_key`. CLI: `freeinvoiceautomation key create`. - Creating an `auto_send` key needs a fresh sign-in: a web session that is not fresh gets `401 fresh_signin_required`. API keys cannot perform fresh-sign-in actions. - Send an `Idempotency-Key` header so a retried request never creates two keys. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `name` (string, required): Your name for the key (1 to 100 characters). - `scopes` (string[], required): Permission keys the key may use, e.g. `documents.view`. Each must be one you hold. - `mode` (enum, default: draft_only): `auto_send` lets the key send without confirmation and needs a fresh sign-in. One of: `draft_only`, `auto_send`. - `business_ids` (string[]): Limit the key to these businesses. Leave out for every business you can reach. - `rate_limit_per_minute` (integer): A lower request limit per minute for this key than the default. - `expires_at` (string): When the key stops working. Leave out for no expiry. ## Response - `id` (string): Id of the key. - `name` (string): Your name for the key, e.g. `Accounting export script`. - `prefix` (string): The first characters of the key, for recognising it. The full key is shown only once, when created. - `scopes` (string[]): Permission keys the key may use. At call time the key can do only what both its scopes and its creator's current permissions allow. - `mode` (enum): `draft_only` (default): sends need `"confirm": true`. `auto_send`: the key may send documents without confirmation. One of: `draft_only`, `auto_send`. - `business_ids` (string[] | null): Businesses the key is limited to, or `null` for every business its creator can reach. - `rate_limit_per_minute` (integer | null): This key's own request limit per minute, or `null` for the default. - `source` (enum): `user` for keys you created; `connector_install` for keys issued to a Shopify or WordPress install. One of: `user`, `connector_install`. - `last_used_at` (string | null): When the key last made a request. - `expires_at` (string | null): When the key stops working, or `null` for no expiry. - `revoked_at` (string | null): When the key was revoked. - `created_by` (string): The member who created the key. The key never has more permissions than this member has now. - `created_at` (string): When the key was created. - `key` (string): The full API key. Returned only in this response; store it safely. ## Errors - `fresh_signin_required` - `validation_failed` - `business_out_of_scope` - `idempotency_key_reused` Section: API keys, webhooks and installs Source: https://docs.freeinvoiceautomation.com/api-reference/developers/create-api-key --- # Revoke an API key DELETE https://api.freeinvoiceautomation.com/v1/api-keys/{id} Stops an API key from working, at once and for good. Any request with the key fails with `unauthenticated` from now on. A revoked key cannot be restored: create a new one. Revoking a key issued to a store install stops that store from creating documents; [unlink the install](/api-reference/developers/unlink-connector-install) instead if you mean to disconnect the store. The body must contain `"confirm": true`. The revocation is audited. Permission: `integrations.manage`. Capability: `revoke_api_key`. CLI: `freeinvoiceautomation key revoke`. ## Path parameters - `id` (string, required): Id of the API key. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true`. Without it the call changes nothing and returns `428 confirmation_required` with a preview of what would happen. See [Drafts and confirmations](/api-reference/confirmations). ## Errors - `confirmation_required` - `not_found` Section: API keys, webhooks and installs Source: https://docs.freeinvoiceautomation.com/api-reference/developers/revoke-api-key --- # List webhook endpoints GET https://api.freeinvoiceautomation.com/v1/webhook-endpoints Returns the URLs that receive signed event notifications from your account. Each endpoint chooses its events and can be limited to one business. Signing secrets are never returned; `secret_last4` helps you match them. See [Webhooks](/integrations/webhooks). Permission: `integrations.manage`. Capability: `list_webhook_endpoints`. CLI: `freeinvoiceautomation webhook list`. ## Query parameters - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The endpoints on this page. - `id` (string): Id of the endpoint. - `business_id` (string | null): Only events of this business, or `null` for every business. - `url` (string): HTTPS URL that receives the events. - `events` (string[]): Event types sent to this endpoint, e.g. `document.sent`, `payment.succeeded`. See [Webhook events](/api-reference/webhook-events). - `secret_last4` (string): Last 4 characters of the signing secret. - `status` (enum): `disabled` endpoints receive nothing. An endpoint that keeps failing is disabled automatically. One of: `active`, `disabled`. - `consecutive_failures` (integer): Deliveries in a row that failed. - `created_at` (string): When the endpoint was created. - `updated_at` (string): When the endpoint was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. Section: API keys, webhooks and installs Source: https://docs.freeinvoiceautomation.com/api-reference/developers/list-webhook-endpoints --- # Create or update a webhook endpoint POST https://api.freeinvoiceautomation.com/v1/webhook-endpoints Creates a webhook endpoint, or updates it when the body contains the `id` of an existing one. The same capability also answers `PATCH /v1/webhook-endpoints/{id}` for partial updates. On create, the response contains the signing secret in `secret`, **once**. Each delivery is a `POST` with a JSON body and an `FIA-Signature` header: `t=,v1=`, where `v1` is HMAC-SHA256 of `.` with your secret. Compare in constant time and reject timestamps older than 5 minutes. URLs must be HTTPS on port 443 and may not point to private or internal addresses; redirects are not followed. Failed deliveries are retried with backoff for about 24 hours. Order is not guaranteed, and the event `id` is the same on every retry. See [Webhooks](/integrations/webhooks) and [Webhook events](/api-reference/webhook-events). Permission: `integrations.manage`. Capability: `upsert_webhook_endpoint`. CLI: `freeinvoiceautomation webhook upsert`. - Send an `Idempotency-Key` header so a retried request never creates the endpoint twice. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `id` (string): Id of an existing endpoint to update. Leave out to create one. - `url` (string, required): HTTPS URL to deliver to. - `events` (string[], required): Event types to receive. - `business_id` (string | null): Only events of this business. Leave out or `null` for every business. - `status` (enum): Set `disabled` to pause deliveries, `active` to resume. One of: `active`, `disabled`. ## Response - `id` (string): Id of the endpoint. - `business_id` (string | null): Only events of this business, or `null` for every business. - `url` (string): HTTPS URL that receives the events. - `events` (string[]): Event types sent to this endpoint, e.g. `document.sent`, `payment.succeeded`. See [Webhook events](/api-reference/webhook-events). - `secret_last4` (string): Last 4 characters of the signing secret. - `status` (enum): `disabled` endpoints receive nothing. An endpoint that keeps failing is disabled automatically. One of: `active`, `disabled`. - `consecutive_failures` (integer): Deliveries in a row that failed. - `created_at` (string): When the endpoint was created. - `updated_at` (string): When the endpoint was last changed. - `secret` (string): The signing secret. Returned only when the endpoint is created. ## Errors - `validation_failed` - `business_out_of_scope` - `idempotency_key_reused` Section: API keys, webhooks and installs Source: https://docs.freeinvoiceautomation.com/api-reference/developers/upsert-webhook-endpoint --- # Delete a webhook endpoint DELETE https://api.freeinvoiceautomation.com/v1/webhook-endpoints/{id} Stops sending events to an endpoint and removes it. No new deliveries are made to the endpoint. To pause deliveries for a while instead, update it with `status: disabled`. Permission: `integrations.manage`. Capability: `delete_webhook_endpoint`. CLI: `freeinvoiceautomation webhook delete`. ## Path parameters - `id` (string, required): Id of the webhook endpoint. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Errors - `not_found` Section: API keys, webhooks and installs Source: https://docs.freeinvoiceautomation.com/api-reference/developers/delete-webhook-endpoint --- # Test a webhook endpoint POST https://api.freeinvoiceautomation.com/v1/webhook-endpoints/{id}/test Sends a signed test delivery to the endpoint and returns what happened. Use it to check your URL and signature verification before real events arrive. The test is signed exactly like real deliveries and appears in the endpoint's [delivery log](/api-reference/developers/list-webhook-deliveries). Nothing in your account changes. Permission: `integrations.manage`. Capability: `test_webhook_endpoint`. CLI: `freeinvoiceautomation webhook test`. ## Path parameters - `id` (string, required): Id of the webhook endpoint. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Id of the delivery. - `endpoint_id` (string): The endpoint it was sent to. - `event_id` (string): Id of the event. The same on every retry and redelivery, so receivers can ignore duplicates. - `event_type` (string): Event type, e.g. `document.paid`. - `payload` (object): The JSON body that was sent. - `status` (enum): `failed` is retried with exponential backoff; after the last attempt (about 24 hours) it becomes `dead` and can be redelivered by hand. One of: `pending`, `succeeded`, `failed`, `dead`. - `attempts` (integer): Attempts made so far. - `last_response_status` (integer | null): HTTP status your endpoint returned last time. - `next_attempt_at` (string | null): When the next retry is due. - `delivered_at` (string | null): When your endpoint accepted it with a 2xx response. - `created_at` (string): When the delivery was created. ## Errors - `connection_failed` - `not_found` Section: API keys, webhooks and installs Source: https://docs.freeinvoiceautomation.com/api-reference/developers/test-webhook-endpoint --- # List webhook deliveries GET https://api.freeinvoiceautomation.com/v1/webhook-endpoints/{id}/deliveries Returns the delivery log of one endpoint, newest first. Shows each event sent, how many attempts it took, your endpoint's last HTTP status, and whether it is still being retried. Deliveries that ran out of attempts are `dead`; send them again with [Redeliver a webhook](/api-reference/developers/redeliver-webhook). Results use [cursor pagination](/api-reference/pagination). Permission: `integrations.manage`. Capability: `list_webhook_deliveries`. CLI: `freeinvoiceautomation webhook deliveries`. ## Path parameters - `id` (string, required): Id of the webhook endpoint. ## Query parameters - `status` (enum): Only deliveries with this status. Comma-separate several values. One of: `pending`, `succeeded`, `failed`, `dead`. - `event_type` (string): Only deliveries of this event type. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The deliveries on this page. - `id` (string): Id of the delivery. - `endpoint_id` (string): The endpoint it was sent to. - `event_id` (string): Id of the event. The same on every retry and redelivery, so receivers can ignore duplicates. - `event_type` (string): Event type, e.g. `document.paid`. - `payload` (object): The JSON body that was sent. - `status` (enum): `failed` is retried with exponential backoff; after the last attempt (about 24 hours) it becomes `dead` and can be redelivered by hand. One of: `pending`, `succeeded`, `failed`, `dead`. - `attempts` (integer): Attempts made so far. - `last_response_status` (integer | null): HTTP status your endpoint returned last time. - `next_attempt_at` (string | null): When the next retry is due. - `delivered_at` (string | null): When your endpoint accepted it with a 2xx response. - `created_at` (string): When the delivery was created. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `not_found` Section: API keys, webhooks and installs Source: https://docs.freeinvoiceautomation.com/api-reference/developers/list-webhook-deliveries --- # Redeliver a webhook POST https://api.freeinvoiceautomation.com/v1/webhook-deliveries/{id}/redeliver Sends a past delivery again, with the same event id. The payload and event `id` are unchanged, so a receiver that already processed the event can recognise and skip it. Useful after fixing an endpoint whose deliveries became `dead`. Permission: `integrations.manage`. Capability: `redeliver_webhook`. CLI: `freeinvoiceautomation webhook redeliver`. ## Path parameters - `id` (string, required): Id of the delivery. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Id of the delivery. - `endpoint_id` (string): The endpoint it was sent to. - `event_id` (string): Id of the event. The same on every retry and redelivery, so receivers can ignore duplicates. - `event_type` (string): Event type, e.g. `document.paid`. - `payload` (object): The JSON body that was sent. - `status` (enum): `failed` is retried with exponential backoff; after the last attempt (about 24 hours) it becomes `dead` and can be redelivered by hand. One of: `pending`, `succeeded`, `failed`, `dead`. - `attempts` (integer): Attempts made so far. - `last_response_status` (integer | null): HTTP status your endpoint returned last time. - `next_attempt_at` (string | null): When the next retry is due. - `delivered_at` (string | null): When your endpoint accepted it with a 2xx response. - `created_at` (string): When the delivery was created. ## Errors - `not_found` Section: API keys, webhooks and installs Source: https://docs.freeinvoiceautomation.com/api-reference/developers/redeliver-webhook --- # Link a connector install POST https://api.freeinvoiceautomation.com/v1/connector-installs Links a Shopify shop or WordPress site to a business. Called by the [Shopify app](/integrations/shopify) and the [WordPress plugin](/integrations/woocommerce) during setup, after you sign in and pick a business. The install gets its own API key (`source: connector_install`) and a webhook secret. The WordPress plugin signs each call with that secret, and only a verified store "paid" event is recorded as a payment. Store orders create documents in your business with numbers from your own gap-free sequence. Mirroring a store cancel or refund (void, credit note) happens automatically and is audited with actor `connector`. Sending documents to the store's customer stays off until you turn on "send automatically". Permission: `integrations.manage`. Capability: `link_connector_install`. Available from the week of 25 January 2027. - Send an `Idempotency-Key` header so a retried request never links the store twice. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `connector` (enum, required): Which connector. One of: `shopify`, `wordpress`. - `external_id` (string, required): Shop domain (Shopify) or site URL (WordPress). - `business_id` (string, required): Business that receives the store's documents. - `settings` (object): Initial settings. `send_to_customer` cannot be turned on here. - `events` (string[]): Store events that create or change documents. Shopify: `orders/create`, `orders/paid`, `orders/cancelled`, `refunds/create`. WooCommerce: `processing`, `completed`, `refunded`, `cancelled`. - `document_type` (enum): What an order creates. One of: `invoice`, `receipt`. - `numbering_prefix` (string): Prefix for numbers of documents created by the store. Numbers still come from the business's own gap-free sequence, never from store order numbers. - `tax_mapping` (object): How store taxes map to your tax rates. ## Response - `id` (string): Id of the install. - `business_id` (string): The business the store is linked to. - `connector` (enum): Which connector. One of: `shopify`, `wordpress`. - `external_id` (string): Shop domain (Shopify) or site URL (WordPress). - `api_key_id` (string): The API key issued to the install (`source: connector_install`). - `settings` (object): What the store does. - `events` (string[]): Store events that create or change documents. Shopify: `orders/create`, `orders/paid`, `orders/cancelled`, `refunds/create`. WooCommerce: `processing`, `completed`, `refunded`, `cancelled`. - `document_type` (enum): What an order creates. One of: `invoice`, `receipt`. - `numbering_prefix` (string): Prefix for numbers of documents created by the store. Numbers still come from the business's own gap-free sequence, never from store order numbers. - `send_to_customer` (boolean): "Send automatically" to the store's customer. Off by default: documents stay drafts until someone sends them. Turning it on needs a fresh sign-in. - `tax_mapping` (object): How store taxes map to your tax rates. - `status` (enum): Install state. One of: `active`, `uninstalled`, `suspended`. - `installed_at` (string): When the store was linked. - `uninstalled_at` (string | null): When it was unlinked. - `last_seen_at` (string | null): When the store last called the API. - `created_at` (string): When the install was created. - `updated_at` (string): When the install was last changed. ## Errors - `conflict` - `validation_failed` - `business_out_of_scope` - `idempotency_key_reused` Section: API keys, webhooks and installs Source: https://docs.freeinvoiceautomation.com/api-reference/developers/link-connector-install --- # List connector installs GET https://api.freeinvoiceautomation.com/v1/connector-installs Returns the Shopify shops and WordPress sites linked to the account. The same list as the connected stores in **Settings > Integrations**. Includes unlinked installs so you can see history; filter with `status=active`. Results use [cursor pagination](/api-reference/pagination). Permission: `integrations.manage`. Capability: `list_connector_installs`. CLI: `freeinvoiceautomation connector list`. Available from the week of 25 January 2027. ## Query parameters - `connector` (enum): Only installs of this connector. One of: `shopify`, `wordpress`. - `business_id` (string): Only installs linked to this business. - `status` (enum): Only installs with this status. One of: `active`, `uninstalled`, `suspended`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The installs on this page. - `id` (string): Id of the install. - `business_id` (string): The business the store is linked to. - `connector` (enum): Which connector. One of: `shopify`, `wordpress`. - `external_id` (string): Shop domain (Shopify) or site URL (WordPress). - `api_key_id` (string): The API key issued to the install (`source: connector_install`). - `settings` (object): What the store does. - `events` (string[]): Store events that create or change documents. Shopify: `orders/create`, `orders/paid`, `orders/cancelled`, `refunds/create`. WooCommerce: `processing`, `completed`, `refunded`, `cancelled`. - `document_type` (enum): What an order creates. One of: `invoice`, `receipt`. - `numbering_prefix` (string): Prefix for numbers of documents created by the store. Numbers still come from the business's own gap-free sequence, never from store order numbers. - `send_to_customer` (boolean): "Send automatically" to the store's customer. Off by default: documents stay drafts until someone sends them. Turning it on needs a fresh sign-in. - `tax_mapping` (object): How store taxes map to your tax rates. - `status` (enum): Install state. One of: `active`, `uninstalled`, `suspended`. - `installed_at` (string): When the store was linked. - `uninstalled_at` (string | null): When it was unlinked. - `last_seen_at` (string | null): When the store last called the API. - `created_at` (string): When the install was created. - `updated_at` (string): When the install was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. Section: API keys, webhooks and installs Source: https://docs.freeinvoiceautomation.com/api-reference/developers/list-connector-installs --- # Update a connector install PATCH https://api.freeinvoiceautomation.com/v1/connector-installs/{id} Changes what a linked store does: events, document type, numbering prefix, tax mapping and "send automatically". Partial update of `settings`. Turning on `send_to_customer` ("send automatically") lets documents created from store orders be sent to the store's customer without anyone confirming them, so it needs a **fresh sign-in**, like creating an `auto_send` API key. Turning it off needs none. Permission: `integrations.manage`. Capability: `update_connector_install`. Available from the week of 25 January 2027. - Turning on `send_to_customer` needs a fresh sign-in: a web session that is not fresh gets `401 fresh_signin_required`. API keys cannot perform fresh-sign-in actions. ## Path parameters - `id` (string, required): Id of the connector install. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `settings` (object, required): Settings to change. - `events` (string[]): Store events that create or change documents. Shopify: `orders/create`, `orders/paid`, `orders/cancelled`, `refunds/create`. WooCommerce: `processing`, `completed`, `refunded`, `cancelled`. - `document_type` (enum): What an order creates. One of: `invoice`, `receipt`. - `numbering_prefix` (string): Prefix for numbers of documents created by the store. Numbers still come from the business's own gap-free sequence, never from store order numbers. - `send_to_customer` (boolean): "Send automatically" to the store's customer. Off by default: documents stay drafts until someone sends them. Turning it on needs a fresh sign-in. - `tax_mapping` (object): How store taxes map to your tax rates. ## Response - `id` (string): Id of the install. - `business_id` (string): The business the store is linked to. - `connector` (enum): Which connector. One of: `shopify`, `wordpress`. - `external_id` (string): Shop domain (Shopify) or site URL (WordPress). - `api_key_id` (string): The API key issued to the install (`source: connector_install`). - `settings` (object): What the store does. - `events` (string[]): Store events that create or change documents. Shopify: `orders/create`, `orders/paid`, `orders/cancelled`, `refunds/create`. WooCommerce: `processing`, `completed`, `refunded`, `cancelled`. - `document_type` (enum): What an order creates. One of: `invoice`, `receipt`. - `numbering_prefix` (string): Prefix for numbers of documents created by the store. Numbers still come from the business's own gap-free sequence, never from store order numbers. - `send_to_customer` (boolean): "Send automatically" to the store's customer. Off by default: documents stay drafts until someone sends them. Turning it on needs a fresh sign-in. - `tax_mapping` (object): How store taxes map to your tax rates. - `status` (enum): Install state. One of: `active`, `uninstalled`, `suspended`. - `installed_at` (string): When the store was linked. - `uninstalled_at` (string | null): When it was unlinked. - `last_seen_at` (string | null): When the store last called the API. - `created_at` (string): When the install was created. - `updated_at` (string): When the install was last changed. ## Errors - `fresh_signin_required` - `validation_failed` - `not_found` Section: API keys, webhooks and installs Source: https://docs.freeinvoiceautomation.com/api-reference/developers/update-connector-install --- # Unlink a connector install DELETE https://api.freeinvoiceautomation.com/v1/connector-installs/{id} Disconnects a Shopify shop or WordPress site from the account. The install becomes `uninstalled` and the store stops creating documents. Documents and payments it already created stay in your business. Uninstalling the Shopify app from your shop does the same. The body must contain `"confirm": true`, and the action is audited. Permission: `integrations.manage`. Capability: `unlink_connector_install`. CLI: `freeinvoiceautomation connector unlink`. Available from the week of 25 January 2027. ## Path parameters - `id` (string, required): Id of the connector install. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true`. Without it the call changes nothing and returns `428 confirmation_required` with a preview of what would happen. See [Drafts and confirmations](/api-reference/confirmations). ## Errors - `confirmation_required` - `not_found` Section: API keys, webhooks and installs Source: https://docs.freeinvoiceautomation.com/api-reference/developers/unlink-connector-install --- # Get billing GET https://api.freeinvoiceautomation.com/v1/billing Returns the account's plan, Team subscription state, seats and billing card. Use it to show the plan, when the paid period ends, and whether a renewal has failed. When `status` is `past_due`, Team keeps working until `grace_ends_at` (5 days after the failed renewal); if it is still unpaid then, the account moves to Free. Nothing is deleted on a downgrade and your clients' pay pages keep working. See [Failed renewal and downgrade](/billing/failed-renewal). All values come from Stripe's signed webhooks, never from a browser redirect. Only members with `billing.manage` (the Owner by default) can read billing. Permission: `billing.manage`. Capability: `get_billing`. CLI: `freeinvoiceautomation billing get`. ## Response - `account_id` (string): The account. - `plan` (enum): The plan in effect now. One of: `free`, `team`, `enterprise`. - `status` (enum): `none`: never paid. `active`: Team is paid. `past_due`: a renewal failed and the grace period is running; Team keeps working. `canceled`: back on Free after the grace period, a cancellation or a money-back refund. One of: `none`, `active`, `past_due`, `canceled`. - `price_tier` (enum | null): Which Team price applies, from the billing country. One of: `standard`, `regional`. - `billing_country` (string | null): Billing country from Stripe Checkout (ISO 3166). - `seats_included` (integer | null): Members included in the plan (Team: 5). - `extra_seats` (integer): Paid members above the included ones. - `current_period_end` (string | null): End of the period already paid. - `cancel_at_period_end` (boolean): `true` when Team was cancelled and ends at `current_period_end`. - `past_due_since` (string | null): When the current failed renewal started. - `grace_ends_at` (string | null): When the grace period ends. If the renewal is still unpaid then, the account moves to Free. - `first_paid_at` (string | null): First Team payment. The 30-day money-back window starts here. - `downgraded_at` (string | null): When the account last moved from Team to Free. - `downgrade_reason` (enum | null): Why it moved to Free. One of: `grace_expired`, `canceled`, `money_back`. - `card` (object | null): The billing card saved for the Free welcome credits, if any. Never charged. - `brand` (string): Card brand, e.g. `visa`. - `last4` (string): Last 4 digits. - `verified_at` (string): When the $0 verification succeeded. - `status` (enum): Card state. One of: `active`, `removed`. Section: Billing Source: https://docs.freeinvoiceautomation.com/api-reference/billing/get-billing --- # Start Team checkout POST https://api.freeinvoiceautomation.com/v1/billing/checkout Creates a Stripe Checkout session for the Team plan and returns its URL. Open the returned `url` in the user's browser. Team costs $10 a month or $96 a year, with 5 members included and $2 a month ($20 a year) for each extra member. Buyers whose billing country is on the regional list pay $5 a month or $48 a year ($1 or $10 per extra member); the price is chosen from the billing country, and if the card's country does not match the billing address, the standard price applies. There is no trial. The first Team payment has a [30-day money-back guarantee](/billing/money-back). The account becomes Team only when Stripe's signed webhook confirms the payment, never because the browser came back from Checkout. Needs a **fresh sign-in**. See [Upgrade to Team](/billing/team). Permission: `billing.manage`. Capability: `start_team_checkout`. - Needs a fresh sign-in: a web session that is not fresh gets `401 fresh_signin_required`. API keys cannot perform fresh-sign-in actions, so this is done from the app. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `interval` (enum, required): Billing interval. One of: `month`, `year`. - `extra_members` (integer, default: 0): Members to pay for above the 5 included. ## Response - `url` (string): Stripe Checkout page. It expires after a short time; start a new checkout if it does. ## Errors - `fresh_signin_required` - `validation_failed` - `not_configured` - `upstream_error` Section: Billing Source: https://docs.freeinvoiceautomation.com/api-reference/billing/start-team-checkout --- # Open the billing portal POST https://api.freeinvoiceautomation.com/v1/billing/portal Returns a link to the Stripe Customer Portal to cancel Team, change the card or download invoices. This is also where you update the card after a failed renewal. If you cancel, Team stays until the end of the period already paid and then the account moves to Free, with no grace period. Needs a **fresh sign-in**. Permission: `billing.manage`. Capability: `open_billing_portal`. - Needs a fresh sign-in: a web session that is not fresh gets `401 fresh_signin_required`. API keys cannot perform fresh-sign-in actions, so this is done from the app. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Response - `url` (string): Stripe Customer Portal link, valid for a short time. ## Errors - `fresh_signin_required` - `upstream_error` Section: Billing Source: https://docs.freeinvoiceautomation.com/api-reference/billing/open-billing-portal --- # Add a billing card POST https://api.freeinvoiceautomation.com/v1/billing/card Starts a $0 card verification that unlocks the Free plan's 250 welcome AI credits. The card is never charged. Returns the `client_secret` of a Stripe SetupIntent. Collect the card with Stripe Elements using that secret; card details go straight to Stripe and never reach our servers. Stripe verifies the card for $0. When Stripe's signed webhook confirms it, we keep only the Stripe ids, brand, last 4, fingerprint and funding type, and grant the 250 welcome credits for the first month after sign-up. The card is **never charged**: not for credit packs and not automatically. One welcome grant per card and per account. **Prepaid cards** cannot unlock the welcome credits and are refused with `card_not_eligible`; they can still pay for [credit packs](/billing/credit-packs). A card that already unlocked credits on another account, or an account past its first month, gets `welcome_credits_not_eligible`. Needs a verified email and a **fresh sign-in**. Verifications are rate limited per account and per IP to stop card testing. See [Billing card](/billing/billing-card). Permission: `billing.manage`. Capability: `add_billing_card`. CLI: `freeinvoiceautomation billing card-add`. - Needs a fresh sign-in: a web session that is not fresh gets `401 fresh_signin_required`. API keys cannot perform fresh-sign-in actions, so this is done from the app. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Response - `client_secret` (string): Stripe SetupIntent client secret, for Stripe Elements in the browser. Use it once. ## Errors - `fresh_signin_required` - `email_not_verified` - `card_not_eligible` - `welcome_credits_not_eligible` - `rate_limited` Section: Billing Source: https://docs.freeinvoiceautomation.com/api-reference/billing/add-billing-card --- # Remove the billing card DELETE https://api.freeinvoiceautomation.com/v1/billing/card Detaches the billing card at Stripe. Credits already granted stay until they expire. Removing and adding a card again does not give a second welcome grant. The body must contain `"confirm": true`. Permission: `billing.manage`. Capability: `remove_billing_card`. CLI: `freeinvoiceautomation billing card-remove`. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true`. Without it the call changes nothing and returns `428 confirmation_required` with a preview of what would happen. See [Drafts and confirmations](/api-reference/confirmations). ## Errors - `confirmation_required` - `not_found` Section: Billing Source: https://docs.freeinvoiceautomation.com/api-reference/billing/remove-billing-card --- # Buy a credit pack POST https://api.freeinvoiceautomation.com/v1/billing/credit-packs Creates a Stripe Checkout session for one pack of 1,000 AI credits ($5) and returns its URL. Call this only when the user clicks "Buy 1,000 credits" (in **Settings > Usage**, or in the agent when credits run out). The user pays on Stripe Checkout; any card works, including prepaid cards. We **never charge automatically**: there is no auto top-up and no charge to a saved card. The purchase stays `pending` until Stripe's signed webhook confirms the payment; only then are the credits added. Returning from Checkout does not add credits. Pack credits expire 12 months after purchase and are spent after your free credits. Available on Free and Team. Needs a **fresh sign-in**. See [Credit packs](/billing/credit-packs). Permission: `billing.manage`. Capability: `buy_credit_pack`. CLI: `freeinvoiceautomation credits buy`. - Needs a fresh sign-in: a web session that is not fresh gets `401 fresh_signin_required`. API keys cannot perform fresh-sign-in actions, so this is done from the app. - The CLI prints the Checkout URL for you to open. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Id of the purchase. - `credits` (integer): Credits in the pack (1,000). - `amount_minor` (string): Price paid in minor units, e.g. `"500"` = 5.00 USD. - `currency` (string): Currency of the price (`USD`). - `status` (enum): `pending` until Stripe confirms the payment; credits are added only on `succeeded`. One of: `pending`, `succeeded`, `failed`, `refunded`. - `credits_remaining` (integer): Credits left. Packs are spent oldest first, after free credits. - `expires_at` (string | null): 12 months after the payment succeeded. - `created_at` (string): When the purchase was started. - `url` (string): Stripe Checkout page for this pack. ## Errors - `fresh_signin_required` - `rate_limited` - `not_configured` - `upstream_error` Section: Billing Source: https://docs.freeinvoiceautomation.com/api-reference/billing/buy-credit-pack --- # List credit packs GET https://api.freeinvoiceautomation.com/v1/billing/credit-packs Returns the credit packs bought on this account, with credits left and expiry dates. Shows every purchase, including pending and refunded ones. A refunded pack loses its unused credits. For the current balance across all credits, use [Get usage](/api-reference/messaging/get-usage). See [AI credits](/billing/ai-credits). Permission: `billing.manage`. Capability: `list_credit_packs`. CLI: `freeinvoiceautomation credits packs`. ## Query parameters - `status` (enum): Only purchases with this status. One of: `pending`, `succeeded`, `failed`, `refunded`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The purchases on this page. - `id` (string): Id of the purchase. - `credits` (integer): Credits in the pack (1,000). - `amount_minor` (string): Price paid in minor units, e.g. `"500"` = 5.00 USD. - `currency` (string): Currency of the price (`USD`). - `status` (enum): `pending` until Stripe confirms the payment; credits are added only on `succeeded`. One of: `pending`, `succeeded`, `failed`, `refunded`. - `credits_remaining` (integer): Credits left. Packs are spent oldest first, after free credits. - `expires_at` (string | null): 12 months after the payment succeeded. - `created_at` (string): When the purchase was started. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. Section: Billing Source: https://docs.freeinvoiceautomation.com/api-reference/billing/list-credit-packs --- # Apply for Free Team POST https://api.freeinvoiceautomation.com/v1/free-team-applications Applies for the Team plan at no charge for a verified non-profit, education organisation or student. Upload your proof first with [Upload a file](/api-reference/account/upload-file) (`purpose: free_team_proof`) and pass its id. The application is reviewed by our team; when it is approved, the account gets Team at no charge. You can see the result in `status` and `review_note`. Permission: `billing.manage`. Capability: `apply_free_team`. Available from a later release. - Send an `Idempotency-Key` header so a retried request never creates two applications. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. Required for API-key and MCP calls that create something; optional otherwise. See [Idempotency](/api-reference/idempotency). ## Body - `org_type` (enum, required): Kind of organisation. One of: `non_profit`, `education`, `student`. - `proof_attachment_id` (string, required): Id of the uploaded proof. ## Response - `id` (string): Id of the application. - `org_type` (enum): Kind of organisation. One of: `non_profit`, `education`, `student`. - `proof_attachment_id` (string): The proof document you uploaded. - `status` (enum): Review state. One of: `pending`, `approved`, `rejected`. - `reviewed_at` (string | null): When it was reviewed. - `review_note` (string | null): Note from the reviewer. - `created_at` (string): When you applied. ## Errors - `validation_failed` - `conflict` - `idempotency_key_reused` Section: Billing Source: https://docs.freeinvoiceautomation.com/api-reference/billing/apply-free-team --- # List reminder sequences GET https://api.freeinvoiceautomation.com/v1/reminder-sequences Returns the reminder sequences of the businesses you can reach, with their steps. Every business starts with three presets, Gentle, Standard and Firm, and can add its own named sequences. One sequence per business is the default; a client or a single invoice can use a different one. When a reminder is planned the invoice's own sequence wins, then the client's, then the business default. See [Reminders](/collections/reminders) for how sequences run. Results use [cursor pagination](/api-reference/pagination). Permission: `documents.view`. Capability: `list_reminder_sequences`. CLI: `freeinvoiceautomation reminder list`. Available from the week of 11 January 2027. ## Query parameters - `business_id` (string): Only sequences of this business. May be left out when you can reach exactly one business. - `enabled` (boolean): Only enabled (`true`) or disabled (`false`) sequences. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The sequences on this page. - `id` (string): Unique id of the sequence (uuid v7). - `business_id` (string): The business the sequence belongs to. - `name` (string): Name shown in pickers, e.g. `Standard`. - `preset_key` (enum | null): The preset this sequence was created from, or `null` for a sequence you built yourself. One of: `gentle`, `standard`, `firm`. - `is_default` (boolean): Whether this is the business's default sequence. A business has at most one default. - `stop_conditions` (string[]): Events that stop the sequence for an invoice: paid, void, a credit note covering the balance, a promise-to-pay date set by the business, or the client reporting payment with proof. - `enabled` (boolean): Whether the sequence runs. A disabled sequence sends nothing. - `steps` (object[]): The steps of the sequence, in order. - `id` (string): Unique id of the step. - `sequence_id` (string): The sequence this step belongs to. - `position` (integer): Order of the step in the sequence, starting at 1. - `offset_days` (integer): Days from the due date. Negative numbers are before the due date (for example `-3`), `0` is the due date, positive numbers are after it (`3`, `7`, `14`, `30`). - `channel` (enum): How the reminder is sent. `email` is available from January 2027; `whatsapp`, `telegram` and `sms` work once those senders ship. One of: `email`, `whatsapp`, `telegram`, `sms`. - `message_template_id` (string | null): Message template with variables used for this step. See [Message templates](/messaging/templates). - `created_at` (string): When the sequence was created (RFC 3339, UTC). - `updated_at` (string): When the sequence was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Reminders, late fees and recurring Source: https://docs.freeinvoiceautomation.com/api-reference/collections/list-reminder-sequences --- # Create or update a reminder sequence POST https://api.freeinvoiceautomation.com/v1/reminder-sequences Creates a reminder sequence, or updates it when the body contains the `id` of an existing one. The same capability answers `PATCH /v1/reminder-sequences/{id}` for partial updates. Sending `steps` replaces all steps of the sequence. Each step is an offset in days from the due date (negative is before it), a channel and an optional message template with variables. Two steps cannot share the same offset and channel. Setting `is_default: true` makes this the business default and clears the flag on the previous default. A sequence stops for an invoice when it is paid, voided, covered by a credit note, given a [promise-to-pay date](/collections/promise-to-pay), or when the client reports payment with proof. Sends respect your platform email quota and each client's reminder opt-out. See [Reminders](/collections/reminders). Permission: `reminders.manage`. Capability: `upsert_reminder_sequence`. CLI: `freeinvoiceautomation reminder upsert`. Available from the week of 11 January 2027. - Send an `Idempotency-Key` header so a retried request never creates the sequence twice. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Body - `id` (string): Id of an existing sequence to update. Leave out to create a new one. - `business_id` (string): Business that owns the sequence. May be left out when you can reach exactly one business. - `name` (string, required): Name shown in pickers. - `is_default` (boolean): Make this the business's default sequence. - `enabled` (boolean): Whether the sequence runs. - `steps` (object[], required): The steps, in order. Replaces every existing step. - `offset_days` (integer, required): Days from the due date; negative is before it. - `channel` (enum, required): Channel for this step. `email` first; the other channels once their senders ship. One of: `email`, `whatsapp`, `telegram`, `sms`. - `message_template_id` (string): Message template with variables to use for this step. ## Response - `id` (string): Unique id of the sequence (uuid v7). - `business_id` (string): The business the sequence belongs to. - `name` (string): Name shown in pickers, e.g. `Standard`. - `preset_key` (enum | null): The preset this sequence was created from, or `null` for a sequence you built yourself. One of: `gentle`, `standard`, `firm`. - `is_default` (boolean): Whether this is the business's default sequence. A business has at most one default. - `stop_conditions` (string[]): Events that stop the sequence for an invoice: paid, void, a credit note covering the balance, a promise-to-pay date set by the business, or the client reporting payment with proof. - `enabled` (boolean): Whether the sequence runs. A disabled sequence sends nothing. - `steps` (object[]): The steps of the sequence, in order. - `id` (string): Unique id of the step. - `sequence_id` (string): The sequence this step belongs to. - `position` (integer): Order of the step in the sequence, starting at 1. - `offset_days` (integer): Days from the due date. Negative numbers are before the due date (for example `-3`), `0` is the due date, positive numbers are after it (`3`, `7`, `14`, `30`). - `channel` (enum): How the reminder is sent. `email` is available from January 2027; `whatsapp`, `telegram` and `sms` work once those senders ship. One of: `email`, `whatsapp`, `telegram`, `sms`. - `message_template_id` (string | null): Message template with variables used for this step. See [Message templates](/messaging/templates). - `created_at` (string): When the sequence was created (RFC 3339, UTC). - `updated_at` (string): When the sequence was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` Section: Reminders, late fees and recurring Source: https://docs.freeinvoiceautomation.com/api-reference/collections/upsert-reminder-sequence --- # Delete a reminder sequence DELETE https://api.freeinvoiceautomation.com/v1/reminder-sequences/{id} Deletes a reminder sequence so it sends nothing more. The sequence is soft-deleted: it disappears from lists and pickers and no further steps are sent from it. Reminders already sent stay in the message log and on each invoice's timeline. To stop reminders for a while without losing the steps, update the sequence with `enabled: false` instead. Permission: `reminders.manage`. Capability: `delete_reminder_sequence`. CLI: `freeinvoiceautomation reminder delete`. Available from the week of 11 January 2027. ## Path parameters - `id` (string, required): Id of the reminder sequence. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). Section: Reminders, late fees and recurring Source: https://docs.freeinvoiceautomation.com/api-reference/collections/delete-reminder-sequence --- # Send a reminder now POST https://api.freeinvoiceautomation.com/v1/documents/{id}/reminders Sends one payment reminder for an unpaid invoice right away, outside its sequence. Without `confirm: true` the call sends nothing: it returns `428 confirmation_required` with a preview of the reminder (recipient, channel, subject and text) so a person can check it. Send the same request again with `confirm: true` to send it. An API key or MCP token with `mode: auto_send` may send without `confirm`. See [Drafts and confirmations](/api-reference/confirmations). The reminder goes through the business's sender (your own SMTP or the platform mailer). Platform mail counts against your monthly quota; when it is used up the call returns `quota_exceeded` with the suggestion to connect your own SMTP. Clients who opted out of reminders are not sent one. Voided invoices cannot be reminded. This is also the MCP tool `send_reminder`. See [Reminders](/collections/reminders). Permission: `documents.send`. Capability: `send_reminder`. MCP tool: `send_reminder`. CLI: `freeinvoiceautomation doc remind`. Available from the week of 11 January 2027. - Needs `confirm: true` unless the API key or MCP token has `mode: auto_send`. - Sends are idempotent, so a retried request with the same `Idempotency-Key` never emails twice. ## Path parameters - `id` (string, required): Id of the invoice. ## Headers - `Idempotency-Key` (string, required): Required for API key and MCP calls on sends. A retry with the same key never sends twice. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, default: false): Set to `true` to send. Without it the response is a preview and nothing is sent. ## Response - `id` (string): Id of the message log entry. - `document_id` (string): The invoice the reminder is about. - `client_id` (string): The client it was sent to. - `channel` (enum): Channel used. One of: `email`, `whatsapp`, `telegram`, `sms`. - `sender` (string): Sender used, e.g. `smtp` (your own mail server) or `platform_mail`. - `to_address` (string): Address the reminder goes to. - `subject` (string | null): Email subject. - `status` (enum): Delivery status. Sends are queued and then delivered. One of: `queued`, `sent`, `failed`, `bounced`. - `counted_against_quota` (boolean): `true` when it went through the platform mailer and counts against your monthly platform email quota. - `queued_at` (string): When the send was queued. - `sent_at` (string | null): When the provider accepted it. ## Errors - `confirmation_required` - `quota_exceeded` - `platform_mail_paused` - `email_not_verified` - `document_voided` - `idempotency_key_reused` - `idempotency_in_progress` Section: Reminders, late fees and recurring Source: https://docs.freeinvoiceautomation.com/api-reference/collections/send-reminder --- # Set a promise-to-pay date POST https://api.freeinvoiceautomation.com/v1/documents/{id}/promise-to-pay Records the date a client promised to pay an invoice and pauses its reminders until then. When a client tells you they will pay on a certain date, record it here. The invoice's reminder sequence sends no further steps until the promise-to-pay date has passed; if the invoice is still unpaid after that, the sequence carries on. Send `null` to clear the date. The date is stored on the invoice and recorded in the audit log. See [Promise to pay](/collections/promise-to-pay). Permission: `documents.edit`. Capability: `set_promise_to_pay`. CLI: `freeinvoiceautomation doc promise`. Available from the week of 11 January 2027. ## Path parameters - `id` (string, required): Id of the invoice. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Body - `promise_to_pay_date` (string | null, required): The date the client promised to pay (`YYYY-MM-DD`), or `null` to clear it. ## Response - `id` (string): Id of the invoice. - `number` (string | null): Invoice number. - `status` (enum): Invoice status. One of: `draft`, `sent`, `partially_paid`, `paid`, `overdue`, `void`. - `due_date` (string | null): Due date of the invoice. - `balance_minor` (string): Amount still owed, in minor units. - `currency` (string): Invoice currency. - `promise_to_pay_date` (string | null): The promise-to-pay date now on the invoice. ## Errors - `document_voided` Section: Reminders, late fees and recurring Source: https://docs.freeinvoiceautomation.com/api-reference/collections/set-promise-to-pay --- # List late-fee policies GET https://api.freeinvoiceautomation.com/v1/late-fee-policies Returns the late-fee policies of your businesses, including client overrides. A business has at most one policy, plus at most one override per client (`client_id` set). For a late invoice the client's policy is used when there is one, otherwise the business policy. Late fees are off by default. See [Late fees](/collections/late-fees). Permission: `documents.view`. Capability: `list_late_fee_policies`. CLI: `freeinvoiceautomation late-fee list`. Available from the week of 11 January 2027. ## Query parameters - `business_id` (string): Only policies of this business. - `client_id` (string): Only the override for this client. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The policies on this page. - `id` (string): Unique id of the policy. - `business_id` (string): The business the policy belongs to. - `client_id` (string | null): Client this policy overrides the business policy for, or `null` for the business policy. - `type` (enum): `fixed` adds a fixed amount, `percent` adds a percent of the overdue balance, `statutory` uses a statutory preset. One of: `fixed`, `percent`, `statutory`. - `amount_minor` (string | null): Fee for `fixed` policies, in minor units of `currency`. - `currency` (string | null): ISO 4217 code of `amount_minor` (fixed policies). - `percent` (string | null): Percent of the overdue balance for `percent` policies, e.g. `"1.5000"` = 1.5%. - `statutory_preset_key` (enum | null): Statutory preset for `statutory` policies: EU Late Payment Directive interest plus the fixed sum for B2B, or UK late payment interest plus the fixed sum. One of: `eu_late_payment`, `uk_late_payment`. - `grace_days` (integer): Days after the due date before the first fee is added. - `repeat` (enum): Add the fee once, or again every `repeat_every_days` days. One of: `once`, `every_n_days`. - `repeat_every_days` (integer | null): Days between repeated fees when `repeat` is `every_n_days`. - `max_total_minor` (string | null): Cap on the total of all fees on one invoice, in minor units. When reached, no more fees are added. - `enabled` (boolean): Whether the policy is on. Late fees are off by default. - `created_at` (string): When the policy was created. - `updated_at` (string): When the policy was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Reminders, late fees and recurring Source: https://docs.freeinvoiceautomation.com/api-reference/collections/list-late-fee-policies --- # Create or update a late-fee policy POST https://api.freeinvoiceautomation.com/v1/late-fee-policies Creates the late-fee policy of a business or a client override, or updates it when the body contains an `id`. The same capability answers `PATCH /v1/late-fee-policies/{id}` for partial updates. A policy is a fixed amount, a percent of the overdue balance, or a statutory preset (EU Late Payment Directive interest plus the fixed sum for B2B, UK late payment interest plus the fixed sum). It has grace days, applies once or every N days, and can have a maximum total. Fees are applied only to invoices whose terms showed the fee rule when they were sent, because fees not stated in the terms are not enforceable in many countries. Each fee is added as a **revision** of the invoice with a "Late fee" line, a new snapshot and an audit entry, and the next reminder tells the client. Voided invoices never get fees, and fees can be turned off per invoice. See [Late fees](/collections/late-fees). Permission: `late_fees.manage`. Capability: `upsert_late_fee_policy`. CLI: `freeinvoiceautomation late-fee upsert`. Available from the week of 11 January 2027. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Body - `id` (string): Id of an existing policy to update. Leave out to create one. - `business_id` (string): Business of the policy. May be left out when you can reach exactly one business. - `client_id` (string): Set to make a client override. Leave out for the business policy. - `type` (enum, required): Kind of fee. One of: `fixed`, `percent`, `statutory`. - `amount_minor` (string): Required for `fixed`: fee in minor units. - `currency` (string): Required for `fixed`: ISO 4217 code of the fee. - `percent` (string): Required for `percent`: percent of the overdue balance, e.g. `"1.5"`. - `statutory_preset_key` (enum): Required for `statutory`. One of: `eu_late_payment`, `uk_late_payment`. - `grace_days` (integer, required): Days after the due date before the first fee. - `repeat` (enum, required): Apply once, or every N days. One of: `once`, `every_n_days`. - `repeat_every_days` (integer): Required when `repeat` is `every_n_days`. - `max_total_minor` (string): Cap on all fees on one invoice, in minor units. - `enabled` (boolean, default: false): Turn the policy on. ## Response - `id` (string): Unique id of the policy. - `business_id` (string): The business the policy belongs to. - `client_id` (string | null): Client this policy overrides the business policy for, or `null` for the business policy. - `type` (enum): `fixed` adds a fixed amount, `percent` adds a percent of the overdue balance, `statutory` uses a statutory preset. One of: `fixed`, `percent`, `statutory`. - `amount_minor` (string | null): Fee for `fixed` policies, in minor units of `currency`. - `currency` (string | null): ISO 4217 code of `amount_minor` (fixed policies). - `percent` (string | null): Percent of the overdue balance for `percent` policies, e.g. `"1.5000"` = 1.5%. - `statutory_preset_key` (enum | null): Statutory preset for `statutory` policies: EU Late Payment Directive interest plus the fixed sum for B2B, or UK late payment interest plus the fixed sum. One of: `eu_late_payment`, `uk_late_payment`. - `grace_days` (integer): Days after the due date before the first fee is added. - `repeat` (enum): Add the fee once, or again every `repeat_every_days` days. One of: `once`, `every_n_days`. - `repeat_every_days` (integer | null): Days between repeated fees when `repeat` is `every_n_days`. - `max_total_minor` (string | null): Cap on the total of all fees on one invoice, in minor units. When reached, no more fees are added. - `enabled` (boolean): Whether the policy is on. Late fees are off by default. - `created_at` (string): When the policy was created. - `updated_at` (string): When the policy was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` Section: Reminders, late fees and recurring Source: https://docs.freeinvoiceautomation.com/api-reference/collections/upsert-late-fee-policy --- # Delete a late-fee policy DELETE https://api.freeinvoiceautomation.com/v1/late-fee-policies/{id} Deletes a late-fee policy so no new fees are added under it. The policy is soft-deleted. Fees already applied stay on their invoices; waive them one by one with [Waive a late fee](/api-reference/collections/waive-late-fee) if needed. Deleting a client override makes that client fall back to the business policy. Permission: `late_fees.manage`. Capability: `delete_late_fee_policy`. CLI: `freeinvoiceautomation late-fee delete`. Available from the week of 11 January 2027. ## Path parameters - `id` (string, required): Id of the late-fee policy. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). Section: Reminders, late fees and recurring Source: https://docs.freeinvoiceautomation.com/api-reference/collections/delete-late-fee-policy --- # Waive a late fee POST https://api.freeinvoiceautomation.com/v1/late-fee-charges/{id}/waive Removes an applied late fee from its invoice. The fee is removed by a new revision of the invoice (new snapshot without the "Late fee" line), so the history stays complete. The charge keeps its record with `status: waived`, who waived it and when, and an audit entry is written. Waiving is a separate permission from managing policies, so an Editor can waive a fee for a good client without being able to change the policy. See [Late fees](/collections/late-fees). Permission: `late_fees.waive`. Capability: `waive_late_fee`. CLI: `freeinvoiceautomation late-fee waive`. Available from the week of 11 January 2027. ## Path parameters - `id` (string, required): Id of the late-fee charge. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Unique id of the fee charge. - `document_id` (string): The invoice the fee was added to. - `installment_id` (string | null): The instalment the fee belongs to, when the invoice has a payment schedule. - `policy_id` (string): The policy that produced the fee. - `amount_minor` (string): Fee amount in minor units. - `currency` (string): ISO 4217 code of the fee (the invoice currency). - `applied_for_date` (string): The day the fee was applied for. A fee is never applied twice for the same day. - `document_version` (integer): The invoice revision that added the "Late fee" line. - `line_id` (string | null): The "Late fee" line on the invoice. - `status` (enum): Whether the fee stands or was waived. One of: `applied`, `waived`. - `waived_by` (string | null): Who waived the fee. - `waived_at` (string | null): When the fee was waived. - `created_at` (string): When the fee was applied. ## Errors - `conflict` - `document_voided` Section: Reminders, late fees and recurring Source: https://docs.freeinvoiceautomation.com/api-reference/collections/waive-late-fee --- # List recurring schedules GET https://api.freeinvoiceautomation.com/v1/recurring-schedules Returns the recurring invoice schedules of your businesses. Use the filters to find the schedules of one client or the ones that are paused. See [Recurring invoices](/collections/recurring). Permission: `documents.view`. Capability: `list_recurring_schedules`. CLI: `freeinvoiceautomation recurring list`. Available from the week of 11 January 2027. ## Query parameters - `business_id` (string): Only schedules of this business. - `client_id` (string): Only schedules for this client. - `status` (enum): Filter by status; comma-separate several. One of: `active`, `paused`, `ended`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The schedules on this page. - `id` (string): Unique id of the schedule. - `business_id` (string): The business that issues the invoices. - `client_id` (string): The client the invoices are for. - `template_document_id` (string): A draft invoice copied on every run. Edit this draft to change future invoices. - `interval_unit` (enum): Unit of the repeat interval. One of: `week`, `month`, `year`. - `interval_count` (integer): Number of units between runs, e.g. `1` with `month` is monthly, `3` with `month` is quarterly. - `next_run_date` (string): The date the next invoice is created. - `end_date` (string | null): Last date the schedule may run. `null` for no end date. - `remaining_runs` (integer | null): How many invoices are still to be created. `null` for no count limit. - `auto_send` (boolean): `true` sends each new invoice automatically. `false` (default) creates a draft for you to check and send. - `auto_charge` (boolean): Whether each invoice is charged to the client's saved card on its due date (from March 2027). Change it with [Set auto-charge](/api-reference/cards-contracts/set-auto-charge), not here. - `saved_payment_method_id` (string | null): The saved card used for auto-charge. - `status` (enum): `active` runs on `next_run_date`, `paused` does not run, `ended` has passed its end date or run count. One of: `active`, `paused`, `ended`. - `last_run_at` (string | null): When the schedule last created an invoice. - `created_at` (string): When the schedule was created. - `updated_at` (string): When the schedule was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Reminders, late fees and recurring Source: https://docs.freeinvoiceautomation.com/api-reference/collections/list-recurring-schedules --- # Create or update a recurring schedule POST https://api.freeinvoiceautomation.com/v1/recurring-schedules Creates a schedule that makes an invoice from a template draft every week, month or year, or updates one by `id`. The same capability answers `PATCH /v1/recurring-schedules/{id}`. On each `next_run_date` a copy of the template draft becomes a new invoice with the next gap-free number for the business. A run is idempotent: if it is retried for the same date, only one invoice exists. New invoices are **drafts** unless `auto_send` is `true` for the schedule. The schedule ends at `end_date` or after `remaining_runs` invoices, whichever comes first. Auto-charge to a saved card is set separately with [Set auto-charge](/api-reference/cards-contracts/set-auto-charge) because it moves money. See [Recurring invoices](/collections/recurring). Permission: `documents.create`. Capability: `upsert_recurring_schedule`. CLI: `freeinvoiceautomation recurring upsert`. Available from the week of 11 January 2027. ## Headers - `Idempotency-Key` (string, required): Required for API key and MCP calls on sends. A retry with the same key never sends twice. See [Idempotency](/api-reference/idempotency). ## Body - `id` (string): Id of an existing schedule to update. Leave out to create one. - `business_id` (string): Business that issues the invoices. - `client_id` (string, required): Client the invoices are for. - `template_document_id` (string, required): A draft invoice of the same business and client, copied on every run. - `interval_unit` (enum, required): Unit of the interval. One of: `week`, `month`, `year`. - `interval_count` (integer, required): Units between runs, e.g. `1` with `month` for monthly. - `next_run_date` (string, required): Date of the first (or next) run. - `end_date` (string): Stop after this date. - `remaining_runs` (integer): Stop after this many invoices. - `auto_send` (boolean, default: false): Send each new invoice automatically instead of leaving a draft. ## Response - `id` (string): Unique id of the schedule. - `business_id` (string): The business that issues the invoices. - `client_id` (string): The client the invoices are for. - `template_document_id` (string): A draft invoice copied on every run. Edit this draft to change future invoices. - `interval_unit` (enum): Unit of the repeat interval. One of: `week`, `month`, `year`. - `interval_count` (integer): Number of units between runs, e.g. `1` with `month` is monthly, `3` with `month` is quarterly. - `next_run_date` (string): The date the next invoice is created. - `end_date` (string | null): Last date the schedule may run. `null` for no end date. - `remaining_runs` (integer | null): How many invoices are still to be created. `null` for no count limit. - `auto_send` (boolean): `true` sends each new invoice automatically. `false` (default) creates a draft for you to check and send. - `auto_charge` (boolean): Whether each invoice is charged to the client's saved card on its due date (from March 2027). Change it with [Set auto-charge](/api-reference/cards-contracts/set-auto-charge), not here. - `saved_payment_method_id` (string | null): The saved card used for auto-charge. - `status` (enum): `active` runs on `next_run_date`, `paused` does not run, `ended` has passed its end date or run count. One of: `active`, `paused`, `ended`. - `last_run_at` (string | null): When the schedule last created an invoice. - `created_at` (string): When the schedule was created. - `updated_at` (string): When the schedule was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` - `business_out_of_scope` Section: Reminders, late fees and recurring Source: https://docs.freeinvoiceautomation.com/api-reference/collections/upsert-recurring-schedule --- # Pause or resume a recurring schedule POST https://api.freeinvoiceautomation.com/v1/recurring-schedules/{id}/pause Pauses a recurring schedule so it creates no invoices until you resume it. `POST /v1/recurring-schedules/{id}/resume` is the same capability in the other direction: it sets the status back to `active`. While a schedule is `paused` no invoices are created; invoices it already created are not changed. See [Recurring invoices](/collections/recurring). Permission: `documents.create`. Capability: `pause_recurring_schedule`. CLI: `freeinvoiceautomation recurring pause`. Available from the week of 11 January 2027. ## Path parameters - `id` (string, required): Id of the recurring schedule. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Unique id of the schedule. - `business_id` (string): The business that issues the invoices. - `client_id` (string): The client the invoices are for. - `template_document_id` (string): A draft invoice copied on every run. Edit this draft to change future invoices. - `interval_unit` (enum): Unit of the repeat interval. One of: `week`, `month`, `year`. - `interval_count` (integer): Number of units between runs, e.g. `1` with `month` is monthly, `3` with `month` is quarterly. - `next_run_date` (string): The date the next invoice is created. - `end_date` (string | null): Last date the schedule may run. `null` for no end date. - `remaining_runs` (integer | null): How many invoices are still to be created. `null` for no count limit. - `auto_send` (boolean): `true` sends each new invoice automatically. `false` (default) creates a draft for you to check and send. - `auto_charge` (boolean): Whether each invoice is charged to the client's saved card on its due date (from March 2027). Change it with [Set auto-charge](/api-reference/cards-contracts/set-auto-charge), not here. - `saved_payment_method_id` (string | null): The saved card used for auto-charge. - `status` (enum): `active` runs on `next_run_date`, `paused` does not run, `ended` has passed its end date or run count. One of: `active`, `paused`, `ended`. - `last_run_at` (string | null): When the schedule last created an invoice. - `created_at` (string): When the schedule was created. - `updated_at` (string): When the schedule was last changed. Section: Reminders, late fees and recurring Source: https://docs.freeinvoiceautomation.com/api-reference/collections/pause-recurring-schedule --- # Delete a recurring schedule DELETE https://api.freeinvoiceautomation.com/v1/recurring-schedules/{id} Deletes a recurring schedule. Invoices it already created are kept. The schedule is soft-deleted and creates nothing more. Invoices it created stay as they are, with their numbers and payments. To stop for a while instead, [pause the schedule](/api-reference/collections/pause-recurring-schedule). Permission: `documents.create`. Capability: `delete_recurring_schedule`. CLI: `freeinvoiceautomation recurring delete`. Available from the week of 11 January 2027. ## Path parameters - `id` (string, required): Id of the recurring schedule. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). Section: Reminders, late fees and recurring Source: https://docs.freeinvoiceautomation.com/api-reference/collections/delete-recurring-schedule --- # Set a payment schedule PUT https://api.freeinvoiceautomation.com/v1/documents/{id}/payment-schedule Adds or replaces the deposit and instalments of an invoice or estimate. Use this to ask for a deposit (a percent or fixed amount, due on acceptance or on a date) and/or split the rest into instalments (for example 3 monthly parts, or your own dates and amounts). Each part is given as a percent or as an amount. The parts are computed exactly so that they add up to the document total, and the last part absorbs any rounding. If you give amounts that do not add up, the call fails with `schedule_total_mismatch`. The pay page and client portal show the schedule and ask for the **next amount due**; the client may pay more. Payments are allocated to the parts oldest first. Each part has its own due date, so reminders and late fees run per instalment. Accepting an estimate with a deposit creates the deposit invoice automatically. `PUT` replaces the whole schedule. This is also the MCP tool `set_payment_schedule`. See [Deposits and schedules](/payments/schedules). Permission: `documents.edit`. Capability: `set_payment_schedule`. MCP tool: `set_payment_schedule`. CLI: `freeinvoiceautomation doc schedule`. Available from the week of 25 January 2027. ## Path parameters - `id` (string, required): Id of the invoice or estimate. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Body - `installments` (object[], required): The deposit and instalments, in order. - `kind` (enum, required): Deposit or instalment. One of: `deposit`, `installment`. - `label` (string, required): Label shown to the client. - `input_percent` (string): Percent of the document total. Give this or `amount_minor`. - `amount_minor` (string): Fixed amount in minor units. Give this or `input_percent`. - `due_trigger` (enum, required): `on_acceptance` is for deposits on estimates. One of: `date`, `on_acceptance`. - `due_date` (string): Required when `due_trigger` is `date`. ## Response - `id` (string): Unique id of the schedule. - `business_id` (string): The business of the document. - `document_id` (string): The invoice or estimate. A document has at most one schedule. - `installments` (object[]): The deposit and instalments, oldest first. - `id` (string): Unique id of the part. - `position` (integer): Order of the part, oldest first. Payments are allocated in this order. - `kind` (enum): A deposit or an instalment. One of: `deposit`, `installment`. - `label` (string): Label shown on the pay page and PDF, e.g. `Deposit` or `Instalment 2 of 3`. - `input_percent` (string | null): The percent you entered, if you entered one, e.g. `"30.0000"`. - `amount_minor` (string): Exact amount of this part in minor units. The parts always add up to the document total; the last part absorbs rounding. - `due_trigger` (enum): `date` is due on `due_date`; `on_acceptance` is due when the estimate is accepted (deposits). One of: `date`, `on_acceptance`. - `due_date` (string | null): Due date of the part when `due_trigger` is `date`. - `status` (enum): Status of the part. One of: `pending`, `due`, `partially_paid`, `paid`, `overdue`, `void`. - `paid_amount_minor` (string): Amount paid towards this part so far. - `deposit_invoice_id` (string | null): The deposit invoice created when an estimate with a deposit is accepted, or when its contract is signed. - `auto_charge` (boolean): Whether this part is charged to the client's saved card on its due date (from March 2027). Set it with [Set auto-charge](/api-reference/cards-contracts/set-auto-charge). - `saved_payment_method_id` (string | null): Saved card used for auto-charge. - `created_at` (string): When the schedule was set. - `updated_at` (string): When the schedule was last changed. ## Errors - `schedule_total_mismatch` - `validation_failed` - `document_voided` Section: Payment schedules and reports Source: https://docs.freeinvoiceautomation.com/api-reference/reports/set-payment-schedule --- # Get a payment schedule GET https://api.freeinvoiceautomation.com/v1/documents/{id}/payment-schedule Returns the deposit and instalments of a document, with what has been paid on each. Returns `404 not_found` when the document has no schedule. Documents also include their `payment_schedule` when one exists, so you often do not need this call. See [Deposits and schedules](/payments/schedules). Permission: `documents.view`. Capability: `get_payment_schedule`. CLI: `freeinvoiceautomation doc schedule --show`. Available from the week of 25 January 2027. ## Path parameters - `id` (string, required): Id of the invoice or estimate. ## Response - `id` (string): Unique id of the schedule. - `business_id` (string): The business of the document. - `document_id` (string): The invoice or estimate. A document has at most one schedule. - `installments` (object[]): The deposit and instalments, oldest first. - `id` (string): Unique id of the part. - `position` (integer): Order of the part, oldest first. Payments are allocated in this order. - `kind` (enum): A deposit or an instalment. One of: `deposit`, `installment`. - `label` (string): Label shown on the pay page and PDF, e.g. `Deposit` or `Instalment 2 of 3`. - `input_percent` (string | null): The percent you entered, if you entered one, e.g. `"30.0000"`. - `amount_minor` (string): Exact amount of this part in minor units. The parts always add up to the document total; the last part absorbs rounding. - `due_trigger` (enum): `date` is due on `due_date`; `on_acceptance` is due when the estimate is accepted (deposits). One of: `date`, `on_acceptance`. - `due_date` (string | null): Due date of the part when `due_trigger` is `date`. - `status` (enum): Status of the part. One of: `pending`, `due`, `partially_paid`, `paid`, `overdue`, `void`. - `paid_amount_minor` (string): Amount paid towards this part so far. - `deposit_invoice_id` (string | null): The deposit invoice created when an estimate with a deposit is accepted, or when its contract is signed. - `auto_charge` (boolean): Whether this part is charged to the client's saved card on its due date (from March 2027). Set it with [Set auto-charge](/api-reference/cards-contracts/set-auto-charge). - `saved_payment_method_id` (string | null): Saved card used for auto-charge. - `created_at` (string): When the schedule was set. - `updated_at` (string): When the schedule was last changed. Section: Payment schedules and reports Source: https://docs.freeinvoiceautomation.com/api-reference/reports/get-payment-schedule --- # Remove a payment schedule DELETE https://api.freeinvoiceautomation.com/v1/documents/{id}/payment-schedule Removes the deposit and instalments from a document so it is paid as one amount. After this the pay page asks for the whole balance again, and reminders and late fees follow the document's own due date. Payments already recorded stay on the document. Permission: `documents.edit`. Capability: `delete_payment_schedule`. CLI: `freeinvoiceautomation doc schedule --remove`. Available from the week of 25 January 2027. ## Path parameters - `id` (string, required): Id of the invoice or estimate. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). Section: Payment schedules and reports Source: https://docs.freeinvoiceautomation.com/api-reference/reports/delete-payment-schedule --- # Get aged receivables GET https://api.freeinvoiceautomation.com/v1/reports/aged-receivables Shows who owes you and for how long, grouped Current, 1 to 30, 31 to 60, 61 to 90 and 90+ days, per client. Open invoices are grouped by how many days past their due date they are, per client, with totals. Amounts in several currencies are returned per currency, plus a total in the business currency at the frozen exchange rate of each document. This returns the same numbers as the Aged receivables report in the app and as `GET /v1/reports/aged-receivables` through [Get a report](/api-reference/reports/get-report). It is also the MCP tool `get_aged_receivables`. See [Reports](/features/reports). Permission: `reports.view`. Capability: `get_aged_receivables`. MCP tool: `get_aged_receivables`. CLI: `freeinvoiceautomation report aged`. Available from the week of 25 January 2027. ## Query parameters - `business_id` (string): Business to report on. May be left out when you can reach exactly one business. - `to` (string): End of the date range (`YYYY-MM-DD`). - `client_id` (string): Only this client. Required for `client-statement`. - `currency` (string): Only documents in this ISO 4217 currency. ## Response - `business_id` (string): The business reported on. - `rows` (object[]): One row per client and currency. - `client_id` (string): The client. - `client_name` (string): Client name. - `currency` (string): Currency of the amounts in this row. - `current_minor` (string): Owed and not yet due. - `days_1_30_minor` (string): 1 to 30 days overdue. - `days_31_60_minor` (string): 31 to 60 days overdue. - `days_61_90_minor` (string): 61 to 90 days overdue. - `days_90_plus_minor` (string): More than 90 days overdue. - `total_minor` (string): Total owed by this client in this currency. - `totals` (object[]): Totals per currency. - `currency` (string): ISO 4217 code. - `total_minor` (string): Total in that currency, in minor units. - `business_currency_total` (object): Grand total in the business currency, converted at the frozen exchange rate of each document. - `currency` (string): ISO 4217 code. - `total_minor` (string): Total in that currency, in minor units. ## Errors - `business_out_of_scope` Section: Payment schedules and reports Source: https://docs.freeinvoiceautomation.com/api-reference/reports/get-aged-receivables --- # Get a report GET https://api.freeinvoiceautomation.com/v1/reports/{name} Returns one of the standard reports as JSON, filtered by business, client, date range and currency. Available reports: - `aged-receivables`: who owes you and for how long (Current, 1 to 30, 31 to 60, 61 to 90, 90+ days), per client. - `outstanding-by-client`: open balance per client. - `client-statement`: invoices, credit notes and payments for one client over a date range, with a running balance. Needs `client_id`. - `revenue`: invoiced and paid per month. - `tax-collected`: tax per tax rate and period, net of credit notes. - `payments-received`: payments per rail and month. Amounts in several currencies are returned per currency, plus a total in the business currency at the frozen exchange rate of each document. The app, CSV and PDF exports and this call give the same numbers. This is also the MCP tool `get_report`; to download a file use [Export a report](/api-reference/reports/export-report). See [Reports](/features/reports). Permission: `reports.view`. Capability: `get_report`. MCP tool: `get_report`. CLI: `freeinvoiceautomation report get`. Available from the week of 25 January 2027. ## Path parameters - `name` (enum, required): Which report. One of: `aged-receivables`, `outstanding-by-client`, `client-statement`, `revenue`, `tax-collected`, `payments-received`. ## Query parameters - `business_id` (string): Business to report on. May be left out when you can reach exactly one business. - `from` (string): Start of the date range (`YYYY-MM-DD`). - `to` (string): End of the date range (`YYYY-MM-DD`). - `client_id` (string): Only this client. Required for `client-statement`. - `currency` (string): Only documents in this ISO 4217 currency. - `format` (enum, default: json): Leave as `json` here. `csv` and `pdf` are [Export a report](/api-reference/reports/export-report). One of: `json`, `csv`, `pdf`. ## Response - `name` (string): The report name. - `business_id` (string): The business reported on. - `filters` (object): The filters that were applied (`from`, `to`, `client_id`, `currency`). - `rows` (object[]): The report rows. Their columns depend on the report, e.g. `period`, `invoiced_minor` and `paid_minor` for `revenue`; amounts are strings of minor units with a `currency`. - `totals` (object[]): Totals per currency. - `currency` (string): ISO 4217 code. - `total_minor` (string): Total in that currency, in minor units. - `business_currency_total` (object): Grand total in the business currency, converted at the frozen exchange rate of each document. - `currency` (string): ISO 4217 code. - `total_minor` (string): Total in that currency, in minor units. ## Errors - `validation_failed` - `business_out_of_scope` Section: Payment schedules and reports Source: https://docs.freeinvoiceautomation.com/api-reference/reports/get-report --- # Export a report GET https://api.freeinvoiceautomation.com/v1/reports/{name} Downloads a report as a CSV or PDF file with the same numbers as the JSON report. This is `GET /v1/reports/{name}` with `format=csv` or `format=pdf`. It takes the same filters as [Get a report](/api-reference/reports/get-report) and returns a file instead of JSON. Exporting needs its own permission, `reports.export`, which the Accountant role has by default. See [Reports](/features/reports). Permission: `reports.export`. Capability: `export_report`. CLI: `freeinvoiceautomation report export`. Available from the week of 25 January 2027. ## Path parameters - `name` (enum, required): Which report. One of: `aged-receivables`, `outstanding-by-client`, `client-statement`, `revenue`, `tax-collected`, `payments-received`. ## Query parameters - `format` (enum, required): File format. One of: `csv`, `pdf`. - `business_id` (string): Business to report on. May be left out when you can reach exactly one business. - `from` (string): Start of the date range (`YYYY-MM-DD`). - `to` (string): End of the date range (`YYYY-MM-DD`). - `client_id` (string): Only this client. Required for `client-statement`. - `currency` (string): Only documents in this ISO 4217 currency. ## Errors - `validation_failed` - `business_out_of_scope` Section: Payment schedules and reports Source: https://docs.freeinvoiceautomation.com/api-reference/reports/export-report --- # Run the AI agent POST https://api.freeinvoiceautomation.com/v1/agent/runs Asks the AI invoice agent to create invoices, estimates or clients from a plain-language request. It only ever creates drafts. Describe what you want ("Invoice Acme for 10 hours of design at $50") or paste text such as an email. The agent works with the same capabilities as this API and MCP (`upsert_client`, `create_invoice`, `list_documents`, `record_payment` and others), under **your** permissions, and every call it makes is audited. Its output is checked against the document schema before anything is saved. **Drafts only:** everything the agent creates is a draft. Nothing is sent until a person confirms, for example with [Send a document](/api-reference/documents/send-document). **Credits:** on our AI key a request that creates or updates up to one document uses 1 credit; reading a receipt or PDF uses 2 credits (from February 2027); a run that fails before calling any tool uses none. With your [own AI key](/ai/agent/own-key) no credits are used. At 0 credits the call returns `ai_credits_exhausted`; on Free without credits or an own key the agent is locked (`feature_locked`). A monthly token ceiling and a per-minute limit apply per account even when credits remain. The run is processed in the background: the call returns `202` with the run, and you poll [Get an agent run](/api-reference/agent/get-agent-run) until `status` is no longer `running`. See [Agent overview](/ai/agent/overview) and [Credits and costs](/ai/agent/credits). Permission: `ai.use`. Capability: `run_agent`. CLI: `freeinvoiceautomation agent run`. - Creates drafts only; sends always need a separate, confirmed call. - Uses AI credits unless you run it on your own AI key. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Body - `business_id` (string): Business to work in. May be left out when you can reach exactly one business. - `text` (string, required): Your request in plain language, or pasted text such as an email. - `attachment_ids` (string[]): Receipts or PDFs to read, uploaded first with [Upload a file](/api-reference/account/upload-file). Reading files ships in February 2027 and uses 2 credits per file. ## Response - `id` (string): Unique id of the run. - `business_id` (string | null): The business the run worked in. - `actor_type` (enum): Who started the run: a member, or one of your automations. One of: `user`, `automation`. - `actor_id` (string): Id of the member or the automation. - `automation_id` (string | null): The automation that started the run, if any. - `input` (object): What the agent was asked. - `text` (string): The request in plain language, or pasted text such as an email. - `attachment_ids` (string[]): Uploaded receipts or PDFs the agent was asked to read. - `steps` (object[]): The tool calls the agent made, in order. Each tool is a capability of this API (for example `upsert_client`, `create_invoice`), called with your permissions and audited. - `documents_created` (string[]): Ids of the documents the run created. They are always drafts. - `credits_used` (integer): Credits charged for the run. `0` when it failed before calling any tool, or when it ran on your own AI key. - `key_source` (enum): `platform` ran on our AI key and used credits; `own_key` ran on your own AI key and used none. One of: `platform`, `own_key`. - `provider` (string): AI provider used, e.g. `anthropic`. - `model` (string | null): Model used. - `status` (enum): State of the run. One of: `running`, `succeeded`, `failed`, `stopped`. - `error` (string | null): Why the run failed or stopped. - `started_at` (string): When the run started. - `finished_at` (string | null): When it finished; `null` while running. ## Errors - `ai_credits_exhausted` - `feature_locked` - `rate_limited` - `upstream_error` - `not_configured` - `idempotency_key_reused` Section: AI agent and pay-me page Source: https://docs.freeinvoiceautomation.com/api-reference/agent/run-agent --- # List agent runs GET https://api.freeinvoiceautomation.com/v1/agent/runs Returns the agent's activity: each run with its input, steps, documents created, credits used and status. This is the "Agent activity" list in **Automate > AI agent**. Runs are newest first. See [Agent overview](/ai/agent/overview). Permission: `ai.use`. Capability: `list_agent_runs`. CLI: `freeinvoiceautomation agent runs`. ## Query parameters - `business_id` (string): Only runs in this business. - `status` (enum): Filter by status; comma-separate several. One of: `running`, `succeeded`, `failed`, `stopped`. - `automation_id` (string): Only runs started by this automation. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The runs on this page. - `id` (string): Unique id of the run. - `business_id` (string | null): The business the run worked in. - `actor_type` (enum): Who started the run: a member, or one of your automations. One of: `user`, `automation`. - `actor_id` (string): Id of the member or the automation. - `automation_id` (string | null): The automation that started the run, if any. - `input` (object): What the agent was asked. - `text` (string): The request in plain language, or pasted text such as an email. - `attachment_ids` (string[]): Uploaded receipts or PDFs the agent was asked to read. - `steps` (object[]): The tool calls the agent made, in order. Each tool is a capability of this API (for example `upsert_client`, `create_invoice`), called with your permissions and audited. - `documents_created` (string[]): Ids of the documents the run created. They are always drafts. - `credits_used` (integer): Credits charged for the run. `0` when it failed before calling any tool, or when it ran on your own AI key. - `key_source` (enum): `platform` ran on our AI key and used credits; `own_key` ran on your own AI key and used none. One of: `platform`, `own_key`. - `provider` (string): AI provider used, e.g. `anthropic`. - `model` (string | null): Model used. - `status` (enum): State of the run. One of: `running`, `succeeded`, `failed`, `stopped`. - `error` (string | null): Why the run failed or stopped. - `started_at` (string): When the run started. - `finished_at` (string | null): When it finished; `null` while running. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. Section: AI agent and pay-me page Source: https://docs.freeinvoiceautomation.com/api-reference/agent/list-agent-runs --- # Get an agent run GET https://api.freeinvoiceautomation.com/v1/agent/runs/{id} Returns one agent run. Poll this after starting a run until it is no longer `running`. When `status` is `succeeded`, open the drafts in `documents_created` to check and send them. When it is `failed` or `stopped`, `error` says why, and a run that failed before calling any tool used no credits. Permission: `ai.use`. Capability: `get_agent_run`. CLI: `freeinvoiceautomation agent run-get`. ## Path parameters - `id` (string, required): Id of the agent run. ## Response - `id` (string): Unique id of the run. - `business_id` (string | null): The business the run worked in. - `actor_type` (enum): Who started the run: a member, or one of your automations. One of: `user`, `automation`. - `actor_id` (string): Id of the member or the automation. - `automation_id` (string | null): The automation that started the run, if any. - `input` (object): What the agent was asked. - `text` (string): The request in plain language, or pasted text such as an email. - `attachment_ids` (string[]): Uploaded receipts or PDFs the agent was asked to read. - `steps` (object[]): The tool calls the agent made, in order. Each tool is a capability of this API (for example `upsert_client`, `create_invoice`), called with your permissions and audited. - `documents_created` (string[]): Ids of the documents the run created. They are always drafts. - `credits_used` (integer): Credits charged for the run. `0` when it failed before calling any tool, or when it ran on your own AI key. - `key_source` (enum): `platform` ran on our AI key and used credits; `own_key` ran on your own AI key and used none. One of: `platform`, `own_key`. - `provider` (string): AI provider used, e.g. `anthropic`. - `model` (string | null): Model used. - `status` (enum): State of the run. One of: `running`, `succeeded`, `failed`, `stopped`. - `error` (string | null): Why the run failed or stopped. - `started_at` (string): When the run started. - `finished_at` (string | null): When it finished; `null` while running. Section: AI agent and pay-me page Source: https://docs.freeinvoiceautomation.com/api-reference/agent/get-agent-run --- # List agent automations GET https://api.freeinvoiceautomation.com/v1/agent/automations Returns the automations that run the agent on a schedule or when something happens. See [Automations](/ai/agent/automations). Permission: `ai.manage`. Capability: `list_agent_automations`. CLI: `freeinvoiceautomation agent automations`. ## Query parameters - `business_id` (string): Only automations of this business. - `enabled` (boolean): Only enabled (`true`) or disabled (`false`) automations. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The automations on this page. - `id` (string): Unique id of the automation. - `business_id` (string): The business the automation works in. - `name` (string): Your name for the automation. - `trigger` (object): When it runs: a schedule (for example every Monday) or an event (for example when a Stripe payment arrives). - `instructions` (string): What the agent should do each time, in plain language. - `enabled` (boolean): Whether the automation runs. - `auto_send` (boolean): `false` (default): the agent leaves drafts for you to send. `true`: documents it creates are sent automatically. Turning this on needs a fresh sign-in. - `auto_send_enabled_by` (string | null): Who turned on auto-send. - `auto_send_enabled_at` (string | null): When auto-send was turned on. - `last_run_at` (string | null): When it last ran. - `last_run_id` (string | null): The last [agent run](/api-reference/agent/get-agent-run). - `next_run_at` (string | null): When a scheduled automation runs next. - `created_at` (string): When it was created. - `updated_at` (string): When it was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. Section: AI agent and pay-me page Source: https://docs.freeinvoiceautomation.com/api-reference/agent/list-agent-automations --- # Create or update an agent automation POST https://api.freeinvoiceautomation.com/v1/agent/automations Creates an automation that runs the agent on a trigger, or updates one when the body contains its `id`. The same capability answers `PATCH /v1/agent/automations/{id}`. An automation has a trigger (a schedule such as "every Monday", or an event such as "when a Stripe payment arrives") and instructions such as "make the receipt". Each run uses 1 credit on our AI key, or none on your own key, and appears in the [agent runs](/api-reference/agent/list-agent-runs). Automations create **drafts** unless `auto_send` is on for that automation. Turning `auto_send` on needs a **fresh sign-in**, so it can only be done from a recent session in the app; API keys cannot do it and get `fresh_signin_required`. Who turned it on and when is recorded. See [Automations](/ai/agent/automations). Permission: `ai.manage`. Capability: `upsert_agent_automation`. CLI: `freeinvoiceautomation agent automation-upsert`. - Turning on `auto_send` needs a fresh sign-in. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Body - `id` (string): Id of an existing automation to update. Leave out to create one. - `business_id` (string): Business the automation works in. - `name` (string, required): Your name for the automation. - `trigger` (object, required): A schedule or an event that starts the run. - `instructions` (string, required): What the agent should do each time. - `enabled` (boolean): Whether the automation runs. - `auto_send` (boolean, default: false): Send what the agent creates without a person confirming. Needs a fresh sign-in to turn on. ## Response - `id` (string): Unique id of the automation. - `business_id` (string): The business the automation works in. - `name` (string): Your name for the automation. - `trigger` (object): When it runs: a schedule (for example every Monday) or an event (for example when a Stripe payment arrives). - `instructions` (string): What the agent should do each time, in plain language. - `enabled` (boolean): Whether the automation runs. - `auto_send` (boolean): `false` (default): the agent leaves drafts for you to send. `true`: documents it creates are sent automatically. Turning this on needs a fresh sign-in. - `auto_send_enabled_by` (string | null): Who turned on auto-send. - `auto_send_enabled_at` (string | null): When auto-send was turned on. - `last_run_at` (string | null): When it last ran. - `last_run_id` (string | null): The last [agent run](/api-reference/agent/get-agent-run). - `next_run_at` (string | null): When a scheduled automation runs next. - `created_at` (string): When it was created. - `updated_at` (string): When it was last changed. ## Errors - `fresh_signin_required` - `validation_failed` - `idempotency_key_reused` Section: AI agent and pay-me page Source: https://docs.freeinvoiceautomation.com/api-reference/agent/upsert-agent-automation --- # Delete an agent automation DELETE https://api.freeinvoiceautomation.com/v1/agent/automations/{id} Deletes an automation so it no longer runs. The automation is soft-deleted. Its past runs stay in the agent activity, and drafts it created are not changed. To stop it for a while, update it with `enabled: false` instead. Permission: `ai.manage`. Capability: `delete_agent_automation`. CLI: `freeinvoiceautomation agent automation-delete`. ## Path parameters - `id` (string, required): Id of the automation. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). Section: AI agent and pay-me page Source: https://docs.freeinvoiceautomation.com/api-reference/agent/delete-agent-automation --- # Get the pay-me page GET https://api.freeinvoiceautomation.com/v1/businesses/{id}/pay-me Returns the settings of a business's public pay-me page. The pay-me page at `freeinvoiceautomation.com/[business-slug]` lets anyone pay the business without an invoice link, with every rail the business has enabled. It has no analytics and no ads. See [Pay-me page](/payments/pay-me). Permission: `businesses.view`. Capability: `get_pay_me_page`. Available from February 2027. ## Path parameters - `id` (string, required): Id of the business. ## Response - `id` (string): Unique id of the pay-me page. - `business_id` (string): The business the page belongs to. - `enabled` (boolean): Whether the page is public at `freeinvoiceautomation.com/[business-slug]`. - `headline` (string | null): Heading shown at the top of the page. - `description` (string | null): Short text under the heading. - `allow_custom_amount` (boolean): Whether the payer can enter their own amount (proposed). - `created_at` (string): When the page was created. - `updated_at` (string): When it was last changed. ## Errors - `business_out_of_scope` Section: AI agent and pay-me page Source: https://docs.freeinvoiceautomation.com/api-reference/agent/get-pay-me-page --- # Update the pay-me page PUT https://api.freeinvoiceautomation.com/v1/businesses/{id}/pay-me Turns a business's pay-me page on or off and sets its heading and text. The page shows the business and every enabled payment rail; manage the rails themselves with your connections. Payments made on the page follow the normal rules: card payments are confirmed by the rail's webhook, and bank transfers and QR payments are recorded by you. Changing the page needs `rails.manage` because it decides how people pay you. See [Pay-me page](/payments/pay-me). Permission: `rails.manage`. Capability: `update_pay_me_page`. Available from February 2027. ## Path parameters - `id` (string, required): Id of the business. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Body - `enabled` (boolean, required): Make the page public (`true`) or hide it (`false`). - `headline` (string): Heading at the top of the page. - `description` (string): Short text under the heading. - `allow_custom_amount` (boolean): Let the payer enter their own amount (proposed). ## Response - `id` (string): Unique id of the pay-me page. - `business_id` (string): The business the page belongs to. - `enabled` (boolean): Whether the page is public at `freeinvoiceautomation.com/[business-slug]`. - `headline` (string | null): Heading shown at the top of the page. - `description` (string | null): Short text under the heading. - `allow_custom_amount` (boolean): Whether the payer can enter their own amount (proposed). - `created_at` (string): When the page was created. - `updated_at` (string): When it was last changed. ## Errors - `business_out_of_scope` - `validation_failed` Section: AI agent and pay-me page Source: https://docs.freeinvoiceautomation.com/api-reference/agent/update-pay-me-page --- # List a client's saved cards GET https://api.freeinvoiceautomation.com/v1/clients/{id}/saved-payment-methods Returns the cards a client saved for future invoices, showing only brand, last 4 and the consent record. On the first Stripe Checkout a client may tick "Save this card for future invoices from {Business}". Stripe stores the card on the business's own connected Stripe account; we keep only the Stripe customer and payment method ids, the brand, the last 4 digits and the client's consent. No card number, expiry or CVC ever reaches us, and you can never see the full card. The client can remove a saved card from the portal at any time; it then shows `status: removed` and can no longer be charged. See [Saved cards and auto-charge](/payments/auto-charge). Permission: `payments.view`. Capability: `list_saved_payment_methods`. CLI: `freeinvoiceautomation client cards`. Available from March 2027. ## Path parameters - `id` (string, required): Id of the client. ## Query parameters - `status` (enum): Only active or removed cards. One of: `active`, `removed`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The saved cards on this page. - `id` (string): Unique id of the saved card. - `business_id` (string): The business whose Stripe account holds the card. - `client_id` (string): The client who saved the card. - `connection_id` (string): The business's Stripe connection. - `stripe_customer_id` (string): Stripe customer id on the business's connected account. - `stripe_payment_method_id` (string): Stripe payment method id. The card itself stays at Stripe. - `card_brand` (string): Card brand, for display, e.g. `visa`. - `card_last4` (string): Last 4 digits, for display. - `consent_text` (string): The consent the client ticked, exactly as shown, e.g. "Save this card for future invoices from Acme Design Studio". - `consent_at` (string): When the client gave consent. - `status` (enum): `active` can be charged; `removed` cannot. One of: `active`, `removed`. - `removed_at` (string | null): When the card was removed. - `removed_by_type` (enum | null): Who removed it: the client (in the portal), the business, or the system. One of: `client`, `business`, `system`. - `created_at` (string): When the card was saved. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. Section: Saved cards, contracts and views Source: https://docs.freeinvoiceautomation.com/api-reference/cards-contracts/list-saved-payment-methods --- # Set auto-charge PUT https://api.freeinvoiceautomation.com/v1/recurring-schedules/{id}/auto-charge Turns on or off charging a client's saved card on the due date of a recurring invoice or an instalment. The same capability answers `PUT /v1/schedule-installments/{id}/auto-charge` for one instalment of a payment schedule. This moves money, so it is **destructive**: without `confirm: true` the call changes nothing and returns `428 confirmation_required` with a preview. In the app it is a confirmation dialog. See [Drafts and confirmations](/api-reference/confirmations). The card must be an `active` saved card of the same client with a consent record; otherwise the call fails with `consent_required`. On the due date an off-session payment is made on the business's own Stripe account, and the invoice is marked paid **only when Stripe's webhook confirms it**. If the bank asks for authentication or the charge fails, the client gets the normal pay link and the reminder sequence runs. Retries follow the auto-charge retry settings (by default 3 tries over 7 days). See [Saved cards and auto-charge](/payments/auto-charge). Permission: `payments.auto_charge`. Capability: `set_auto_charge`. CLI: `freeinvoiceautomation auto-charge set`. Available from March 2027. - Destructive: needs `confirm: true` (or the confirmation dialog in the app). ## Path parameters - `id` (string, required): Id of the recurring schedule (or of the instalment on the `/schedule-installments/{id}/auto-charge` route). ## Headers - `Idempotency-Key` (string, required): Required for API key and MCP calls on creates and money-moving changes. See [Idempotency](/api-reference/idempotency). ## Body - `auto_charge` (boolean, required): `true` to charge the saved card on each due date, `false` to stop. - `saved_payment_method_id` (string): Required when turning on: an active saved card of the same client. - `confirm` (boolean, required): Must be `true` to apply the change. Without it the response is a preview and nothing changes. ## Response - `id` (string): Id of the recurring schedule or instalment. - `auto_charge` (boolean): Whether auto-charge is now on. - `saved_payment_method_id` (string | null): The saved card that will be charged. - `status` (string): Status of the schedule (`active`, `paused`, `ended`) or instalment. - `next_run_date` (string): Recurring schedules: the next date an invoice is created. ## Errors - `confirmation_required` - `consent_required` - `rail_not_connected` - `idempotency_key_reused` Section: Saved cards, contracts and views Source: https://docs.freeinvoiceautomation.com/api-reference/cards-contracts/set-auto-charge --- # Create a contract POST https://api.freeinvoiceautomation.com/v1/contracts Creates a draft contract from a template, filled with the business's and client's details. A contract is its own document type, built from a template with variables from the business and the client. Attach it to an estimate to make the estimate "accept and sign": the client signs on the estimate page or in the portal. If the estimate has a deposit, signing can create the deposit invoice. The contract is created as a **draft**. To send it for signing, use [Send a document](/api-reference/documents/send-document) with its `document_id`. The client reads it, ticks the consent text and types or draws a signature; the business can counter-sign. Signatures are simple electronic signatures (eIDAS, ESIGN and UETA); qualified signatures are not offered. This is also the MCP tool `create_contract`. See [Contracts and e-signature](/features/contracts). Permission: `contracts.manage`. Capability: `create_contract`. MCP tool: `create_contract`. CLI: `freeinvoiceautomation contract create`. Available from March 2027. - Creates a draft. Sending is a separate, confirmed call. ## Headers - `Idempotency-Key` (string, required): Required for API key and MCP calls on creates and money-moving changes. See [Idempotency](/api-reference/idempotency). ## Body - `business_id` (string): Business that issues the contract. May be left out when you can reach exactly one business. - `client_id` (string, required): Client who signs. - `template_key` (string): Contract template to build from. Templates are data, so new ones need no code change. - `estimate_document_id` (string): Estimate to attach the contract to ("accept and sign"). - `creates_deposit_invoice` (boolean): Create the estimate's deposit invoice when the contract is signed. - `signer_order` (object[]): Order of signers, e.g. `[{"role":"client"},{"role":"business"}]`. It can never change once the contract is sealed. ## Response - `id` (string): Unique id of the contract. - `business_id` (string): The business. - `document_id` (string): The `contract` document. Send it with [Send a document](/api-reference/documents/send-document). - `client_id` (string): The client who signs. - `template_key` (string | null): The contract template it was built from. - `content_snapshot` (object): The contract content with business and client variables filled in. Fixed once the contract is sealed. - `signer_order` (object[]): The fixed order of signers, e.g. `[{"role":"client"},{"role":"business"}]`. A signer who is not next is refused. - `estimate_document_id` (string | null): The estimate this contract is attached to ("accept and sign"). - `creates_deposit_invoice` (boolean): Whether signing creates the deposit invoice of the attached estimate. - `status` (enum): Contract status. One of: `draft`, `sent`, `partially_signed`, `signed`, `sealed`, `superseded`. - `signatures` (object[]): Signatures so far, in signing order. - `signer_role` (enum): Which side signed. One of: `client`, `business`. - `signer_name` (string): Name of the signer. - `signer_email` (string): Email of the signer. - `method` (enum): Typed name or drawn signature. One of: `typed`, `drawn`. - `consent_text` (string): The consent text the signer agreed to, as shown. - `signed_at` (string): When they signed. - `sealed_pdf_sha256` (string | null): SHA-256 hash (hex) of the sealed PDF. Checked on every download. - `sealed_at` (string | null): When the final PDF was sealed. - `version` (integer): Version number. A change to a contract is a new version that must be signed again. - `created_at` (string): When the contract was created. - `updated_at` (string): When it was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` - `business_out_of_scope` Section: Saved cards, contracts and views Source: https://docs.freeinvoiceautomation.com/api-reference/cards-contracts/create-contract --- # List contracts GET https://api.freeinvoiceautomation.com/v1/contracts Returns the contracts of your businesses, newest first. Use `status` to find contracts waiting for a signature (`sent`, `partially_signed`) or already sealed. See [Contracts and e-signature](/features/contracts). Permission: `contracts.view`. Capability: `list_contracts`. CLI: `freeinvoiceautomation contract list`. Available from March 2027. ## Query parameters - `business_id` (string): Only contracts of this business. - `client_id` (string): Only contracts with this client. - `status` (enum): Filter by status; comma-separate several. One of: `draft`, `sent`, `partially_signed`, `signed`, `sealed`, `superseded`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The contracts on this page. - `id` (string): Unique id of the contract. - `business_id` (string): The business. - `document_id` (string): The `contract` document. Send it with [Send a document](/api-reference/documents/send-document). - `client_id` (string): The client who signs. - `template_key` (string | null): The contract template it was built from. - `content_snapshot` (object): The contract content with business and client variables filled in. Fixed once the contract is sealed. - `signer_order` (object[]): The fixed order of signers, e.g. `[{"role":"client"},{"role":"business"}]`. A signer who is not next is refused. - `estimate_document_id` (string | null): The estimate this contract is attached to ("accept and sign"). - `creates_deposit_invoice` (boolean): Whether signing creates the deposit invoice of the attached estimate. - `status` (enum): Contract status. One of: `draft`, `sent`, `partially_signed`, `signed`, `sealed`, `superseded`. - `signatures` (object[]): Signatures so far, in signing order. - `signer_role` (enum): Which side signed. One of: `client`, `business`. - `signer_name` (string): Name of the signer. - `signer_email` (string): Email of the signer. - `method` (enum): Typed name or drawn signature. One of: `typed`, `drawn`. - `consent_text` (string): The consent text the signer agreed to, as shown. - `signed_at` (string): When they signed. - `sealed_pdf_sha256` (string | null): SHA-256 hash (hex) of the sealed PDF. Checked on every download. - `sealed_at` (string | null): When the final PDF was sealed. - `version` (integer): Version number. A change to a contract is a new version that must be signed again. - `created_at` (string): When the contract was created. - `updated_at` (string): When it was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Saved cards, contracts and views Source: https://docs.freeinvoiceautomation.com/api-reference/cards-contracts/list-contracts --- # Get a contract GET https://api.freeinvoiceautomation.com/v1/contracts/{id} Returns one contract with its content, signer order and signatures. Permission: `contracts.view`. Capability: `get_contract`. CLI: `freeinvoiceautomation contract get`. Available from March 2027. ## Path parameters - `id` (string, required): Id of the contract. ## Response - `id` (string): Unique id of the contract. - `business_id` (string): The business. - `document_id` (string): The `contract` document. Send it with [Send a document](/api-reference/documents/send-document). - `client_id` (string): The client who signs. - `template_key` (string | null): The contract template it was built from. - `content_snapshot` (object): The contract content with business and client variables filled in. Fixed once the contract is sealed. - `signer_order` (object[]): The fixed order of signers, e.g. `[{"role":"client"},{"role":"business"}]`. A signer who is not next is refused. - `estimate_document_id` (string | null): The estimate this contract is attached to ("accept and sign"). - `creates_deposit_invoice` (boolean): Whether signing creates the deposit invoice of the attached estimate. - `status` (enum): Contract status. One of: `draft`, `sent`, `partially_signed`, `signed`, `sealed`, `superseded`. - `signatures` (object[]): Signatures so far, in signing order. - `signer_role` (enum): Which side signed. One of: `client`, `business`. - `signer_name` (string): Name of the signer. - `signer_email` (string): Email of the signer. - `method` (enum): Typed name or drawn signature. One of: `typed`, `drawn`. - `consent_text` (string): The consent text the signer agreed to, as shown. - `signed_at` (string): When they signed. - `sealed_pdf_sha256` (string | null): SHA-256 hash (hex) of the sealed PDF. Checked on every download. - `sealed_at` (string | null): When the final PDF was sealed. - `version` (integer): Version number. A change to a contract is a new version that must be signed again. - `created_at` (string): When the contract was created. - `updated_at` (string): When it was last changed. Section: Saved cards, contracts and views Source: https://docs.freeinvoiceautomation.com/api-reference/cards-contracts/get-contract --- # Counter-sign a contract POST https://api.freeinvoiceautomation.com/v1/contracts/{id}/countersign Adds the business's signature to a contract when it is the business's turn in the signer order. The signer order is fixed per contract; if the business is not next (for example the client has not signed yet), the call is refused. The signature stores the signer's name and email, the method, the consent text, IP address, user agent and time. When the last signer has signed, the contract becomes `signed`. The final PDF is then sealed with a signature certificate page and a SHA-256 hash, and the contract becomes `sealed`. A signed contract never changes; to change it, create a [new version](/api-reference/cards-contracts/new-contract-version). See [Contracts and e-signature](/features/contracts). Permission: `contracts.sign`. Capability: `countersign_contract`. CLI: `freeinvoiceautomation contract countersign`. Available from March 2027. ## Path parameters - `id` (string, required): Id of the contract. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Body - `method` (enum, required): Typed name or drawn signature. One of: `typed`, `drawn`. - `typed_name` (string): Required for `typed`: the name as signed. - `signature_attachment_id` (string): Required for `drawn`: the signature image, uploaded first with [Upload a file](/api-reference/account/upload-file). - `consent` (boolean, required): Must be `true`: you agree to the consent text, which is stored with the signature. ## Response - `id` (string): Unique id of the contract. - `business_id` (string): The business. - `document_id` (string): The `contract` document. Send it with [Send a document](/api-reference/documents/send-document). - `client_id` (string): The client who signs. - `template_key` (string | null): The contract template it was built from. - `content_snapshot` (object): The contract content with business and client variables filled in. Fixed once the contract is sealed. - `signer_order` (object[]): The fixed order of signers, e.g. `[{"role":"client"},{"role":"business"}]`. A signer who is not next is refused. - `estimate_document_id` (string | null): The estimate this contract is attached to ("accept and sign"). - `creates_deposit_invoice` (boolean): Whether signing creates the deposit invoice of the attached estimate. - `status` (enum): Contract status. One of: `draft`, `sent`, `partially_signed`, `signed`, `sealed`, `superseded`. - `signatures` (object[]): Signatures so far, in signing order. - `signer_role` (enum): Which side signed. One of: `client`, `business`. - `signer_name` (string): Name of the signer. - `signer_email` (string): Email of the signer. - `method` (enum): Typed name or drawn signature. One of: `typed`, `drawn`. - `consent_text` (string): The consent text the signer agreed to, as shown. - `signed_at` (string): When they signed. - `sealed_pdf_sha256` (string | null): SHA-256 hash (hex) of the sealed PDF. Checked on every download. - `sealed_at` (string | null): When the final PDF was sealed. - `version` (integer): Version number. A change to a contract is a new version that must be signed again. - `created_at` (string): When the contract was created. - `updated_at` (string): When it was last changed. ## Errors - `conflict` - `contract_sealed` - `validation_failed` Section: Saved cards, contracts and views Source: https://docs.freeinvoiceautomation.com/api-reference/cards-contracts/countersign-contract --- # Create a new contract version POST https://api.freeinvoiceautomation.com/v1/contracts/{id}/versions Makes a new draft version of a contract when something must change. The new version must be signed again. Signed contracts never change. To change a contract, create a new version: it starts as a draft copy with `version` increased by one, linked to the previous version, and the previous contract becomes `superseded`. Edit and send the new version; every signer signs again. The sealed PDF of the old version stays available and unchanged. See [Contracts and e-signature](/features/contracts). Permission: `contracts.manage`. Capability: `new_contract_version`. CLI: `freeinvoiceautomation contract revise`. Available from March 2027. ## Path parameters - `id` (string, required): Id of the contract. ## Headers - `Idempotency-Key` (string, required): Required for API key and MCP calls on creates and money-moving changes. See [Idempotency](/api-reference/idempotency). ## Body - `template_key` (string): Rebuild from a different template. Leave out to copy the current content. - `signer_order` (object[]): New order of signers. Leave out to keep it. - `creates_deposit_invoice` (boolean): Whether signing creates the deposit invoice. ## Response - `id` (string): Unique id of the contract. - `business_id` (string): The business. - `document_id` (string): The `contract` document. Send it with [Send a document](/api-reference/documents/send-document). - `client_id` (string): The client who signs. - `template_key` (string | null): The contract template it was built from. - `content_snapshot` (object): The contract content with business and client variables filled in. Fixed once the contract is sealed. - `signer_order` (object[]): The fixed order of signers, e.g. `[{"role":"client"},{"role":"business"}]`. A signer who is not next is refused. - `estimate_document_id` (string | null): The estimate this contract is attached to ("accept and sign"). - `creates_deposit_invoice` (boolean): Whether signing creates the deposit invoice of the attached estimate. - `status` (enum): Contract status. One of: `draft`, `sent`, `partially_signed`, `signed`, `sealed`, `superseded`. - `signatures` (object[]): Signatures so far, in signing order. - `signer_role` (enum): Which side signed. One of: `client`, `business`. - `signer_name` (string): Name of the signer. - `signer_email` (string): Email of the signer. - `method` (enum): Typed name or drawn signature. One of: `typed`, `drawn`. - `consent_text` (string): The consent text the signer agreed to, as shown. - `signed_at` (string): When they signed. - `sealed_pdf_sha256` (string | null): SHA-256 hash (hex) of the sealed PDF. Checked on every download. - `sealed_at` (string | null): When the final PDF was sealed. - `version` (integer): Version number. A change to a contract is a new version that must be signed again. - `created_at` (string): When the contract was created. - `updated_at` (string): When it was last changed. ## Errors - `idempotency_key_reused` Section: Saved cards, contracts and views Source: https://docs.freeinvoiceautomation.com/api-reference/cards-contracts/new-contract-version --- # Download a signed contract GET https://api.freeinvoiceautomation.com/v1/contracts/{id}/pdf Downloads the sealed PDF of a signed contract, after checking its SHA-256 hash. Every download checks the file against the SHA-256 hash stored when the contract was sealed. If they do not match, the download is blocked with `contract_hash_mismatch` and an alert is raised, so you never receive a changed file. The PDF includes the signature certificate page. Clients can download the same sealed PDF from the portal. See [Contracts and e-signature](/features/contracts). Permission: `contracts.view`. Capability: `download_signed_contract`. CLI: `freeinvoiceautomation contract pdf`. Available from March 2027. ## Path parameters - `id` (string, required): Id of the contract. ## Errors - `conflict` - `contract_hash_mismatch` Section: Saved cards, contracts and views Source: https://docs.freeinvoiceautomation.com/api-reference/cards-contracts/download-signed-contract --- # List saved views GET https://api.freeinvoiceautomation.com/v1/saved-views Returns your saved list views: named filters, sort and columns. You see your own views and views shared with the account. See [Saved views](/features/saved-views). Permission: `documents.view`. Capability: `list_saved_views`. Available from March 2027. ## Query parameters - `entity` (string): Only views for this list, e.g. `documents`. - `business_id` (string): Only views for this business. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The views on this page. - `id` (string): Unique id of the view. - `business_id` (string | null): Business the view is limited to, or `null` for all your businesses. - `user_id` (string | null): The member the view belongs to. `null` for a view shared with the account. - `entity` (string): The list the view is for, e.g. `documents` or `clients`. - `name` (string): Name of the view. - `filters` (object): Saved filters, using the list's query parameter names (e.g. `status`, `client_id`, `due_before`). - `sort` (object): Saved sort order. - `columns` (object): Columns shown and their order. - `created_at` (string): When the view was created. - `updated_at` (string): When it was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. Section: Saved cards, contracts and views Source: https://docs.freeinvoiceautomation.com/api-reference/cards-contracts/list-saved-views --- # Create or update a saved view POST https://api.freeinvoiceautomation.com/v1/saved-views Saves a list's filters, sort and columns under a name, or updates a view by `id`. The same capability answers `PATCH /v1/saved-views/{id}`. A new view belongs to you. See [Saved views](/features/saved-views). Permission: `documents.view`. Capability: `upsert_saved_view`. Available from March 2027. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Body - `id` (string): Id of an existing view to update. Leave out to create one. - `business_id` (string): Limit the view to one business. - `entity` (string, required): The list the view is for, e.g. `documents` or `clients`. - `name` (string, required): Name of the view. - `filters` (object): Filters, using the list's query parameter names. - `sort` (object): Sort order. - `columns` (object): Columns shown and their order. ## Response - `id` (string): Unique id of the view. - `business_id` (string | null): Business the view is limited to, or `null` for all your businesses. - `user_id` (string | null): The member the view belongs to. `null` for a view shared with the account. - `entity` (string): The list the view is for, e.g. `documents` or `clients`. - `name` (string): Name of the view. - `filters` (object): Saved filters, using the list's query parameter names (e.g. `status`, `client_id`, `due_before`). - `sort` (object): Saved sort order. - `columns` (object): Columns shown and their order. - `created_at` (string): When the view was created. - `updated_at` (string): When it was last changed. ## Errors - `validation_failed` Section: Saved cards, contracts and views Source: https://docs.freeinvoiceautomation.com/api-reference/cards-contracts/upsert-saved-view --- # Delete a saved view DELETE https://api.freeinvoiceautomation.com/v1/saved-views/{id} Deletes a saved view. The documents it showed are not affected. Permission: `documents.view`. Capability: `delete_saved_view`. Available from March 2027. ## Path parameters - `id` (string, required): Id of the saved view. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). Section: Saved cards, contracts and views Source: https://docs.freeinvoiceautomation.com/api-reference/cards-contracts/delete-saved-view --- # Log time POST https://api.freeinvoiceautomation.com/v1/time-entries Records time worked for a client, with start and end times or a duration. Use this for manual entries, or for timers you track elsewhere. The hourly rate is taken from the most specific rate set for the member, the client or the business, and stored on the entry so later rate changes do not change it. Billable time appears in [unbilled time](/api-reference/time-expenses/list-unbilled-time) until it is invoiced. This is also the MCP tool `log_time`. To time work as it happens, use [Start a timer](/api-reference/time-expenses/start-timer). See [Time tracking](/features/time-tracking). Permission: `time.log`. Capability: `log_time`. MCP tool: `log_time`. CLI: `freeinvoiceautomation time log`. Available from April 2027. ## Headers - `Idempotency-Key` (string, required): Required for API key and MCP calls on creates. A retry with the same key never creates a second record. See [Idempotency](/api-reference/idempotency). ## Body - `business_id` (string): Business the work was for. May be left out when you can reach exactly one business. - `client_id` (string, required): Client the work was for. - `project_tag` (string): Optional tag to group work on the invoice. - `description` (string): What was done. - `started_at` (string): Start time. Give `started_at` and `ended_at`, or `duration_seconds`. - `ended_at` (string): End time. - `duration_seconds` (integer): Length in seconds, for entries without start and end times. - `billable` (boolean): Whether the time can be billed. Defaults to the business or client setting (billable unless changed). ## Response - `id` (string): Unique id of the time entry. - `business_id` (string): The business the work was for. - `client_id` (string): The client the work was for. - `user_id` (string): The member who did the work. - `project_tag` (string | null): Optional tag to group work, e.g. `website-redesign`. There is no separate projects module. - `description` (string | null): What was done. Becomes the invoice line text. - `started_at` (string | null): When the work started. - `ended_at` (string | null): When it ended; `null` while the timer runs. - `duration_seconds` (integer | null): Length of a manual entry, in seconds. - `is_running` (boolean): Whether this is a running timer. A member has at most one running timer. - `rate_minor` (string | null): Hourly rate in minor units, taken from the most specific rate (member, then client, then business) when the entry was created. - `currency` (string | null): Currency of `rate_minor`. - `billable` (boolean): Whether the time can be billed to the client. - `invoiced_document_id` (string | null): The invoice this time was billed on. Once set, the entry is locked. - `invoiced_line_id` (string | null): The invoice line it was billed on. - `created_at` (string): When the entry was created. - `updated_at` (string): When it was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` - `business_out_of_scope` Section: Time and expenses Source: https://docs.freeinvoiceautomation.com/api-reference/time-expenses/log-time --- # Start a timer POST https://api.freeinvoiceautomation.com/v1/time-entries/timer Starts a running timer for you on a client. Creates a time entry with `is_running: true` and `started_at` set to now. You can have only one running timer; stop it with [Stop the timer](/api-reference/time-expenses/stop-timer) before starting another. See [Time tracking](/features/time-tracking). Permission: `time.log`. Capability: `start_timer`. CLI: `freeinvoiceautomation time start`. Available from April 2027. ## Headers - `Idempotency-Key` (string, required): Required for API key and MCP calls on creates. A retry with the same key never creates a second record. See [Idempotency](/api-reference/idempotency). ## Body - `business_id` (string): Business the work is for. - `client_id` (string, required): Client the work is for. - `project_tag` (string): Optional tag to group work. - `description` (string): What you are working on. - `billable` (boolean): Whether the time can be billed. ## Response - `id` (string): Unique id of the time entry. - `business_id` (string): The business the work was for. - `client_id` (string): The client the work was for. - `user_id` (string): The member who did the work. - `project_tag` (string | null): Optional tag to group work, e.g. `website-redesign`. There is no separate projects module. - `description` (string | null): What was done. Becomes the invoice line text. - `started_at` (string | null): When the work started. - `ended_at` (string | null): When it ended; `null` while the timer runs. - `duration_seconds` (integer | null): Length of a manual entry, in seconds. - `is_running` (boolean): Whether this is a running timer. A member has at most one running timer. - `rate_minor` (string | null): Hourly rate in minor units, taken from the most specific rate (member, then client, then business) when the entry was created. - `currency` (string | null): Currency of `rate_minor`. - `billable` (boolean): Whether the time can be billed to the client. - `invoiced_document_id` (string | null): The invoice this time was billed on. Once set, the entry is locked. - `invoiced_line_id` (string | null): The invoice line it was billed on. - `created_at` (string): When the entry was created. - `updated_at` (string): When it was last changed. ## Errors - `conflict` - `idempotency_key_reused` Section: Time and expenses Source: https://docs.freeinvoiceautomation.com/api-reference/time-expenses/start-timer --- # Stop the timer POST https://api.freeinvoiceautomation.com/v1/time-entries/timer/stop Stops your running timer and saves the time entry. Sets `ended_at` to now and `is_running` to `false` on your running timer. Returns `404 not_found` when no timer is running. See [Time tracking](/features/time-tracking). Permission: `time.log`. Capability: `stop_timer`. CLI: `freeinvoiceautomation time stop`. Available from April 2027. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Unique id of the time entry. - `business_id` (string): The business the work was for. - `client_id` (string): The client the work was for. - `user_id` (string): The member who did the work. - `project_tag` (string | null): Optional tag to group work, e.g. `website-redesign`. There is no separate projects module. - `description` (string | null): What was done. Becomes the invoice line text. - `started_at` (string | null): When the work started. - `ended_at` (string | null): When it ended; `null` while the timer runs. - `duration_seconds` (integer | null): Length of a manual entry, in seconds. - `is_running` (boolean): Whether this is a running timer. A member has at most one running timer. - `rate_minor` (string | null): Hourly rate in minor units, taken from the most specific rate (member, then client, then business) when the entry was created. - `currency` (string | null): Currency of `rate_minor`. - `billable` (boolean): Whether the time can be billed to the client. - `invoiced_document_id` (string | null): The invoice this time was billed on. Once set, the entry is locked. - `invoiced_line_id` (string | null): The invoice line it was billed on. - `created_at` (string): When the entry was created. - `updated_at` (string): When it was last changed. Section: Time and expenses Source: https://docs.freeinvoiceautomation.com/api-reference/time-expenses/stop-timer --- # List time entries GET https://api.freeinvoiceautomation.com/v1/time-entries Returns time entries, newest first, filtered by client, member, tag or billable state. Results use [cursor pagination](/api-reference/pagination). See [Time tracking](/features/time-tracking). Permission: `time.view`. Capability: `list_time_entries`. CLI: `freeinvoiceautomation time list`. Available from April 2027. ## Query parameters - `business_id` (string): Only entries of this business. - `client_id` (string): Only entries for this client. - `user_id` (string): Only entries by this member. - `project_tag` (string): Only entries with this tag. - `billable` (boolean): Only billable (`true`) or non-billable (`false`) entries. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The time entries on this page. - `id` (string): Unique id of the time entry. - `business_id` (string): The business the work was for. - `client_id` (string): The client the work was for. - `user_id` (string): The member who did the work. - `project_tag` (string | null): Optional tag to group work, e.g. `website-redesign`. There is no separate projects module. - `description` (string | null): What was done. Becomes the invoice line text. - `started_at` (string | null): When the work started. - `ended_at` (string | null): When it ended; `null` while the timer runs. - `duration_seconds` (integer | null): Length of a manual entry, in seconds. - `is_running` (boolean): Whether this is a running timer. A member has at most one running timer. - `rate_minor` (string | null): Hourly rate in minor units, taken from the most specific rate (member, then client, then business) when the entry was created. - `currency` (string | null): Currency of `rate_minor`. - `billable` (boolean): Whether the time can be billed to the client. - `invoiced_document_id` (string | null): The invoice this time was billed on. Once set, the entry is locked. - `invoiced_line_id` (string | null): The invoice line it was billed on. - `created_at` (string): When the entry was created. - `updated_at` (string): When it was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Time and expenses Source: https://docs.freeinvoiceautomation.com/api-reference/time-expenses/list-time-entries --- # Update a time entry PATCH https://api.freeinvoiceautomation.com/v1/time-entries/{id} Changes a time entry that has not been billed yet. You can change your own entries with `time.log`; changing another member's entries needs `time.manage`. Billed entries are locked to the invoice they were billed on and return `time_entry_locked`; to correct billed time, change the invoice instead. See [Time tracking](/features/time-tracking). Permission: `time.log`. Capability: `update_time_entry`. CLI: `freeinvoiceautomation time update`. Available from April 2027. - Own entries need `time.log`; other members' entries need `time.manage`. ## Path parameters - `id` (string, required): Id of the time entry. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Body - `client_id` (string): Client the work was for. - `project_tag` (string): Optional tag to group work on the invoice. - `description` (string): What was done. - `started_at` (string): Start time. Give `started_at` and `ended_at`, or `duration_seconds`. - `ended_at` (string): End time. - `duration_seconds` (integer): Length in seconds, for entries without start and end times. - `billable` (boolean): Whether the time can be billed. Defaults to the business or client setting (billable unless changed). ## Response - `id` (string): Unique id of the time entry. - `business_id` (string): The business the work was for. - `client_id` (string): The client the work was for. - `user_id` (string): The member who did the work. - `project_tag` (string | null): Optional tag to group work, e.g. `website-redesign`. There is no separate projects module. - `description` (string | null): What was done. Becomes the invoice line text. - `started_at` (string | null): When the work started. - `ended_at` (string | null): When it ended; `null` while the timer runs. - `duration_seconds` (integer | null): Length of a manual entry, in seconds. - `is_running` (boolean): Whether this is a running timer. A member has at most one running timer. - `rate_minor` (string | null): Hourly rate in minor units, taken from the most specific rate (member, then client, then business) when the entry was created. - `currency` (string | null): Currency of `rate_minor`. - `billable` (boolean): Whether the time can be billed to the client. - `invoiced_document_id` (string | null): The invoice this time was billed on. Once set, the entry is locked. - `invoiced_line_id` (string | null): The invoice line it was billed on. - `created_at` (string): When the entry was created. - `updated_at` (string): When it was last changed. ## Errors - `time_entry_locked` - `validation_failed` Section: Time and expenses Source: https://docs.freeinvoiceautomation.com/api-reference/time-expenses/update-time-entry --- # Delete a time entry DELETE https://api.freeinvoiceautomation.com/v1/time-entries/{id} Deletes a time entry that has not been billed. The entry is soft-deleted. Own entries need `time.log`; other members' entries need `time.manage`. Billed entries are locked and cannot be deleted. Permission: `time.log`. Capability: `delete_time_entry`. CLI: `freeinvoiceautomation time delete`. Available from April 2027. - Own entries need `time.log`; other members' entries need `time.manage`. ## Path parameters - `id` (string, required): Id of the time entry. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Errors - `time_entry_locked` Section: Time and expenses Source: https://docs.freeinvoiceautomation.com/api-reference/time-expenses/delete-time-entry --- # List unbilled time GET https://api.freeinvoiceautomation.com/v1/time-entries/unbilled Returns billable time entries that are not on an invoice yet. Use this to see what you can bill a client before running [Invoice unbilled time](/api-reference/time-expenses/invoice-unbilled). Running timers are not included. This is also the MCP tool `list_unbilled_time`. See [Time tracking](/features/time-tracking). Permission: `time.view`. Capability: `list_unbilled_time`. MCP tool: `list_unbilled_time`. CLI: `freeinvoiceautomation time unbilled`. Available from April 2027. ## Query parameters - `business_id` (string): Only entries of this business. - `client_id` (string): Only entries for this client. - `project_tag` (string): Only entries with this tag. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The unbilled entries on this page. - `id` (string): Unique id of the time entry. - `business_id` (string): The business the work was for. - `client_id` (string): The client the work was for. - `user_id` (string): The member who did the work. - `project_tag` (string | null): Optional tag to group work, e.g. `website-redesign`. There is no separate projects module. - `description` (string | null): What was done. Becomes the invoice line text. - `started_at` (string | null): When the work started. - `ended_at` (string | null): When it ended; `null` while the timer runs. - `duration_seconds` (integer | null): Length of a manual entry, in seconds. - `is_running` (boolean): Whether this is a running timer. A member has at most one running timer. - `rate_minor` (string | null): Hourly rate in minor units, taken from the most specific rate (member, then client, then business) when the entry was created. - `currency` (string | null): Currency of `rate_minor`. - `billable` (boolean): Whether the time can be billed to the client. - `invoiced_document_id` (string | null): The invoice this time was billed on. Once set, the entry is locked. - `invoiced_line_id` (string | null): The invoice line it was billed on. - `created_at` (string): When the entry was created. - `updated_at` (string): When it was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. Section: Time and expenses Source: https://docs.freeinvoiceautomation.com/api-reference/time-expenses/list-unbilled-time --- # Invoice unbilled time POST https://api.freeinvoiceautomation.com/v1/documents/from-unbilled Creates a draft invoice for a client from their unbilled time and billable expenses. Unbilled billable time for the client is grouped by project tag or by day into invoice lines, each priced as time times rate. Billable expenses for the client are added with their markup. The time entries and expenses are then linked to the invoice and locked, so the same work is never billed twice. The invoice is a **draft**: check it and send it with [Send a document](/api-reference/documents/send-document). AI automations such as "invoice Client X for last week's time" use this capability. This is also the MCP tool `invoice_unbilled`. See [Time tracking](/features/time-tracking) and [Expenses](/features/expenses). Permission: `documents.create`. Capability: `invoice_unbilled`. MCP tool: `invoice_unbilled`. CLI: `freeinvoiceautomation invoice unbilled`. Available from April 2027. - Creates a draft. Sending is a separate, confirmed call. ## Headers - `Idempotency-Key` (string, required): Required for API key and MCP calls on creates. A retry with the same key never creates a second record. See [Idempotency](/api-reference/idempotency). ## Body - `business_id` (string): Business that issues the invoice. - `client_id` (string, required): Client to invoice. - `group_by` (enum): Group time into lines by project tag or by day. Defaults to the business setting (project). One of: `project`, `day`. ## Response - `id` (string): Id of the new draft invoice. - `business_id` (string): The business. - `client_id` (string): The client. - `type` (string): Always `invoice`. - `status` (string): Always `draft`. - `number` (string | null): `null`: a draft gets its number when it is first sent. - `currency` (string): Invoice currency. - `subtotal_minor` (string): Sum of the lines before tax. - `tax_minor` (string): Tax. - `total_minor` (string): Invoice total. ## Errors - `validation_failed` - `idempotency_key_reused` - `business_out_of_scope` Section: Time and expenses Source: https://docs.freeinvoiceautomation.com/api-reference/time-expenses/invoice-unbilled --- # Record an expense POST https://api.freeinvoiceautomation.com/v1/expenses Records a business expense, optionally with a receipt and billable to a client. Upload the receipt photo or PDF first with [Upload a file](/api-reference/account/upload-file); it is stored privately. Categories come from the expense categories list with your country's preset. A billable expense needs a client and can carry a markup; it is added to that client's next invoice made with [Invoice unbilled time](/api-reference/time-expenses/invoice-unbilled) and is then linked to the invoice. To have AI read the receipt for you, use [Read a receipt](/api-reference/time-expenses/read-receipt) first and pass its `agent_run_id` here. This is also the MCP tool `record_expense`. See [Expenses](/features/expenses). Permission: `expenses.manage`. Capability: `record_expense`. MCP tool: `record_expense`. CLI: `freeinvoiceautomation expense record`. Available from April 2027. ## Headers - `Idempotency-Key` (string, required): Required for API key and MCP calls on creates. A retry with the same key never creates a second record. See [Idempotency](/api-reference/idempotency). ## Body - `business_id` (string): Business that spent the money. - `expense_date` (string, required): Date of the expense. - `vendor` (string): Who you paid. - `category_key` (string, required): Expense category key from your country's list. One of: `advertising`, `bank_fees`, `equipment`, `meals`, `office_supplies`, `professional_fees`, `rent`, `software`, `travel`, `utilities`, `other`. - `amount_minor` (string, required): Amount in minor units, e.g. `"4900"` for 49.00. - `currency` (string, required): ISO 4217 code. - `tax_minor` (string): Tax included, in minor units. - `tax_rate_id` (string): Tax rate of the business. - `receipt_attachment_id` (string): Receipt photo or PDF, uploaded first with [Upload a file](/api-reference/account/upload-file). - `billable` (boolean): Bill the expense on to a client. - `client_id` (string): Required when `billable` is `true`. - `markup_percent` (string): Markup when billing, e.g. `"10"`. Defaults to the business or client default markup. - `notes` (string): Your own notes. - `agent_run_id` (string): The run of [Read a receipt](/api-reference/time-expenses/read-receipt) that filled these fields, if any. ## Response - `id` (string): Unique id of the expense. - `business_id` (string): The business that spent the money. - `expense_date` (string): Date of the expense. - `vendor` (string | null): Who you paid. - `category_key` (string): Expense category from the categories list for your country, e.g. `software` or `travel`. One of: `advertising`, `bank_fees`, `equipment`, `meals`, `office_supplies`, `professional_fees`, `rent`, `software`, `travel`, `utilities`, `other`. - `amount_minor` (string): Amount in minor units of `currency`. - `currency` (string): ISO 4217 code. - `tax_minor` (string | null): Tax included in the amount, in minor units. - `tax_rate_id` (string | null): Tax rate of the expense. - `receipt_attachment_id` (string | null): The receipt photo or PDF, stored privately. - `agent_run_id` (string | null): Set when the receipt was read by AI. - `billable` (boolean): Whether the expense is billed on to a client. - `client_id` (string | null): The client to bill. Required when `billable` is `true`. - `markup_percent` (string | null): Markup added when billing the expense, e.g. `"10.0000"` = 10%. - `invoiced_document_id` (string | null): The invoice the expense was billed on. - `invoiced_line_id` (string | null): The invoice line it was billed on. - `notes` (string | null): Your own notes. - `created_at` (string): When the expense was recorded. - `updated_at` (string): When it was last changed. ## Errors - `validation_failed` - `idempotency_key_reused` - `business_out_of_scope` Section: Time and expenses Source: https://docs.freeinvoiceautomation.com/api-reference/time-expenses/record-expense --- # List expenses GET https://api.freeinvoiceautomation.com/v1/expenses Returns expenses, newest first, filtered by client, category, currency or billable state. Results use [cursor pagination](/api-reference/pagination). See [Expenses](/features/expenses). Permission: `expenses.view`. Capability: `list_expenses`. CLI: `freeinvoiceautomation expense list`. Available from April 2027. ## Query parameters - `business_id` (string): Only expenses of this business. - `client_id` (string): Only expenses billable to this client. - `category_key` (string): Only this category; comma-separate several. - `currency` (string): Only this currency. - `billable` (boolean): Only billable (`true`) or non-billable (`false`) expenses. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The expenses on this page. - `id` (string): Unique id of the expense. - `business_id` (string): The business that spent the money. - `expense_date` (string): Date of the expense. - `vendor` (string | null): Who you paid. - `category_key` (string): Expense category from the categories list for your country, e.g. `software` or `travel`. One of: `advertising`, `bank_fees`, `equipment`, `meals`, `office_supplies`, `professional_fees`, `rent`, `software`, `travel`, `utilities`, `other`. - `amount_minor` (string): Amount in minor units of `currency`. - `currency` (string): ISO 4217 code. - `tax_minor` (string | null): Tax included in the amount, in minor units. - `tax_rate_id` (string | null): Tax rate of the expense. - `receipt_attachment_id` (string | null): The receipt photo or PDF, stored privately. - `agent_run_id` (string | null): Set when the receipt was read by AI. - `billable` (boolean): Whether the expense is billed on to a client. - `client_id` (string | null): The client to bill. Required when `billable` is `true`. - `markup_percent` (string | null): Markup added when billing the expense, e.g. `"10.0000"` = 10%. - `invoiced_document_id` (string | null): The invoice the expense was billed on. - `invoiced_line_id` (string | null): The invoice line it was billed on. - `notes` (string | null): Your own notes. - `created_at` (string): When the expense was recorded. - `updated_at` (string): When it was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Time and expenses Source: https://docs.freeinvoiceautomation.com/api-reference/time-expenses/list-expenses --- # Get an expense GET https://api.freeinvoiceautomation.com/v1/expenses/{id} Returns one expense. Permission: `expenses.view`. Capability: `get_expense`. CLI: `freeinvoiceautomation expense get`. Available from April 2027. ## Path parameters - `id` (string, required): Id of the expense. ## Response - `id` (string): Unique id of the expense. - `business_id` (string): The business that spent the money. - `expense_date` (string): Date of the expense. - `vendor` (string | null): Who you paid. - `category_key` (string): Expense category from the categories list for your country, e.g. `software` or `travel`. One of: `advertising`, `bank_fees`, `equipment`, `meals`, `office_supplies`, `professional_fees`, `rent`, `software`, `travel`, `utilities`, `other`. - `amount_minor` (string): Amount in minor units of `currency`. - `currency` (string): ISO 4217 code. - `tax_minor` (string | null): Tax included in the amount, in minor units. - `tax_rate_id` (string | null): Tax rate of the expense. - `receipt_attachment_id` (string | null): The receipt photo or PDF, stored privately. - `agent_run_id` (string | null): Set when the receipt was read by AI. - `billable` (boolean): Whether the expense is billed on to a client. - `client_id` (string | null): The client to bill. Required when `billable` is `true`. - `markup_percent` (string | null): Markup added when billing the expense, e.g. `"10.0000"` = 10%. - `invoiced_document_id` (string | null): The invoice the expense was billed on. - `invoiced_line_id` (string | null): The invoice line it was billed on. - `notes` (string | null): Your own notes. - `created_at` (string): When the expense was recorded. - `updated_at` (string): When it was last changed. Section: Time and expenses Source: https://docs.freeinvoiceautomation.com/api-reference/time-expenses/get-expense --- # Update an expense PATCH https://api.freeinvoiceautomation.com/v1/expenses/{id} Changes an expense. Send only the fields you want to change. Setting `billable` to `true` needs a `client_id`. See [Expenses](/features/expenses). Permission: `expenses.manage`. Capability: `update_expense`. CLI: `freeinvoiceautomation expense update`. Available from April 2027. ## Path parameters - `id` (string, required): Id of the expense. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Body - `expense_date` (string): Date of the expense. - `vendor` (string): Who you paid. - `category_key` (string): Expense category key from your country's list. One of: `advertising`, `bank_fees`, `equipment`, `meals`, `office_supplies`, `professional_fees`, `rent`, `software`, `travel`, `utilities`, `other`. - `amount_minor` (string): Amount in minor units, e.g. `"4900"` for 49.00. - `currency` (string): ISO 4217 code. - `tax_minor` (string): Tax included, in minor units. - `tax_rate_id` (string): Tax rate of the business. - `receipt_attachment_id` (string): Receipt photo or PDF, uploaded first with [Upload a file](/api-reference/account/upload-file). - `billable` (boolean): Bill the expense on to a client. - `client_id` (string): Required when `billable` is `true`. - `markup_percent` (string): Markup when billing, e.g. `"10"`. Defaults to the business or client default markup. - `notes` (string): Your own notes. ## Response - `id` (string): Unique id of the expense. - `business_id` (string): The business that spent the money. - `expense_date` (string): Date of the expense. - `vendor` (string | null): Who you paid. - `category_key` (string): Expense category from the categories list for your country, e.g. `software` or `travel`. One of: `advertising`, `bank_fees`, `equipment`, `meals`, `office_supplies`, `professional_fees`, `rent`, `software`, `travel`, `utilities`, `other`. - `amount_minor` (string): Amount in minor units of `currency`. - `currency` (string): ISO 4217 code. - `tax_minor` (string | null): Tax included in the amount, in minor units. - `tax_rate_id` (string | null): Tax rate of the expense. - `receipt_attachment_id` (string | null): The receipt photo or PDF, stored privately. - `agent_run_id` (string | null): Set when the receipt was read by AI. - `billable` (boolean): Whether the expense is billed on to a client. - `client_id` (string | null): The client to bill. Required when `billable` is `true`. - `markup_percent` (string | null): Markup added when billing the expense, e.g. `"10.0000"` = 10%. - `invoiced_document_id` (string | null): The invoice the expense was billed on. - `invoiced_line_id` (string | null): The invoice line it was billed on. - `notes` (string | null): Your own notes. - `created_at` (string): When the expense was recorded. - `updated_at` (string): When it was last changed. ## Errors - `validation_failed` Section: Time and expenses Source: https://docs.freeinvoiceautomation.com/api-reference/time-expenses/update-expense --- # Delete an expense DELETE https://api.freeinvoiceautomation.com/v1/expenses/{id} Deletes an expense. The expense is soft-deleted and disappears from lists. Permission: `expenses.manage`. Capability: `delete_expense`. CLI: `freeinvoiceautomation expense delete`. Available from April 2027. ## Path parameters - `id` (string, required): Id of the expense. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). Section: Time and expenses Source: https://docs.freeinvoiceautomation.com/api-reference/time-expenses/delete-expense --- # Read a receipt POST https://api.freeinvoiceautomation.com/v1/expenses/read-receipt Reads an uploaded receipt photo or PDF with AI and returns the expense fields for you to check. Upload the receipt first with [Upload a file](/api-reference/account/upload-file). The AI reads the date, vendor, amount, tax and currency and suggests a category. Nothing is saved as an expense: check the fields, then save them with [Record an expense](/api-reference/time-expenses/record-expense), passing the `agent_run_id`. Reading a receipt uses **2 credits** on our AI key, or none with your [own AI key](/ai/agent/own-key). Without credits the call returns `ai_credits_exhausted`; on Free without credits or an own key the feature is locked. See [Read receipts](/ai/agent/receipts) and [Credits and costs](/ai/agent/credits). Permission: `ai.use`. Capability: `read_receipt`. CLI: `freeinvoiceautomation expense read`. Available from April 2027. - Uses 2 credits on the platform AI key; none with your own key. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. A retry with the same key returns the stored response. See [Idempotency](/api-reference/idempotency). ## Body - `business_id` (string): Business the expense belongs to. - `receipt_attachment_id` (string, required): The uploaded receipt photo or PDF. ## Response - `agent_run_id` (string): The AI run that read the receipt. Pass it to Record an expense. - `credits_used` (integer): Credits used: 2 on our AI key, 0 on your own key. - `receipt_attachment_id` (string): The receipt that was read. - `expense_date` (string | null): Date read from the receipt. - `vendor` (string | null): Vendor read from the receipt. - `category_key` (string | null): Suggested category. - `amount_minor` (string | null): Amount read, in minor units. - `currency` (string | null): Currency read. - `tax_minor` (string | null): Tax read, in minor units. ## Errors - `ai_credits_exhausted` - `feature_locked` - `rate_limited` - `upstream_error` - `not_configured` Section: Time and expenses Source: https://docs.freeinvoiceautomation.com/api-reference/time-expenses/read-receipt --- # Connect QuickBooks or Xero POST https://api.freeinvoiceautomation.com/v1/accounting-connections Starts connecting one business to a QuickBooks Online company or a Xero organisation and returns the provider's sign-in URL. Send the person to the returned URL. They sign in at QuickBooks or Xero and approve access; the provider then sends them back to us, we store the tokens sealed and encrypted, record the realm or tenant id, and return them to **Settings > Integrations**. The connection appears in [List connections](/api-reference/accounting/list-accounting-connections) once that callback has finished. Each business can have one QuickBooks company or one Xero organisation. The sync is **one-way, from us to them**: clients, items, invoices, credit notes, payments and billable expenses are pushed; nothing is read back and their ledger is never changed beyond what we push. See [QuickBooks Online](/integrations/quickbooks) and [Xero](/integrations/xero). This is a sensitive action: from the web app it needs a fresh sign-in, and API keys cannot perform it. Permission: `accounting.manage`. Capability: `connect_accounting`. Available from May 2027. - Needs a fresh sign-in; the API returns `fresh_signin_required` when the session is not fresh. - The platform's QuickBooks or Xero app must be configured, otherwise the call returns `not_configured`. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `business_id` (string): Business to connect. May be left out when you can reach exactly one business. - `provider` (enum, required): Which accounting tool to connect. One of: `quickbooks`, `xero`. - `sync_start_date` (string, required): Documents dated on or after this day are pushed. You choose it; older documents can be pushed later with a backfill. ## Response - `authorization_url` (string): The QuickBooks or Xero sign-in and consent page to send the person to. It carries a one-time `state` value that the callback checks. ## Errors - `fresh_signin_required` - `business_out_of_scope` - `conflict` - `not_configured` - `validation_failed` Section: QuickBooks and Xero sync Source: https://docs.freeinvoiceautomation.com/api-reference/accounting/connect-accounting --- # List accounting connections GET https://api.freeinvoiceautomation.com/v1/accounting-connections Returns the QuickBooks and Xero connections of the businesses you can reach. Use it to show each business's sync state: `active`, `needs_reauth` (the provider stopped accepting our tokens; connect again) or `disconnected`. Tokens are never returned. Results use [cursor pagination](/api-reference/pagination). Permission: `accounting.manage`. Capability: `list_accounting_connections`. CLI: `freeinvoiceautomation accounting list`. Available from May 2027. ## Query parameters - `business_id` (string): Only the connection of this business. - `provider` (enum): Only connections to this tool. One of: `quickbooks`, `xero`. - `status` (string): Comma-separated statuses, e.g. `active,needs_reauth`. One of: `active`, `needs_reauth`, `disconnected`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The connections on this page. - `id` (string): Unique id of the accounting connection (uuid v7). - `business_id` (string): The business this connection belongs to. A business has at most one active connection. - `provider` (enum): The accounting tool on the other side. One of: `quickbooks`, `xero`. - `external_tenant_id` (string): QuickBooks realm id or Xero tenant id of the connected company or organisation. - `external_tenant_name` (string | null): Name of the connected QuickBooks company or Xero organisation. - `mapping` (object): How our records map to their accounts: tax rates, income account, payment (deposit) account and items. Set with [Update the mapping](/api-reference/accounting/update-accounting-mapping). - `sync_start_date` (string): Documents dated on or after this day are pushed. Older documents can be pushed once with [Backfill](/api-reference/accounting/backfill-accounting). - `status` (enum): `active` while syncing, `needs_reauth` when QuickBooks or Xero no longer accepts our tokens (connect again), `disconnected` after a disconnect. One of: `active`, `needs_reauth`, `disconnected`. - `token_expires_at` (string | null): When the current access token expires. Tokens are refreshed automatically before then. - `last_sync_at` (string | null): When the last push finished. - `last_error` (string | null): The last connection-level error, if any. - `created_at` (string): When the connection was made (RFC 3339, UTC). - `updated_at` (string): When the connection was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: QuickBooks and Xero sync Source: https://docs.freeinvoiceautomation.com/api-reference/accounting/list-accounting-connections --- # Update the mapping PUT https://api.freeinvoiceautomation.com/v1/accounting-connections/{id}/mapping Sets how tax rates, income, payments and items map to accounts and items in QuickBooks or Xero. A new connection starts with sensible defaults for the business's country; use this to change them. `PUT` replaces the whole mapping, so send every part. The new mapping applies to records pushed from now on. Records that failed because of a wrong mapping can be pushed again with [Retry a sync item](/api-reference/accounting/retry-accounting-sync). Permission: `accounting.manage`. Capability: `update_accounting_mapping`. Available from May 2027. ## Path parameters - `id` (string, required): Id of the accounting connection. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `mapping` (object, required): The full mapping. - `tax_rates` (object): Your tax rate ids mapped to tax codes or tax rates in QuickBooks or Xero. - `income_account` (string): Id of the income account in QuickBooks or Xero that invoice lines post to. - `deposit_account` (string): Id of the payment (deposit) account in QuickBooks or Xero that payments go to. - `items` (object): Your saved item ids mapped to products or items in QuickBooks or Xero. ## Response - `id` (string): Unique id of the accounting connection (uuid v7). - `business_id` (string): The business this connection belongs to. A business has at most one active connection. - `provider` (enum): The accounting tool on the other side. One of: `quickbooks`, `xero`. - `external_tenant_id` (string): QuickBooks realm id or Xero tenant id of the connected company or organisation. - `external_tenant_name` (string | null): Name of the connected QuickBooks company or Xero organisation. - `mapping` (object): How our records map to their accounts: tax rates, income account, payment (deposit) account and items. Set with [Update the mapping](/api-reference/accounting/update-accounting-mapping). - `sync_start_date` (string): Documents dated on or after this day are pushed. Older documents can be pushed once with [Backfill](/api-reference/accounting/backfill-accounting). - `status` (enum): `active` while syncing, `needs_reauth` when QuickBooks or Xero no longer accepts our tokens (connect again), `disconnected` after a disconnect. One of: `active`, `needs_reauth`, `disconnected`. - `token_expires_at` (string | null): When the current access token expires. Tokens are refreshed automatically before then. - `last_sync_at` (string | null): When the last push finished. - `last_error` (string | null): The last connection-level error, if any. - `created_at` (string): When the connection was made (RFC 3339, UTC). - `updated_at` (string): When the connection was last changed. ## Errors - `not_found` - `validation_failed` - `conflict` Section: QuickBooks and Xero sync Source: https://docs.freeinvoiceautomation.com/api-reference/accounting/update-accounting-mapping --- # Backfill older documents POST https://api.freeinvoiceautomation.com/v1/accounting-connections/{id}/backfill Pushes every document from the chosen start date to QuickBooks or Xero in one batch. Use this after connecting when you have used Free Invoice Automation for a while and want older documents in your books. The push runs in the background, so the call answers `202`; follow progress with [List sync items](/api-reference/accounting/list-sync-items). Every push is an idempotent upsert keyed by our record id, so running a backfill twice never creates duplicates in QuickBooks or Xero. Invoicing is never blocked while it runs. Permission: `accounting.manage`. Capability: `backfill_accounting`. CLI: `freeinvoiceautomation accounting backfill`. Available from May 2027. ## Path parameters - `id` (string, required): Id of the accounting connection. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `from` (string): Push documents dated on or after this day. Defaults to the connection's `sync_start_date`. ## Response - `id` (string): Unique id of the accounting connection (uuid v7). - `business_id` (string): The business this connection belongs to. A business has at most one active connection. - `provider` (enum): The accounting tool on the other side. One of: `quickbooks`, `xero`. - `external_tenant_id` (string): QuickBooks realm id or Xero tenant id of the connected company or organisation. - `external_tenant_name` (string | null): Name of the connected QuickBooks company or Xero organisation. - `mapping` (object): How our records map to their accounts: tax rates, income account, payment (deposit) account and items. Set with [Update the mapping](/api-reference/accounting/update-accounting-mapping). - `sync_start_date` (string): Documents dated on or after this day are pushed. Older documents can be pushed once with [Backfill](/api-reference/accounting/backfill-accounting). - `status` (enum): `active` while syncing, `needs_reauth` when QuickBooks or Xero no longer accepts our tokens (connect again), `disconnected` after a disconnect. One of: `active`, `needs_reauth`, `disconnected`. - `token_expires_at` (string | null): When the current access token expires. Tokens are refreshed automatically before then. - `last_sync_at` (string | null): When the last push finished. - `last_error` (string | null): The last connection-level error, if any. - `created_at` (string): When the connection was made (RFC 3339, UTC). - `updated_at` (string): When the connection was last changed. ## Errors - `not_found` - `conflict` - `upstream_error` Section: QuickBooks and Xero sync Source: https://docs.freeinvoiceautomation.com/api-reference/accounting/backfill-accounting --- # List sync items GET https://api.freeinvoiceautomation.com/v1/accounting-connections/{id}/sync-items Returns the sync state of each record pushed through a connection, with the error for any that failed. Each record (client, item, invoice, credit note, payment or expense) has one sync item. Filter by `status=error` to find what needs attention; the same errors are shown on the document in the app with a **Retry** button. Results use [cursor pagination](/api-reference/pagination). Permission: `accounting.manage`. Capability: `list_sync_items`. CLI: `freeinvoiceautomation accounting status`. Available from May 2027. ## Path parameters - `id` (string, required): Id of the accounting connection. ## Query parameters - `status` (string): Comma-separated statuses. One of: `pending`, `synced`, `error`. - `entity_type` (string): Comma-separated record types. One of: `client`, `item`, `invoice`, `credit_note`, `payment`, `expense`. - `local_id` (string): Only the sync item of this record. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The sync items on this page. - `id` (string): Unique id of the sync item. - `business_id` (string): The business the record belongs to. - `accounting_connection_id` (string): The connection the record is pushed through. - `entity_type` (enum): What kind of record this is. One of: `client`, `item`, `invoice`, `credit_note`, `payment`, `expense`. - `local_id` (string): Id of the record in Free Invoice Automation. - `external_id` (string | null): Id of the matching record in QuickBooks or Xero, once it has been created there. - `status` (enum): `pending` while waiting to be pushed, `synced` when QuickBooks or Xero accepted it, `error` when the last push failed. One of: `pending`, `synced`, `error`. - `error` (string | null): Why the last push failed, in the words QuickBooks or Xero used. - `attempts` (integer): How many times this record has been pushed. - `last_pushed_at` (string | null): When the record was last pushed. - `created_at` (string): When the record was first queued for sync. - `updated_at` (string): When the sync state last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `not_found` Section: QuickBooks and Xero sync Source: https://docs.freeinvoiceautomation.com/api-reference/accounting/list-sync-items --- # Retry a sync item POST https://api.freeinvoiceautomation.com/v1/accounting-sync-items/{id}/retry Queues one failed record to be pushed to QuickBooks or Xero again. Fix the cause first (for example the mapping, or an inactive tax code on their side), then retry. The item goes back to `pending` and is pushed in the background; the push is an idempotent upsert, so a retry never creates a duplicate. This is the API form of the **Retry** button shown on a document with a sync error. Permission: `accounting.manage`. Capability: `retry_accounting_sync`. CLI: `freeinvoiceautomation accounting retry`. Available from May 2027. ## Path parameters - `id` (string, required): Id of the sync item. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Unique id of the sync item. - `business_id` (string): The business the record belongs to. - `accounting_connection_id` (string): The connection the record is pushed through. - `entity_type` (enum): What kind of record this is. One of: `client`, `item`, `invoice`, `credit_note`, `payment`, `expense`. - `local_id` (string): Id of the record in Free Invoice Automation. - `external_id` (string | null): Id of the matching record in QuickBooks or Xero, once it has been created there. - `status` (enum): `pending` while waiting to be pushed, `synced` when QuickBooks or Xero accepted it, `error` when the last push failed. One of: `pending`, `synced`, `error`. - `error` (string | null): Why the last push failed, in the words QuickBooks or Xero used. - `attempts` (integer): How many times this record has been pushed. - `last_pushed_at` (string | null): When the record was last pushed. - `created_at` (string): When the record was first queued for sync. - `updated_at` (string): When the sync state last changed. ## Errors - `not_found` - `conflict` Section: QuickBooks and Xero sync Source: https://docs.freeinvoiceautomation.com/api-reference/accounting/retry-accounting-sync --- # Disconnect QuickBooks or Xero DELETE https://api.freeinvoiceautomation.com/v1/accounting-connections/{id} Stops syncing a business to QuickBooks or Xero and removes the stored tokens. After disconnecting, nothing more is pushed. Records already in QuickBooks or Xero stay there: we never delete or change anything in their ledger. The connection is soft-deleted and kept with status `disconnected` for your history. This is a destructive action: send `"confirm": true`. Without it the API returns `confirmation_required` with a preview of what would happen. See [Drafts and confirmations](/api-reference/confirmations). Permission: `accounting.manage`. Capability: `disconnect_accounting`. Available from May 2027. ## Path parameters - `id` (string, required): Id of the accounting connection. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true` to disconnect. Without it you get the preview and `confirmation_required`. ## Errors - `confirmation_required` - `not_found` Section: QuickBooks and Xero sync Source: https://docs.freeinvoiceautomation.com/api-reference/accounting/disconnect-accounting --- # List ledger accounts GET https://api.freeinvoiceautomation.com/v1/ledger-accounts Returns the chart of accounts of a business. Each business has its own chart of accounts, created from a country template and editable. Accounts with a `system_key` are the ones automatic posting uses (receivable, revenue, tax payable, bank, rail clearing, rail fees, client credit, late fee income). See [Accounting ledger](/features/ledger). Archived accounts are left out unless you pass `archived=true`. Results use [cursor pagination](/api-reference/pagination). Permission: `ledger.view`. Capability: `list_ledger_accounts`. CLI: `freeinvoiceautomation ledger accounts`. Available from June to July 2027. ## Query parameters - `business_id` (string): Business whose chart to list. May be left out when you can reach exactly one business. - `type` (string): Comma-separated account types. One of: `asset`, `liability`, `equity`, `income`, `expense`. - `q` (string): Text search over code and name. - `archived` (boolean, default: false): Set to `true` to list archived accounts instead. - `sort` (string, default: code): Sort field; a leading `-` sorts descending. One of: `code`, `-code`, `name`, `-name`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The accounts on this page. - `id` (string): Unique id of the ledger account (uuid v7). - `business_id` (string): The business whose chart of accounts this account is in. - `code` (string): Account code, unique within the business, e.g. `1100`. - `name` (string): Account name, e.g. `Accounts receivable`. - `type` (enum): Account type. One of: `asset`, `liability`, `equity`, `income`, `expense`. - `parent_id` (string | null): Parent account when accounts are grouped. - `system_key` (string | null): The role this account plays in automatic posting, if any. Each role is used by at most one account per business. One of: `accounts_receivable`, `revenue`, `tax_payable`, `bank`, `rail_clearing`, `rail_fees`, `client_credit`, `late_fee_income`. - `template_key` (string | null): The chart-of-accounts template the account came from, or `null` for an account you added. - `currency` (string | null): ISO 4217 code, set on bank accounts. - `archived_at` (string | null): When the account was archived. Archived accounts are hidden from lists; postings already made to them stay in reports. - `created_at` (string): When the account was created (RFC 3339, UTC). - `updated_at` (string): When the account was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Accounting ledger Source: https://docs.freeinvoiceautomation.com/api-reference/ledger/list-ledger-accounts --- # Create or update a ledger account POST https://api.freeinvoiceautomation.com/v1/ledger-accounts Adds an account to the chart of accounts, or updates it when the body contains the `id` of an existing account. The same capability also answers `PATCH /v1/ledger-accounts/{id}` for partial updates. Codes are unique within a business. Posted journal lines keep pointing at the account, so renaming an account changes how it is shown in reports but never changes amounts already posted. Accounts are archived rather than deleted (`archived: true`), so past postings stay in reports. Permission: `ledger.manage`. Capability: `upsert_ledger_account`. CLI: `freeinvoiceautomation ledger account-upsert`. Available from June to July 2027. - Send an `Idempotency-Key` header so a retried request never creates the account twice. ## Headers - `Idempotency-Key` (string): Required when creating. Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `id` (string): Id of an existing account to update. Leave out to create a new account. - `business_id` (string): Business whose chart the account is in. May be left out when you can reach exactly one business. - `code` (string, required): Account code, unique within the business. - `name` (string, required): Account name. - `type` (enum, required): Account type. One of: `asset`, `liability`, `equity`, `income`, `expense`. - `parent_id` (string): Parent account in the same business. - `currency` (string): ISO 4217 code, for bank accounts. - `archived` (boolean): `true` archives the account; `false` brings it back. ## Response - `id` (string): Unique id of the ledger account (uuid v7). - `business_id` (string): The business whose chart of accounts this account is in. - `code` (string): Account code, unique within the business, e.g. `1100`. - `name` (string): Account name, e.g. `Accounts receivable`. - `type` (enum): Account type. One of: `asset`, `liability`, `equity`, `income`, `expense`. - `parent_id` (string | null): Parent account when accounts are grouped. - `system_key` (string | null): The role this account plays in automatic posting, if any. Each role is used by at most one account per business. One of: `accounts_receivable`, `revenue`, `tax_payable`, `bank`, `rail_clearing`, `rail_fees`, `client_credit`, `late_fee_income`. - `template_key` (string | null): The chart-of-accounts template the account came from, or `null` for an account you added. - `currency` (string | null): ISO 4217 code, set on bank accounts. - `archived_at` (string | null): When the account was archived. Archived accounts are hidden from lists; postings already made to them stay in reports. - `created_at` (string): When the account was created (RFC 3339, UTC). - `updated_at` (string): When the account was last changed. ## Errors - `validation_failed` - `conflict` - `idempotency_key_reused` Section: Accounting ledger Source: https://docs.freeinvoiceautomation.com/api-reference/ledger/upsert-ledger-account --- # Apply a chart-of-accounts template POST https://api.freeinvoiceautomation.com/v1/ledger-accounts/apply-template Creates a business's chart of accounts from a country template. Templates exist for a generic small business, and for Pakistan, the UK, India and the EU. Each template includes the accounts that automatic posting needs (receivable, revenue, tax payable, bank, rail clearing, rail fees, client credit, late fee income). After applying it you can edit, add and archive accounts with [Create or update a ledger account](/api-reference/ledger/upsert-ledger-account). The ledger produces reports for your accountant; it does not file tax returns. Permission: `ledger.manage`. Capability: `apply_coa_template`. CLI: `freeinvoiceautomation ledger template`. Available from June to July 2027. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `business_id` (string): Business to set up. May be left out when you can reach exactly one business. - `template_key` (enum, default: the business country's template, else `generic`): Which template to apply. One of: `generic`, `pk`, `uk`, `in`, `eu`. ## Response - `data` (object[]): The accounts on this page. - `id` (string): Unique id of the ledger account (uuid v7). - `business_id` (string): The business whose chart of accounts this account is in. - `code` (string): Account code, unique within the business, e.g. `1100`. - `name` (string): Account name, e.g. `Accounts receivable`. - `type` (enum): Account type. One of: `asset`, `liability`, `equity`, `income`, `expense`. - `parent_id` (string | null): Parent account when accounts are grouped. - `system_key` (string | null): The role this account plays in automatic posting, if any. Each role is used by at most one account per business. One of: `accounts_receivable`, `revenue`, `tax_payable`, `bank`, `rail_clearing`, `rail_fees`, `client_credit`, `late_fee_income`. - `template_key` (string | null): The chart-of-accounts template the account came from, or `null` for an account you added. - `currency` (string | null): ISO 4217 code, set on bank accounts. - `archived_at` (string | null): When the account was archived. Archived accounts are hidden from lists; postings already made to them stay in reports. - `created_at` (string): When the account was created (RFC 3339, UTC). - `updated_at` (string): When the account was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `validation_failed` - `conflict` - `business_out_of_scope` Section: Accounting ledger Source: https://docs.freeinvoiceautomation.com/api-reference/ledger/apply-coa-template --- # List journal entries GET https://api.freeinvoiceautomation.com/v1/journal-entries Returns posted journal entries with their lines, newest first. Includes entries posted automatically from invoices, payments, credit notes, refunds, late fees, expenses and rail fees, plus manual entries and reversals. Entries are append-only: they are never edited or deleted, so this list is a complete history. Results use [cursor pagination](/api-reference/pagination). Permission: `ledger.view`. Capability: `list_journal_entries`. CLI: `freeinvoiceautomation ledger journal`. Available from June to July 2027. ## Query parameters - `business_id` (string): Only entries of this business. - `from` (string): Entries dated on or after this day. - `to` (string): Entries dated on or before this day. - `source_type` (string): Comma-separated source types. One of: `invoice`, `payment`, `credit_note`, `refund`, `late_fee`, `expense`, `rail_fee`, `manual`, `reversal`. - `source_id` (string): Only entries caused by this document, payment or expense. - `ledger_account_id` (string): Only entries with a line on this account. - `sort` (string, default: -entry_date): Sort field; a leading `-` sorts descending. One of: `entry_date`, `-entry_date`, `created_at`, `-created_at`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The entries on this page. - `id` (string): Unique id of the journal entry. - `business_id` (string): The business whose books the entry is in. - `entry_date` (string): Accounting date. Always inside an open period. - `description` (string): What the entry is for. - `source_type` (enum): What created the entry. Everything except `manual` and `reversal` is posted automatically. One of: `invoice`, `payment`, `credit_note`, `refund`, `late_fee`, `expense`, `rail_fee`, `manual`, `reversal`. - `source_id` (string | null): Id of the document, payment or expense that caused an automatic entry. - `source_event` (string | null): The event that caused an automatic entry, e.g. `sent` or `voided`. - `reverses_entry_id` (string | null): On a reversing entry, the entry it reverses. An entry can be reversed only once. - `posted_by_type` (enum): `system` for automatic entries, `user` for manual entries and reversals. One of: `system`, `user`. - `posted_by` (string | null): The member who posted a manual entry or reversal. - `lines` (object[]): The entry's lines. Debits always equal credits. - `id` (string): Unique id of the line. - `ledger_account_id` (string): The account the line posts to. - `debit_minor` (string): Debit amount in minor units of `currency`. Exactly one of `debit_minor` and `credit_minor` is above zero. - `credit_minor` (string): Credit amount in minor units of `currency`. - `currency` (string): The business's base currency. - `original_amount_minor` (string | null): The foreign-currency amount when the source document was in another currency. - `original_currency` (string | null): Currency of `original_amount_minor`. - `fx_rate` (string | null): The document's frozen exchange rate to the base currency. - `client_id` (string | null): Client the line relates to, if any. - `description` (string | null): Line text. - `created_at` (string): When the entry was posted. Entries are never edited afterwards. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Accounting ledger Source: https://docs.freeinvoiceautomation.com/api-reference/ledger/list-journal-entries --- # Post a manual journal entry POST https://api.freeinvoiceautomation.com/v1/journal-entries Posts a manual journal entry. Debits must equal credits or the entry is rejected. Use manual entries for things the automatic posting does not cover, such as opening balances. Every entry needs at least two lines, each line is either a debit or a credit (never both), and total debits must equal total credits; otherwise the call fails with `journal_unbalanced` and nothing is saved. The `entry_date` must fall in an open period: posting into a [locked period](/api-reference/ledger/lock-period) fails with `period_locked`. Once posted, an entry can never be edited or deleted. To correct it, [reverse it](/api-reference/ledger/reverse-journal-entry) and post a new one. Permission: `ledger.journal`. Capability: `post_journal_entry`. CLI: `freeinvoiceautomation ledger post`. Available from June to July 2027. - Amounts are integer minor units in the business's base currency, sent as strings. ## Headers - `Idempotency-Key` (string, required): Required for API-key and MCP calls. Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `business_id` (string): Business whose books to post in. May be left out when you can reach exactly one business. - `entry_date` (string, required): Accounting date, inside an open period. - `description` (string, required): What the entry is for. - `lines` (object[], required): Two or more lines whose debits and credits balance. - `ledger_account_id` (string, required): Account in the same business. - `debit_minor` (string, default: "0"): Debit amount in minor units. Set this or `credit_minor`, not both. - `credit_minor` (string, default: "0"): Credit amount in minor units. - `client_id` (string): Client the line relates to. - `description` (string): Line text. ## Response - `id` (string): Unique id of the journal entry. - `business_id` (string): The business whose books the entry is in. - `entry_date` (string): Accounting date. Always inside an open period. - `description` (string): What the entry is for. - `source_type` (enum): What created the entry. Everything except `manual` and `reversal` is posted automatically. One of: `invoice`, `payment`, `credit_note`, `refund`, `late_fee`, `expense`, `rail_fee`, `manual`, `reversal`. - `source_id` (string | null): Id of the document, payment or expense that caused an automatic entry. - `source_event` (string | null): The event that caused an automatic entry, e.g. `sent` or `voided`. - `reverses_entry_id` (string | null): On a reversing entry, the entry it reverses. An entry can be reversed only once. - `posted_by_type` (enum): `system` for automatic entries, `user` for manual entries and reversals. One of: `system`, `user`. - `posted_by` (string | null): The member who posted a manual entry or reversal. - `lines` (object[]): The entry's lines. Debits always equal credits. - `id` (string): Unique id of the line. - `ledger_account_id` (string): The account the line posts to. - `debit_minor` (string): Debit amount in minor units of `currency`. Exactly one of `debit_minor` and `credit_minor` is above zero. - `credit_minor` (string): Credit amount in minor units of `currency`. - `currency` (string): The business's base currency. - `original_amount_minor` (string | null): The foreign-currency amount when the source document was in another currency. - `original_currency` (string | null): Currency of `original_amount_minor`. - `fx_rate` (string | null): The document's frozen exchange rate to the base currency. - `client_id` (string | null): Client the line relates to, if any. - `description` (string | null): Line text. - `created_at` (string): When the entry was posted. Entries are never edited afterwards. ## Errors - `journal_unbalanced` - `period_locked` - `validation_failed` - `idempotency_key_reused` - `idempotency_in_progress` Section: Accounting ledger Source: https://docs.freeinvoiceautomation.com/api-reference/ledger/post-journal-entry --- # Reverse a journal entry POST https://api.freeinvoiceautomation.com/v1/journal-entries/{id}/reverse Posts a new entry that cancels an earlier one line for line. The original entry is never changed. This is the only way to correct a posted entry (rule 17: the ledger is append-only). The reversing entry has `source_type: "reversal"`, swaps every debit and credit of the original, and points back to it with `reverses_entry_id`. An entry can be reversed only once; a second attempt fails with `entry_already_reversed`. The reversing entry must be dated in an open period. This is a destructive action: send `"confirm": true`, otherwise the API returns `confirmation_required` with a preview of the reversing entry. See [Drafts and confirmations](/api-reference/confirmations). Permission: `ledger.journal`. Capability: `reverse_journal_entry`. CLI: `freeinvoiceautomation ledger reverse`. Available from June to July 2027. ## Path parameters - `id` (string, required): Id of the entry to reverse. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true` to post the reversal. Without it you get the preview and `confirmation_required`. - `entry_date` (string): Accounting date of the reversing entry, inside an open period. - `description` (string): Why the entry is reversed. ## Response - `id` (string): Unique id of the journal entry. - `business_id` (string): The business whose books the entry is in. - `entry_date` (string): Accounting date. Always inside an open period. - `description` (string): What the entry is for. - `source_type` (enum): What created the entry. Everything except `manual` and `reversal` is posted automatically. One of: `invoice`, `payment`, `credit_note`, `refund`, `late_fee`, `expense`, `rail_fee`, `manual`, `reversal`. - `source_id` (string | null): Id of the document, payment or expense that caused an automatic entry. - `source_event` (string | null): The event that caused an automatic entry, e.g. `sent` or `voided`. - `reverses_entry_id` (string | null): On a reversing entry, the entry it reverses. An entry can be reversed only once. - `posted_by_type` (enum): `system` for automatic entries, `user` for manual entries and reversals. One of: `system`, `user`. - `posted_by` (string | null): The member who posted a manual entry or reversal. - `lines` (object[]): The entry's lines. Debits always equal credits. - `id` (string): Unique id of the line. - `ledger_account_id` (string): The account the line posts to. - `debit_minor` (string): Debit amount in minor units of `currency`. Exactly one of `debit_minor` and `credit_minor` is above zero. - `credit_minor` (string): Credit amount in minor units of `currency`. - `currency` (string): The business's base currency. - `original_amount_minor` (string | null): The foreign-currency amount when the source document was in another currency. - `original_currency` (string | null): Currency of `original_amount_minor`. - `fx_rate` (string | null): The document's frozen exchange rate to the base currency. - `client_id` (string | null): Client the line relates to, if any. - `description` (string | null): Line text. - `created_at` (string): When the entry was posted. Entries are never edited afterwards. ## Errors - `confirmation_required` - `entry_already_reversed` - `period_locked` - `not_found` Section: Accounting ledger Source: https://docs.freeinvoiceautomation.com/api-reference/ledger/reverse-journal-entry --- # List accounting periods GET https://api.freeinvoiceautomation.com/v1/accounting-periods Returns a business's accounting periods and whether each is open or locked. Periods of one business never overlap. A locked period accepts no postings, automatic or manual. Results use [cursor pagination](/api-reference/pagination). Permission: `ledger.view`. Capability: `list_accounting_periods`. CLI: `freeinvoiceautomation ledger periods`. Available from June to July 2027. ## Query parameters - `business_id` (string): Only periods of this business. - `status` (enum): Only open or only locked periods. One of: `open`, `locked`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The periods on this page. - `id` (string): Unique id of the period. - `business_id` (string): The business the period belongs to. - `period_start` (string): First day of the period. - `period_end` (string): Last day of the period (inclusive). Periods of one business never overlap. - `status` (enum): `locked` periods accept no postings. One of: `open`, `locked`. - `locked_by` (string | null): Member who locked the period. - `locked_at` (string | null): When the period was locked. - `created_at` (string): When the period was created. - `updated_at` (string): When the period last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Accounting ledger Source: https://docs.freeinvoiceautomation.com/api-reference/ledger/list-accounting-periods --- # Lock a period POST https://api.freeinvoiceautomation.com/v1/accounting-periods/{id}/lock Closes a period so nothing can be posted into it. Lock a month once your accountant has closed it. From then on every posting dated inside it is rejected with `period_locked`, including automatic entries and reversals. Corrections for a closed month are posted in the next open period. Permission: `ledger.manage`. Capability: `lock_period`. CLI: `freeinvoiceautomation ledger lock`. Available from June to July 2027. ## Path parameters - `id` (string, required): Id of the period. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Response - `id` (string): Unique id of the period. - `business_id` (string): The business the period belongs to. - `period_start` (string): First day of the period. - `period_end` (string): Last day of the period (inclusive). Periods of one business never overlap. - `status` (enum): `locked` periods accept no postings. One of: `open`, `locked`. - `locked_by` (string | null): Member who locked the period. - `locked_at` (string | null): When the period was locked. - `created_at` (string): When the period was created. - `updated_at` (string): When the period last changed. ## Errors - `not_found` - `conflict` Section: Accounting ledger Source: https://docs.freeinvoiceautomation.com/api-reference/ledger/lock-period --- # Unlock a period POST https://api.freeinvoiceautomation.com/v1/accounting-periods/{id}/unlock Opens a locked period again so postings dated inside it are accepted. Unlocking changes books your accountant may already have closed, so it is a destructive action: send `"confirm": true`, otherwise the API returns `confirmation_required` with a preview. Unlocking never changes entries already posted. The change is written to the audit log. Permission: `ledger.manage`. Capability: `unlock_period`. CLI: `freeinvoiceautomation ledger unlock`. Available from June to July 2027. ## Path parameters - `id` (string, required): Id of the period. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true` to unlock. Without it you get the preview and `confirmation_required`. ## Response - `id` (string): Unique id of the period. - `business_id` (string): The business the period belongs to. - `period_start` (string): First day of the period. - `period_end` (string): Last day of the period (inclusive). Periods of one business never overlap. - `status` (enum): `locked` periods accept no postings. One of: `open`, `locked`. - `locked_by` (string | null): Member who locked the period. - `locked_at` (string | null): When the period was locked. - `created_at` (string): When the period was created. - `updated_at` (string): When the period last changed. ## Errors - `confirmation_required` - `not_found` - `conflict` Section: Accounting ledger Source: https://docs.freeinvoiceautomation.com/api-reference/ledger/unlock-period --- # Get a ledger report GET https://api.freeinvoiceautomation.com/v1/ledger/reports/{name} Returns a trial balance, profit and loss, balance sheet or general ledger, on cash or accrual basis. Reports are built from posted journal entries and total in the business's base currency. Use `format=csv` or `format=pdf` to download the report as a file for your accountant; the default is JSON. The ledger produces reports an accountant can use; it does not generate country tax returns. The same report is available to AI assistants through the MCP tool `get_ledger_report`. Permission: `ledger.view`. Capability: `get_ledger_report`. MCP tool: `get_ledger_report`. CLI: `freeinvoiceautomation ledger report`. Available from June to July 2027. ## Path parameters - `name` (enum, required): Which report. One of: `trial-balance`, `profit-and-loss`, `balance-sheet`, `general-ledger`. ## Query parameters - `business_id` (string): Business to report on. May be left out when you can reach exactly one business. - `basis` (enum, default: accrual): Cash or accrual basis. The default comes from the business's ledger settings. One of: `cash`, `accrual`. - `from` (string): First day of the report period. - `to` (string): Last day of the report period (or the balance date for a balance sheet). - `format` (enum, default: json): Response format. One of: `json`, `csv`, `pdf`. ## Response - `name` (string): The report name. - `basis` (enum): Basis used. One of: `cash`, `accrual`. - `from` (string): Start of the period. - `to` (string): End of the period. - `currency` (string): The business's base currency. - `rows` (object[]): One row per ledger account. - `ledger_account_id` (string): The account. - `code` (string): Account code. - `name` (string): Account name. - `type` (string): Account type. - `debit_minor` (string): Total debits in minor units. - `credit_minor` (string): Total credits in minor units. ## Errors - `validation_failed` - `business_out_of_scope` Section: Accounting ledger Source: https://docs.freeinvoiceautomation.com/api-reference/ledger/get-ledger-report --- # Import a bank statement POST https://api.freeinvoiceautomation.com/v1/bank-statements Uploads a CSV or OFX bank statement so its lines can be matched to payments and expenses. Send the file as `multipart/form-data` in the field `file`, with the bank account it belongs to. The import runs in the background and answers `202`; the lines then appear in [List bank transactions](/api-reference/ledger/list-bank-transactions) as `unmatched`. Lines already imported for the same bank account are recognised and skipped, so importing an overlapping statement does not create duplicates. Live bank feeds are not supported. Permission: `ledger.manage`. Capability: `import_bank_statement`. CLI: `freeinvoiceautomation bank import`. Available from June to July 2027. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `file` (file, required): The statement, CSV or OFX, up to 5 MB. - `ledger_account_id` (string, required): The bank account (a ledger account of type `asset`) the statement belongs to. - `format` (enum, required): File format. One of: `csv`, `ofx`. ## Response - `id` (string): Id of the import. - `business_id` (string): The business. - `ledger_account_id` (string): The bank account. - `attachment_id` (string): The stored statement file. - `format` (enum): File format. One of: `csv`, `ofx`. - `line_count` (integer | null): Number of lines read, once processing has finished. - `created_at` (string): When the file was uploaded. ## Errors - `validation_failed` - `not_found` Section: Accounting ledger Source: https://docs.freeinvoiceautomation.com/api-reference/ledger/import-bank-statement --- # List bank transactions GET https://api.freeinvoiceautomation.com/v1/bank-transactions Returns imported bank statement lines and whether each is matched. Filter by `status=unmatched` to see what is left to reconcile. Results use [cursor pagination](/api-reference/pagination). Permission: `ledger.view`. Capability: `list_bank_transactions`. CLI: `freeinvoiceautomation bank list`. Available from June to July 2027. ## Query parameters - `business_id` (string): Only lines of this business. - `ledger_account_id` (string): Only lines of this bank account. - `import_id` (string): Only lines from this statement import. - `status` (string): Comma-separated statuses. One of: `unmatched`, `matched`, `ignored`. - `from` (string): Lines dated on or after this day. - `to` (string): Lines dated on or before this day. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The bank transactions on this page. - `id` (string): Unique id of the bank transaction. - `business_id` (string): The business the bank account belongs to. - `import_id` (string): The statement import the line came from. - `ledger_account_id` (string): The bank account (a ledger account) the line belongs to. - `transaction_date` (string): Date on the statement. - `amount_minor` (string): Signed amount in minor units: positive for money in, negative for money out. - `currency` (string): ISO 4217 code. - `description` (string): Text from the statement. - `reference` (string | null): Bank reference, when the statement has one. - `status` (enum): Reconciliation state of the line. One of: `unmatched`, `matched`, `ignored`. - `matched_type` (enum | null): What the line is matched to. One of: `payment`, `expense`, `journal_entry`. - `matched_id` (string | null): Id of the matched payment, expense or journal entry. - `created_at` (string): When the line was imported. - `updated_at` (string): When the line was last changed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `business_out_of_scope` Section: Accounting ledger Source: https://docs.freeinvoiceautomation.com/api-reference/ledger/list-bank-transactions --- # Match a bank transaction POST https://api.freeinvoiceautomation.com/v1/bank-transactions/{id}/match Links a bank statement line to the payment, expense or journal entry it represents. Matching reconciles your bank statement with your books; it does not post anything or change paid state. To record money you received, record the payment first through [Record a payment](/api-reference/payments/record-payment), then match the line to it. Permission: `ledger.manage`. Capability: `match_bank_transaction`. CLI: `freeinvoiceautomation bank match`. Available from June to July 2027. ## Path parameters - `id` (string, required): Id of the bank transaction. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `matched_type` (enum, required): What to match the line to. One of: `payment`, `expense`, `journal_entry`. - `matched_id` (string, required): Id of the payment, expense or journal entry, in the same business. ## Response - `id` (string): Unique id of the bank transaction. - `business_id` (string): The business the bank account belongs to. - `import_id` (string): The statement import the line came from. - `ledger_account_id` (string): The bank account (a ledger account) the line belongs to. - `transaction_date` (string): Date on the statement. - `amount_minor` (string): Signed amount in minor units: positive for money in, negative for money out. - `currency` (string): ISO 4217 code. - `description` (string): Text from the statement. - `reference` (string | null): Bank reference, when the statement has one. - `status` (enum): Reconciliation state of the line. One of: `unmatched`, `matched`, `ignored`. - `matched_type` (enum | null): What the line is matched to. One of: `payment`, `expense`, `journal_entry`. - `matched_id` (string | null): Id of the matched payment, expense or journal entry. - `created_at` (string): When the line was imported. - `updated_at` (string): When the line was last changed. ## Errors - `not_found` - `conflict` - `validation_failed` Section: Accounting ledger Source: https://docs.freeinvoiceautomation.com/api-reference/ledger/match-bank-transaction --- # Generate a PDF without an account POST https://api.freeinvoiceautomation.com/v1/public/generate-pdf Renders an invoice PDF from the data you send. No account, no API key, and nothing is stored. This is the API and MCP form of the [free invoice generator](/generator). Send the same document shape as [Create an invoice](/api-reference/documents/create-invoice), plus the business and client details and a PDF template key; you get the PDF back as `application/pdf` (base64 over MCP, through the no-auth tool `generate_pdf`). **Nothing is stored**: no document, client or number is created, and the data is not kept after the response. The PDF is rendered with the same templates as the browser generator and always carries the "Powered by Free Invoice Automation" footer. It has no verification QR code, because there is no stored document to verify. The route is limited per IP address and by request size (proposed: 30 requests per hour per IP). To keep your invoices, numbers and payment links, [create an account](/account/sign-up) and use the authenticated API. Permission: `none`. Capability: `generate_pdf`. MCP tool: `generate_pdf`. ## Body - `type` (enum, default: invoice): Document type to render. One of: `invoice`, `estimate`, `receipt`. - `number` (string): The number to print, e.g. `INV-0001`. You choose it; nothing is reserved. - `issue_date` (string): Issue date. - `due_date` (string): Due date. - `currency` (string, required): ISO 4217 code. - `business` (object, required): Who the document is from. - `legal_name` (string, required): Legal name of the issuer. - `email` (string): Contact email. - `phone` (string): Phone number. - `address` (object): Postal address. - `tax_ids` (object[]): Tax ids, each `{ type, value }`. - `client` (object, required): Who the document is for. - `name` (string, required): Client name. - `company_name` (string): Company name. - `email` (string): Email. - `address` (object): Postal address. - `tax_id` (string): Client tax id. - `lines` (object[], required): Document lines. Totals are computed for you with the same maths as stored documents. - `description` (string, required): Line text. - `quantity` (string, required): Quantity as a decimal string, greater than 0. - `unit` (string): Unit label. - `unit_price_minor` (string, required): Price per unit in minor units. - `discount_type` (enum): Line discount kind. One of: `percent`, `amount`. - `discount_value` (string): Discount percent, or amount in minor units. - `tax_name` (string): Tax name printed on the line. - `tax_rate` (string): Tax percent, e.g. `"20"`. - `tax_calculation` (enum, default: exclusive): Whether the price excludes or includes the tax. One of: `exclusive`, `inclusive`. - `terms` (string): Payment terms. - `notes` (string): Notes printed on the document. - `template_key` (string): PDF template key. Defaults to the standard template. ## Errors - `validation_failed` - `rate_limited` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/generate-pdf --- # Verify a document GET https://api.freeinvoiceautomation.com/v1/public/verify/{code} Checks a verification code from a PDF and returns who issued the document, its amount and its current status. Every PDF from a signed-in account carries a verification QR code that opens `freeinvoiceautomation.com/v/`; this route backs that page. Anyone holding the code can confirm the document is real and see whether it is paid or void. See [Verification codes](/invoicing/verification). It returns only what is printed on the PDF and needed to verify it: no client address and no line items. The page has no analytics, and the route is rate-limited per IP. Permission: `none`. Capability: `verify_document`. ## Path parameters - `code` (string, required): The short code from the QR or the `/v/` link. ## Response - `issuer` (string): Name of the business that issued the document. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`, `contract`. - `number` (string): Document number. - `total_minor` (string): Document total in minor units. - `currency` (string): ISO 4217 code. - `issue_date` (string): Issue date. - `status` (string): Current status, e.g. `paid`, `partially_paid`, `overdue` or `void`. ## Errors - `not_found` - `rate_limited` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/verify-document --- # Join a connector waitlist POST https://api.freeinvoiceautomation.com/v1/public/waitlist Signs an email address up to hear once when a "Coming soon" connector ships. Backs the **Notify me** button on connectors shown as coming soon, such as the Shopify app and the WordPress plugin. No email is sent until the connector ships; then each address gets one email with an unsubscribe link. The form must show consent text, and `consent` must be `true`. The route needs no account and is rate-limited per IP. Permission: `none`. Capability: `join_waitlist`. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `connector` (enum, required): Connector key. One of: `shopify`, `wordpress`. - `email` (string, required): Address to notify. - `consent` (boolean, required): Must be `true`: the person agreed to the consent text shown on the form. - `source` (enum): Where the sign-up came from (proposed). One of: `site`, `pricing`, `settings`. ## Response - `connector` (string): Connector key. - `email` (string): The address signed up. - `consent_at` (string): When consent was recorded. ## Errors - `validation_failed` - `rate_limited` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/join-waitlist --- # Get public plans GET https://api.freeinvoiceautomation.com/v1/public/plans Returns the public plans with their prices and limits, as shown on the pricing page. Prices and allowances are data, not code, so the pricing page and this route always show the current values. Prices are integer minor units in USD. Regional prices apply by billing country. See [Free and Team plans](/billing/plans). Permission: `none`. Capability: `get_public_plans`. ## Response - `data` (object[]): Public plans in display order. - `key` (enum): Plan key. One of: `free`, `team`, `enterprise`. - `name` (string): Display name. - `prices` (object[]): Active prices of the plan. - `item` (enum): What the price is for. One of: `base`, `extra_member`, `credit_pack`. - `tier` (enum): `regional` applies in the countries with regional pricing. One of: `standard`, `regional`. - `interval` (enum): Billing interval. One of: `month`, `year`, `one_time`. - `amount_minor` (string): Price in minor units. - `currency` (string): Always `USD`. - `limits` (object): Plan limits by settings key, e.g. `members.included`, `ai.monthly_credits`, `platform_mail.monthly_quota`. Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/get-public-plans --- # Get a pay page GET https://api.freeinvoiceautomation.com/v1/public/pay/{token} Returns everything the pay page shows for one invoice: business branding, the invoice, the balance and the ways to pay. Backs `freeinvoiceautomation.com/pay/`. The token has the single purpose `pay`, expires, and is revoked when the invoice is voided or the business revokes the link. A voided invoice shows its void state and no payment options; a credited invoice shows the credit notes and the reduced balance. See [The pay page](/invoicing/pay-page). The pay page has no analytics and no ads, and the route is rate-limited per IP. Permission: `none`. Capability: `get_pay_page`. ## Path parameters - `token` (string, required): The pay token from the link. 256-bit random and single-purpose; only its hash is stored. ## Response - `business` (object): Branding of the issuing business. - `display_name` (string): The business's name as shown to clients (display name, else legal name). - `logo_url` (string | null): Short-lived signed URL of the business logo. - `email` (string | null): The business's contact email. - `address` (object | null): The business's address as printed on documents. - `tax_ids` (object[] | null): Tax ids as printed on documents, each `{ type, value }`. - `document` (object): The invoice as sent (latest revision). - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `number` (string): Document number, e.g. `INV-2027-0042`. - `status` (string): Current status, e.g. `sent`, `partially_paid`, `paid`, `overdue` or `void` for an invoice. See [Document statuses](/invoicing/statuses). - `issue_date` (string): Issue date. - `due_date` (string | null): Due date. - `currency` (string): ISO 4217 code. - `lines` (object[]): Lines as sent (the latest revision). - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string. - `unit` (string | null): Unit label. - `unit_price_minor` (string): Price per unit in minor units. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line. - `total_minor` (string): Line total in minor units. - `subtotal_minor` (string): Subtotal in minor units. - `discount_minor` (string): Discount in minor units. - `tax_minor` (string): Tax in minor units. - `total_minor` (string): Total in minor units. - `terms` (string | null): Payment terms. - `notes` (string | null): Notes to the client. - `amount_paid_minor` (string): Confirmed payments so far, in minor units. - `amount_credited_minor` (string): Credit notes applied, in minor units. - `balance_minor` (string): What is still owed: total minus paid minus credited. - `accepted_methods` (object[]): The ways this invoice can be paid. - `connection_id` (string): Pass this to [Start a checkout](/api-reference/public/start-checkout) for hosted rails. - `rail` (string): Rail key, e.g. `stripe` or `bank_transfer`. - `instructions` (object | null): Bank details or QR image for instruction rails; `null` for hosted checkout rails. - `payment_schedule` (object[] | null): Deposit and instalments with their due dates and status, and the next amount due (from January 2027). - `credit_notes` (object[]): Credit notes issued against the invoice, each with number and amount. - `voided` (boolean): `true` when the invoice is void. No payment options are offered. - `save_card_available` (boolean): Whether the client may tick "Save this card" at Stripe checkout (from March 2027). ## Errors - `token_invalid` - `token_expired` - `token_revoked` - `rate_limited` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/get-pay-page --- # Start a checkout POST https://api.freeinvoiceautomation.com/v1/public/pay/{token}/checkout Creates a hosted checkout for the invoice (Stripe Checkout, or a Whop link) and returns its URL. Send the client to the returned URL. Card details are entered only on the provider's hosted page, never on ours. Partial payments are allowed: send the amount the client wants to pay. With a payment schedule, pass the instalment; the client may also pay more than the next amount due. **This call never marks anything paid**, and neither does the client coming back from the checkout. The invoice becomes paid only when the rail's verified webhook arrives (rule 5); the return page says the payment is being confirmed. Permission: `none`. Capability: `start_checkout`. ## Path parameters - `token` (string, required): The pay token from the link. 256-bit random and single-purpose; only its hash is stored. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `connection_id` (string, required): A hosted rail from the pay page's `accepted_methods`. - `amount_minor` (string, required): Amount to pay in minor units of the invoice currency. - `installment_id` (string): The instalment being paid, when the invoice has a payment schedule (from January 2027). - `save_card` (boolean, default: false): The client ticked "Save this card for future invoices" (Stripe, from March 2027). The consent is recorded with the card. ## Response - `checkout_url` (string): The hosted checkout page to send the client to. ## Errors - `token_invalid` - `token_expired` - `token_revoked` - `rate_limited` - `rail_not_connected` - `document_voided` - `validation_failed` - `upstream_error` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/start-checkout --- # Submit "I have paid" POST https://api.freeinvoiceautomation.com/v1/public/pay/{token}/proofs Tells the business the client has paid by bank transfer or another instruction rail, optionally with a proof file. Creates a payment proof with status `pending` and notifies the business. **It never changes paid state**: the business checks its bank and accepts the proof, which records the payment and is audited, or rejects it. See [Payment proofs](/payments/payment-proofs). To attach a file, first get an upload URL from [Upload a proof file](/api-reference/public/upload-payment-proof-file), upload the file, then pass its `attachment_id` here. Limited per token (proposed: 5 per hour). Permission: `none`. Capability: `submit_payment_proof`. ## Path parameters - `token` (string, required): The pay token from the link. 256-bit random and single-purpose; only its hash is stored. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `claimed_amount_minor` (string): The amount the client says they paid, in minor units of the invoice currency. - `claimed_paid_on` (string): The day the client says they paid. - `note` (string): A message for the business, e.g. the bank reference used. - `attachment_id` (string): The uploaded proof file. ## Response - `id` (string): Id of the payment proof. - `status` (enum): Always `pending` when created. One of: `pending`, `accepted`, `rejected`. - `claimed_amount_minor` (string | null): Amount claimed. - `currency` (string | null): Invoice currency. - `claimed_paid_on` (string | null): Date claimed. - `created_at` (string): When the proof was submitted. ## Errors - `token_invalid` - `token_expired` - `token_revoked` - `rate_limited` - `validation_failed` - `document_voided` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/submit-payment-proof --- # Upload a proof file POST https://api.freeinvoiceautomation.com/v1/public/pay/{token}/uploads Returns a signed URL to upload a payment proof file (PDF, PNG or JPEG). Upload the file to `upload_url` with an HTTP `PUT`, then send the returned `attachment_id` with [Submit "I have paid"](/api-reference/public/submit-payment-proof). Files are stored privately and served only through short-lived signed URLs. Accepted types are PDF, PNG and JPEG up to 10 MB (proposed). Images are re-encoded and PDFs are checked by their content, not just the name. Permission: `none`. Capability: `upload_payment_proof_file`. ## Path parameters - `token` (string, required): The pay token from the link. 256-bit random and single-purpose; only its hash is stored. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `content_type` (enum, required): MIME type of the file. One of: `application/pdf`, `image/png`, `image/jpeg`. - `size_bytes` (integer, required): File size in bytes. - `original_name` (string): The file's name on the client's device. ## Response - `attachment_id` (string): Pass this when submitting the proof. - `upload_url` (string): Signed URL to `PUT` the file to. It expires shortly. ## Errors - `token_invalid` - `token_expired` - `token_revoked` - `rate_limited` - `validation_failed` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/upload-payment-proof-file --- # Get an estimate page GET https://api.freeinvoiceautomation.com/v1/public/estimates/{token} Returns the estimate a client opens from their link, with what they can do next. Backs `freeinvoiceautomation.com/estimate/`, where the client can accept the estimate, request changes or (from March 2027) sign an attached contract. The token has the single purpose `estimate` and expires (proposed: 30 days, like the estimate). See [Estimates](/invoicing/estimates). The page has no analytics and no ads. Permission: `none`. Capability: `get_estimate_page`. ## Path parameters - `token` (string, required): The estimate token from the link. 256-bit random and single-purpose; only its hash is stored. ## Response - `business` (object): Branding of the issuing business. - `display_name` (string): The business's name as shown to clients (display name, else legal name). - `logo_url` (string | null): Short-lived signed URL of the business logo. - `email` (string | null): The business's contact email. - `address` (object | null): The business's address as printed on documents. - `tax_ids` (object[] | null): Tax ids as printed on documents, each `{ type, value }`. - `document` (object): The estimate as sent. `status` is one of `sent`, `changes_requested`, `accepted`, `expired`, `converted` or `void`. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `number` (string): Document number, e.g. `INV-2027-0042`. - `status` (string): Current status, e.g. `sent`, `partially_paid`, `paid`, `overdue` or `void` for an invoice. See [Document statuses](/invoicing/statuses). - `issue_date` (string): Issue date. - `due_date` (string | null): Due date. - `currency` (string): ISO 4217 code. - `lines` (object[]): Lines as sent (the latest revision). - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string. - `unit` (string | null): Unit label. - `unit_price_minor` (string): Price per unit in minor units. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line. - `total_minor` (string): Line total in minor units. - `subtotal_minor` (string): Subtotal in minor units. - `discount_minor` (string): Discount in minor units. - `tax_minor` (string): Tax in minor units. - `total_minor` (string): Total in minor units. - `terms` (string | null): Payment terms. - `notes` (string | null): Notes to the client. - `expires_at` (string | null): Last day the estimate can be accepted. - `payment_schedule` (object[] | null): Deposit and instalments, when the estimate has them (from January 2027). - `contract` (object | null): The attached contract to read and sign, with its status (from March 2027). ## Errors - `token_invalid` - `token_expired` - `token_revoked` - `rate_limited` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/get-estimate-page --- # Accept an estimate POST https://api.freeinvoiceautomation.com/v1/public/estimates/{token}/accept Marks the estimate as accepted by the client and notifies the business. The estimate's status becomes `accepted`, an audit entry is written and the business is notified. When the estimate has a deposit (from January 2027), the deposit invoice is created automatically. Turning an accepted estimate into an invoice is done by the business. An expired or void estimate cannot be accepted. Permission: `none`. Capability: `accept_estimate`. ## Path parameters - `token` (string, required): The estimate token from the link. 256-bit random and single-purpose; only its hash is stored. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Response - `status` (string): `accepted`. - `accepted_at` (string): When the client accepted. ## Errors - `token_invalid` - `token_expired` - `token_revoked` - `rate_limited` - `conflict` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/accept-estimate --- # Request changes to an estimate POST https://api.freeinvoiceautomation.com/v1/public/estimates/{token}/request-changes Sends the client's change request to the business and marks the estimate as changes requested. The client's message is shown to the business on the estimate, and the status becomes `changes_requested`. The business can then edit and send the estimate again. Permission: `none`. Capability: `request_estimate_changes`. ## Path parameters - `token` (string, required): The estimate token from the link. 256-bit random and single-purpose; only its hash is stored. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `message` (string, required): What the client would like changed. ## Response - `status` (string): `changes_requested`. ## Errors - `token_invalid` - `token_expired` - `token_revoked` - `rate_limited` - `conflict` - `validation_failed` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/request-estimate-changes --- # Sign a contract POST https://api.freeinvoiceautomation.com/v1/public/estimates/{token}/sign Records the client's electronic signature on the contract attached to an estimate. The client reads the contract, ticks the consent text and types or draws a signature. We store the signer's name and email, the method, the consent text as shown, the IP address, the user agent and the time. When every signer in the fixed order has signed, the final PDF is sealed with a SHA-256 hash and a signature certificate page, and it never changes afterwards; a change needs a new version signed again. Signing can create the deposit invoice. See [Contracts and e-signature](/features/contracts). This is a simple electronic signature. Contracts that legally need a qualified signature are out of scope. Permission: `none`. Capability: `sign_contract`. Available from March 2027. ## Path parameters - `token` (string, required): The estimate token from the link. 256-bit random and single-purpose; only its hash is stored. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `signer_name` (string, required): Full name of the person signing. - `signer_email` (string, required): Email of the person signing. - `method` (enum, required): How the signature was made. One of: `typed`, `drawn`. - `typed_name` (string): The typed signature. Required when `method` is `typed`. - `signature_image` (string): The drawn signature as a PNG data URL. Required when `method` is `drawn`. - `consent` (boolean, required): Must be `true`: the signer ticked the consent text. ## Response - `contract_status` (enum): Status after this signature. One of: `partially_signed`, `signed`, `sealed`. - `signed_at` (string): When the signature was recorded. ## Errors - `token_invalid` - `token_expired` - `token_revoked` - `rate_limited` - `contract_sealed` - `validation_failed` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/sign-contract --- # Request a portal link POST https://api.freeinvoiceautomation.com/v1/public/portal/magic-link Emails a single-use sign-in link to the client portal of one business. Always answers `202`. The client enters their email on the business's portal page. If that email belongs to a client of the business, we email a single-use link that opens the [client portal](/clients/portal) for that business and that client only. Clients never have a password. **The response is always `202` with no body**, whether or not the email is known, so the route cannot be used to find out who is a client. The link is single use and short-lived (proposed: 15 minutes). Requests are limited per email and per IP (proposed: 3 per 15 minutes per email, 20 per hour per IP). Permission: `none`. Capability: `request_portal_link`. Available from the week of 11 January 2027. ## Body - `business_slug` (string): The business's slug. Send this or `business_id`. - `business_id` (string): The business's id. Send this or `business_slug`. - `email` (string, required): The client's email address. ## Errors - `validation_failed` - `rate_limited` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/request-portal-link --- # Open a portal session POST https://api.freeinvoiceautomation.com/v1/public/portal/session Exchanges a portal magic-link token for a client-scoped session cookie. The magic-link token is used once: after this call it no longer works. The response sets a secure, HttpOnly session cookie scoped to one business and one client; every [portal route](/api-reference/portal/portal-list-documents) then reads only that client's data. The session expires (proposed: 24 hours) or ends with [End the portal session](/api-reference/portal/portal-end-session). Permission: `none`. Capability: `open_portal_session`. Available from the week of 11 January 2027. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `token` (string, required): The token from the magic link. ## Response - `business` (object): The business whose portal is open. - `display_name` (string): Business name. - `client` (object): The client signed in. - `name` (string): Client name. - `expires_at` (string): When the portal session ends. ## Errors - `token_invalid` - `token_expired` - `token_revoked` - `rate_limited` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/open-portal-session --- # Get a pay-me page GET https://api.freeinvoiceautomation.com/v1/public/pay-me/{slug} Returns a business's public pay-me page: its name, logo and every enabled way to pay. Backs `freeinvoiceautomation.com/`, where anyone can pay the business without an invoice link, and the WordPress `[fia_pay_me]` block. The page has no analytics. See [Pay-me page](/payments/pay-me). Permission: `none`. Capability: `get_pay_me_page_public`. Available from February 2027. ## Path parameters - `slug` (string, required): The business's slug. ## Response - `business` (object): Business name and logo. - `display_name` (string): The business's name as shown to clients (display name, else legal name). - `logo_url` (string | null): Short-lived signed URL of the business logo. - `headline` (string | null): Headline the business set for the page. - `description` (string | null): Text the business set for the page. - `allow_custom_amount` (boolean): Whether the payer can enter their own amount (proposed). - `rails` (object[]): Enabled ways to pay. - `connection_id` (string): The connection. - `rail` (string): Rail key, e.g. `stripe`, `whop`, `bank_transfer`, `raast`. - `instructions` (object | null): Bank details or QR image for instruction rails. ## Errors - `not_found` - `rate_limited` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/get-pay-me-page-public --- # Opt out of reminders POST https://api.freeinvoiceautomation.com/v1/public/opt-out/{token} Stops reminder emails from one business to the client who opened the link. Every reminder carries an opt-out link with a single-purpose `reminder_opt_out` token. Opening it turns on the client's reminder opt-out for that business: later reminder steps are skipped and the skip is logged. Documents and pay links keep working. See [Reminders](/collections/reminders). Permission: `none`. Capability: `reminder_opt_out`. Available from the week of 11 January 2027. ## Path parameters - `token` (string, required): The opt-out token from the link. 256-bit random and single-purpose; only its hash is stored. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Response - `reminders_opt_out` (boolean): Always `true` after the call. ## Errors - `token_invalid` - `token_expired` - `token_revoked` - `rate_limited` Section: Public pages Source: https://docs.freeinvoiceautomation.com/api-reference/public/reminder-opt-out --- # List portal documents GET https://api.freeinvoiceautomation.com/v1/portal/documents Returns the signed-in client's documents from one business: invoices, estimates, credit notes and receipts. Portal routes use the session cookie from [Open a portal session](/api-reference/public/open-portal-session), not an API key. The session is scoped to one business and one client, so this list only ever contains that client's documents; drafts are never shown. See [Client portal](/clients/portal). The client can open each document, download its PDF, and pay invoices with a balance through `pay_url`. The portal has no analytics, ads or sponsors. Results use [cursor pagination](/api-reference/pagination). Permission: `client scope`. Capability: `portal_list_documents`. Available from the week of 11 January 2027. ## Query parameters - `type` (string): Comma-separated document types. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `status` (string): Comma-separated statuses, e.g. `sent,overdue`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The documents on this page. - `id` (string): Id of the document. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `number` (string): Document number, e.g. `INV-2027-0042`. - `status` (string): Current status, e.g. `sent`, `partially_paid`, `paid`, `overdue` or `void`. See [Document statuses](/invoicing/statuses). - `issue_date` (string): Issue date. - `due_date` (string | null): Due date. - `currency` (string): ISO 4217 code. - `total_minor` (string): Total in minor units. - `balance_minor` (string): What is still owed, in minor units: total minus paid minus credited. - `pay_url` (string | null): Pay page link for an invoice with a balance, else `null`. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `unauthenticated` Section: Client portal Source: https://docs.freeinvoiceautomation.com/api-reference/portal/portal-list-documents --- # Get a portal document GET https://api.freeinvoiceautomation.com/v1/portal/documents/{id} Returns one of the client's documents as sent, with payments, credits and related documents. `GET /v1/portal/documents/{id}/pdf` returns the same document as a PDF (`application/pdf`). A voided invoice is returned with status `void`, and the PDF shows the VOID stamp. A document of another client, or of another business, is answered with `not_found`. Permission: `client scope`. Capability: `portal_get_document`. Available from the week of 11 January 2027. ## Path parameters - `id` (string, required): Id of the document. ## Response - `id` (string): Id of the document. - `type` (enum): Document type. One of: `invoice`, `estimate`, `credit_note`, `receipt`. - `number` (string): Document number, e.g. `INV-2027-0042`. - `status` (string): Current status, e.g. `sent`, `partially_paid`, `paid`, `overdue` or `void`. See [Document statuses](/invoicing/statuses). - `issue_date` (string): Issue date. - `due_date` (string | null): Due date. - `currency` (string): ISO 4217 code. - `total_minor` (string): Total in minor units. - `lines` (object[]): Lines as sent (the latest revision). - `description` (string): Line text. - `quantity` (string): Quantity as a decimal string. - `unit` (string | null): Unit label. - `unit_price_minor` (string): Price per unit in minor units. - `tax_name` (string | null): Tax name frozen on the line. - `tax_rate` (string | null): Tax percent frozen on the line. - `total_minor` (string): Line total in minor units. - `subtotal_minor` (string): Subtotal in minor units. - `discount_minor` (string): Discount in minor units. - `tax_minor` (string): Tax in minor units. - `amount_paid_minor` (string): Confirmed payments, in minor units. - `amount_credited_minor` (string): Credit notes applied, in minor units. - `balance_minor` (string): What is still owed, in minor units. - `terms` (string | null): Payment terms. - `notes` (string | null): Notes to the client. - `links` (object[]): Related documents, e.g. credit notes against this invoice or the estimate it was made from. Each has `type`, `document_id` and `number`. - `payment_schedule` (object[] | null): Deposit and instalments with due dates and status (from January 2027). - `pay_url` (string | null): Pay page link for an invoice with a balance. ## Errors - `not_found` - `unauthenticated` Section: Client portal Source: https://docs.freeinvoiceautomation.com/api-reference/portal/portal-get-document --- # Get the client statement GET https://api.freeinvoiceautomation.com/v1/portal/statement Returns the client's statement with the business: invoices, credit notes and payments, and what is still owed. Amounts are shown per currency. Only documents and payments of the signed-in client and business are included. Permission: `client scope`. Capability: `portal_get_statement`. Available from the week of 11 January 2027. ## Query parameters - `from` (string): First day of the statement. - `to` (string): Last day of the statement. - `currency` (string): Only this currency. - `format` (enum, default: json): Response format. One of: `json`, `pdf`. ## Response - `from` (string | null): Start of the statement. - `to` (string): End of the statement. - `currencies` (object[]): One block per currency. - `currency` (string): ISO 4217 code. - `entries` (object[]): Invoices, credit notes and payments in date order. - `date` (string): Date of the entry. - `kind` (enum): Entry kind. One of: `invoice`, `credit_note`, `payment`, `refund`. - `document_id` (string): The related document. - `number` (string): Document number. - `amount_minor` (string): Amount in minor units. - `balance_minor` (string): Total still owed in this currency. ## Errors - `unauthenticated` - `validation_failed` Section: Client portal Source: https://docs.freeinvoiceautomation.com/api-reference/portal/portal-get-statement --- # List saved cards GET https://api.freeinvoiceautomation.com/v1/portal/saved-payment-methods Returns the cards the client saved for this business's invoices. A card is saved only when the client ticks "Save this card for future invoices" at Stripe checkout. The card stays at Stripe; we keep only Stripe's ids, the brand, the last four digits and the consent record. See [Saved cards and auto-charge](/payments/auto-charge). Permission: `client scope`. Capability: `portal_list_saved_payment_methods`. Available from March 2027. ## Query parameters - `status` (enum, default: active): Active or removed cards. One of: `active`, `removed`. - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The saved cards on this page. - `id` (string): Id of the saved card. - `card_brand` (string): Card brand, for display, e.g. `visa`. - `card_last4` (string): Last four digits, for display. The card itself stays at Stripe. - `consent_text` (string): The consent text the client agreed to when saving the card. - `consent_at` (string): When the client gave consent. - `status` (enum): `active` cards can be charged for this business's invoices. One of: `active`, `removed`. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `unauthenticated` Section: Client portal Source: https://docs.freeinvoiceautomation.com/api-reference/portal/portal-list-saved-payment-methods --- # Remove a saved card DELETE https://api.freeinvoiceautomation.com/v1/portal/saved-payment-methods/{id} Removes a saved card. It is detached at Stripe and can no longer be charged. The client can remove a saved card at any time. The card is detached at Stripe and marked `removed`; any automatic charges that would have used it fall back to the normal pay link and reminders. The consent record is kept. This is a destructive action: send `"confirm": true`. In the portal the confirmation is a dialog. See [Drafts and confirmations](/api-reference/confirmations). Permission: `client scope`. Capability: `portal_remove_saved_payment_method`. Available from March 2027. ## Path parameters - `id` (string, required): Id of the saved card. ## Headers - `Idempotency-Key` (string): Any unique string up to 255 characters. See [Idempotency](/api-reference/idempotency). ## Body - `confirm` (boolean, required): Must be `true` to remove the card. Without it you get the preview and `confirmation_required`. ## Errors - `confirmation_required` - `not_found` - `unauthenticated` - `upstream_error` Section: Client portal Source: https://docs.freeinvoiceautomation.com/api-reference/portal/portal-remove-saved-payment-method --- # List signed contracts GET https://api.freeinvoiceautomation.com/v1/portal/contracts Returns the client's contracts with this business, with a download of each sealed PDF. `GET /v1/portal/contracts/{id}/pdf` downloads the sealed PDF. Its SHA-256 hash is checked on every download; if the file does not match the stored hash the download fails with `contract_hash_mismatch` and an alert is raised. Signed contracts never change: a change is a new version that is signed again. See [Contracts and e-signature](/features/contracts). Permission: `client scope`. Capability: `portal_list_contracts`. Available from March 2027. ## Query parameters - `limit` (integer, default: 50): Page size, 1 to 200. - `cursor` (string): Cursor from the previous page's `next_cursor`. ## Response - `data` (object[]): The contracts on this page. - `id` (string): Id of the contract document. - `number` (string): Contract number. - `status` (enum): Contract status. One of: `sent`, `partially_signed`, `signed`, `sealed`, `superseded`. - `version` (integer): Contract version. A change to a signed contract is a new version, signed again. - `sealed_at` (string | null): When the signed PDF was sealed. - `pdf_url` (string | null): Path of the sealed PDF download, `/v1/portal/contracts/{id}/pdf`, once sealed. - `next_cursor` (string | null): Cursor for the next page, or `null` on the last page. - `has_more` (boolean): Whether another page exists. ## Errors - `unauthenticated` - `contract_hash_mismatch` - `not_found` Section: Client portal Source: https://docs.freeinvoiceautomation.com/api-reference/portal/portal-list-contracts --- # End the portal session POST https://api.freeinvoiceautomation.com/v1/portal/logout Signs the client out of the portal and clears the session cookie. After this call the portal routes answer `unauthenticated` until the client opens a new magic link from [Request a portal link](/api-reference/public/request-portal-link). Permission: `client scope`. Capability: `portal_end_session`. Available from the week of 11 January 2027. ## Errors - `unauthenticated` Section: Client portal Source: https://docs.freeinvoiceautomation.com/api-reference/portal/portal-end-session --- # Build with AI Three ways to use AI with Free Invoice Automation. Your own assistant through MCP, our built-in invoice agent, and docs written for AI tools. Free Invoice Automation was built so that an AI assistant can do your invoicing as safely as you can. You can connect the assistant you already use (Claude, ChatGPT, Grok, Gemini CLI and others) to our MCP server, use the AI invoice agent inside the web app, or point any AI tool at these docs in plain Markdown. All three use the same actions, with the same permissions, as the web app and the REST API. ## Choose how you want to work | You want to | Use | AI key needed | Credits used | | --- | --- | --- | --- | | Ask Claude, ChatGPT, Grok or another assistant to create and send invoices | [MCP server](/ai/mcp/overview) | No: your assistant does the AI work | No | | Describe an invoice in plain words inside the app, or run it on a schedule | [AI invoice agent](/ai/agent/overview) | No: it runs on our key, or yours if you prefer | Yes, unless you use your own key | | Give an AI tool accurate answers about the product and API | [Docs for AI](/ai/llms-txt) | No | No | | Write your own integration with an AI framework | [REST API](/api-reference/introduction) | Your choice | No | MCP server Connect your assistant with a browser sign-in. No setup, no AI key. AI invoice agent Plain-language drafts and automations inside the dashboard. Docs for AI llms.txt, the full docs in one file, and every page as Markdown. ## Connect your AI assistant The quickest start is the MCP server. Pick your client below, sign in when your browser opens, and approve the account and scopes. Once connected, try a request like this: ```text Example request Create a draft invoice for Acme Ltd for 10 hours of design work at 50 USD an hour, due in 14 days. Show me the preview before sending. ``` Your assistant calls `list_clients` to find Acme, `create_invoice` to make the draft, and `preview_send` to show you what the email will look like. Nothing is sent until you confirm. See the [tool reference](/ai/mcp/tools) for every tool. ## One capability, every surface Every action in Free Invoice Automation is defined once, with its input, its output, a permission key and a hint that says whether it only reads, writes, or is destructive. The web app, the REST API, the MCP server, the CLI, the store connectors and the AI agent are all built from that same definition. In practice this means: - **The same rules everywhere.** An MCP tool cannot do anything the matching API endpoint cannot do, and the API cannot do anything your role does not allow. - **The same data everywhere.** An invoice created by your assistant shows up in the app at once, with the same number sequence, totals and statuses. - **No feature is app-only.** If you can click it, there is an API route for it. Only some actions are exposed as MCP tools (28 in total, listed in the [tool reference](/ai/mcp/tools)); the others are available through the API, the CLI and the agent. ## Safe by default AI tools make mistakes and can be misled by text they read, such as a pasted email that says "send this now". We designed every AI surface around that: - **Drafts first.** Assistants and the agent create drafts. Sending a document needs `confirm: true` on the call, unless you deliberately created a token or key with `auto_send` mode. - **Confirmation for destructive actions.** Voiding an invoice or issuing a credit note always needs `confirm: true`. Without it you get a preview of what would happen and the error `confirmation_required`. - **Your permissions, never more.** An MCP token or API key holds only the scopes you chose, and at every call it is limited to what your role allows right now. If your role loses a permission, your tokens lose it on the next request. - **Everything is audited.** Every write records who did it, including whether it was you in the app, an API key, an MCP client or the agent. Read [Drafts and confirmations](/ai/mcp/safety) for the details. ## The AI invoice agent The agent lives in **Automate > AI agent** in the web app. Type "Invoice Acme for 10 hours of design at $50" or paste an email, and it creates the client if needed and a draft invoice for you to check. You can also set up [automations](/ai/agent/automations) that run on a schedule or when something happens, such as "when a Stripe payment arrives, make the receipt". The agent runs on our own AI key, so you do not need one. Each run uses [credits](/ai/agent/credits): - **Free**: 250 welcome credits in your first month after sign-up, unlocked by adding a card (a $0 verification; the card is never charged). - **Team**: 1,000 credits a month, and 2,000 in your first paid month. - **Credit packs**: 1,000 credits for $5 on any plan, bought only when you click to buy. - **Your own key**: connect an Anthropic, OpenAI, Google or OpenAI-compatible key and the agent uses no credits at all. See [Use your own AI key](/ai/agent/own-key). The MCP server never uses credits. When you work through Claude or ChatGPT, your own assistant does the AI work and our server only runs the actions it asks for. ## Docs for AI tools Every page of these docs is available as plain Markdown, and the whole site is indexed in `/llms.txt` with the full text in `/llms-full.txt`. Use the **Copy page** menu at the top of any page to copy it or open it in ChatGPT or Claude. See [Docs for AI](/ai/llms-txt). ## What you need - A Free Invoice Automation account. [Sign up](https://app.freeinvoiceautomation.com/sign-up) with email and password, Google or a magic link. - For MCP: an MCP-capable client. No AI key is needed. - For the agent: credits or your own AI key, and the `ai.use` permission on your role. - For the API: an [API key](/api-reference/authentication) from **Settings > API keys**. The MCP server, the agent and the API all ship at launch on 1 January 2027. Some tools arrive later with their features; each one shows its release in the [tool reference](/ai/mcp/tools). ## Related pages MCP authentication OAuth sign-in, API keys, scopes and modes. Tool reference All 28 MCP tools with hints, permissions and releases. Credits and costs What each agent action costs and how credits expire. Permission keys The keys behind every tool and endpoint. Source: https://docs.freeinvoiceautomation.com/ai/overview --- # Docs for AI (llms.txt) Give AI tools accurate, current answers about Free Invoice Automation with llms.txt, the full docs in one file, and every page as Markdown. AI assistants answer better when they read the real documentation instead of guessing. These docs publish themselves in formats that language models read easily: an index at `/llms.txt`, the full text of every page at `/llms-full.txt`, and each page as plain Markdown under `/raw/`. Use them when you build an integration with an AI coding tool, when you want your assistant to explain a feature, or when you feed our docs into your own AI system. ## What we publish | URL | What it contains | Use it when | | --- | --- | --- | | [`/llms.txt`](/llms.txt) | An index of every docs page: title, one-line description and a link to its Markdown version, grouped by the four docs tabs | Your tool follows the llms.txt convention, or you want the assistant to pick the pages it needs | | [`/llms-full.txt`](/llms-full.txt) | The full text of every page in one file, each followed by its source URL | You want to load everything at once into a long context or an index | | `/raw/.md` | One page as Markdown, for example [`/raw/ai/mcp/tools.md`](/raw/ai/mcp/tools.md) | You need just one page, such as the tool reference or an endpoint | All three are plain text served with `Content-Type: text/plain; charset=utf-8`. They are built from the same source files as the pages you are reading, so they never drift from the website. ### `/llms.txt` The index follows the [llms.txt](https://llmstxt.org) format. It starts with a title and a one-paragraph summary of the product, then gives the address of the full text, then lists every page under headings for each tab (Documentation, Integrations, API Reference and Build with AI): ```markdown llms.txt (shortened) # Free Invoice Automation > Free, paperless invoicing for everyone. Create invoices, estimates, credit notes and receipts, get paid through Stripe, bank transfer and more, from the web app, the REST API, the MCP server or the AI invoice agent. We never take a cut of payments. Full text of every page: https://docs.freeinvoiceautomation.com/llms-full.txt ## Documentation - [Introduction](https://docs.freeinvoiceautomation.com/raw/introduction.md): Free, paperless invoicing for everyone. ... - [Quickstart](https://docs.freeinvoiceautomation.com/raw/quickstart.md): ... ## Build with AI - [MCP overview](https://docs.freeinvoiceautomation.com/raw/ai/mcp/overview.md): ... ``` Each link points to the Markdown version of the page, so an assistant that follows a link gets clean text rather than a web page full of navigation. ### `/llms-full.txt` The full file joins every page's Markdown, separated by a horizontal rule (`---`). After each page it adds a `Source:` line with the page's web address, so an assistant can cite where an answer came from. The file is large; use it with tools that accept long documents or that split text into chunks for search. ### Markdown pages Add `/raw/` before any page's path and `.md` after it to get the Markdown version: ```text https://docs.freeinvoiceautomation.com/ai/mcp/authentication https://docs.freeinvoiceautomation.com/raw/ai/mcp/authentication.md ``` The Markdown keeps headings, tables, code samples and notes, which is what a model needs to follow instructions correctly. ## The Copy page menu Every page has a **Copy page** button next to its title. It gives you four options: - **Copy page**: copies the page as Markdown to your clipboard, ready to paste into any chat. - **View as Markdown**: opens the `/raw/` version of the page in a new tab. - **Open in ChatGPT**: opens ChatGPT with a prompt asking it to read this page so you can ask questions about it. - **Open in Claude**: does the same in Claude. Copying is the most reliable option when your assistant cannot browse the web, or when you want it to work from exactly this text. ## How to use them Chat assistants Paste the Markdown of the page you need, or give the assistant the `/raw/` link if it can read web pages. For broad questions ("how do credit notes and refunds work?"), give it `/llms.txt` and ask it to read the pages it needs. ```text Example prompt Read https://docs.freeinvoiceautomation.com/llms.txt and the pages it links about credit notes. Then explain how to refund part of a paid invoice through the API. ``` AI coding tools Most AI editors let you add documentation by URL. Add `https://docs.freeinvoiceautomation.com/llms-full.txt` (or `/llms.txt`) as a docs source, then ask the editor to write code against our API. It will use real endpoint paths, the `Idempotency-Key` header and money as strings of minor units, instead of guessing. Your own AI system Download `/llms-full.txt` and split it on the `---` separators to get one chunk per page, each with its `Source:` URL. Index the chunks for search and fetch them again from time to time, because the docs change as features ship. ## Docs versus the MCP server These files teach an assistant **about** Free Invoice Automation. They do not let it act on your account. To let your assistant create invoices, list clients or check payments, connect it to the [MCP server](/ai/mcp/overview). The two work well together: the MCP tools carry their own titles and descriptions, and a docs link for deeper reading. | | Docs for AI | MCP server | | --- | --- | --- | | What the assistant gets | Knowledge: how features and the API work | Actions: create, read and update your data | | Sign-in | None, public | OAuth sign-in or an API key | | Changes your data | Never | Yes, within your scopes and permissions | | Uses AI credits | No | No | ## Good to know - The docs describe the product as designed for launch on 1 January 2027. Pages for later features carry a "Coming" note with the month they ship; the Markdown versions include the same page text. - Nothing in these files is personal. They are the public docs and contain no account data. - The [API reference](/api-reference/introduction) pages are included too, so an assistant can look up endpoint paths, permissions, fields and errors. ## Related pages Build with AI All the ways to use AI with Free Invoice Automation. MCP overview Let your assistant act on your account, safely. Source: https://docs.freeinvoiceautomation.com/ai/llms-txt --- # MCP overview Connect Claude, ChatGPT, Grok, Gemini CLI or any MCP client to Free Invoice Automation with a browser sign-in and no AI key. The Model Context Protocol (MCP) is an open standard that lets an AI assistant use tools from other services. Our hosted MCP server gives your assistant tools to create invoices and estimates, look up clients, record payments, check what is overdue and send documents, all on your own account and under your own permissions. You sign in through your browser; there is nothing to install for remote clients and no AI key to provide. ## Server details | | | | --- | --- | | Server URL | `https://mcp.freeinvoiceautomation.com/mcp` | | Transport | Streamable HTTP | | Server name | `freeinvoiceautomation-mcp` | | Sign-in | OAuth 2.0 in your browser, or an API key | | Local option | `freeinvoiceautomation-mcp` stdio client on npm, with an API key | | Tools | 28 in total; see the [tool reference](/ai/mcp/tools) | | Cost | Free on every plan. MCP calls never use AI credits | ## Set up your client Step-by-step guides for each client: Claude Claude apps and Claude Code. ChatGPT A custom connector in developer mode. Grok A custom connector, or the xAI API. Gemini CLI `settings.json` and `/mcp`. Other clients Any streamable HTTP client, or the local stdio client. Authentication OAuth, API keys, scopes and modes. ## How it works Your assistant connects You add the server URL to your client. The first time it connects, the client opens our sign-in page in your browser. You sign in and approve You sign in to Free Invoice Automation, pick the account to connect, and approve the scopes (what the assistant may do) and the mode: `draft_only`, the default, or `auto_send`. The consent screen shows the name of the client asking for access. Your assistant calls tools When you ask for something, the assistant picks tools such as `list_clients` and `create_invoice` and calls them. Each call is checked against the token's scopes and your role's current permissions, runs the same action as the matching API endpoint, and is written to the audit log. You confirm anything that matters New documents are drafts. Sending needs `confirm: true` unless the token is `auto_send`, and destructive tools such as `void_document` always need `confirm: true`. Without it the tool returns a preview so the assistant can show you what would happen. ## What your assistant can do At launch on 1 January 2027 the server has 18 tools; ten more arrive with their features during 2027. | Area | Tools at launch | Later | | --- | --- | --- | | Documents | `create_invoice`, `update_invoice`, `create_estimate`, `list_documents`, `get_document`, `list_overdue`, `render_pdf` | `set_payment_schedule` (Jan), `create_contract` (Mar) | | Sending | `preview_send`, `send_document` | `send_reminder` (Jan) | | Corrections | `void_document`, `create_credit_note` | | | Clients and items | `list_clients`, `upsert_client`, `list_items`, `upsert_item` | | | Payments | `record_payment`, `get_payment_status` | | | Reports | | `get_aged_receivables`, `get_report` (Jan), `get_ledger_report` (Jun to Jul) | | Time and expenses | | `log_time`, `list_unbilled_time`, `invoice_unbilled`, `record_expense` (Apr) | | No sign-in | `generate_pdf` | | Each tool carries annotations from its definition: a title, `readOnlyHint` or `destructiveHint`, `idempotentHint` (sends are idempotent, so a retried send never emails twice), and a link to its docs page. Clients use these to decide when to ask you before running a tool. Some actions exist in the API but are not MCP tools, for example deleting a client, refunding a payment or managing team members. Your assistant cannot do these through MCP. Use the web app or the [API](/api-reference/introduction) for them. ## Example conversation ```text You Who owes me money right now? ``` The assistant calls `list_overdue` and summarises the overdue invoices by client. ```text You Send Acme a copy of invoice INV-2027-0004 again, and create a new draft for 3 days of consulting at 400 USD a day. ``` The assistant calls `preview_send` for the existing invoice and shows you the email, then calls `create_invoice` for the new draft. It asks before calling `send_document` with `confirm: true`, because your token is `draft_only`. ## Requirements and limits - **An account.** Any plan. The MCP server is free, and the tools work the same on Free and Team. - **Permissions.** A tool works only if your role has its permission key, for example `documents.create` for `create_invoice`. A Viewer can read but not write. - **One account per connection.** A token is bound to one user and one account. If you belong to several accounts, connect once per account. - **Rate limits.** Calls are rate-limited per token, like API keys. Over the limit you get `rate_limited` with a `Retry-After` value. See [Rate limits](/api-reference/rate-limits). - **Fair use on Free.** API and MCP calls count toward the Free plan's monthly fair-use limits, which exist only to stop abuse. ## The MCP server and the AI agent The MCP server and the [AI invoice agent](/ai/agent/overview) call the same actions, but the AI runs in different places: - With **MCP**, your own assistant (Claude, ChatGPT and so on) does the thinking. Our server only runs the tools it calls. No credits are used. - With the **agent**, the thinking happens inside Free Invoice Automation, on our AI key or your own. It uses credits unless you connect your own key. ## Related pages Tool reference Every tool with its hint, permission and release. Drafts and confirmations How we stop an assistant from sending or voiding by mistake. Source: https://docs.freeinvoiceautomation.com/ai/mcp/overview --- # Claude Connect Claude on the web, desktop or Claude Code to Free Invoice Automation as a custom connector and create invoices by asking. Claude can use our MCP server as a custom connector. Once connected, you can ask Claude to draft invoices, find clients, check who is overdue and send documents, and it calls our tools on your account. You sign in through your browser, so you never paste a password or an AI key into Claude. ## Before you start - A Free Invoice Automation account on any plan. [Sign up](https://app.freeinvoiceautomation.com/sign-up) if you do not have one. - A Claude plan or app that supports custom connectors (remote MCP servers). Claude decides which plans include this; check Claude's own help pages if you do not see the option. - At least one business and, ideally, one client in your account, so Claude has something to work with. The server URL is: ```text https://mcp.freeinvoiceautomation.com/mcp ``` ## Add the connector Claude (web and desktop) Connectors"> In Claude, open **Settings**, then **Connectors**. On some workspace plans an owner adds custom connectors for the whole organisation first. Add a custom connector Choose **Add custom connector**. Give it a name you will recognise, such as `Free Invoice Automation`, and paste the server URL. Leave any OAuth client fields empty: our server supports sign-in without them. Connect and sign in Choose **Connect**. Your browser opens our sign-in page. Sign in, pick the account to connect, and approve the scopes and mode. Keep `draft_only` unless you are sure you want Claude to send without asking. Turn it on in a chat In a new chat, open the tools menu and make sure the connector is enabled. Claude can now see our tools. Claude Code Add the server from your terminal: ```bash claude mcp add --transport http freeinvoiceautomation https://mcp.freeinvoiceautomation.com/mcp ``` Then start Claude Code and run `/mcp`. Choose `freeinvoiceautomation` and authenticate; your browser opens our sign-in page. After you approve, the tools are listed under the server. By default the server is added for the current project. Add `--scope user` to make it available in every project, or `--scope project` to store it in the project's `.mcp.json` so your team gets the same setup (each person still signs in with their own account). If you work with several Free Invoice Automation accounts, add the connector once per account with a different name, for example `FIA Studio` and `FIA Shop`. Each connection is bound to the account you picked at sign-in. ## Try it Start with read-only requests to check the connection: ```text List my clients in Free Invoice Automation. ``` ```text Which invoices are overdue, and how much is outstanding in total per currency? ``` Then create a draft: ```text Create a draft invoice for Acme Ltd: 10 hours of design at 50 USD, due in 14 days. Show me the totals. ``` Claude calls `list_clients` to find Acme (or `upsert_client` to add it if you ask), then `create_invoice`. Amounts are stored in minor units, so 10 hours at 50 USD becomes a total of `"50000"` with `"currency": "USD"`, which Claude will show you as 500.00 USD. The invoice has no number until it is sent. To send it: ```text Show me the email preview for that invoice, then send it. ``` Claude calls `preview_send` first. With a `draft_only` connection, `send_document` needs `confirm: true`, so Claude should show you the preview and ask before it sends. See [Drafts and confirmations](/ai/mcp/safety). ## Approving tool calls in Claude Claude asks for your permission before it uses a connector's tools, and you can choose to allow a tool once or always. We recommend: - **Allow always** for read-only tools: `list_documents`, `get_document`, `list_clients`, `list_items`, `list_overdue`, `get_payment_status`, `preview_send`, `render_pdf`. - **Allow once** for tools that write, such as `create_invoice`, `upsert_client` and `record_payment`, until you trust the workflow. - **Always review** `send_document`, `void_document` and `create_credit_note`. These change what your client sees, and our server requires `confirm: true` for them anyway. Our tools tell Claude which of them only read (`readOnlyHint`) and which are destructive (`destructiveHint`), so Claude can treat them differently. ## What Claude cannot do Claude can only use the [tools on our MCP server](/ai/mcp/tools), within the scopes you approved and the permissions of your role. It cannot, for example, delete clients, refund payments, change your Stripe connection, invite members or change billing. Those actions are in the web app and the [API](/api-reference/introduction) only. ## Disconnect Remove or disconnect the connector in Claude's **Settings > Connectors** (or run `claude mcp remove freeinvoiceautomation` in Claude Code). If a team member leaves or is deactivated, their MCP tokens for your account are revoked automatically, and a role change applies to their tokens on the next request. ## Troubleshooting The browser sign-in does not open or fails Check that the URL is exactly `https://mcp.freeinvoiceautomation.com/mcp`. If you use a workspace plan, an owner may need to allow custom connectors first. Sign out of Free Invoice Automation in the browser and connect again if you picked the wrong account. A tool fails with scope_missing or permission_denied `scope_missing` means you did not approve that scope when you connected: disconnect and connect again with the scope. `permission_denied` means your role in the account does not have the permission key; ask the Owner or an Admin. Sending fails with confirmation_required This is expected on a `draft_only` connection. The response includes a preview. Ask Claude to show it to you and then confirm the send. Claude says the tools are not available Make sure the connector is enabled in the chat's tools menu. In Claude Code, run `/mcp` to see whether the server is connected and authenticated. ## Related pages Tool reference What each tool does and which permission it needs. Authentication Scopes, modes and API keys. Source: https://docs.freeinvoiceautomation.com/ai/mcp/claude --- # ChatGPT Add Free Invoice Automation to ChatGPT as a custom connector with developer mode, sign in through your browser and work with invoices in chat. ChatGPT can connect to remote MCP servers as custom connectors. With our server connected, ChatGPT can list your clients and documents, draft invoices and estimates, record payments and, after you confirm, send documents. Sign-in happens on our own page in your browser, and ChatGPT never sees your password. ## Before you start - A Free Invoice Automation account on any plan. - A ChatGPT plan that allows custom connectors with developer mode. OpenAI decides which plans include this and may change the menu names; if your screens look different, follow OpenAI's current help for adding a custom MCP connector. - On business workspaces, an admin may need to allow custom connectors or developer mode first. The server URL is: ```text https://mcp.freeinvoiceautomation.com/mcp ``` ## Add the connector Turn on developer mode In ChatGPT, open **Settings**, then **Apps and Connectors** (sometimes shown as **Connectors**). Open the advanced settings and turn on **Developer mode**. Developer mode lets ChatGPT use tools that write data, not only read it. Create a connector Choose **Create** (or **Add connector**). Fill in: - **Name**: `Free Invoice Automation` - **Description**: for example "Create and send invoices, list clients, record payments" - **MCP server URL**: `https://mcp.freeinvoiceautomation.com/mcp` - **Authentication**: OAuth Accept OpenAI's notice about custom connectors and create it. Sign in and approve ChatGPT opens our sign-in page. Sign in, pick the account to connect, and approve the scopes and the mode. `draft_only` is the default and the safe choice. Use it in a chat Start a new chat, open the tools or plus menu, choose developer mode and enable the Free Invoice Automation connector. ChatGPT now lists our tools. ## Try it ```text Using Free Invoice Automation, list my five most recent invoices with their status and balance. ``` ```text Create a draft estimate for Northwind for a website redesign: 1 x 2,400 EUR, valid for 30 days. ``` ChatGPT calls `list_documents` for the first request and `list_clients` plus `create_estimate` for the second. Money is exchanged as strings of minor units with a currency, so 2,400 EUR is `"240000"` with `"currency": "EUR"`. ChatGPT converts it for you when it answers. To send: ```text Preview the email for that estimate, then send it to the client. ``` ChatGPT calls `preview_send` and then `send_document`. On a `draft_only` connection our server answers the first send attempt with `confirmation_required` and a preview, and only sends when the call includes `confirm: true`. ChatGPT also asks you to confirm write actions in developer mode before it runs them. ## Good habits in ChatGPT - **Read the confirmation card.** In developer mode ChatGPT shows the tool name and its input before running a write action. Check the client, the amounts and the currency. - **Name the account and business.** If you have several businesses, say which one: "for my business Studio North". Tools accept a `business_id`; when you can reach only one business, it is used automatically. - **Ask for previews.** `preview_send` shows the email and recipient without sending anything, and `render_pdf` returns the PDF. - **Keep `draft_only`.** Only choose `auto_send` if you want ChatGPT to send without asking, for example in a tightly scripted workflow. ## What ChatGPT cannot do ChatGPT only sees the [tools on our MCP server](/ai/mcp/tools), and only those your approved scopes and your role allow. It cannot delete clients, refund payments, change payment connections, manage members, or touch billing. Those stay in the web app and the [API](/api-reference/introduction). ## Disconnect Delete or disconnect the connector in ChatGPT's connector settings. Members who are deactivated or removed from your account lose their MCP access at once, because their tokens are revoked. ## Troubleshooting I cannot find developer mode or custom connectors Your ChatGPT plan or workspace may not include them, or an admin has turned them off. Check OpenAI's help for your plan. ChatGPT can read but not create invoices Check that the chat is in developer mode with the connector enabled. Then check your role: creating documents needs `documents.create`. If the error is `scope_missing`, reconnect and approve the scope. The connector connected to the wrong account Each connection is bound to the account you picked when you signed in. Delete the connector, sign out of Free Invoice Automation in your browser if needed, and create it again. I get rate_limited Calls are limited per token per minute. Wait for the `Retry-After` time, or ask ChatGPT to fetch fewer items at once. ## Related pages Drafts and confirmations `confirm: true`, draft-only and auto-send explained. Tool reference All 28 tools. Source: https://docs.freeinvoiceautomation.com/ai/mcp/chatgpt --- # Grok Connect Grok to the Free Invoice Automation MCP server with a custom connector on grok.com, or as a remote MCP tool in the xAI API. Grok can call tools on remote MCP servers. Connect our server and Grok can create draft invoices, look up clients, check what is overdue and record payments on your own account. There are two ways to connect: a custom connector in the Grok apps, which signs you in through your browser, or a remote MCP tool in the xAI API, which uses one of our API keys. ## Before you start - A Free Invoice Automation account on any plan. - For the Grok apps: a Grok account with access to connectors. Custom connectors may depend on your Grok plan; Grok shows the option in its settings when your plan includes it. - For the xAI API: an xAI API key and one of our API keys. ## Add a custom connector in Grok Open connectors On grok.com, open **Connectors** from your settings and choose to add a new, custom connector. Paste the server URL Name it `Free Invoice Automation` and paste the server URL: ```text https://mcp.freeinvoiceautomation.com/mcp ``` Sign in and approve Grok opens our sign-in page in your browser. Sign in, pick the account to connect, and approve the scopes and the mode. `draft_only`, the default, is the safe choice. Use it in a chat Back in Grok, the connector lists our tools. Ask Grok to use Free Invoice Automation in a chat. The connector belongs to your Grok account, so it is available wherever you use that account. ## Use the xAI API The xAI Responses API accepts a remote MCP server in its `tools` list. Send our server URL and pass one of our API keys in the `authorization` field; Grok then calls our tools while it answers. ```bash cURL curl https://api.x.ai/v1/responses \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $XAI_API_KEY" \ -d '{ "model": "grok-4.7", "input": [ { "role": "user", "content": "List my overdue invoices." } ], "tools": [ { "type": "mcp", "server_url": "https://mcp.freeinvoiceautomation.com/mcp", "server_label": "freeinvoiceautomation", "server_description": "Invoices, estimates, clients and payments", "authorization": "Bearer '"$FIA_API_KEY"'" } ] }' ``` Use any current Grok model that supports remote MCP tools. To limit what Grok can call, add `allowed_tools` with a list of tool names, for example `["list_overdue", "get_document"]`. The xAI API runs tool calls without asking you first. Use a `draft_only` key, and leave `send_document`, `void_document`, `create_credit_note` and `record_payment` out of `allowed_tools` unless your own code checks each call. Even then, a `draft_only` key cannot send without `confirm: true`. Create the key in **Settings > API keys** and keep it in an environment variable, never in code you commit. See [Authentication](/ai/mcp/authentication) for scopes and modes on keys. ## Try it Ask Grok: ```text Using Free Invoice Automation, list my clients and show the ids. ``` ```text Create a draft invoice for client 0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60: 1 x "Logo design" at 900.00 USD. ``` The second request calls `create_invoice` with `"unit_price_minor": "90000"` and `"currency": "USD"`. The draft appears in the web app at once. Grok shows you the draft; nothing is emailed until you confirm a send. ## Troubleshooting I cannot find the custom connector option Custom connectors may not be included in every Grok plan or region. Check your Grok plan, or use the xAI API instead. The connector shows no tools Check the URL ends in `/mcp` and sign in again. If you belong to several accounts, make sure you approved the right one. A call fails with unauthenticated The token or key is invalid, expired or revoked. In the API, check the `authorization` value starts with `Bearer `. Keys and tokens of a member who was deactivated are revoked and do not come back on reactivation; create a new key or connect again. ## Related pages Tool reference Inputs, hints and permissions of every tool. Drafts and confirmations How we stop an assistant from sending or voiding by mistake. Source: https://docs.freeinvoiceautomation.com/ai/mcp/grok --- # Gemini Connect Gemini CLI to the Free Invoice Automation MCP server through settings.json, sign in with /mcp, and manage invoices from your terminal. Gemini CLI, Google's open-source AI agent for the terminal, supports remote MCP servers. Add our server to its settings file, sign in once in your browser, and Gemini can create draft invoices, look up clients and documents, and check payments for you. This page covers Gemini CLI; other Gemini-based tools that read the same settings file work the same way. ## Before you start - A Free Invoice Automation account on any plan. - Gemini CLI installed and signed in to your Google account, following Google's instructions. ## Add the server Gemini CLI reads MCP servers from `settings.json`: | File | Scope | | --- | --- | | `~/.gemini/settings.json` | Every folder you run Gemini CLI in | | `.gemini/settings.json` in a project | That project only | Add our server under `mcpServers`. Use `httpUrl`, which tells Gemini CLI the server uses streamable HTTP: ```json ~/.gemini/settings.json { "mcpServers": { "freeinvoiceautomation": { "httpUrl": "https://mcp.freeinvoiceautomation.com/mcp" } } } ``` If the file already has settings, add the `mcpServers` block (or just the `freeinvoiceautomation` entry) without removing what is there. ## Sign in Start Gemini CLI Run `gemini` in your terminal. Authenticate Run `/mcp auth freeinvoiceautomation`. Your browser opens our sign-in page. Sign in, pick the account, and approve the scopes and mode (`draft_only` is the default). Check the tools Run `/mcp` to list servers. `freeinvoiceautomation` should show as connected, with its tools listed. Run `/mcp desc` to see each tool's description. Gemini CLI stores the sign-in so you do not need to repeat it each session. If the token expires or is revoked, run `/mcp auth freeinvoiceautomation` again. ## Use an API key instead If you prefer a fixed key, for example on a server where no browser is available, create one in **Settings > API keys** and send it as a header: ```json ~/.gemini/settings.json { "mcpServers": { "freeinvoiceautomation": { "httpUrl": "https://mcp.freeinvoiceautomation.com/mcp", "headers": { "Authorization": "Bearer $FIA_API_KEY" } } } } ``` Set `FIA_API_KEY` in your shell environment. A `draft_only` key with only the scopes you need is the safe default. See [Authentication](/ai/mcp/authentication). ## Try it ```text Use freeinvoiceautomation to show invoices that are overdue, oldest first. ``` ```text Draft an invoice for Globex: 2 days of training at 650 GBP a day, due on 2027-02-15. ``` Gemini calls `list_overdue` for the first request and `list_clients` plus `create_invoice` for the second. The invoice total comes back as `"130000"` with `"currency": "GBP"`, which is 1,300.00 GBP. ## Tool confirmations Gemini CLI asks for your approval before it runs an MCP tool, unless you mark the server as trusted in `settings.json` or run in a mode that approves everything. We recommend leaving confirmations on for this server. Even with them off, our server still requires `confirm: true` for `void_document` and `create_credit_note`, and for `send_document` on a `draft_only` connection. See [Drafts and confirmations](/ai/mcp/safety). If you want Gemini to see only some tools, Gemini CLI lets you list the tools to include or exclude for a server in its settings. For example, you could give a reporting setup only the read-only tools. ## Gemini in other places This setup is for Gemini CLI. Other Google products decide for themselves whether and how they support remote MCP servers. If a Gemini product you use can add a remote MCP server by URL with OAuth, use `https://mcp.freeinvoiceautomation.com/mcp` and follow [Other MCP clients](/ai/mcp/other-clients). ## Troubleshooting /mcp shows the server as disconnected Check the JSON is valid and that you used `httpUrl` (not `url`, which Gemini CLI treats as the older SSE transport). Then run `/mcp auth freeinvoiceautomation`. The browser does not open on a remote machine Use an API key in the `headers` setting instead of the browser sign-in. A tool fails with permission_denied Your role in the account lacks the permission key that tool needs. The [tool reference](/ai/mcp/tools) lists the key for each tool. ## Related pages Tool reference Every tool and the permission it needs. Other MCP clients Generic setup and the local stdio client. Source: https://docs.freeinvoiceautomation.com/ai/mcp/gemini --- # Other MCP clients Connect any MCP client by server URL with OAuth or an API key, or run our local stdio client for clients that only start local servers. Our MCP server follows the open Model Context Protocol, so any client that supports it can connect, not only the ones with their own guide. There are three ways to connect: the remote server with a browser sign-in, the remote server with an API key, and a small local client that runs on your machine over stdio. Pick the first one your client supports. ## Choose a connection method | Your client supports | Use | Sign-in | | --- | --- | --- | | Remote servers (streamable HTTP) with OAuth | [Remote server with OAuth](#remote-server-with-oauth) | Browser sign-in, scopes and mode on the consent screen | | Remote servers with custom headers | [Remote server with an API key](#remote-server-with-an-api-key) | API key from **Settings > API keys** | | Only local servers started as a command | [Local stdio client](#local-stdio-client) | API key in an environment variable | ## Remote server with OAuth Give your client the server URL: ```text https://mcp.freeinvoiceautomation.com/mcp ``` The transport is **streamable HTTP**. When the client first connects, our server asks it to sign in. The client opens our sign-in page in your browser; you sign in, pick the account, and approve the scopes and mode. The client then receives a token bound to your user and that one account. Clients do not need to be registered with us in advance. We support OAuth client metadata documents (CIMD), where the client identifies itself with a URL that describes it, and PKCE, which protects the sign-in code. If your client asks for a client id and secret, leave them empty; if it insists on them, use an API key instead. See [Authentication](/ai/mcp/authentication). ### Example: VS Code VS Code (with GitHub Copilot's agent mode) reads MCP servers from `.vscode/mcp.json` in your workspace, or from your user settings: ```json .vscode/mcp.json { "servers": { "freeinvoiceautomation": { "type": "http", "url": "https://mcp.freeinvoiceautomation.com/mcp" } } } ``` Start the server from the file or the MCP view, and sign in when your browser opens. ### Other editors and apps Most clients use a JSON entry with a name and a URL. The field may be called `url`, `serverUrl` or `httpUrl` depending on the client; check its docs for remote or HTTP servers. The values are always the same: name `freeinvoiceautomation`, URL `https://mcp.freeinvoiceautomation.com/mcp`. ## Remote server with an API key The remote server also accepts an API key instead of OAuth. This is useful for headless setups, shared machines, or when you want a key restricted to a few scopes or to some businesses. Create a key In the web app open **Settings > API keys** and create a key. Choose only the scopes the assistant needs, and keep the mode on `draft_only`. The key is shown once; copy it to your password manager or secret store. Creating keys needs the `integrations.manage` permission. Send it as a bearer token Configure your client to send the header `Authorization: Bearer ` with every request to the server URL. ```json Generic client config { "mcpServers": { "freeinvoiceautomation": { "url": "https://mcp.freeinvoiceautomation.com/mcp", "headers": { "Authorization": "Bearer fia_your_key" } } } } ``` Store the key in an environment variable or your client's secret settings rather than in a file you share or commit. ## Local stdio client Some clients can only start local MCP servers as a command and talk to them over standard input and output. For them we publish a thin stdio client, `freeinvoiceautomation-mcp`, on npm. It has no business logic of its own: it passes each tool call to our API with your API key, so it can do exactly what the key allows and nothing more. Try it from a terminal (it needs Node.js): ```bash FIA_API_KEY=fia_your_key npx -y freeinvoiceautomation-mcp ``` Most clients take a command, arguments and environment variables: ```json Client config (stdio) { "mcpServers": { "freeinvoiceautomation": { "command": "npx", "args": ["-y", "freeinvoiceautomation-mcp"], "env": { "FIA_API_KEY": "fia_your_key" } } } } ``` The stdio client and the CLI are the only parts of Free Invoice Automation published to npm. ## Which tools you get Every connection method exposes the same [28 tools](/ai/mcp/tools), filtered by what your token or key may do. One tool, `generate_pdf`, needs no sign-in at all: it turns invoice details into a PDF and stores nothing, like the [free invoice generator](/generator). Every other tool needs a signed-in token or an API key. ## Checklist for any client - The client supports MCP **tools**: everything our server offers is a tool. - The URL ends in `/mcp`. - For OAuth, the client can open a browser window and receive the redirect back. - The client shows tool calls and asks you before running write tools. Our server adds its own safety on top: drafts first and `confirm: true` for sends and destructive tools. See [Drafts and confirmations](/ai/mcp/safety). ## Troubleshooting 401 unauthenticated The token or key is missing, wrong, expired or revoked. Check the `Authorization` header format: `Bearer` followed by a space and the key. Keys of a deactivated member stay revoked even after reactivation. 403 scope_missing The key or token does not include the scope for that tool. Create a new key with the scope, or reconnect and approve it. The client cannot connect at all Check that it supports streamable HTTP. If it supports only local servers, use the stdio client. 428 confirmation_required The tool needs `confirm: true`. The response includes a preview of what would happen. Show it to the user, then call again with `confirm: true`. ## Related pages Authentication OAuth, API keys, scopes and modes in detail. API keys Creating and revoking keys. Source: https://docs.freeinvoiceautomation.com/ai/mcp/other-clients --- # Authentication How MCP clients sign in with OAuth (CIMD and PKCE) or an API key, and how scopes, modes and your current permissions limit every call. Every MCP call except `generate_pdf` acts on one Free Invoice Automation account, so the server must know who is calling and what they may do. There are two ways to authenticate: **OAuth**, where you sign in through your browser and approve access, and an **API key**, which you create in the web app and give to the client. Both carry the same two controls: **scopes** (which actions are allowed) and a **mode** (`draft_only` or `auto_send`). ## At a glance | | OAuth | API key | | --- | --- | --- | | Best for | Claude, ChatGPT, Grok, Gemini CLI and other interactive clients | Headless setups, the local stdio client, scripts | | How you get it | Sign in on our page when the client connects | **Settings > API keys** in the web app | | Bound to | Your user and one account | One account (created by you) | | Scopes and mode | Chosen on the consent screen | Chosen when you create the key | | Sent as | A token the client manages for you | `Authorization: Bearer ` | | Stored by us | As a hash only | As a hash only; shown to you once | ## OAuth sign-in OAuth is the default for remote clients. You never give your password to the AI client; you sign in on our own page, and the client receives a token. The client connects Your client calls `https://mcp.freeinvoiceautomation.com/mcp`. Our server answers that sign-in is needed and tells the client where to authorise. The client identifies itself We support **Client ID Metadata Documents (CIMD)**: the client's id is a web address that publishes its name and allowed redirect addresses. This means MCP clients work without being registered with us in advance, and we know which client is asking. You sign in and consent Your browser opens our sign-in page. Sign in with any of your usual methods (email and password, Google, Apple, magic link or passkey, plus two-factor if you turned it on). Then the consent screen shows: - the **name of the client** asking for access, so you can spot a fake; - the **account** to connect, if you belong to more than one; - the **scopes** it asks for, which you can approve; - the **mode**: `draft_only` (default) or `auto_send`. The client gets a token Our server sends the client back to its registered redirect address with a one-time code. The client exchanges the code for a token using **PKCE**, a check that proves the same client that started the sign-in is the one finishing it, so an intercepted code is useless. The token is bound to **your user** and **one account**. Tokens expire and are refreshed by the client automatically; we store only hashes of the access and refresh tokens. To work with a second account, connect the client again and pick that account. ## API keys The MCP server also accepts an API key with the server URL. Use one for the [local stdio client](/ai/mcp/other-clients#local-stdio-client), for clients that cannot open a browser, or when you want a long-lived credential with narrow scopes. API keys"> You need the `integrations.manage` permission (Owner and Admin by default). Choose name, scopes and mode Give the key a name that says where it is used, such as `Gemini CLI on laptop`. Tick only the scopes the assistant needs and keep `draft_only` unless you need automatic sending. A key can never hold a scope you do not have yourself. Copy the key The key is shown once. We store only a hash, so we cannot show it again. If you lose it, revoke it and create a new one. Clients send it as a bearer token: ```http Authorization: Bearer fia_your_key ``` The same key works with the REST API at `https://api.freeinvoiceautomation.com/v1`. You can also create keys through the API with [Create an API key](/api-reference/developers/create-api-key) and revoke them with [Revoke an API key](/api-reference/developers/revoke-api-key). Creating a key with `auto_send` needs a fresh sign-in, so it cannot be done with another API key. ## Scopes Scopes are [permission keys](/api-reference/permissions) in the form `module.action`, the same keys your role is built from. Each MCP tool needs one, for example: | Tool | Scope needed | | --- | --- | | `list_documents`, `get_document`, `list_overdue`, `preview_send`, `render_pdf` | `documents.view` | | `create_invoice`, `create_estimate`, `invoice_unbilled` | `documents.create` | | `update_invoice`, `set_payment_schedule` | `documents.edit` | | `send_document`, `send_reminder` | `documents.send` | | `void_document` | `documents.void` | | `create_credit_note` | `documents.credit` | | `list_clients` | `clients.view` | | `upsert_client` | `clients.create` (new) or `clients.edit` (existing) | | `record_payment` | `payments.record` | | `get_payment_status` | `payments.view` | The [tool reference](/ai/mcp/tools) lists the scope for all 28 tools. ### Your current permissions always apply A token or key is limited at **call time** to the overlap of its scopes and your role's **current** permissions: - If your role loses a permission, your tokens and keys lose it on the next request. - If you are deactivated in the account, your MCP tokens and API keys for it are **revoked** at once. Reactivation does not bring them back; you connect again or create new keys. - If you are removed from the account, your keys and tokens stop working. - When a Team account goes back to Free, members other than the Owner are deactivated, so their tokens and keys are revoked too. - On Team, a member's business scope also applies: a token cannot reach a business its creator cannot reach (`business_out_of_scope`). ## Modes: draft_only and auto_send | Mode | Sends (`send_document`, `send_reminder`) | Destructive tools | | --- | --- | --- | | `draft_only` (default) | Need `confirm: true` on the call; without it you get a preview and `confirmation_required` | Always need `confirm: true` | | `auto_send` | Run without `confirm: true` | Always need `confirm: true` | `auto_send` does not remove the confirmation for destructive tools such as `void_document` and `create_credit_note`. Read [Drafts and confirmations](/ai/mcp/safety) for how this works in practice. ## Revoking access - **OAuth**: remove the connector in your AI client. Deactivating or removing a member revokes their MCP tokens for the account. - **API keys**: revoke the key in **Settings > API keys**. It stops working on the next request. - If you think a key or token leaked, revoke it straight away and check the [audit log](/team/audit-log) (Team) for calls made with it. ## Errors | Code | HTTP | Meaning | | --- | --- | --- | | `unauthenticated` | 401 | No token or key, or it is invalid, expired or revoked | | `scope_missing` | 403 | The token or key does not have the scope this tool needs | | `permission_denied` | 403 | Your role does not have the permission key | | `business_out_of_scope` | 403 | You cannot reach that business | | `member_deactivated` | 403 | You are deactivated in this account | | `fresh_signin_required` | 401 | The action needs a recent sign-in in the web app | | `rate_limited` | 429 | Too many calls; wait for `Retry-After` | All errors use the standard [error format](/api-reference/errors) with a request id. ## Related pages API authentication API keys for the REST API. Roles and permissions What each role can do by default. Source: https://docs.freeinvoiceautomation.com/ai/mcp/authentication --- # Tool reference All 28 MCP tools with what each does, its read-only, write or destructive hint, the permission it needs, when it ships and its API endpoint. The MCP server exposes 28 tools. Each tool is the same capability as one REST endpoint: same inputs, same outputs, same permission key and the same audit entry. This page lists every tool with what it does, its hint, the permission key (scope) it needs, when it ships, and a link to the endpoint page where you will find every field. ## How to read this page - **Hint** comes from the tool's definition and is sent to your client as an annotation: - **Read-only** (`readOnlyHint`): only reads data. - **Write**: creates or changes data. New documents are drafts. - **Destructive** (`destructiveHint`): voids, credits or moves money. Always needs `confirm: true`. - **Permission** is the [permission key](/api-reference/permissions) your token or key needs as a scope, and that your role must still have at call time. - **Release**: Launch is 1 January 2027. Later tools ship with their feature. - Money is always a string of integer minor units with a `currency`, for example `"unit_price_minor": "120000"` with `"currency": "USD"` for 1,200.00 USD. Ids are uuid strings. Dates are `YYYY-MM-DD`. - When you can reach only one business, `business_id` can be left out; otherwise pass it. ## All tools | Tool | Hint | Permission | Release | | --- | --- | --- | --- | | `create_invoice` | Write (draft) | `documents.create` | Launch | | `update_invoice` | Write | `documents.edit` | Launch | | `create_estimate` | Write (draft) | `documents.create` | Launch | | `list_documents` | Read-only | `documents.view` | Launch | | `get_document` | Read-only | `documents.view` | Launch | | `list_overdue` | Read-only | `documents.view` | Launch | | `preview_send` | Read-only | `documents.view` | Launch | | `send_document` | Write (confirm unless `auto_send`) | `documents.send` | Launch | | `render_pdf` | Read-only | `documents.view` | Launch | | `void_document` | Destructive | `documents.void` | Launch | | `create_credit_note` | Destructive | `documents.credit` | Launch | | `list_clients` | Read-only | `clients.view` | Launch | | `upsert_client` | Write | `clients.create` / `clients.edit` | Launch | | `list_items` | Read-only | `items.view` | Launch | | `upsert_item` | Write | `items.manage` | Launch | | `record_payment` | Write (audited) | `payments.record` | Launch | | `get_payment_status` | Read-only | `payments.view` | Launch | | `generate_pdf` | Read-only, no sign-in | none | Launch | | `send_reminder` | Write (confirm unless `auto_send`) | `documents.send` | Week of 11 Jan 2027 | | `set_payment_schedule` | Write | `documents.edit` | Week of 25 Jan 2027 | | `get_aged_receivables` | Read-only | `reports.view` | Week of 25 Jan 2027 | | `get_report` | Read-only | `reports.view` | Week of 25 Jan 2027 | | `create_contract` | Write (draft) | `contracts.manage` | March 2027 | | `log_time` | Write | `time.log` | April 2027 | | `list_unbilled_time` | Read-only | `time.view` | April 2027 | | `invoice_unbilled` | Write (draft) | `documents.create` | April 2027 | | `record_expense` | Write | `expenses.manage` | April 2027 | | `get_ledger_report` | Read-only | `ledger.view` | June to July 2027 | The first 14 tools in the original launch list (including `generate_pdf`) were joined by 14 more in the current plan. Four of those (`void_document`, `create_credit_note`, `list_items`, `upsert_item`) ship at launch with their features, so 18 tools are live on 1 January 2027. --- ## Documents ### create_invoice Creates a **draft** invoice for a client. The draft has no number yet; it gets the next number from the business's gap-free sequence when it is first sent. Totals, taxes and discounts are calculated by the server, so the assistant sends lines, not totals. Main inputs: `client_id`, `currency`, `lines` (each with `description`, `quantity` as a string, `unit_price_minor`, and optionally `item_id`, `unit`, `tax_rate_id`, a line discount), plus optional `business_id`, `issue_date`, `due_date`, `terms` and `notes`. Returns the document with its computed `subtotal_minor`, `tax_minor`, `total_minor` and `balance_minor`. - **Hint:** Write (draft) · **Permission:** `documents.create` · **Release:** Launch - **Endpoint:** [Create an invoice](/api-reference/documents/create-invoice) ```json Example input { "client_id": "0193f2a1-0c11-7d2e-9a40-1b2c3d4e5f60", "currency": "USD", "due_date": "2027-02-15", "lines": [ { "description": "Website design", "quantity": "10", "unit": "hour", "unit_price_minor": "5000" } ] } ``` ### update_invoice Changes an invoice. A draft is edited in place. A document that was already sent is never edited in place: the change becomes a new **revision**, with a new snapshot, so what your client received stays on record. Send only the fields you want to change. - **Hint:** Write · **Permission:** `documents.edit` · **Release:** Launch - **Endpoint:** [Update an invoice](/api-reference/documents/update-invoice) - Errors to expect: `document_voided` on a void invoice. ### create_estimate Creates a **draft** estimate (a quote). Same lines model as invoices, with an optional expiry date. Your client can accept it or request changes on its public page, and an accepted estimate can be converted into an invoice in the app or API. - **Hint:** Write (draft) · **Permission:** `documents.create` · **Release:** Launch - **Endpoint:** [Create an estimate](/api-reference/documents/create-estimate) ### list_documents Lists invoices, estimates, credit notes and receipts, newest first, with cursor pagination (`limit` up to 200, `cursor`). Filters: `type`, `status` (several values comma-separated, for example `sent,overdue`), `client_id`, `business_id`, `currency`, `due_before`, `due_after`, `issued_after`, `issued_before`, `updated_after`, and text search `q` on number, name and email. Sort with `sort`, for example `-total_minor`. - **Hint:** Read-only · **Permission:** `documents.view` · **Release:** Launch - **Endpoint:** [List documents](/api-reference/documents/list-documents) ### get_document Returns one document with its lines, computed totals, status, number, links to related documents (credit notes, the estimate it came from), its payment schedule when it has one, and its pay page address when a share link exists. - **Hint:** Read-only · **Permission:** `documents.view` · **Release:** Launch - **Endpoint:** [Get a document](/api-reference/documents/get-document) ### list_overdue Lists invoices past their due date with a balance left to pay. A quick way to answer "who owes me money?". Results use the same cursor pagination as every list. - **Hint:** Read-only · **Permission:** `documents.view` · **Release:** Launch - **Endpoint:** [List overdue invoices](/api-reference/documents/list-overdue) ### preview_send Shows exactly what `send_document` would do, without doing it: the recipient, the subject and body of the email, and the document. Assistants should call this before every send and show you the result. - **Hint:** Read-only · **Permission:** `documents.view` · **Release:** Launch - **Endpoint:** [Preview a send](/api-reference/documents/preview-send) ### send_document Sends a document to the client by email with its share link and pay page. On the first send the document gets its number and a snapshot is saved. Sends are idempotent per document, event and channel, so a retried call never emails twice. On a `draft_only` token or key the call must include `"confirm": true`. Without it you get `confirmation_required` (428) with the preview. On an `auto_send` token it runs straight away. Platform emails count toward your monthly quota (Free 20, Team 100); when it runs out you get `quota_exceeded` and can [connect your own SMTP](/messaging/smtp). - **Hint:** Write (confirm unless `auto_send`) · **Permission:** `documents.send` · **Release:** Launch - **Endpoint:** [Send a document](/api-reference/documents/send-document) ```json Example input { "id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "confirm": true } ``` ### render_pdf Returns the PDF of a document using its business's template, with the verification QR code that leads to its public verification page. - **Hint:** Read-only · **Permission:** `documents.view` · **Release:** Launch - **Endpoint:** [Render a PDF](/api-reference/documents/render-pdf) --- ## Corrections ### void_document Voids a sent invoice. A reason is required. The invoice keeps its number, shows a VOID stamp on its PDF, pay page and portal, stops reminders and late fees, and its open payment links stop working. It is never deleted. An invoice with recorded payments cannot be voided (`document_has_payments`): issue a credit note instead. Always needs `"confirm": true`, whatever the token's mode; without it you get `confirmation_required` with a preview. - **Hint:** Destructive · **Permission:** `documents.void` · **Release:** Launch - **Endpoint:** [Void a document](/api-reference/documents/void-document) ```json Example input { "id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f", "reason": "Issued to the wrong client", "confirm": true } ``` ### create_credit_note Issues a credit note against an invoice, with its own gap-free number (for example `CN-2027-0001`) and a link to the invoice. It can be **full** or **partial** (pick lines, or enter an amount) and reduces the invoice's balance. If the invoice was already paid, choose `settlement`: `"refund"` (through the payment rail when it supports refunds, such as Stripe, otherwise recorded as refunded manually) or `"client_credit"` (kept as credit for the client's next invoice). Always needs `"confirm": true`. A credit larger than what can be credited fails with `credit_exceeds_balance`. - **Hint:** Destructive · **Permission:** `documents.credit` · **Release:** Launch - **Endpoint:** [Create a credit note](/api-reference/documents/create-credit-note) --- ## Clients and items ### list_clients Lists clients with search (`q` on name and email) and cursor pagination. Use it to find a `client_id` before creating a document. - **Hint:** Read-only · **Permission:** `clients.view` · **Release:** Launch - **Endpoint:** [List clients](/api-reference/clients/list-clients) ### upsert_client Creates a client, or updates one when you pass an existing `id`. Fields include `name` (required for a new client), `company_name`, `email`, `phone`, `address`, `country` (two-letter code), `tax_id`, `default_currency`, `language` and `notes`. Creating needs `clients.create`; updating needs `clients.edit`. - **Hint:** Write · **Permission:** `clients.create` / `clients.edit` · **Release:** Launch - **Endpoint:** [Create or update a client](/api-reference/clients/upsert-client) ### list_items Lists your saved products and services (name, description, SKU, unit, price, currency, default tax rate), so the assistant can fill invoice lines from them. - **Hint:** Read-only · **Permission:** `items.view` · **Release:** Launch - **Endpoint:** [List items](/api-reference/items/list-items) ### upsert_item Creates a saved item, or updates one when you pass its `id`. Fields: `name`, `description`, `sku`, `unit`, `unit_price_minor`, `currency`, `default_tax_rate_id`. Documents keep a copy of line values, so changing an item never changes old documents. - **Hint:** Write · **Permission:** `items.manage` · **Release:** Launch - **Endpoint:** [Create or update an item](/api-reference/items/upsert-item) --- ## Payments ### record_payment Records a payment you received outside an automatic rail, for example a bank transfer, against a document: `document_id`, `amount_minor`, `currency`, `received_at`, and optionally `reference` and `notes`. Partial payments are allowed; the document moves to partially paid or paid. An invoice is only marked paid by a verified payment event or a person's confirmation. When your assistant calls this tool, it records your confirmation as an audited manual payment made through your token, so only use it for money you have actually received. - **Hint:** Write (audited human confirmation) · **Permission:** `payments.record` · **Release:** Launch - **Endpoint:** [Record a payment](/api-reference/payments/record-payment) ### get_payment_status Returns the payment state of a document: its status, amount paid, amount credited, balance, and the payments recorded against it. - **Hint:** Read-only · **Permission:** `payments.view` · **Release:** Launch - **Endpoint:** [Get payment status](/api-reference/payments/get-payment-status) --- ## No sign-in ### generate_pdf Turns invoice details into a PDF without an account, like the [free invoice generator](/generator). Input: the same document fields as `create_invoice`, plus your business and client details and a PDF template key. Returns the PDF as base64. **Nothing is stored.** The PDF carries the "Powered by Free Invoice Automation" footer and has no verification QR, because there is no stored document to verify. Limited per IP address; it fetches nothing from the internet (no remote images). - **Hint:** Read-only, no sign-in · **Permission:** none · **Release:** Launch - **Endpoint:** [Generate a PDF](/api-reference/public/generate-pdf) --- ## Reminders and schedules ### send_reminder Sends a payment reminder for an unpaid invoice now, outside its automatic reminder sequence. Like `send_document`, it needs `"confirm": true` unless the token is `auto_send`, and it respects your email quota and the client's reminder opt-out. - **Hint:** Write (confirm unless `auto_send`) · **Permission:** `documents.send` · **Release:** Week of 11 January 2027 - **Endpoint:** [Send a reminder](/api-reference/collections/send-reminder) ### set_payment_schedule Adds or replaces a deposit and instalments on an invoice or estimate. A deposit is a percent or a fixed amount, due on acceptance or on a date; instalments have their own dates and amounts. The parts must add up exactly to the total (the last part absorbs rounding), or the call fails with `schedule_total_mismatch`. - **Hint:** Write · **Permission:** `documents.edit` · **Release:** Week of 25 January 2027 - **Endpoint:** [Set a payment schedule](/api-reference/reports/set-payment-schedule) --- ## Reports ### get_aged_receivables Returns who owes you and for how long, grouped Current, 1 to 30, 31 to 60, 61 to 90 and over 90 days, per client with totals. Filters: business, client, date range and currency. Amounts in several currencies are shown per currency, plus a total in the business currency at each document's frozen exchange rate. - **Hint:** Read-only · **Permission:** `reports.view` · **Release:** Week of 25 January 2027 - **Endpoint:** [Get aged receivables](/api-reference/reports/get-aged-receivables) ### get_report Returns any basic report by `name`: `aged-receivables`, `outstanding-by-client`, `client-statement`, `revenue`, `tax-collected` or `payments-received`. Takes `from`, `to`, `client_id`, `currency` and `business_id`. - **Hint:** Read-only · **Permission:** `reports.view` · **Release:** Week of 25 January 2027 - **Endpoint:** [Get a report](/api-reference/reports/get-report) ### get_ledger_report Returns a report from the double-entry ledger: `trial-balance`, `profit-and-loss`, `balance-sheet` or `general-ledger`, on a `cash` or `accrual` `basis`, for a `from` and `to` period. - **Hint:** Read-only · **Permission:** `ledger.view` · **Release:** June to July 2027 - **Endpoint:** [Get a ledger report](/api-reference/ledger/get-ledger-report) --- ## Contracts ### create_contract Creates a **draft** contract from a template, filled with variables from the business and client, optionally attached to an estimate so the client can accept and sign in one step. Send it with `send_document`. Once signed, a contract never changes; changes need a new version. - **Hint:** Write (draft) · **Permission:** `contracts.manage` · **Release:** March 2027 - **Endpoint:** [Create a contract](/api-reference/cards-contracts/create-contract) --- ## Time and expenses ### log_time Logs a time entry for a client: `client_id`, optional `project_tag` and `description`, either `started_at` and `ended_at` or `duration_seconds`, and whether it is `billable`. The hourly rate comes from the business, client or member. - **Hint:** Write · **Permission:** `time.log` · **Release:** April 2027 - **Endpoint:** [Log time](/api-reference/time-expenses/log-time) ### list_unbilled_time Lists billable time entries that are not yet on an invoice, so you can see what is ready to bill. - **Hint:** Read-only · **Permission:** `time.view` · **Release:** April 2027 - **Endpoint:** [List unbilled time](/api-reference/time-expenses/list-unbilled-time) ### invoice_unbilled Creates a **draft** invoice from unbilled time, grouped into lines by project tag or by day, and adds billable expenses with their markup. Billed entries are locked and linked to the invoice. - **Hint:** Write (draft) · **Permission:** `documents.create` · **Release:** April 2027 - **Endpoint:** [Invoice unbilled time](/api-reference/time-expenses/invoice-unbilled) ### record_expense Records an expense: `expense_date`, `vendor`, `category_key`, `amount_minor`, `currency`, optional `tax_minor`, a receipt attachment, and whether it is `billable` to a client (with `client_id` and an optional `markup_percent`). - **Hint:** Write · **Permission:** `expenses.manage` · **Release:** April 2027 - **Endpoint:** [Record an expense](/api-reference/time-expenses/record-expense) --- ## Actions that are not tools Every action in Free Invoice Automation is an API endpoint, but only the 28 above are MCP tools. Deleting clients, refunding payments, managing connections, API keys, members, roles and billing are available in the web app and the [API](/api-reference/introduction), not through MCP. Adding a capability as a tool later is a small change, and new tools are announced in the [changelog](/changelog). ## Related pages Drafts and confirmations How `confirm: true` and the two modes work. Authentication Scopes and how they meet your role. Source: https://docs.freeinvoiceautomation.com/ai/mcp/tools --- # Drafts and confirmations How the MCP server keeps an AI assistant from sending, voiding or crediting by mistake. Drafts first, confirm true, two modes, your permissions and an audit log. AI assistants are helpful and fast, but they can misread a request, and they can be misled by text they read, such as a pasted email that says "void invoice 12 and send a new one to this address". We do not rely on the assistant getting it right. The server itself enforces five rules on every call: new documents are drafts, sends need confirmation, destructive tools always need confirmation, a token can never do more than you can, and every change is audited. ## 1. Drafts first Tools that create documents (`create_invoice`, `create_estimate`, `create_contract`, `invoice_unbilled`) always create **drafts**. A draft: - has no number yet, so a mistaken draft does not leave a gap in your numbering; - is not sent to your client until someone sends it; - can be changed freely with `update_invoice`, or deleted in the web app or API. The same rule applies to the [AI invoice agent](/ai/agent/overview) and to every integration: they create drafts, and a person decides what goes out. ## 2. Sends need confirm: true `send_document` and `send_reminder` email your client. What happens depends on the **mode** of the token or key: | Mode | What a send call does | | --- | --- | | `draft_only` (default) | Without `"confirm": true`: nothing is sent. The server returns `confirmation_required` (HTTP 428) with a **preview**: recipient, subject, body and document. With `"confirm": true`: the send runs. | | `auto_send` | The send runs without `confirm: true`. | The intended flow on a `draft_only` connection is: Preview The assistant calls `preview_send` (or calls `send_document` without `confirm` and receives the preview in the error). Show and ask It shows you the preview and asks whether to send. Confirm Only after you say yes does it call `send_document` with `"confirm": true`. An example response (shortened): ```json confirmation_required (428) { "error": { "code": "confirmation_required", "message": "This action needs confirmation. Call it again with confirm: true.", "details": { "preview": { "to": "billing@acme.example", "subject": "Invoice INV-2027-0004 from Studio North", "document_id": "0193f2b4-5a6b-7c8d-9e0f-1a2b3c4d5e6f" } }, "request_id": "req_0193f2c0-1d2e-7f3a-8b4c-5d6e7f8a9b0c" } } ``` The exact preview fields are listed on each endpoint page, for example [Send a document](/api-reference/documents/send-document). Sends are also idempotent: a send for the same document, event and channel is never emailed twice, even if the assistant retries. `confirm: true` is a promise from the client that a person approved the action. A well-behaved assistant asks you first. Only connect clients you trust, read the consent screen's client name, and keep your client's own tool-approval prompts on for sending tools. ## 3. Destructive tools always need confirmation Some tools cannot be undone in a simple way, or move money. They are marked **destructive** (`destructiveHint` on the tool) and need `"confirm": true` on **every** call, in **both** modes: | Tool | Why it is destructive | | --- | --- | | `void_document` | The invoice is voided for good: it keeps its number, shows a VOID stamp and its pay links stop working | | `create_credit_note` | Reduces what the client owes and, on a paid invoice, may refund money through Stripe | The same rule covers auto-charge settings when saved cards arrive in March 2027; those are API and app actions, not MCP tools. Without `confirm: true`, a destructive tool returns its preview and `confirmation_required`, so you can see exactly what would change. ## 4. Never more than you can do A token or API key is limited in three ways at once: - **Scopes**: only the permission keys you approved on the consent screen or ticked when creating the key. - **Your current role**: at every call, the token is limited to the overlap of its scopes and your role's permissions **right now**. If your role loses `documents.void`, your token loses it on the next request too. - **Your membership**: if you are deactivated or removed, your tokens and keys for that account are revoked. When a Team account falls back to Free, every member except the Owner is deactivated, and their tokens are revoked. A key can never be created with a scope its creator does not hold, and a role can never be edited to grant a permission the editor does not hold. On Team, business scopes apply too: a token cannot reach a business its creator cannot reach. Some actions are not MCP tools at all, so no assistant can do them over MCP: deleting clients, refunding payments, connecting or removing payment rails, managing members and roles, API keys and billing. ## 5. Everything is audited Every write through MCP, like every write anywhere in Free Invoice Automation, records an audit entry in the same transaction as the change. The entry includes: - the **actor** and its type: for MCP calls `mcp_token`, for API keys `api_key`, for the agent `agent`; - the account, business, action and the id of what changed; - time, IP address, user agent and request id; - a before and after summary. On Team, Owners and Admins can read the [audit log](/team/audit-log) in the web app or through the API. Documents also record their `source`, so you can see which ones were created through MCP. ## Choosing a mode When to keep draft_only Almost always. It costs one extra "yes" per send and protects you from a misunderstood request or a prompt hidden in text the assistant read. When auto_send makes sense For a narrow, well-tested workflow, ideally with a dedicated API key that has only the scopes it needs (for example `documents.view` and `documents.send`). Creating an `auto_send` API key needs a fresh sign-in in the web app. Even with `auto_send`, voids and credit notes still need `confirm: true`. What about the AI invoice agent? The agent in the web app also creates drafts only, and you confirm sends. An automation can be set to send automatically, which needs a fresh sign-in step and is audited. See [Automations](/ai/agent/automations). ## Errors you may see | Code | HTTP | Meaning | | --- | --- | --- | | `confirmation_required` | 428 | Call again with `confirm: true` after a person approves; the response includes the preview | | `scope_missing` | 403 | The token does not have the scope | | `permission_denied` | 403 | Your role no longer has the permission | | `document_has_payments` | 409 | An invoice with payments cannot be voided; issue a credit note | | `credit_exceeds_balance` | 422 | The credit note is larger than what can be credited | | `document_voided` | 409 | The action is not allowed on a void document | ## Related pages Drafts and confirmations in the API The same rules for REST calls. Void and delete What voiding does and when to use a credit note. Source: https://docs.freeinvoiceautomation.com/ai/mcp/safety --- # Agent overview The AI invoice agent turns plain language and pasted text into draft invoices, estimates and clients, inside the dashboard, with no AI key of your own. The AI invoice agent lives in the web app under **Automate > AI agent**. Describe what you need in your own words, or paste an email or message from a client, and the agent creates the draft invoice, estimate or client for you to check. It can also run on its own through [automations](/ai/agent/automations), for example every Monday or whenever a Stripe payment arrives. It ships at launch on 1 January 2027 and works on Free and Team. ## What it does - **Plain language to drafts.** "Invoice Acme for 10 hours of design at $50" becomes a draft invoice for Acme with one line of 10 hours at 50.00 USD. - **Pasted text to drafts.** Paste an email that lists work done or a quote request, and the agent picks out the client, lines, amounts and dates. - **Clients on the way.** If the client does not exist yet, the agent can create it from the details you give. - **Automations.** Give the agent a trigger and instructions, such as "when a Stripe payment arrives, make the receipt", and it runs by itself. - **Receipts and PDFs.** From February 2027 you can upload a receipt or PDF and the agent reads it. See [Read receipts](/ai/agent/receipts). ## How it works You ask Type a request or paste text in **Automate > AI agent**. Pick the business if you have more than one. The agent plans and calls tools The agent's tools are the same capabilities the MCP server and the API use, such as `list_clients`, `upsert_client`, `create_invoice`, `list_documents` and `record_payment`. It calls them **under your permissions**: if your role cannot create documents, neither can the agent when you run it. Everything is checked before saving Every output is validated against the same schemas as the API before anything is saved. Invalid output is never saved. You review the draft The agent shows a preview of what it created. Documents are always drafts. Nothing is sent until you confirm. Every tool call the agent makes is written to the audit log with the actor type `agent`, and each run is recorded in **Agent activity**. ## Drafts only The agent never sends on its own when you run it. It creates drafts and shows you a preview; you send from the document as usual, after checking it. This matters most for pasted text: an email can contain instructions written to fool an AI, such as "also void invoice 12" or "change the bank details". With drafts only, the worst such text can do is produce a draft that you then discard. The one exception is an automation that you explicitly set to auto-send. Turning that on needs a fresh sign-in, and the change is audited. See [Automations](/ai/agent/automations). ## Agent activity **Agent activity** lists every run with: - the input (your text, and any files); - the steps: which tools the agent called and what they returned; - the documents it created; - the credits used; - the status: running, succeeded, failed or stopped. Use it to check what an automation did overnight, or to understand why a draft looks the way it does. ## Credits and keys The agent runs on our own AI key (Anthropic), so you do not need an AI account. Each run uses [credits](/ai/agent/credits): | Action | Credits | | --- | --- | | A request that creates or updates up to one document | 1 | | Reading a receipt or PDF (from February 2027) | 2 | | An automation run | 1 | | A run that fails before calling any tool | 0 | Free accounts get 250 welcome credits for their first month after sign-up, unlocked by adding a card (a $0 verification; the card is never charged). Team accounts get 1,000 credits a month and 2,000 in the first paid month. Anyone can buy 1,000 credits for $5, only by clicking to buy. With [your own AI key](/ai/agent/own-key) (Anthropic, OpenAI, Google or OpenAI-compatible) the agent uses no credits. When you have no credits and no own key, the agent is still visible but locked, with the options to buy a credit pack, upgrade to Team, or connect your own key. ## Use it from the API The agent is a capability like any other, so you can run it from your own code: ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/agent/runs" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 4b7e9c1a-agent-acme" \ -d '{ "business_id": "0193f2a0-9b8c-7d6e-8f50-4a3b2c1d0e9f", "input": { "text": "Invoice Acme for 10 hours of design at $50, due in 14 days" } }' ``` A run is asynchronous: the call returns `202` with the run's id, and you read the result with [Get an agent run](/api-reference/agent/get-agent-run) or list runs with [List agent runs](/api-reference/agent/list-agent-runs). If you use [webhooks](/integrations/webhooks), the events `agent_run.completed` and `agent_run.failed` tell you when a run ends. See [Run the agent](/api-reference/agent/run-agent) for every field. You do not need the agent to use AI with your account from Claude or ChatGPT. The [MCP server](/ai/mcp/overview) lets your own assistant call the same tools, and uses no credits. ## Permissions | Permission | Lets a member | Default roles | | --- | --- | --- | | `ai.use` | Run the agent and see agent runs | Owner, Admin, Editor | | `ai.manage` | Create, change and delete automations, and connect an own AI key | Owner, Admin | On top of these, each tool the agent calls checks its own permission, for example `documents.create` for `create_invoice`. ## Limits - **Credits**: the agent stops at 0 and offers a credit pack, Team or your own key. You see a warning when 80% is used. - **Rate and size guards**: each account has a per-minute limit on agent requests and a monthly ceiling on AI usage. When either is reached, the agent stops for that account even if credits remain. These guards apply to runs on our key. - **Errors**: `ai_credits_exhausted` when no credits are left, `feature_locked` when the agent is locked on Free, `rate_limited` when you send too many requests too quickly. ## Your data When the agent runs on our key, your request and the tool results it needs are sent to our AI provider, Anthropic, in the United States, only for that run. We tell you this before you first use the agent. With your own key, the data goes to your chosen provider instead. The input text and uploaded files of a run are kept for 90 days; the run summary (credits used, documents created) is kept for the life of your account. See [How we protect data](/security/overview). ## Related pages Writing requests How to ask so the first draft is right. Automations Run the agent on a schedule or an event. Credits and costs Allowances, packs, expiry and order of use. Use your own AI key Unlimited agent use with no credits. Source: https://docs.freeinvoiceautomation.com/ai/agent/overview --- # Writing requests How to ask the AI invoice agent so the first draft is right, with examples for invoices, estimates, clients, pasted emails and follow-ups. The agent understands ordinary language, so you do not need special commands. But like a new assistant, it does better when you give it the facts it cannot guess: who the client is, what you did, how much it costs, in which currency, and when it is due. This page shows what to include, gives examples you can copy, and explains what the agent does with requests it cannot complete. ## What a good request includes | Detail | Why it matters | Example | | --- | --- | --- | | **Client** | The agent looks the client up by name. If two clients have similar names, or the client is new, say more | "Acme Ltd (billing@acme.example)" | | **What and how much** | Each piece of work becomes a line with a quantity and unit price | "10 hours of design at 50 USD an hour" | | **Currency** | Otherwise the client's or business's default is used | "in EUR" | | **Dates** | Issue date and due date, or payment terms | "due in 14 days", "due 2027-02-15" | | **Document type** | Invoice is the default; say so for an estimate | "an estimate, valid for 30 days" | | **Business** | Only if you have several businesses | "for Studio North" | | **Extras** | Tax rate, discount, notes, terms | "add 20% VAT", "note: thank you for your business" | You do not need to calculate totals. The server calculates subtotals, tax, discounts and the total from the lines, exactly as in the editor. ## Examples ### A simple invoice ```text Invoice Acme for 10 hours of design at $50, due in 14 days. ``` One draft invoice for Acme: one line, quantity 10, unit price 50.00 USD, total 500.00 USD, due 14 days from today. Costs 1 credit. ### Several lines ```text Invoice Northwind Traders in EUR: - Website redesign, fixed price 2,400 - Hosting setup, 1 x 150 - Two extra revision rounds at 90 each Due at the end of the month. Add a note that bank transfer details are on the pay page. ``` ### An estimate ```text Make an estimate for Globex: brand workshop, 2 days at 650 GBP a day, plus travel 120 GBP. Valid for 30 days. ``` ### A new client and an invoice ```text Add a new client: Lena Fischer, Fischer Design GmbH, lena@fischer.example, Germany, VAT ID DE123456789. Then invoice her 3 days of consulting at 400 EUR. ``` The agent calls `upsert_client` to create the client, then `create_invoice`. Each request that creates or updates up to one document costs 1 credit. ### From a saved item ```text Invoice Acme for 3 x our "Monthly maintenance" item. ``` The agent looks up your [saved items](/invoicing/saved-items) and fills the line from the item, keeping a copy of its name, price and tax rate. ### From a pasted email Paste the client's message and add one line of instruction: ```text Create an invoice from this email. Use our standard 14-day terms. --- Hi, thanks for the work this week. As agreed: logo refresh (fixed 800), business cards layout (2 hours at 60), and the print-ready files. Please bill Acme Ltd, accounts@acme.example. --- ``` The agent picks out the client and lines and creates the draft. If the pasted text contains instructions that try to make it send, void or change details, they get nowhere: a run only produces drafts, and every output is validated before it is saved, so the result is always a draft for you to check. ### Questions about your data ```text Which of my invoices to Acme are still unpaid? ``` The agent can read your documents and clients with tools such as `list_documents`. Requests that only read do not create documents. ## Follow-ups The agent works best one task at a time. After it creates a draft, ask for changes in a new request that names the draft: ```text On the Acme draft from today, change the rate to 55 and add a 10% discount. ``` Changes to a draft are made with `update_invoice`. If the invoice was already sent, the change becomes a new revision of the invoice, never an edit in place. ## When the agent cannot finish - **Missing information**: if the request does not say which client or what price, the draft may be incomplete or the run may fail. Open **Agent activity** to see which steps ran, then try again with the missing detail. - **No permission**: if your role cannot do something (for example create clients), the tool call is refused and the run reports it. - **Fails before any tool call**: if the run fails before calling a tool, it costs 0 credits. - **Out of credits**: the agent stops and offers a credit pack, Team or your own key. See [Credits and costs](/ai/agent/credits). ## Tips Write amounts the way you would say them "1,200 USD", "$50 an hour" and "650 GBP a day" are all fine. The agent stores amounts in minor units for you; you never need to type cents. Be explicit about currency when it differs If the client usually pays in one currency and this job is in another, say so. Each document has one currency. Check the preview, then send yourself The agent never sends on its own when you run it. Open the draft, check it, then send. That is the safest habit, and it is how the agent is designed. Use automations for repeated work If you type the same request every week, turn it into an [automation](/ai/agent/automations). ## Related pages Agent overview How the agent works and what it can do. Line items Quantities, units, prices and discounts on lines. Source: https://docs.freeinvoiceautomation.com/ai/agent/prompts --- # Automations Run the AI invoice agent on a schedule or when something happens, such as every Monday or when a Stripe payment arrives, with drafts or auto-send. An automation is the AI invoice agent working for you without being asked each time. You give it a **trigger** (a schedule or an event) and **instructions** in plain language, and the agent runs whenever the trigger fires. Use automations for invoicing you repeat: weekly time billing, a receipt after each card payment, or a monthly retainer invoice built from your notes. ## How an automation works Each automation has: | Part | What it is | | --- | --- | | **Name** | A label so you can find it, such as "Acme weekly invoice" | | **Business** | The business it works in | | **Trigger** | A **schedule** (for example "every Monday") or an **event** (for example "when a Stripe payment arrives") | | **Instructions** | What the agent should do, in plain language, like a request you would type yourself | | **Enabled** | On or off. Turn it off to pause without losing the setup | | **Auto-send** | Off by default: documents stay drafts. On: the automation may send what it creates. Turning it on needs a fresh sign-in | When the trigger fires, the agent runs with your instructions and the event's details, calls the same tools it uses when you ask it directly, and records the run in **Agent activity**. Each automation shows when it last ran, the result of that run, and when it will run next. ## Examples Receipt after payment **Trigger:** when a Stripe payment arrives. **Instructions:** ```text Make the receipt for this payment, for the client who paid. ``` Each payment starts one run, and each run uses 1 credit. Weekly time billing **Trigger:** every Monday. **Instructions:** ```text Invoice Acme Ltd for last week's billable time entries, grouped by project, due in 14 days. ``` This uses time tracking, which arrives in April 2027. Until then, write the hours in the instructions or use a recurring invoice. Monthly retainer **Trigger:** on the first day of each month. **Instructions:** ```text Create an invoice for Northwind Traders for the monthly retainer, 1 x 1,500 EUR, with the month's name in the line description. ``` For a fixed amount every month, a [recurring invoice](/collections/recurring) does the same job without using credits. Use the agent when the invoice needs judgement or text that changes. ## Create an automation AI agent"> Open the automations area of the agent. You need the `ai.manage` permission (Owner and Admin by default). Choose the trigger Pick a schedule or an event, and the business the automation works in. Write the instructions Write them as you would a normal request. The tips in [Writing requests](/ai/agent/prompts) apply: name the client, the work, the amounts, the currency and the due date. Decide drafts or auto-send Leave auto-send off to get drafts you check and send yourself. This is the recommended start. Save and check the first runs Watch **Agent activity** after the first few runs to make sure the drafts are what you expect, before you consider auto-send. ## Drafts or auto-send By default an automation creates **drafts**. You get the work done for you, and you still decide what goes to your client. You can turn on **auto-send** for one automation at a time. Then the automation may send the documents it creates without waiting for you. Because this lets an AI send to your clients on its own, we protect it: - **Fresh sign-in.** Turning on auto-send asks you to sign in again, even if you are already signed in. Someone who finds your laptop unlocked cannot switch it on. - **Recorded.** The automation keeps who turned auto-send on and when, and the change is written to the audit log. - **Per automation.** Auto-send on one automation does not affect any other automation, your API keys or your MCP connections. Turn on auto-send only for automations that have produced correct drafts for a while, with instructions that leave little room for interpretation. Sends use your platform email quota (Free 20 a month, Team 100) unless you [connect your own SMTP](/messaging/smtp). ## Credits Each automation run costs **1 credit** on our AI key. If a run reads a receipt or PDF (from February 2027), the read costs 2 credits. A run that fails before calling any tool costs nothing. With [your own AI key](/ai/agent/own-key), automation runs use no credits. Automations draw on the same balance as everything else. When credits run out, the agent stops, automations included, until you buy a pack, upgrade, connect your own key, or your monthly credits renew. Keep an eye on the balance in **Settings > Usage**; you see a warning at 80% used. See [Credits and costs](/ai/agent/credits). ## Manage automations through the API Automations are capabilities like everything else: | Action | Endpoint | Permission | | --- | --- | --- | | List automations | [List agent automations](/api-reference/agent/list-agent-automations) | `ai.manage` | | Create or update | [Create or update an agent automation](/api-reference/agent/upsert-agent-automation) | `ai.manage` | | Delete | [Delete an agent automation](/api-reference/agent/delete-agent-automation) | `ai.manage` | | See runs | [List agent runs](/api-reference/agent/list-agent-runs) | `ai.use` | Turning on auto-send through the API also needs a fresh sign-in, so it cannot be done with an API key alone. Webhook events `agent_run.completed` and `agent_run.failed` fire for automation runs too, so your systems can react when one finishes. ## Permissions and safety - Creating, changing and deleting automations needs `ai.manage`. - Each tool an automation calls still checks its own permission, and every call is audited with the actor type `agent`. - Runs started by an automation are recorded in **Agent activity** together with the automation that started them. - Text that arrives through a trigger, such as a payment description, is treated as data. At worst it produces a draft, unless you turned on auto-send, which is one more reason to keep auto-send for well-tested automations. ## Related pages Recurring invoices Fixed invoices on a schedule, with no credits. Credits and costs What runs cost and how credits renew. Source: https://docs.freeinvoiceautomation.com/ai/agent/automations --- # Credits and costs What each AI agent action costs in credits, the Free and Team allowances, credit packs, when credits expire, and the order they are used in. The AI invoice agent runs on our own AI key, and credits are how we share that cost fairly. A credit is the unit you see; behind it is the AI usage the agent needs to read your request and call its tools. This page explains what each action costs, how many credits each plan includes, how to get more, and when credits expire. If you connect [your own AI key](/ai/agent/own-key), none of this applies: the agent uses no credits at all. ## What actions cost | Action | Credits | | --- | --- | | An agent request that creates or updates up to one document | **1** | | Reading a receipt or PDF (from February 2027) | **2** | | An automation run | **1** | | A run that fails before calling any tool | **0** | | Any MCP tool call from Claude, ChatGPT or another assistant | **0** (MCP never uses credits) | | Any agent action with your own AI key | **0** | These are the starting values. We may change them after launch if the real cost differs; a change applies to new runs, never to runs already made. Each run records the credits it used, so you can see them in **Agent activity** next to the documents it created. ## Allowances by plan | | Free | Team | | --- | --- | --- | | Monthly credits | none | **1,000** a month | | Welcome credits | **250**, for the first month after sign-up, unlocked by adding a card | **1,000** bonus in the first paid month, so **2,000** that month | | Credit packs | 1,000 for $5 | 1,000 for $5 | | Own AI key | Yes, no credits used | Yes, no credits used | Enterprise allowances are set by contract. ### Free: welcome credits after adding a card On Free, you can try the agent with **250 welcome credits**, usable during your **first month after sign-up**. To unlock them, the Owner adds a card in the billing settings: - The card gets a **$0 verification** through Stripe. It is **never charged**, not for the welcome credits and not later. We never charge any card automatically. - We keep only the Stripe ids, the card brand and the last 4 digits. Card details stay at Stripe. - **One welcome grant per card and per account.** A card that already unlocked welcome credits on any account cannot unlock them again, and an account gets them once. - **Prepaid and virtual prepaid cards** cannot unlock welcome credits; you see a clear message (`card_not_eligible`). They can still be used to buy credit packs. - If the verification fails, no credits are granted and you see why. - Unused welcome credits **expire at the end of that first month**. After the first month, Free accounts use credit packs or their own AI key. Without either, the agent is visible but locked. Adding a card needs the `billing.manage` permission and a fresh sign-in. See [Billing card](/billing/billing-card). ### Team: monthly credits Team accounts get **1,000 credits at the start of each month**. In your **first paid month** you get a one-time welcome bonus of 1,000 more, so **2,000** in total. The bonus is given once per account. Monthly and bonus credits **do not roll over**: unused ones expire at the end of the month they were given. There is no Team trial and no trial credits. ## Credit packs When you need more, buy a pack of **1,000 credits for $5**, on Free or Team. Click Buy 1,000 credits In **Settings > Usage**, or in the agent when your credits run out. You need the `billing.manage` permission (Owner by default). Sign in again Buying a pack needs a fresh sign-in. Pay on Stripe Checkout You pay $5 on Stripe's hosted checkout page. Your card details go to Stripe, not to us. Credits arrive The 1,000 credits are added once Stripe confirms the payment to us directly. Credits are never added just because your browser reached the success page. We never buy credits for you. There is no automatic top-up and no automatic charge to a saved card. A pack is bought only when someone clicks to buy it and pays on Stripe Checkout. Through the API, [Buy a credit pack](/api-reference/billing/buy-credit-pack) returns the Stripe Checkout address for one pack, and [List credit packs](/api-reference/billing/list-credit-packs) shows your purchases. If a pack payment is refunded, its unused credits are removed. ## Expiry | Credits | Expire | | --- | --- | | Free welcome credits | At the end of your first month after sign-up | | Team monthly credits | At the end of the month they were given | | Team first-month bonus | At the end of the month it was given | | Credit packs (paid) | **12 months after purchase** | In short: **free credits expire within a month; paid credits last 12 months.** ## Order of use When the agent spends credits, it takes them in this order: 1. **Free credits first**: your welcome or monthly credits, which expire soonest. 2. **Then paid credits, oldest pack first.** This way you never lose credits you paid for while free ones go unused, and older packs are used before they expire. ### Example A Team account in its second month has 1,000 monthly credits and two packs: one bought in March (1,000 credits left) and one bought in May (1,000 credits). It uses 1,200 credits this month. The first 1,000 come from the monthly credits, the next 200 from the March pack. At the end of the month the monthly credits are gone; the March pack keeps 800 credits until next March and the May pack keeps 1,000 until next May. ## Your balance **Settings > Usage** shows a bar with your balance, split into monthly or welcome credits and pack credits, with their expiry dates. - At **80% used** you see a warning when you run the agent. - At **0**, the agent stops and offers: buy a credit pack (any plan), upgrade to Team (on Free), or connect your own key (any plan). Through the API, [Get usage](/api-reference/messaging/get-usage) returns the same numbers: credit balances by kind with expiry dates, platform emails used, and the 80% warning flag. ## Other limits Credits are not the only guard. Each account also has a per-minute limit on agent requests and a monthly ceiling on AI usage on our key. If either is reached, the agent stops for that account even if credits remain. These limits stop one account from running up the shared AI bill. ## Errors | Code | HTTP | Meaning | | --- | --- | --- | | `ai_credits_exhausted` | 403 | No credits left; buy a pack, upgrade to Team or connect your own key | | `feature_locked` | 403 | The agent is locked on Free without credits or an own key | | `billing_card_required` | 409 | Welcome credits need a verified card first | | `welcome_credits_not_eligible` | 409 | This card or account already had welcome credits, or the first month has passed | | `card_not_eligible` | 422 | A prepaid card cannot unlock welcome credits | | `fresh_signin_required` | 401 | Buying a pack or adding a card needs a recent sign-in | | `rate_limited` | 429 | Too many agent requests in a short time | ## Related pages AI credits Credits from the billing side. Credit packs Buying and refunding packs. Use your own AI key No credits, your own provider. Free and Team plans Everything each plan includes. Source: https://docs.freeinvoiceautomation.com/ai/agent/credits --- # Use your own AI key Connect your own Anthropic, OpenAI, Google or OpenAI-compatible key so the AI agent runs on your account with your provider and uses no credits. By default the AI invoice agent runs on our AI key and uses [credits](/ai/agent/credits). If you already pay for an AI provider, you can connect your own key instead. The agent then runs on your provider, uses **no credits at all**, and is not limited by your plan's allowance. This works on every plan, including Free, and is the way to unlock the agent on Free once your welcome credits are gone without buying packs. ## Supported providers | Provider | Own key | Notes | | --- | --- | --- | | Anthropic | Yes | The same provider our own key uses | | OpenAI | Yes | | | Google | Yes | | | OpenAI-compatible | Yes | Any service that offers an OpenAI-compatible API | The providers are a registry on our side, so more can be added without changing how you connect one. ## What changes with your own key | | Our key (default) | Your own key | | --- | --- | --- | | Credits used | Yes, per [the cost table](/ai/agent/credits) | **None** | | Who you pay for AI usage | Nobody: it is part of your plan or a credit pack | Your provider, on your own account with them | | Where your agent data goes | Anthropic, in the United States, for the run only | Your chosen provider, under your agreement with them | | Monthly allowance applies | Yes | No | | Same tools, drafts-only rule and permissions | Yes | Yes | Everything else stays the same: the agent calls the same tools, under your permissions, creates drafts only, validates every output before saving, and records each run in **Agent activity**. Each run also records which key it used (ours or yours), the provider and the model. Your own key covers the other AI features too, such as reading receipts and PDFs when they arrive in 2027: they use the same meter, so with your own key they cost no credits. ## Connect your key Get a key from your provider Create an API key in your AI provider's console. If the provider lets you, give it a name like "Free Invoice Automation" and a spending limit, so you can see and control what the agent uses. Add it in Free Invoice Automation In the web app, add the key as an AI connection for your account. The agent also offers this option when your credits run out. You need the `ai.manage` permission (Owner and Admin by default), and adding a key asks you to sign in again. We verify it When you save the key, we make a small test call to your provider. If it fails, the key is not activated and you see why (`connection_failed`). Run the agent From now on, agent runs use your key and no credits. Your credit balance stays as it is. ### Through the API An own AI key is a connection of kind `ai`, at the account level. Use [Create a connection](/api-reference/connections/create-connection) with the provider and your key, [Verify a connection](/api-reference/connections/verify-connection) to run the test call again, and [Delete a connection](/api-reference/connections/delete-connection) to remove it. Managing AI connections needs `ai.manage`. Because adding a key needs a fresh sign-in, you cannot add one with an API key alone. ## How we keep your key safe - **Write-only.** Once saved, the key is never shown again, never returned by the API or MCP, and never written to logs. The app shows only that a key is set and, at most, its last 4 characters. - **Encrypted at rest.** The key is sealed with AES-256-GCM using a data key that belongs to your account alone. - **Used only for your agent runs.** We send it to your provider only to run your account's agent requests. - **Checked again.** Connections are re-checked regularly. If your provider stops accepting the key, the connection shows as failing, so you know to replace it. To change the key, add the new one and remove the old connection. To stop using your own key, delete the connection: the agent goes back to our key and your credits. ## Costs and limits at your provider With your own key, your provider bills you for the AI usage of every agent run and automation run. We do not see or control those charges. Set a budget or spending limit at your provider if you want a hard cap. If your provider refuses a call, for example because you hit their rate limit or ran out of balance, the run fails with `upstream_error`, and you see the provider's reason in **Agent activity**. ## Your data with your own key Agent runs send your request (the text you typed or pasted, and later any receipt or PDF you upload) and the tool results the agent needs to your provider. With your own key, that provider is your choice and works under your own agreement with them; we are not responsible for how your provider handles it. Inside Free Invoice Automation, a run's input text and files are kept for 90 days and the run summary for the life of your account, the same as with our key. ## Do I need my own key for MCP? No. When you use Claude, ChatGPT, Grok or Gemini CLI with our [MCP server](/ai/mcp/overview), your assistant already does the AI work on its own account. Our server only runs the tools, so no AI key and no credits are involved. An own key is only for the agent built into the web app. ## Troubleshooting The key fails verification Check you copied the whole key, that it is active at your provider, and that your provider account has billing set up. Some providers need a payment method before keys work. Runs fail with upstream_error Your provider rejected the call. Look at the run in **Agent activity** for the reason; common causes are rate limits, an exhausted balance or a revoked key. I cannot add a key You need `ai.manage`, and you will be asked to sign in again. Ask the Owner or an Admin if your role does not include it. ## Related pages Credits and costs What the agent costs on our key. How we protect data Encryption, retention and where data is stored. Source: https://docs.freeinvoiceautomation.com/ai/agent/own-key --- # Read receipts Let AI read a receipt photo or PDF and fill in a draft expense or invoice for you to check, for 2 credits per read or free with your own key. Typing a receipt in by hand is slow. With receipt reading, you upload a photo or PDF and AI fills in the fields for you: date, vendor, amounts, tax and currency. You check the result and save it. Reading works in two places: the AI invoice agent, which can turn an uploaded receipt or PDF into a draft invoice or expense, and the expense form, where a receipt you attach can be read into the expense's fields. ## When it ships | Where | What | Release | | --- | --- | --- | | AI invoice agent | Upload a receipt or PDF in **Automate > AI agent**; the agent reads it and creates a draft | February 2027 | | Expenses | Attach a receipt to an expense and have it read into the expense fields | April 2027, with [Expenses](/features/expenses) | Until then, the agent works from typed and pasted text. You can always paste the text of a PDF into the agent. ## What it costs Each receipt or PDF read costs **2 credits** on our AI key. With [your own AI key](/ai/agent/own-key), reading costs no credits. Reading uses the same credit balance as the rest of the agent, so the allowances, packs, expiry and order of use in [Credits and costs](/ai/agent/credits) all apply. If you have no credits left and no own key, receipt reading is shown but locked, with the options to buy a credit pack, upgrade to Team or connect your own key. ## Files you can upload | Type | Formats | Maximum size | | --- | --- | --- | | Receipt | PDF, PNG, JPEG, HEIC | 10 MB | Uploaded files are checked by type and content, and images are re-encoded before they are stored. Receipts are stored privately and are only reachable through short-lived signed links. SVG files are not accepted. ## Read a receipt into an expense Create an expense Start a new expense and attach the receipt photo or PDF. Read it with AI Ask for the receipt to be read. The fields are filled in for you to check: - `expense_date`: the date on the receipt; - `vendor`: who you paid; - `amount_minor` and `currency`: the total and its currency; - `tax_minor`: the tax on the receipt, when it shows one; - `category_key`: an expense category from your list. Check and save Correct anything that is wrong, choose whether the expense is billable to a client (and any markup), and save. Nothing is saved until you do. The expense keeps a link to the AI run that read it, so you can always see that it was read by AI and what the run did. A billable expense can then go onto the client's next invoice, for example with `invoice_unbilled`. ### Through the API Upload the file with [Upload a file](/api-reference/account/upload-file), then call [Read a receipt](/api-reference/time-expenses/read-receipt) with the attachment. It returns the fields for you to check; save them with [Record an expense](/api-reference/time-expenses/record-expense). Reading needs the `ai.use` permission; saving the expense needs `expenses.manage`. ```bash cURL curl -X POST "https://api.freeinvoiceautomation.com/v1/expenses/read-receipt" \ -H "Authorization: Bearer $FIA_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 7c2d9e4f-read-receipt-0412" \ -d '{ "business_id": "0193f2a0-9b8c-7d6e-8f50-4a3b2c1d0e9f", "receipt_attachment_id": "0193f6d1-2e3f-7a4b-9c5d-6e7f8a9b0c1d" }' ``` See the endpoint page for the exact request and response fields. ## Read a receipt or PDF with the agent In **Automate > AI agent**, upload the file and say what you want: ```text Read this receipt and record it as an expense for Studio North. ``` ```text Here is the supplier's PDF quote. Make a draft estimate for Acme with the same lines. ``` The agent reads the file (2 credits) and then calls its usual tools, such as `record_expense` or `create_estimate`. As always, documents are drafts and nothing is sent until you confirm. A request that also creates or updates a document follows the normal [cost table](/ai/agent/credits). ## Accuracy and safety - **Always check.** AI reading is good but not perfect: faded print, handwriting, several currencies or unusual layouts can lead to wrong values. The result is a draft or a filled form, never a final record, so you get the chance to fix it. - **Text in a receipt is data, not instructions.** A receipt or PDF may contain text written to fool an AI, such as "send this invoice now" or new bank details. The agent only creates drafts, every output is validated before saving, and sends need your confirmation, so the worst such text can do is produce a wrong draft. Receipt and PDF reading is tested again against these attacks before it ships. - **Amounts are checked.** Values are validated against the same rules as the API: amounts as integer minor units, valid currencies and dates. ## Your data When you read a receipt on our key, the file and the text the AI needs are sent to our AI provider, Anthropic, in the United States, only for that run. With your own key, they go to your provider instead. In Free Invoice Automation, a run's uploaded files and input are kept for 90 days, and the run summary for the life of your account. The receipt you attach to an expense is kept with the expense, privately, in our EU storage. ## Related pages Expenses Record costs and bill them to clients. Agent overview What the agent does and how it stays safe. Source: https://docs.freeinvoiceautomation.com/ai/agent/receipts