# InvoiceCow for developers

InvoiceCow turns each paid sale into an invoice in your company's name, and gives each buyer a page to add their company details and download. This page describes how a program or an agent works with it. The [agents guide](https://invoicecow.com/developers/agents) is the step-by-step recipe for an agent that connects a service; [llms.txt](https://invoicecow.com/llms.txt) is the short index for models.

## What a person does, and what the agent does

The person does three things, at most:

1. Create or confirm the account and accept the Terms.
2. Approve the agent's token, on one page.
3. Authorize the processor: install the InvoiceCow Stripe App (Pending Stripe review), or enter a Stripe restricted key in the dashboard.

The agent does the rest: the company, the numbering, the connection, the receipt text, the test sale and the check.

## Status today

| Feature | Status |
|---|---|
| Stripe connection | Live |
| Webhook from any checkout | Live |
| CSV import | Live |
| PDF invoices | Live |
| Credit notes for refunds | Live |
| The buyer page, with VIES checks | Live |
| The API, with tokens an agent obtains with one approval | Live |
| A hosted MCP server for agents | Live |
| Stripe in one click, through the InvoiceCow Stripe App | Pending Stripe review |
| E-invoice files: XRechnung, UBL, CII and Peppol BIS | Live |
| Factur-X and ZUGFeRD files | Planned |
| Shopify app | In build |

## Tokens and scopes

