REST API

# 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

> **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](https://ma.manza.dev/docs/recurring-billing).

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

CLI

JavaScript

Ruby

Python

Go

Rust

Elixir

Crystal

PHP

```console
curl "https://ma.manza.dev/api/customers?q=acme" \
  -H "Authorization: Bearer sk_live_..."
```

```console
manza customers list
```

```javascript
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.customers.list({ q: "acme" });
```

```ruby
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.customers.list(q: "acme")
```

```python
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.customers.list(q="acme")
```

```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"})
```

```rust
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)?;
```

```elixir
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

{:ok, page} = Manza.Customers.list(client, q: "acme")
```

```crystal
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

page = client.customers.list(q: "acme")
```

```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.

cURL

CLI

JavaScript

Ruby

Python

Go

Rust

Elixir

Crystal

PHP

```console
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"
  }
}'
```

```console
manza customers create --file customer.json
```

```javascript
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",
});
```

```ruby
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",
)
```

```python
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.customers.create(
    customer_type="business",
    company_name="Acme Corp",
    email="billing@acme.com",
)
```

```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",
})
```

```rust
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",
}))?;
```

```elixir
{: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"
  })
```

```crystal
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"
)
```

```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`

cURL

CLI

JavaScript

Ruby

Python

Go

Rust

Elixir

Crystal

PHP

```console
curl "https://ma.manza.dev/api/customers/01964a3b-0000-7000-8000-customer0001" \
  -H "Authorization: Bearer sk_live_..."
```

```console
manza customers get 01964a3b-0000-7000-8000-customer0001
```

```javascript
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.customers.get("01964a3b-0000-7000-8000-customer0001");
```

```ruby
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.customers.get("01964a3b-0000-7000-8000-customer0001")
```

```python
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.customers.get("01964a3b-0000-7000-8000-customer0001")
```

```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")
```

```rust
let client = manza::Client::builder()
    .api_key(std::env::var("MANZA_API_KEY")?)
    .build()?;

let customer = client.customers().get("01964a3b-0000-7000-8000-customer0001")?;
```

```elixir
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

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

```crystal
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

customer = client.customers.get("01964a3b-0000-7000-8000-customer0001")
```

```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.

cURL

CLI

JavaScript

Ruby

Python

Go

Rust

Elixir

Crystal

PHP

```console
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"
  }
}'
```

```console
manza customers update 01964a3b-0000-7000-8000-customer0001 --file customer.json
```

```javascript
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" });
```

```ruby
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.customers.update("01964a3b-0000-7000-8000-customer0001", email: "newemail@acme.com")
```

```python
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.customers.update("01964a3b-0000-7000-8000-customer0001", email="newemail@acme.com")
```

```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"})
```

```rust
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" }))?;
```

```elixir
{: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"})
```

```crystal
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

customer = client.customers.update("01964a3b-0000-7000-8000-customer0001", email: "newemail@acme.com")
```

```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`.

cURL

CLI

JavaScript

Ruby

Python

Go

Rust

Elixir

Crystal

PHP

```console
curl -X DELETE "https://ma.manza.dev/api/customers/01964a3b-0000-7000-8000-customer0001" \
  -H "Authorization: Bearer sk_live_..."
```

```console
manza customers delete 01964a3b-0000-7000-8000-customer0001
```

```javascript
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.customers.delete("01964a3b-0000-7000-8000-customer0001");
```

```ruby
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.customers.delete("01964a3b-0000-7000-8000-customer0001")
```

```python
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.customers.delete("01964a3b-0000-7000-8000-customer0001")
```

```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")
```

```rust
let client = manza::Client::builder()
    .api_key(std::env::var("MANZA_API_KEY")?)
    .build()?;

client.customers().delete("01964a3b-0000-7000-8000-customer0001")?;
```

```elixir
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

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

```crystal
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

client.customers.delete("01964a3b-0000-7000-8000-customer0001")
```

```php
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 |

> **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.

cURL

CLI

JavaScript

Ruby

Python

Go

Rust

Elixir

Crystal

PHP

```console
curl "https://ma.manza.dev/api/invoices?status=sent" \
  -H "Authorization: Bearer sk_live_..."
```

```console
manza invoices list
```

```javascript
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.invoices.list({ status: "sent" });
```

```ruby
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.list(status: "sent")
```

```python
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.list(status="sent")
```

```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"})
```

```rust
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)?;
```

```elixir
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

{:ok, page} = Manza.Invoices.list(client, status: "sent")
```

```crystal
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

page = client.invoices.list(status: "sent")
```

```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`.

cURL

CLI

JavaScript

Ruby

Python

Go

Rust

Elixir

Crystal

PHP

```console
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"
    }
  ]
}'
```

```console
manza invoices create --file invoice.json
```

```javascript
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" },
  ],
});
```

```ruby
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" },
  ],
)
```

```python
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"},
    ],
)
```

```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"},
    },
})
```

```rust
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" },
    ],
}))?;
```

```elixir
{: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"}
    ]
  })
```

```crystal
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"},
  ]
)
```

```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.

cURL

CLI

JavaScript

Ruby

Python

Go

Rust

Elixir

Crystal

PHP

```console
curl "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234" \
  -H "Authorization: Bearer sk_live_..."
```

```console
manza invoices get 01964a3b-7c8d-7000-8000-deadbeef1234
```

```javascript
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.invoices.get("01964a3b-7c8d-7000-8000-deadbeef1234");
```

