Invoices API
Create, manage, and track invoices and customers programmatically. Requires the release_invoicing feature flag.
Required scopes#
| Endpoint | Scope |
|---|---|
| List / Get customers | customers:read |
| Create / Update / Delete customers | customers:write |
| List / Get invoices | invoices:read |
| Create / Update / Delete invoices | invoices:write |
| Send / Mark as paid / Cancel invoices | invoices:send |
| Create credit note | invoices:write |
| Auto-approve created invoices (behavior, not an endpoint) | invoices:auto_approve |
Approval before send#
pending_approval status and does not expose an approve endpoint. Before POST /api/invoices/:id/send works, the invoice must be approved. Dashboard Approve & Send also sends it once approval requirements are met; do not call send again. For unattended issuance, use the auto-approval scope below.invoices:auto_approve scope skips the approval step entirely: POST /api/invoices (and POST /api/invoices/:id/credit_note) return the invoice already in sent status, and the invoice email is queued when send_to_email is set. This bypasses any approval rules configured for the entity — including multi-approval requirements — so grant the scope only to integrations trusted to issue invoices on their own, such as recurring-invoicing automations.Recurring invoices and branding#
For fixed monthly subscriptions, create the customer once and schedule one POST /api/invoices per billing period from your application. Reuse the customer and agreed line items, updating the dates and billing-period reference. The API creates individual invoices; it does not manage subscription schedules. Stop future creation jobs when a subscription ends. Cancelling an existing invoice does not cancel a schedule in your application.
For example, Ruby can build the period reference as "club-#{club_id}-#{billing_month}".
Grant the integration invoices:write and invoices:auto_approve to create invoices without monthly manual approval. No approved=true parameter is needed. This permission bypasses all approval rules and is not limited to an agreed amount. Include send_to_email to queue email; sent alone is not proof of email delivery or payment. For attaching payment links before delivery, see the Recurring billing guide.
Keep a unique billing record per subscription and period in your application. Invoice creation does not provide an Idempotency-Key guarantee, and reference is not a uniqueness constraint. Store returned invoice IDs and reconcile ambiguous failures before retrying creation: a failed response may follow a saved invoice. Use invoices:read to retrieve invoices during reconciliation.
Upload your issuing company's logo once in the dashboard's business profile settings. Invoice previews and PDFs use the issuing entity's logo, including for API-created invoices. The customer object represents the recipient, not the invoice issuer. The public API does not currently expose logo upload or invoice-branding settings; there is no per-invoice logo parameter. Complete this dashboard setup before enabling automatic sending.
Customer object#
Customers must exist before they can be referenced in invoices.
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier (UUIDv7) |
customer_type | string | individual or business (auto-detected from company_name, and from registration_number where the market collects one) |
person_name | string | Individual's name |
company_name | string | Company name (typically for business customers) |
name | string | Computed: company_name if present, else person_name |
email | string | Email address (unique per entity) |
phone | string | Phone number |
tax_id | string | Tax identification number (identifiant fiscal; optional; any country's format, must include at least one digit, at most 50 characters) |
ice_number | string | ICE number (optional; must be exactly 15 digits when set) |
billing_address | object | Billing address |
invoices_count | integer | Number of invoices (list only) |
created_at | string | ISO 8601 timestamp |
updated_at | string | ISO 8601 timestamp |
The billing_address object carries street, street2, city, postal_code, country, and country_code (ISO 3166-1 alpha-2). When an address is provided, all fields except street2 are required.
List customers#
GET /api/customers
Supports q (search by name, company, email) plus cursor pagination (cursor, limit 1–100, default 25).
curl "https://ma.manza.dev/api/customers?q=acme" \
-H "Authorization: Bearer sk_live_..."manza customers listimport { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.customers.list({ q: "acme" });require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.customers.list(q: "acme")from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.customers.list(q="acme")import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
page, err := client.Customers.List(ctx, manza.CustomerListParams{Query: "acme"})let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
let params = manza::CustomerListParams { q: Some("acme".into()), ..Default::default() };
let page = client.customers().list(params)?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, page} = Manza.Customers.list(client, q: "acme")require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
page = client.customers.list(q: "acme")use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$page = $client->customers->list(q: 'acme');Create customer#
POST /api/customers
Requires at least one of person_name or company_name. When customer_type is omitted it is auto-detected: business when a company name is present, individual otherwise.
curl -X POST "https://ma.manza.dev/api/customers" \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"customer_type": "business",
"person_name": "Ahmed Benjelloun",
"company_name": "Acme Corp",
"email": "billing@acme.com",
"phone": "+212600000000",
"tax_id": "TAX123456",
"ice_number": "000000000000001",
"billing_address": {
"street": "123 Main St",
"city": "Casablanca",
"postal_code": "20000",
"country": "Morocco",
"country_code": "MA"
}
}'manza customers create --file customer.jsonimport { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.customers.create({
customer_type: "business",
company_name: "Acme Corp",
email: "billing@acme.com",
});require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.customers.create(
customer_type: "business",
company_name: "Acme Corp",
email: "billing@acme.com",
)from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.customers.create(
customer_type="business",
company_name="Acme Corp",
email="billing@acme.com",
)import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
customer, err := client.Customers.Create(ctx, manza.Attributes{
"customer_type": "business",
"company_name": "Acme Corp",
"email": "billing@acme.com",
})let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
let customer = client.customers().create(&serde_json::json!({
"customer_type": "business",
"company_name": "Acme Corp",
"email": "billing@acme.com",
}))?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, customer} =
Manza.Customers.create(client, %{
"customer_type" => "business",
"company_name" => "Acme Corp",
"email" => "billing@acme.com"
})require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
customer = client.customers.create(
customer_type: "business",
company_name: "Acme Corp",
email: "billing@acme.com"
)use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$customer = $client->customers->create([
'customer_type' => 'business',
'company_name' => 'Acme Corp',
'email' => 'billing@acme.com',
]);Responds 201 Created with the full customer object.
Get customer#
GET /api/customers/:id
curl "https://ma.manza.dev/api/customers/01964a3b-0000-7000-8000-customer0001" \
-H "Authorization: Bearer sk_live_..."manza customers get 01964a3b-0000-7000-8000-customer0001import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.customers.get("01964a3b-0000-7000-8000-customer0001");require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.customers.get("01964a3b-0000-7000-8000-customer0001")from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.customers.get("01964a3b-0000-7000-8000-customer0001")import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
customer, err := client.Customers.Get(ctx, "01964a3b-0000-7000-8000-customer0001")let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
let customer = client.customers().get("01964a3b-0000-7000-8000-customer0001")?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, customer} = Manza.Customers.get(client, "01964a3b-0000-7000-8000-customer0001")require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
customer = client.customers.get("01964a3b-0000-7000-8000-customer0001")use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$customer = $client->customers->get('01964a3b-0000-7000-8000-customer0001');Update customer#
PATCH /api/customers/:id
Partial update — only provided fields change. A supplied billing_address replaces the existing one entirely; setting it to null deletes the address.
curl -X PATCH "https://ma.manza.dev/api/customers/01964a3b-0000-7000-8000-customer0001" \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"email": "newemail@acme.com",
"billing_address": {
"street": "456 New St",
"city": "Rabat",
"postal_code": "10000",
"country": "Morocco",
"country_code": "MA"
}
}'manza customers update 01964a3b-0000-7000-8000-customer0001 --file customer.jsonimport { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.customers.update("01964a3b-0000-7000-8000-customer0001", { email: "newemail@acme.com" });require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.customers.update("01964a3b-0000-7000-8000-customer0001", email: "newemail@acme.com")from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.customers.update("01964a3b-0000-7000-8000-customer0001", email="newemail@acme.com")import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
customer, err := client.Customers.Update(ctx, "01964a3b-0000-7000-8000-customer0001",
manza.Attributes{"email": "newemail@acme.com"})let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
let customer = client.customers()
.update("01964a3b-0000-7000-8000-customer0001", &serde_json::json!({ "email": "newemail@acme.com" }))?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, customer} =
Manza.Customers.update(client, "01964a3b-0000-7000-8000-customer0001", %{"email" => "newemail@acme.com"})require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
customer = client.customers.update("01964a3b-0000-7000-8000-customer0001", email: "newemail@acme.com")use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$customer = $client->customers->update('01964a3b-0000-7000-8000-customer0001', ['email' => 'newemail@acme.com']);Delete customer#
DELETE /api/customers/:id
Responds 204 No Content. A customer that still has invoices cannot be deleted — that returns 422 unprocessable_entity.
curl -X DELETE "https://ma.manza.dev/api/customers/01964a3b-0000-7000-8000-customer0001" \
-H "Authorization: Bearer sk_live_..."manza customers delete 01964a3b-0000-7000-8000-customer0001import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.customers.delete("01964a3b-0000-7000-8000-customer0001");require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.customers.delete("01964a3b-0000-7000-8000-customer0001")from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.customers.delete("01964a3b-0000-7000-8000-customer0001")import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
_, err := client.Customers.Delete(ctx, "01964a3b-0000-7000-8000-customer0001")let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
client.customers().delete("01964a3b-0000-7000-8000-customer0001")?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, _} = Manza.Customers.delete(client, "01964a3b-0000-7000-8000-customer0001")require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
client.customers.delete("01964a3b-0000-7000-8000-customer0001")use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$client->customers->delete('01964a3b-0000-7000-8000-customer0001');Invoice object#
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier (UUIDv7) |
number | string | Auto-generated number (default <prefix><date>-<sequence>, configurable per entity) |
status | string | draft, pending_approval, approved, sent, paid, overdue, cancelled |
currency_code | string | ISO 4217 currency code |
subtotal | string | Sum of item amounts |
discount_total | string | Total discount amount |
tax_total | string | Total tax amount |
total | string | Final amount due |
issue_date | string | Date (YYYY-MM-DD) |
due_date | string | Date (YYYY-MM-DD) |
delivery_date | string | Delivery date (YYYY-MM-DD, optional) |
reference | string | PO number or external reference |
notes | string | Free-text notes |
payment_terms | string | Payment terms text |
send_to_email | string | Email for sending the invoice |
credited_invoice_id | string/null | ID of the credited invoice |
credit_note | boolean | Whether this invoice is a credit note |
sent_at | string | ISO 8601 timestamp |
paid_at | string | ISO 8601 timestamp |
customer | object | Snapshotted customer data |
billing_address | object | Snapshotted billing address |
items | array | Line items (show only) |
discounts | array | Discounts (show only) |
payment_link | object | Attached payment link (show only) |
created_at | string | ISO 8601 timestamp |
updated_at | string | ISO 8601 timestamp |
Items and discounts#
Each item carries description, quantity, unit_price, tax_rate (a fraction, e.g. "0.2000" = 20%; one of the entity's allowed VAT rates: its standard rate, in Morocco also the reduced "0.1000" (10%), or "0" for an export or other zero-rated supply — a business that is not VAT-registered can only issue at 0%), a calculated amount (quantity × unit_price), and a position sort order.
Each discount carries description, discount_type (percentage or fixed), value (percentage 0–100 or fixed amount), a calculated amount, and a position sort order.
List invoices#
GET /api/invoices
Filter by status and customer_id, plus cursor pagination. The list response returns items_count and discounts_count instead of the full arrays, and payment_link_url instead of the full payment-link object.
curl "https://ma.manza.dev/api/invoices?status=sent" \
-H "Authorization: Bearer sk_live_..."manza invoices listimport { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.invoices.list({ status: "sent" });require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.invoices.list(status: "sent")from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.invoices.list(status="sent")import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
page, err := client.Invoices.List(ctx, manza.InvoiceListParams{Status: "sent"})let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
let params = manza::InvoiceListParams { status: Some("sent".into()), ..Default::default() };
let page = client.invoices().list(params)?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, page} = Manza.Invoices.list(client, status: "sent")require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
page = client.invoices.list(status: "sent")use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$page = $client->invoices->list(status: 'sent');Create invoice#
POST /api/invoices
By default, creates an invoice directly in pending_approval status (no draft workflow). With invoices:auto_approve, it is approved and sent automatically. Required: customer_id, issue_date, due_date, and at least one item. Totals are calculated automatically, currency_code defaults to entity currency, and per-item tax_rate defaults to the entity's standard VAT rate if omitted (0% for a business that is not VAT-registered). Any other value is rejected with a 422 whose param is invoice_items.tax_rate.
Optionally pass delivery_date (YYYY-MM-DD) to record when the goods or services were delivered.
Upload the company stamp in the app's invoice settings before you create invoices. The API cannot upload it, and until it is there this endpoint responds 422 state_error.
curl -X POST "https://ma.manza.dev/api/invoices" \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"customer_id": "01964a3b-0000-7000-8000-customer0001",
"currency_code": "MAD",
"issue_date": "2026-03-15",
"due_date": "2026-04-15",
"delivery_date": "2026-04-10",
"reference": "PO-2026-001",
"notes": "Net 30 payment terms",
"payment_terms": "Net 30",
"send_to_email": "billing@acme.com",
"items": [
{
"description": "Consulting services - March 2026",
"quantity": 10,
"unit_price": "150.00"
},
{
"description": "Travel expenses",
"quantity": 1,
"unit_price": "500.00",
"tax_rate": "0.00"
}
],
"discounts": [
{
"description": "Loyalty discount",
"discount_type": "percentage",
"value": "10.00"
}
]
}'manza invoices create --file invoice.jsonimport { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.invoices.create({
customer_id: "01964a3b-0000-7000-8000-customer0001",
currency_code: "MAD",
issue_date: "2026-03-15",
due_date: "2026-04-15",
items: [
{ description: "Consulting services", quantity: 10, unit_price: "150.00" },
],
});require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.invoices.create(
customer_id: "01964a3b-0000-7000-8000-customer0001",
currency_code: "MAD",
issue_date: "2026-03-15",
due_date: "2026-04-15",
items: [
{ description: "Consulting services", quantity: 10, unit_price: "150.00" },
],
)from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.invoices.create(
customer_id="01964a3b-0000-7000-8000-customer0001",
currency_code="MAD",
issue_date="2026-03-15",
due_date="2026-04-15",
items=[
{"description": "Consulting services", "quantity": 10, "unit_price": "150.00"},
],
)import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
invoice, err := client.Invoices.Create(ctx, manza.Attributes{
"customer_id": "01964a3b-0000-7000-8000-customer0001",
"currency_code": "MAD",
"issue_date": "2026-03-15",
"due_date": "2026-04-15",
"items": []manza.Attributes{
{"description": "Consulting services", "quantity": 10, "unit_price": "150.00"},
},
})let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
let invoice = client.invoices().create(&serde_json::json!({
"customer_id": "01964a3b-0000-7000-8000-customer0001",
"currency_code": "MAD",
"issue_date": "2026-03-15",
"due_date": "2026-04-15",
"items": [
{ "description": "Consulting services", "quantity": 10, "unit_price": "150.00" },
],
}))?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, invoice} =
Manza.Invoices.create(client, %{
"customer_id" => "01964a3b-0000-7000-8000-customer0001",
"currency_code" => "MAD",
"issue_date" => "2026-03-15",
"due_date" => "2026-04-15",
"items" => [
%{"description" => "Consulting services", "quantity" => 10, "unit_price" => "150.00"}
]
})require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
invoice = client.invoices.create(
customer_id: "01964a3b-0000-7000-8000-customer0001",
currency_code: "MAD",
issue_date: "2026-03-15",
due_date: "2026-04-15",
items: [
{description: "Consulting services", quantity: 10, unit_price: "150.00"},
]
)use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$invoice = $client->invoices->create([
'customer_id' => '01964a3b-0000-7000-8000-customer0001',
'currency_code' => 'MAD',
'issue_date' => '2026-03-15',
'due_date' => '2026-04-15',
'items' => [
['description' => 'Consulting services', 'quantity' => 10, 'unit_price' => '150.00'],
],
]);Responds 201 Created with the full invoice object.
Get invoice#
GET /api/invoices/:id
Returns the full invoice with items, discounts, customer snapshot, billing address, payment link, and any credit_notes issued against it.
curl "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234" \
-H "Authorization: Bearer sk_live_..."manza invoices get 01964a3b-7c8d-7000-8000-deadbeef1234import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.invoices.get("01964a3b-7c8d-7000-8000-deadbeef1234");require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.invoices.get("01964a3b-7c8d-7000-8000-deadbeef1234")from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.invoices.get("01964a3b-7c8d-7000-8000-deadbeef1234")import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
invoice, err := client.Invoices.Get(ctx, "01964a3b-7c8d-7000-8000-deadbeef1234")let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
let invoice = client.invoices().get("01964a3b-7c8d-7000-8000-deadbeef1234")?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, invoice} = Manza.Invoices.get(client, "01964a3b-7c8d-7000-8000-deadbeef1234")require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
invoice = client.invoices.get("01964a3b-7c8d-7000-8000-deadbeef1234")use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$invoice = $client->invoices->get('01964a3b-7c8d-7000-8000-deadbeef1234');{
"id": "01964a3b-7c8d-7000-8000-deadbeef1234",
"number": "INV-202603-001",
"status": "sent",
"currency_code": "MAD",
"subtotal": "1500.00",
"discount_total": "150.00",
"tax_total": "270.00",
"total": "1620.00",
"issue_date": "2026-03-15",
"due_date": "2026-04-15",
"delivery_date": "2026-04-10",
"customer": {
"id": "01964a3b-0000-7000-8000-customer0001",
"name": "Acme Corp",
"email": "billing@acme.com"
},
"items": [
{
"id": "01964a3b-1111-7000-8000-item00000001",
"description": "Consulting services - March 2026",
"quantity": "10.00",
"unit_price": "150.00",
"tax_rate": "0.2000",
"amount": "1500.00",
"position": 0
}
],
"discounts": [
{
"id": "01964a3b-2222-7000-8000-disc00000001",
"description": "Loyalty discount",
"discount_type": "percentage",
"value": "10.00",
"amount": "150.00",
"position": 0
}
],
"payment_link": {
"id": "01964a3b-3333-7000-8000-link00000001",
"url": "https://pay.zazu.example/payment-link/abc123def456",
"status": "active"
},
"created_at": "2026-03-15T09:00:00Z",
"updated_at": "2026-03-16T10:00:00Z"
}Update invoice#
PATCH /api/invoices/:id
Only invoices in pending_approval or approved status can be updated — otherwise the API returns 422 state_error. currency_code, number, and status are not updatable. Items and discounts use full replacement: entries with a matching id are updated, entries without an id are created, and existing entries absent from the array are deleted. Totals are recalculated automatically.
curl -X PATCH "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234" \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"notes": "Updated terms"}'manza invoices update 01964a3b-7c8d-7000-8000-deadbeef1234 --file invoice.jsonimport { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.invoices.update("01964a3b-7c8d-7000-8000-deadbeef1234", { notes: "Updated terms" });require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.invoices.update("01964a3b-7c8d-7000-8000-deadbeef1234", notes: "Updated terms")from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.invoices.update("01964a3b-7c8d-7000-8000-deadbeef1234", notes="Updated terms")import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
invoice, err := client.Invoices.Update(ctx, "01964a3b-7c8d-7000-8000-deadbeef1234", manza.Attributes{"notes": "Updated terms"})let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
let invoice = client.invoices()
.update("01964a3b-7c8d-7000-8000-deadbeef1234", &serde_json::json!({ "notes": "Updated terms" }))?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, invoice} = Manza.Invoices.update(client, "01964a3b-7c8d-7000-8000-deadbeef1234", %{"notes" => "Updated terms"})require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
invoice = client.invoices.update("01964a3b-7c8d-7000-8000-deadbeef1234", notes: "Updated terms")use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$invoice = $client->invoices->update('01964a3b-7c8d-7000-8000-deadbeef1234', ['notes' => 'Updated terms']);Send invoice#
POST /api/invoices/:id/send
Transitions the invoice to sent, sets sent_at, and queues email when send_to_email is set. Only available from approved. Without auto-approval, API-created invoices start in pending_approval. Dashboard Approve & Send also sends the invoice. Invoices created with invoices:auto_approve are already sent; do not call this endpoint again for them.
curl -X POST "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234/send" \
-H "Authorization: Bearer sk_live_..."manza invoices send 01964a3b-7c8d-7000-8000-deadbeef1234import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.invoices.send("01964a3b-7c8d-7000-8000-deadbeef1234");require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.invoices.send_invoice("01964a3b-7c8d-7000-8000-deadbeef1234")from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.invoices.send_invoice("01964a3b-7c8d-7000-8000-deadbeef1234")import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
invoice, err := client.Invoices.Send(ctx, "01964a3b-7c8d-7000-8000-deadbeef1234")let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
let invoice = client.invoices().send("01964a3b-7c8d-7000-8000-deadbeef1234")?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, invoice} = Manza.Invoices.send(client, "01964a3b-7c8d-7000-8000-deadbeef1234")require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
invoice = client.invoices.send("01964a3b-7c8d-7000-8000-deadbeef1234")use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$invoice = $client->invoices->send('01964a3b-7c8d-7000-8000-deadbeef1234');Mark as paid#
POST /api/invoices/:id/mark_as_paid
Transitions the invoice to paid and sets paid_at. Only available from sent or overdue.
curl -X POST "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234/mark_as_paid" \
-H "Authorization: Bearer sk_live_..."manza invoices mark-as-paid 01964a3b-7c8d-7000-8000-deadbeef1234import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.invoices.markAsPaid("01964a3b-7c8d-7000-8000-deadbeef1234");require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.invoices.mark_as_paid("01964a3b-7c8d-7000-8000-deadbeef1234")from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.invoices.mark_as_paid("01964a3b-7c8d-7000-8000-deadbeef1234")import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
invoice, err := client.Invoices.MarkAsPaid(ctx, "01964a3b-7c8d-7000-8000-deadbeef1234")let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
let invoice = client.invoices().mark_as_paid("01964a3b-7c8d-7000-8000-deadbeef1234")?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, invoice} = Manza.Invoices.mark_as_paid(client, "01964a3b-7c8d-7000-8000-deadbeef1234")require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
invoice = client.invoices.mark_as_paid("01964a3b-7c8d-7000-8000-deadbeef1234")use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$invoice = $client->invoices->markAsPaid('01964a3b-7c8d-7000-8000-deadbeef1234');Cancel invoice#
POST /api/invoices/:id/cancel
Cancels the invoice and its associated payment link (if any). Only available from sent or overdue.
curl -X POST "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234/cancel" \
-H "Authorization: Bearer sk_live_..."manza invoices cancel 01964a3b-7c8d-7000-8000-deadbeef1234import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.invoices.cancel("01964a3b-7c8d-7000-8000-deadbeef1234");require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.invoices.cancel("01964a3b-7c8d-7000-8000-deadbeef1234")from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.invoices.cancel("01964a3b-7c8d-7000-8000-deadbeef1234")import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
invoice, err := client.Invoices.Cancel(ctx, "01964a3b-7c8d-7000-8000-deadbeef1234")let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
let invoice = client.invoices().cancel("01964a3b-7c8d-7000-8000-deadbeef1234")?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, invoice} = Manza.Invoices.cancel(client, "01964a3b-7c8d-7000-8000-deadbeef1234")require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
invoice = client.invoices.cancel("01964a3b-7c8d-7000-8000-deadbeef1234")use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$invoice = $client->invoices->cancel('01964a3b-7c8d-7000-8000-deadbeef1234');Create credit note#
POST /api/invoices/:id/credit_note
Creates a full credit note against a paid invoice: a new invoice with negated line items, starting in pending_approval. Requires the invoices:write scope and responds 201 Created with the credit note.
curl -X POST "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234/credit_note" \
-H "Authorization: Bearer sk_live_..."manza invoices credit-note 01964a3b-7c8d-7000-8000-deadbeef1234import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.invoices.creditNote("01964a3b-7c8d-7000-8000-deadbeef1234");require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.invoices.credit_note("01964a3b-7c8d-7000-8000-deadbeef1234")from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.invoices.credit_note("01964a3b-7c8d-7000-8000-deadbeef1234")import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
creditNote, err := client.Invoices.CreditNote(ctx, "01964a3b-7c8d-7000-8000-deadbeef1234")let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
let credit_note = client.invoices().credit_note("01964a3b-7c8d-7000-8000-deadbeef1234")?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, credit_note} = Manza.Invoices.credit_note(client, "01964a3b-7c8d-7000-8000-deadbeef1234")require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
credit_note = client.invoices.credit_note("01964a3b-7c8d-7000-8000-deadbeef1234")use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$creditNote = $client->invoices->creditNote('01964a3b-7c8d-7000-8000-deadbeef1234');| Scenario | Status | Type |
|---|---|---|
| Invoice is not in paid status | 422 | state_error |
| A credit note already exists for this invoice | 409 | conflict |
Delete invoice#
DELETE /api/invoices/:id
Only invoices in draft or pending_approval status can be deleted. Responds 204 No Content.
curl -X DELETE "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234" \
-H "Authorization: Bearer sk_live_..."manza invoices delete 01964a3b-7c8d-7000-8000-deadbeef1234import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });
await manza.invoices.delete("01964a3b-7c8d-7000-8000-deadbeef1234");require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])
manza.invoices.delete("01964a3b-7c8d-7000-8000-deadbeef1234")from manza import Manza
client = Manza() # reads MANZA_API_KEY
client.invoices.delete("01964a3b-7c8d-7000-8000-deadbeef1234")import manza "github.com/getmanza/manza-go"
client, _ := manza.New(manza.WithAPIKey(os.Getenv("MANZA_API_KEY")))
_, err := client.Invoices.Delete(ctx, "01964a3b-7c8d-7000-8000-deadbeef1234")let client = manza::Client::builder()
.api_key(std::env::var("MANZA_API_KEY")?)
.build()?;
client.invoices().delete("01964a3b-7c8d-7000-8000-deadbeef1234")?;{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))
{:ok, _} = Manza.Invoices.delete(client, "01964a3b-7c8d-7000-8000-deadbeef1234")require "manza"
client = Manza::Client.new # reads MANZA_API_KEY
client.invoices.delete("01964a3b-7c8d-7000-8000-deadbeef1234")use Manza\Client;
$client = new Client(apiKey: getenv('MANZA_API_KEY'));
$client->invoices->delete('01964a3b-7c8d-7000-8000-deadbeef1234');Status transitions#
API-created invoices land in pending_approval and require a member to approve via the dashboard before they can be sent. The request_approval and approve transitions are not exposed in the API by design. With the invoices:auto_approve scope, creates skip both and land directly on sent.
Web UI only (no API endpoint):
draft → request_approval → pending_approval
pending_approval → approve → approved
API + Web UI:
approved → send → sent
sent → mark_as_paid → paid
sent → cancel → cancelled
sent → (automatic, past due_date) → overdue
overdue → mark_as_paid → paid
overdue → cancel → cancelled
draft, pending_approval → (delete)
paid → credit_note → (new invoice: starts in pending_approval)
With invoices:auto_approve on the key:
create / credit_note → (automatic) → sentInvalid transitions return a state_error:
{
"error": {
"type": "state_error",
"message": "Invoice cannot be sent from 'paid' status"
}
}