Every request of the API and the MCP server sends a token of one organization: `Authorization: Bearer <token>`. A person who manages the organization creates one in the dashboard, under API tokens. An agent asks for one through the device flow (RFC 8628), and the person approves it once; the [agents guide](https://invoicecow.com/developers/agents#1-get-a-token) shows the requests. InvoiceCow keeps only a fingerprint of each token, shows it once, and stops it at once when it is revoked or when its person no longer manages the organization.

| Scope | Allows |
|---|---|
| `read` | Read companies, number patterns, connections, payments, invoices, credit notes and the buyer page. |
| `write` | Add and change companies, number patterns, products and the buyer page's address, and import CSV files. |
| `connections` | Open webhook and CSV connections, rotate their secrets, and start the Stripe App install. |
| `downloads` | Download invoices and credit notes. A download releases an invoice, as in the dashboard. |

## The API

The API v1 is JSON at `https://invoicecow.com/api/v1`. The [OpenAPI 3.1 document](https://invoicecow.com/api/v1/openapi.json) describes every endpoint.

- Every write sends an `Idempotency-Key` header: a unique text of 1 to 255 characters, the same when you retry. A write that succeeded runs once per key for 24 hours; a retry answers the record as it is now, and never a secret.
- An error is `{ "code": "...", "message": "..." }`: a code for the program and a sentence for the person.
- A list comes in pages: `?page=2&limit=100` (50 records unless you ask, at most 100), with `pagination.next_page`.
- A token makes at most 1000 requests per hour.
- Money is in minor units (cents) with its currency. Times are ISO 8601 in UTC.
- Every change and every download is in the organization's audit log, with the token's name.

## The MCP server

The MCP server is at `https://invoicecow.com/mcp`, on the Streamable HTTP transport (MCP 2026-07-28 and the handshake versions before it), with the API token as the bearer. An agent adds one URL and installs nothing.

Keep the token in an environment variable, here `INVOICECOW_TOKEN`, and never in a file of a repository.

Claude Code:

```bash
claude mcp add --transport http invoicecow https://invoicecow.com/mcp --header "Authorization: Bearer $INVOICECOW_TOKEN"
```

Cursor, in `.cursor/mcp.json`:

```json
{
  "mcpServers": {
    "invoicecow": {
      "url": "https://invoicecow.com/mcp",
      "headers": { "Authorization": "Bearer ${env:INVOICECOW_TOKEN}" }
    }
  }
}
```

Codex, in `~/.codex/config.toml`:

```toml
[mcp_servers.invoicecow]
url = "https://invoicecow.com/mcp"
bearer_token_env_var = "INVOICECOW_TOKEN"
```


The tools: `create_issuer`, `update_issuer`, `set_number_pattern`, `create_webhook_connection`, `start_stripe_authorization`, `get_connection`, `list_payments`, `get_payment`, `get_invoice`, `download_invoice`, `portal_link`, `set_portal_address`, `receipt_snippets` and `verify_setup`. Each tool needs the scope of its API endpoint. Each tool that changes records takes an optional `idempotency_key`, which works as the API's `Idempotency-Key` and shares its keys: a retry with the same key and arguments changes nothing and returns the record as it is now.

## Stripe

A person connects a Stripe account in the dashboard, under Connections, with a restricted key that can only read, and the signing secret of a webhook endpoint. The key needs read access to: checkout_sessions, payment_intents, charges, disputes, invoices, invoice_payments, customers, account.

The InvoiceCow Stripe App connects in one click (Pending Stripe review): `POST /api/v1/connections/stripe/authorizations` returns the install link, and the person installs the app on Stripe. The app asks for these read permissions: checkout_session_read, payment_intent_read, charge_read, dispute_read, invoice_read, customer_read, event_read.

## The signed webhook

A checkout that is not Stripe sends each paid sale to its webhook connection. Open the connection with `POST /api/v1/connections` and `"kind": "webhook"`; the answer holds its `webhook_url` and its `secret`, once.

Send each event as an HTTP `POST` with a JSON body to the `webhook_url`. Sign the exact bytes you send:

```
InvoiceCow-Signature: t=<unix seconds>,v1=<signature>
```

`<signature>` is the lowercase hex HMAC-SHA256 of `<t>.<body>`, with the connection's secret as the key. InvoiceCow refuses a request with no signature, a wrong signature, or a time more than five minutes from its clock (`400`), and a sale that is not valid (`422`, with the reason in `error` and the field in `details`). It answers `200` when it stored the event. An event id it already has gets `200` and changes nothing, so a retry is safe.

The sale call in Ruby, with the standard library only. Put it in the application's payment path, after the payment succeeds:

```ruby
require "json"
require "net/http"
require "openssl"

# Sends one paid sale to InvoiceCow. The URL and the secret come from the environment.
def send_sale_to_invoicecow(order_id:, paid_at:, currency:, amount:, buyer:, lines:)
  body = JSON.generate(id: "sale-#{order_id}", type: "sale.paid", sale: {
    merchant_sale_id: order_id, currency:, amount:, paid_at: paid_at.utc.iso8601, buyer:, lines:
  })
  timestamp = Time.now.to_i
  signature = OpenSSL::HMAC.hexdigest("SHA256", ENV.fetch("INVOICECOW_WEBHOOK_SECRET"), "#{timestamp}.#{body}")
  response = Net::HTTP.post(URI(ENV.fetch("INVOICECOW_WEBHOOK_URL")), body,
    "Content-Type" => "application/json", "InvoiceCow-Signature" => "t=#{timestamp},v1=#{signature}")
  raise "InvoiceCow refused the sale: #{response.code} #{response.body}" unless response.is_a?(Net::HTTPSuccess)
end
```

The same call in TypeScript, for Node 18 or later:

```ts
import { createHmac } from "node:crypto";

// Sends one paid sale to InvoiceCow. The URL and the secret come from the environment.
export async function sendSaleToInvoiceCow(sale: {
  orderId: string; paidAt: Date; currency: string; amount: number;
  buyer: Record<string, string>; lines: Array<Record<string, unknown>>;
}): Promise<void> {
  const body = JSON.stringify({ id: `sale-${sale.orderId}`, type: "sale.paid", sale: {
    merchant_sale_id: sale.orderId, currency: sale.currency, amount: sale.amount,
    paid_at: sale.paidAt.toISOString(), buyer: sale.buyer, lines: sale.lines } });
  const timestamp = Math.floor(Date.now() / 1000);
  const signature = createHmac("sha256", process.env.INVOICECOW_WEBHOOK_SECRET!)
    .update(`${timestamp}.${body}`).digest("hex");
  const response = await fetch(process.env.INVOICECOW_WEBHOOK_URL!, { method: "POST", body,
    headers: { "Content-Type": "application/json", "InvoiceCow-Signature": `t=${timestamp},v1=${signature}` } });
  if (!response.ok) throw new Error(`InvoiceCow refused the sale: ${response.status} ${await response.text()}`);
}
```

The client libraries (Planned), the gem `invoicecow` for Ruby and the npm package `invoicecow` for JavaScript, wrap the same call, the signature, and the reading of documents through the API. They need no other library.

A sale of Your Company Ltd to a business buyer:

```ruby
send_sale_to_invoicecow(order_id: "order-1001", paid_at: Time.now, currency: "EUR", amount: 49000,
  buyer: { email: "billing@kessler-vogt.example", company_name: "Kessler & Vogt GmbH", tax_id: "DE271828182", country_code: "DE" },
  lines: [ { description: "Pro plan · yearly", quantity: "1", net: 49000, tax: 0, gross: 49000 } ])
```

The rules of the fields:

| Field | Rule |
|---|---|
| `id` | Your event id, unique for the connection. |
| `type` | `sale.paid` or `sale.refunded`. Both carry the whole sale. |
| `processor`, `processor_account_id`, `processor_sale_id` | The sale's key across all connections. Send all three, or none. |
| `merchant_sale_id` | Your own id. Without the three processor fields, it is the sale's key under this connection. |
| `currency` | ISO 4217, three letters. |
| `amount`, `net`, `tax`, `gross`, `unit_amount`, `refunded` | Integers in the currency's minor unit (cents for EUR). |
| `paid_at` | An ISO 8601 time. `supply_date` and `tax_point` are optional ISO 8601 dates; both default to the payment date. |
| `buyer` | Every field is optional text. A company name, a tax id or `"type": "business"` makes a business buyer. `country_code` is ISO 3166-1 alpha-2. The buyer completes missing fields on the buyer page. |
| `lines` | At least one line. For each line `net + tax = gross`, and the lines' `gross` add up to `amount`. The tax is the tax you charged. `product` is `{ "id", "name" }` or absent. |
| `refunded` | The cumulative refunded amount. A higher value issues a credit note for the difference. |

A sale whose seller is not the merchant's company (a marketplace or a merchant of record) does not go here: InvoiceCow would issue an invoice in the merchant's name.


## The buyer page

Each organization has one buyer page, `invoicecow.com/<address>`. There each buyer adds company details and downloads their invoices. `GET /api/v1/portal` returns its link and two texts that share it: a line for the receipt email, and a text for the custom message of Stripe's receipts.

## The rule labels

Each invoice follows the rules of its company's country. Each part of a country's rules (the fields, the deadline, the correction method, the formats) carries a label: **verified**, after a dated review, or **generic**, a general template that is not yet checked against that country's law. The dashboard shows the labels of each payment.
