REST API

Invoices API

Create, manage, and track invoices and customers programmatically. Requires the release_invoicing feature flag.

Required scopes#

EndpointScope
List / Get customerscustomers:read
Create / Update / Delete customerscustomers:write
List / Get invoicesinvoices:read
Create / Update / Delete invoicesinvoices:write
Send / Mark as paid / Cancel invoicesinvoices:send
Create credit noteinvoices:write
Auto-approve created invoices (behavior, not an endpoint)invoices:auto_approve

Approval before send#

Default flow: dashboard approval
By default the API creates invoices in 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.
Auto-approval with the invoices:auto_approve scope
A key granted the 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.

FieldTypeDescription
idstringUnique identifier (UUIDv7)
customer_typestringindividual or business (auto-detected from company_name, and from registration_number where the market collects one)
person_namestringIndividual's name
company_namestringCompany name (typically for business customers)
namestringComputed: company_name if present, else person_name
emailstringEmail address (unique per entity)
phonestringPhone number
tax_idstringTax identification number (identifiant fiscal; optional; any country's format, must include at least one digit, at most 50 characters)
ice_numberstringICE number (optional; must be exactly 15 digits when set)
billing_addressobjectBilling address
invoices_countintegerNumber of invoices (list only)
created_atstringISO 8601 timestamp
updated_atstringISO 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).

request.sh
curl "https://ma.manza.dev/api/customers?q=acme" \
  -H "Authorization: Bearer sk_live_..."
manza
manza customers list
app.js
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.customers.list({ q: "acme" });
app.rb
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.customers.list(q: "acme")
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.customers.list(q="acme")
main.go
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"})
main.rs
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)?;
app.exs
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

{:ok, page} = Manza.Customers.list(client, q: "acme")
app.cr
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

page = client.customers.list(q: "acme")
app.php
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.

request.sh
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
manza customers create --file customer.json
app.js
import { 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",
});
app.rb
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",
)
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.customers.create(
    customer_type="business",
    company_name="Acme Corp",
    email="billing@acme.com",
)
main.go
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",
})
main.rs
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",
}))?;
app.exs
{: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"
  })
app.cr
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"
)
app.php
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

request.sh
curl "https://ma.manza.dev/api/customers/01964a3b-0000-7000-8000-customer0001" \
  -H "Authorization: Bearer sk_live_..."
manza
manza customers get 01964a3b-0000-7000-8000-customer0001
app.js
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.customers.get("01964a3b-0000-7000-8000-customer0001");
app.rb
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.customers.get("01964a3b-0000-7000-8000-customer0001")
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.customers.get("01964a3b-0000-7000-8000-customer0001")
main.go
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")
main.rs
let client = manza::Client::builder()
    .api_key(std::env::var("MANZA_API_KEY")?)
    .build()?;

let customer = client.customers().get("01964a3b-0000-7000-8000-customer0001")?;
app.exs
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

{:ok, customer} = Manza.Customers.get(client, "01964a3b-0000-7000-8000-customer0001")
app.cr
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

customer = client.customers.get("01964a3b-0000-7000-8000-customer0001")
app.php
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.

