Is there a sandbox or dry-run mode?
Not today. Use a low-value live test invoice and a unique idempotency key for the first call.
A detailed guide for trusted systems that need to create customers, create paid invoices, read invoice responses, and send invoice messages in Oxapher.
Use the external API when another system already knows a customer or payment has happened and Oxapher should create the matching customer record, issue the invoice, post the payment, and return the number and print link.
This is the same business workflow the app uses internally. That means numbering, accounts posting, payment status, print/share generation, and delivery rules stay consistent.
| Action | Endpoint | Scope | What you get back |
|---|---|---|---|
| Create or resolve a customer | `POST /api/external/v1/customers` | `customers:write` | `customer`, `created`, and idempotent replay support |
| Create a paid invoice | `POST /api/external/v1/invoices` | `invoices:write` | `invoice_id`, `invoice_number`, `print_url`, `payment_status`, and `delivery` |
| Read invoice details | `GET /api/external/v1/invoices/{sales_document_id}` | `invoices:read` | The normal invoice detail payload used by Oxapher |
| Send invoice by email or WhatsApp | `POST /api/external/v1/invoices/{sales_document_id}/send` | `invoices:send` | `channel`, `status`, and `print_url` |
Send the credential exactly as a bearer token in the request header:
If the credential does not include the required scope, Oxapher returns `403`.
`POST /api/external/v1/customers`
Use this when the upstream system already has the customer identity and you want Oxapher to create the matching customer, or return the existing one if `external_reference` is reused.
Important fields:
| Field | Required | Notes |
|---|---|---|
| `external_reference` | No | Stable upstream key that makes later calls resolve to the same Oxapher customer |
| `business` | No | Company name |
| `contact_name` | No | Person name |
| `mobile` | No | Primary mobile number |
| `email` | No | Email address |
| `country` | No | Defaults to India in the app |
| `branch_id` | No | Use when the customer belongs to a specific branch |
If you send the same `external_reference` again, Oxapher resolves the same customer instead of creating a duplicate.
| Field | Meaning |
|---|---|
| `customer.customer_id` | Oxapher customer id |
| `customer.business` | Business name |
| `customer.contact_name` | Contact person |
| `customer.mobile` | Mobile number |
| `customer.email` | Email address |
| `created` | `true` when a new customer was created, `false` when an existing customer was reused |
`POST /api/external/v1/invoices`
The invoice payload can either embed a `customer` object or send `customer_id` if the customer already exists.
You can create these invoice types:
Important invoice fields:
| Field | Required | Notes |
|---|---|---|
| `invoice_type` | Yes | Controls the invoice variant |
| `customer` or `customer_id` | Yes for all except `cash_memo` / `pos` | Embed a customer object or reference an existing one |
| `invoice.lines` | Yes | Each line should include item, quantity, and rate data the app accepts |
| `paid` | No | Defaults to paid for invoice creation through this API |
| `payment` | No | Payment mode details used when the invoice is posted and paid |
| `deduct_stock` | No | Set to `false` if you do not want stock to move |
| `send` | No | Optional delivery instructions for email and WhatsApp |
The create call does more than save a draft. It posts the invoice, records the payment when `paid` is true, and returns the print link the same way the app would.
| Field | Meaning |
|---|---|
| `invoice_id` | Oxapher sales document id |
| `sales_document_id` | Same value as `invoice_id` |
| `invoice_number` | Human-readable invoice number |
| `document_number` | Same value as `invoice_number` |
| `invoice_type` | The requested invoice variant |
| `status` | Current invoice status |
| `payment_status` | Current payment status |
| `grand_total` | Final invoice amount |
| `customer_created` | Whether the customer was created during this call |
| `account_voucher_id` | Linked Accounts voucher id when posting created one |
| `print_url` | Public share/print link for the invoice |
| `delivery` | Email or WhatsApp delivery results when `send` was supplied |
`GET /api/external/v1/invoices/{sales_document_id}`
Use this when a caller already has the Oxapher invoice id and wants the current document data, status, totals, party details, and approval context.
The response is the standard Oxapher sales-document payload for that invoice. It includes the invoice identity, party details, line items, amounts, status, and any linked approval or accounting context.
`POST /api/external/v1/invoices/{sales_document_id}/send`
Use this when the invoice already exists and you want to trigger delivery after the fact.
| Channel | Notes |
|---|---|
| `email` | Sends to the invoice email recipient or the address you provide |
| `whatsapp` | Sends to the invoice WhatsApp recipient or the mobile number you provide |
| `link` | Returns a fresh print/share link |
You can also send delivery instructions inside the invoice create call through `send`.
There is no separate sandbox endpoint in the current API. The safest first test is a live call with a test customer and one invoice line.
Recommended trial flow:
If the same request runs again with the same idempotency key, Oxapher returns the same result instead of creating a duplicate.
Use these tables as a copy checklist before wiring code:
| Customer trial call | Value |
|---|---|
| Method | `POST` |
| URL | `/api/external/v1/customers` |
| Required headers | `Authorization`, `Content-Type`, `Idempotency-Key` |
| Minimum body | `external_reference`, plus at least one of `business`, `contact_name`, `mobile`, or `email` |
| Success response | `customer.customer_id` and `created` |
| Invoice trial call | Value |
|---|---|
| Method | `POST` |
| URL | `/api/external/v1/invoices` |
| Required headers | `Authorization`, `Content-Type`, `Idempotency-Key` |
| Minimum body | `invoice_type`, `customer` or `customer_id`, and `invoice.lines` |
| Default payment behavior | `paid` is true unless you explicitly send false |
| Success response | `invoice_id`, `invoice_number`, `payment_status`, `grand_total`, `print_url`, and optional `delivery` |
| Common response field | Developer use |
|---|---|
| `invoice_id` | Store this id for later read/send calls |
| `invoice_number` | Show this number to the customer or upstream system |
| `payment_status` | Confirm whether the invoice was posted as paid |
| `print_url` | Open this URL to print or share the invoice |
| `delivery.status` | Check whether email or WhatsApp sending was triggered |
| Status | Meaning |
|---|---|
| `400` | Missing or invalid fields |
| `401` | Missing or invalid bearer token |
| `403` | Credential does not have the needed scope |
| `404` | Invoice or customer could not be found |
| `409` | The idempotency key was reused incorrectly or a request is still processing |
| `429` | Rate limit reached |
Use this shape when a checkout or ERP companion app creates a customer first:
| Field | Example |
|---|---|
| `external_reference` | `stripe-cus_1024` |
| `business` | `Example Stores` |
| `contact_name` | `Asha Patel` |
| `mobile` | `9876543210` |
| `email` | `accounts@example.test` |
| `country` | `India` |
This is the simple billing pattern:
| Field | Example |
|---|---|
| `invoice_type` | `retail_invoice` |
| `customer.external_reference` | `stripe-cus_1024` |
| `paid` | `true` |
| `payment.mode` | `cash` |
| `invoice.lines[0].item_id` | `API-SERVICE-1` |
| `invoice.lines[0].quantity` | `1` |
| `invoice.lines[0].unit_rate` | `500` |
| `invoice.lines[0].gst_rate` | `0` |
| `invoice.billing_address` | `12 Test Road, Ahmedabad, Gujarat, 380015, India` |
| `invoice.shipping_address` | `12 Test Road, Ahmedabad, Gujarat, 380015, India` |
| `send.email.recipient` | `buyer@example.test` |
| `send.whatsapp.recipient` | `9876543210` |
Not today. Use a low-value live test invoice and a unique idempotency key for the first call.
It returns the invoice id, invoice number, payment status, print URL, accounting voucher id, and any delivery results.
Yes. Send a customer object inside the invoice create payload and Oxapher will create or resolve that customer first.
Include a send object during invoice creation or call the separate send endpoint after the invoice exists.
It prevents duplicate customers, duplicate invoices, and duplicate sends when an upstream system retries a request.