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 is the step-by-step recipe for an agent that connects a service; llms.txt is the short index for models.
What a person does, and what the agent does
The person does three things, at most:
- Create or confirm the account and accept the Terms.
- Approve the agent's token, on one page.
- 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 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 describes every endpoint.
- Every write sends an
Idempotency-Keyheader: 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), withpagination.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:
claude mcp add --transport http invoicecow https://invoicecow.com/mcp --header "Authorization: Bearer $INVOICECOW_TOKEN"
Cursor, in .cursor/mcp.json:
{
"mcpServers": {
"invoicecow": {
"url": "https://invoicecow.com/mcp",
"headers": { "Authorization": "Bearer ${env:INVOICECOW_TOKEN}" }
}
}
}
Codex, in ~/.codex/config.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:
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:
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:
send_sale_to_invoicecow(order_id: "order-1001", paid_at: Time.now, currency: "EUR", amount: 49000,
buyer: { email: "[email protected]", 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.