request.sh
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
manza customers update 01964a3b-0000-7000-8000-customer0001 --file customer.json
app.js
import { 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" });
app.rb
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.customers.update("01964a3b-0000-7000-8000-customer0001", email: "newemail@acme.com")
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.customers.update("01964a3b-0000-7000-8000-customer0001", email="newemail@acme.com")
main.go
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"})
main.rs
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" }))?;
app.exs
{: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"})
app.cr
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

customer = client.customers.update("01964a3b-0000-7000-8000-customer0001", email: "newemail@acme.com")
app.php
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.

request.sh
curl -X DELETE "https://ma.manza.dev/api/customers/01964a3b-0000-7000-8000-customer0001" \
  -H "Authorization: Bearer sk_live_..."
manza
manza customers delete 01964a3b-0000-7000-8000-customer0001
app.js
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.customers.delete("01964a3b-0000-7000-8000-customer0001");
app.rb
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.customers.delete("01964a3b-0000-7000-8000-customer0001")
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.customers.delete("01964a3b-0000-7000-8000-customer0001")
main.go
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")
main.rs
let client = manza::Client::builder()
    .api_key(std::env::var("MANZA_API_KEY")?)
    .build()?;

client.customers().delete("01964a3b-0000-7000-8000-customer0001")?;
app.exs
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

{:ok, _} = Manza.Customers.delete(client, "01964a3b-0000-7000-8000-customer0001")
app.cr
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

client.customers.delete("01964a3b-0000-7000-8000-customer0001")
app.php
use Manza\Client;

$client = new Client(apiKey: getenv('MANZA_API_KEY'));

$client->customers->delete('01964a3b-0000-7000-8000-customer0001');

Invoice object#

FieldTypeDescription
idstringUnique identifier (UUIDv7)
numberstringAuto-generated number (default <prefix><date>-<sequence>, configurable per entity)
statusstringdraft, pending_approval, approved, sent, paid, overdue, cancelled
currency_codestringISO 4217 currency code
subtotalstringSum of item amounts
discount_totalstringTotal discount amount
tax_totalstringTotal tax amount
totalstringFinal amount due
issue_datestringDate (YYYY-MM-DD)
due_datestringDate (YYYY-MM-DD)
delivery_datestringDelivery date (YYYY-MM-DD, optional)
referencestringPO number or external reference
notesstringFree-text notes
payment_termsstringPayment terms text
send_to_emailstringEmail for sending the invoice
credited_invoice_idstring/nullID of the credited invoice
credit_notebooleanWhether this invoice is a credit note
sent_atstringISO 8601 timestamp
paid_atstringISO 8601 timestamp
customerobjectSnapshotted customer data
billing_addressobjectSnapshotted billing address
itemsarrayLine items (show only)
discountsarrayDiscounts (show only)
payment_linkobjectAttached payment link (show only)
created_atstringISO 8601 timestamp
updated_atstringISO 8601 timestamp
Amounts are strings
Financial amounts are serialized as strings to preserve decimal precision.

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.

request.sh
curl "https://ma.manza.dev/api/invoices?status=sent" \
  -H "Authorization: Bearer sk_live_..."
manza
manza invoices list
app.js
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.invoices.list({ status: "sent" });
app.rb
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.list(status: "sent")
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.list(status="sent")
main.go
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"})
main.rs
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)?;
app.exs
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

{:ok, page} = Manza.Invoices.list(client, status: "sent")
app.cr
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

page = client.invoices.list(status: "sent")
app.php
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.

request.sh
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
manza invoices create --file invoice.json
app.js
import { 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" },
  ],
});
app.rb
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" },
  ],
)
app.py
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"},
    ],
)
main.go
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"},
    },
})
main.rs
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" },
    ],
}))?;
app.exs
{: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"}
    ]
  })
app.cr
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"},
  ]
)
app.php
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.

request.sh
curl "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234" \
  -H "Authorization: Bearer sk_live_..."
manza
manza invoices get 01964a3b-7c8d-7000-8000-deadbeef1234
app.js
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.invoices.get("01964a3b-7c8d-7000-8000-deadbeef1234");
app.rb
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.get("01964a3b-7c8d-7000-8000-deadbeef1234")
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.get("01964a3b-7c8d-7000-8000-deadbeef1234")
main.go
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")
main.rs
let client = manza::Client::builder()
    .api_key(std::env::var("MANZA_API_KEY")?)
    .build()?;

let invoice = client.invoices().get("01964a3b-7c8d-7000-8000-deadbeef1234")?;
app.exs
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

{:ok, invoice} = Manza.Invoices.get(client, "01964a3b-7c8d-7000-8000-deadbeef1234")
app.cr
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

invoice = client.invoices.get("01964a3b-7c8d-7000-8000-deadbeef1234")
app.php
use Manza\Client;

$client = new Client(apiKey: getenv('MANZA_API_KEY'));

