Oxapher External API docs
A detailed guide for trusted systems that need to create customers, create paid invoices, read invoice responses, and send invoice messages in Oxapher.
Quick start
- Open Document Settings and create an External API credential.
- Copy the one-time API key and store it in your integration vault.
- Send the key as `Authorization: Bearer oxa_live_...`.
- Include an `Idempotency-Key` on every create or send request.
- Use HTTPS only in production.
What this API is for
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.
What you can do
| 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` |
Important rules
- Keys are scope-limited and can be rotated or revoked.
- Requests are rate limited.
- Duplicate create requests return the same result when the idempotency key matches.
- Production requests must use HTTPS.
- The API is live-only today; for a trial call, use a low-value test customer and a single invoice line, then verify the returned `invoice_number` and `print_url`.
Authentication
Send the credential exactly as a bearer token in the request header:
- `Authorization: Bearer oxa_live_...`
- `Content-Type: application/json`
- `Idempotency-Key: your-unique-key`
If the credential does not include the required scope, Oxapher returns `403`.
Customer create: how to call it
`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.
Customer response
| 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 |
Invoice create: how to call it
`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:
- `tax_invoice`
- `retail_invoice`
- `bill_of_supply`
- `cash_memo`
- `pos`
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.
Invoice response
| 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 |
Read invoice details
`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.
Send invoice
`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`.
Trial call
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:
- Create or resolve a customer with `external_reference` like `checkout-customer-42`.
- Create a single-line `retail_invoice` or `tax_invoice`.
- Keep `paid: true`.
- Set a unique `Idempotency-Key`.
- Save the returned `invoice_number`, `invoice_id`, and `print_url`.
If the same request runs again with the same idempotency key, Oxapher returns the same result instead of creating a duplicate.
Trial request and expected response
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 |
Error handling
| 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 |
Example customer payload
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` |
Example invoice payload
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` |