```ruby
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.get("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```python
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.get("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```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")
```

```rust
let client = manza::Client::builder()
    .api_key(std::env::var("MANZA_API_KEY")?)
    .build()?;

let invoice = client.invoices().get("01964a3b-7c8d-7000-8000-deadbeef1234")?;
```

```elixir
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

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

```crystal
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

invoice = client.invoices.get("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```php
use Manza\Client;

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

$invoice = $client->invoices->get('01964a3b-7c8d-7000-8000-deadbeef1234');
```

```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.

cURL

CLI

JavaScript

Ruby

Python

Go

Rust

Elixir

Crystal

PHP

```console
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"}'
```

```console
manza invoices update 01964a3b-7c8d-7000-8000-deadbeef1234 --file invoice.json
```

```javascript
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" });
```

```ruby
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.update("01964a3b-7c8d-7000-8000-deadbeef1234", notes: "Updated terms")
```

```python
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.update("01964a3b-7c8d-7000-8000-deadbeef1234", notes="Updated terms")
```

```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"})
```

```rust
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" }))?;
```

```elixir
{: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"})
```

```crystal
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

invoice = client.invoices.update("01964a3b-7c8d-7000-8000-deadbeef1234", notes: "Updated terms")
```

```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.

cURL

CLI

JavaScript

Ruby

Python

Go

Rust

Elixir

Crystal

PHP

```console
curl -X POST "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234/send" \
  -H "Authorization: Bearer sk_live_..."
```

```console
manza invoices send 01964a3b-7c8d-7000-8000-deadbeef1234
```

```javascript
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.invoices.send("01964a3b-7c8d-7000-8000-deadbeef1234");
```

```ruby
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.send_invoice("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```python
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.send_invoice("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```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")
```

```rust
let client = manza::Client::builder()
    .api_key(std::env::var("MANZA_API_KEY")?)
    .build()?;

let invoice = client.invoices().send("01964a3b-7c8d-7000-8000-deadbeef1234")?;
```

```elixir
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

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

```crystal
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

invoice = client.invoices.send("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```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`.

cURL

CLI

JavaScript

Ruby

Python

Go

Rust

Elixir

Crystal

PHP

```console
curl -X POST "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234/mark_as_paid" \
  -H "Authorization: Bearer sk_live_..."
```

```console
manza invoices mark-as-paid 01964a3b-7c8d-7000-8000-deadbeef1234
```

```javascript
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.invoices.markAsPaid("01964a3b-7c8d-7000-8000-deadbeef1234");
```

```ruby
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.mark_as_paid("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```python
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.mark_as_paid("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```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")
```

```rust
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")?;
```

```elixir
{: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")
```

```crystal
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

invoice = client.invoices.mark_as_paid("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```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`.

cURL

CLI

JavaScript

Ruby

Python

Go

Rust

Elixir

Crystal

PHP

```console
curl -X POST "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234/cancel" \
  -H "Authorization: Bearer sk_live_..."
```

```console
manza invoices cancel 01964a3b-7c8d-7000-8000-deadbeef1234
```

```javascript
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.invoices.cancel("01964a3b-7c8d-7000-8000-deadbeef1234");
```

```ruby
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.cancel("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```python
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.cancel("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```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")
```

```rust
let client = manza::Client::builder()
    .api_key(std::env::var("MANZA_API_KEY")?)
    .build()?;

let invoice = client.invoices().cancel("01964a3b-7c8d-7000-8000-deadbeef1234")?;
```

```elixir
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

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

```crystal
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

invoice = client.invoices.cancel("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```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.

cURL

CLI

JavaScript

Ruby

Python

Go

Rust

Elixir

Crystal

PHP

```console
curl -X POST "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234/credit_note" \
  -H "Authorization: Bearer sk_live_..."
```

```console
manza invoices credit-note 01964a3b-7c8d-7000-8000-deadbeef1234
```

```javascript
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.invoices.creditNote("01964a3b-7c8d-7000-8000-deadbeef1234");
```

```ruby
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.credit_note("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```python
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.credit_note("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```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")
```

```rust
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")?;
```

```elixir
{: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")
```

```crystal
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

credit_note = client.invoices.credit_note("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```php
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

CLI

JavaScript

Ruby

Python

Go

Rust

Elixir

Crystal

PHP

```console
curl -X DELETE "https://ma.manza.dev/api/invoices/01964a3b-7c8d-7000-8000-deadbeef1234" \
  -H "Authorization: Bearer sk_live_..."
```

```console
manza invoices delete 01964a3b-7c8d-7000-8000-deadbeef1234
```

```javascript
import { Manza } from "@getmanza/sdk";
const manza = new Manza({ apiKey: process.env.MANZA_API_KEY });

await manza.invoices.delete("01964a3b-7c8d-7000-8000-deadbeef1234");
```

```ruby
require "manza"
manza = Manza.new(api_key: ENV["MANZA_API_KEY"])

manza.invoices.delete("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```python
from manza import Manza
client = Manza()  # reads MANZA_API_KEY

client.invoices.delete("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```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")
```

```rust
let client = manza::Client::builder()
    .api_key(std::env::var("MANZA_API_KEY")?)
    .build()?;

client.invoices().delete("01964a3b-7c8d-7000-8000-deadbeef1234")?;
```

```elixir
{:ok, client} = Manza.new(api_key: System.fetch_env!("MANZA_API_KEY"))

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

```crystal
require "manza"

client = Manza::Client.new # reads MANZA_API_KEY

client.invoices.delete("01964a3b-7c8d-7000-8000-deadbeef1234")
```

```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`.

```
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:

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