$invoice = $client->invoices->get('01964a3b-7c8d-7000-8000-deadbeef1234');
invoice.json
{
  "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.

request.sh
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
manza invoices update 01964a3b-7c8d-7000-8000-deadbeef1234 --file invoice.json
app.js
import { 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" });
app.rb
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.update("01964a3b-7c8d-7000-8000-deadbeef1234", notes: "Updated terms")
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.update("01964a3b-7c8d-7000-8000-deadbeef1234", notes="Updated terms")
main.go
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"})
main.rs
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" }))?;
app.exs
{: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"})
app.cr
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

invoice = client.invoices.update("01964a3b-7c8d-7000-8000-deadbeef1234", notes: "Updated terms")
app.php
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.

request.sh
curl -X POST "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234/send" \
  -H "Authorization: Bearer sk_live_..."
manza
manza invoices send 01964a3b-7c8d-7000-8000-deadbeef1234
app.js
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.invoices.send("01964a3b-7c8d-7000-8000-deadbeef1234");
app.rb
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.send_invoice("01964a3b-7c8d-7000-8000-deadbeef1234")
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.send_invoice("01964a3b-7c8d-7000-8000-deadbeef1234")
main.go
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")
main.rs
let client = manza::Client::builder()
    .api_key(std::env::var("MANZA_API_KEY")?)
    .build()?;

let invoice = client.invoices().send("01964a3b-7c8d-7000-8000-deadbeef1234")?;
app.exs
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

{:ok, invoice} = Manza.Invoices.send(client, "01964a3b-7c8d-7000-8000-deadbeef1234")
app.cr
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

invoice = client.invoices.send("01964a3b-7c8d-7000-8000-deadbeef1234")
app.php
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.

request.sh
curl -X POST "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234/mark_as_paid" \
  -H "Authorization: Bearer sk_live_..."
manza
manza invoices mark-as-paid 01964a3b-7c8d-7000-8000-deadbeef1234
app.js
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.invoices.markAsPaid("01964a3b-7c8d-7000-8000-deadbeef1234");
app.rb
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.mark_as_paid("01964a3b-7c8d-7000-8000-deadbeef1234")
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.mark_as_paid("01964a3b-7c8d-7000-8000-deadbeef1234")
main.go
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")
main.rs
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")?;
app.exs
{: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")
app.cr
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

invoice = client.invoices.mark_as_paid("01964a3b-7c8d-7000-8000-deadbeef1234")
app.php
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.

request.sh
curl -X POST "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234/cancel" \
  -H "Authorization: Bearer sk_live_..."
manza
manza invoices cancel 01964a3b-7c8d-7000-8000-deadbeef1234
app.js
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.invoices.cancel("01964a3b-7c8d-7000-8000-deadbeef1234");
app.rb
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.cancel("01964a3b-7c8d-7000-8000-deadbeef1234")
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.cancel("01964a3b-7c8d-7000-8000-deadbeef1234")
main.go
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")
main.rs
let client = manza::Client::builder()
    .api_key(std::env::var("MANZA_API_KEY")?)
    .build()?;

let invoice = client.invoices().cancel("01964a3b-7c8d-7000-8000-deadbeef1234")?;
app.exs
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

{:ok, invoice} = Manza.Invoices.cancel(client, "01964a3b-7c8d-7000-8000-deadbeef1234")
app.cr
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

invoice = client.invoices.cancel("01964a3b-7c8d-7000-8000-deadbeef1234")
app.php
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.

request.sh
curl -X POST "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234/credit_note" \
  -H "Authorization: Bearer sk_live_..."
manza
manza invoices credit-note 01964a3b-7c8d-7000-8000-deadbeef1234
app.js
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.invoices.creditNote("01964a3b-7c8d-7000-8000-deadbeef1234");
app.rb
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.credit_note("01964a3b-7c8d-7000-8000-deadbeef1234")
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.credit_note("01964a3b-7c8d-7000-8000-deadbeef1234")
main.go
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")
main.rs
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")?;
app.exs
{: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")
app.cr
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

credit_note = client.invoices.credit_note("01964a3b-7c8d-7000-8000-deadbeef1234")
app.php
use Manza\Client;

$client = new Client(apiKey: getenv('MANZA_API_KEY'));

$creditNote = $client->invoices->creditNote('01964a3b-7c8d-7000-8000-deadbeef1234');
ScenarioStatusType
Invoice is not in paid status422state_error
A credit note already exists for this invoice409conflict

Delete invoice#

DELETE /api/invoices/:id

Only invoices in draft or pending_approval status can be deleted. Responds 204 No Content.

request.sh
curl -X DELETE "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234" \
  -H "Authorization: Bearer sk_live_..."
manza
manza invoices delete 01964a3b-7c8d-7000-8000-deadbeef1234
app.js
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.invoices.delete("01964a3b-7c8d-7000-8000-deadbeef1234");
app.rb
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.delete("01964a3b-7c8d-7000-8000-deadbeef1234")
app.py
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.delete("01964a3b-7c8d-7000-8000-deadbeef1234")
main.go
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")
main.rs
let client = manza::Client::builder()
    .api_key(std::env::var("MANZA_API_KEY")?)
    .build()?;

client.invoices().delete("01964a3b-7c8d-7000-8000-deadbeef1234")?;
app.exs
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

{:ok, _} = Manza.Invoices.delete(client, "01964a3b-7c8d-7000-8000-deadbeef1234")
app.cr
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

client.invoices.delete("01964a3b-7c8d-7000-8000-deadbeef1234")
app.php
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.

transitions.txt
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) → sent

Invalid transitions return a state_error:

state_error.json
{
  "error": {
    "type": "state_error",
    "message": "Invoice cannot be sent from 'paid' status"
  }
}