Sparky Invoices API
Read invoices, clients and payments; create clients and draft invoices; pull clients in from Contacts. Sending and recording payments stay in the app.
- Address
- https://invoices.sparkyli.com/api/v1
- Plan
- Plus and Max, with API access switched on for the organization
- Specification
- https://invoices.sparkyli.com/api/v1/openapi.yaml
Endpoints
GET /clients
Clients, by name.
Inputs
| Name | Type | What it is |
|---|---|---|
| limit | whole number 1 to 100 | Page size. Default 50. |
| offset | whole number | Use next_offset from the previous page. |
Answer: clients (a list) and next_offset
| Name | Type | What it is |
|---|---|---|
| id | text | Use this as client_id when you create an invoice. |
| name | text | The name the client is billed as. |
| contact_name, email, phone, gstin, website | text or null | As entered. |
| payment_terms, currency | text or null | The client's usual terms and three-letter currency. |
| created_at, updated_at | text | UTC time. |
| next_offset | whole number or null | null on the last page. |
POST /clients
Create a client, like New client in the app.
Needs a role that may create clients and a plan with Clients. It does not look for a client with the same name or email: GET /clients first if unsure.
Inputs
| Name | Type | What it is |
|---|---|---|
| Idempotency-Key | header, text up to 120 characters | New for each client. A retry with the same key returns the first answer. |
Body (JSON)
| Name | Type | What it is |
|---|---|---|
| name* | text, up to 200 | The name the client is billed as. |
| contact_name | text, up to 200 | The person at the client. |
| text, up to 200 | Must look like an email address. Invoices are emailed here. | |
| phone | text, up to 40 | |
| billing_address | text, up to 500 | |
| gstin | text, up to 20 | The client's GSTIN. It also sets the place of supply on their invoices. |
| payment_terms | text, up to 100 | For example Net 15. |
| website | text, up to 200 | |
| currency | 3 letters | A supported code such as INR or USD. |
Answer: client
| Name | Type | What it is |
|---|---|---|
| id | text | The new client's id, for POST /invoices. |
| the fields above | As saved, with created_at and updated_at. |
Errors (besides the common ones)
- 400 bad_request: name is missing, the email or currency is not valid, or a field is too long.
Example body
{ "name": "Asha Traders", "email": "accounts@asha.example", "currency": "INR", "payment_terms": "Net 15" } Example answer (201)
{ "client": { "id": "cli_5f0c1d52-7c1e-4e55-9a0b-3d7a8f6c2b10", "name": "Asha Traders", "email": "accounts@asha.example", "currency": "INR", "payment_terms": "Net 15", "created_at": "2026-11-03 07:09:01" } }POST /clients/sync
Pull contacts from Sparky Contacts in as clients, as the Sync page does.
Needs the permission to manage integrations and a plan with syncing from other products. A client edited here is overwritten by its contact's details.
Body (JSON)
| Name | Type | What it is |
|---|---|---|
| contact_id | text | Sync only this contact (its id in Contacts). Leave out to sync every contact changed since the last sync. |
Answer: (top level)
| Name | Type | What it is |
|---|---|---|
| created | whole number | Clients created. |
| updated | whole number | Existing clients updated. |
| links | list | One { contact_id, client_id } per contact synced. Use client_id in POST /invoices. |
Errors (besides the common ones)
- 404 not_found: contact_id does not exist or is archived.
- 502 upstream_unavailable: Sparky Contacts could not be reached. Try again.
Example body
{ "contact_id": "con_8c1f0f3e-1b8e-4a3e-9d0c-6a2c1e5b7a10" } Example answer (201)
{ "created": 1, "updated": 0, "links": [ { "contact_id": "con_8c1f0f3e-1b8e-4a3e-9d0c-6a2c1e5b7a10", "client_id": "cli_5f0c1d52-7c1e-4e55-9a0b-3d7a8f6c2b10" } ] }GET /invoices
Invoices, newest first, with the amount paid so far.
Inputs
| Name | Type | What it is |
|---|---|---|
| status | draft, sent, paid or void | Only invoices in this state. |
| client_id | text | Only this client's invoices. |
| limit, offset | whole numbers | Paging, as for clients. |
Answer: invoices (a list) and next_offset
| Name | Type | What it is |
|---|---|---|
| id, client_id, invoice_number | text | The invoice, its client and its number, like INV-0004. |
| status | draft, sent, paid or void | |
| invoice_type | regular, export_with_tax or export_without_tax | |
| is_credit_note, is_debit_note | 0 or 1 | |
| currency | 3 letters | |
| issue_date, due_date | date, YYYY-MM-DD | due_date can be null. |
| subtotal, tax_total, total, paid | whole number, minor units | For example paise or cents. total is subtotal plus tax. |
| bill_to_name, recipient_email, payment_terms | text or null | |
| sent_at, created_at, updated_at | text or null | UTC time. |
GET /invoices/{id}
One invoice with its lines, and its payments if your role may see them.
Inputs
| Name | Type | What it is |
|---|---|---|
| id* | text | The invoice's id. |
Answer: invoice
| Name | Type | What it is |
|---|---|---|
| (all the list fields, and these) | ||
| notes, place_of_supply, original_invoice_id | text or null | original_invoice_id is set on a credit or debit note. |
| lines | list | description, quantity, unit_price, tax_rate, hsn_sac_code, line_total. |
| payments | list | amount, currency, paid_on, method, label. Only if your role may see payments. |
Errors (besides the common ones)
- 404 not_found: No such invoice in this organization.
GET /payments
Payments recorded against invoices, newest first.
Needs a role that may view payments.
Inputs
| Name | Type | What it is |
|---|---|---|
| invoice_id | text | Only payments on this invoice. |
| method | text | Only this method, for example upi or bank_transfer. |
| from, to | date, YYYY-MM-DD | Earliest and latest payment date. |
| limit, offset | whole numbers | Paging: limit 1 to 100 (default 50); offset from next_offset. |
Answer: payments (a list) and next_offset
| Name | Type | What it is |
|---|---|---|
| id, invoice_id, invoice_number, client_id | text | The payment and the invoice and client it belongs to. |
| bill_to_name | text | The client's name as it was on the invoice. |
| amount | whole number, minor units | As paid, including any tax collected at source. |
| currency | 3 letters | |
| paid_on | date | |
| method | text or null | For example upi, bank_transfer, razorpay or cash. |
| label | text or null | Tags a part payment, such as deposit or balance. |
| tcs_amount, tds_amount | whole number, minor units | Tax collected at source, and tax the payer deducted at source (no cash moved, but it settles the invoice). |
| created_at | text | UTC time. |
GET /payments/{id}
One payment with the invoice it settled. The payment gateway's own reference is never returned.
Inputs
| Name | Type | What it is |
|---|---|---|
| id* | text | The payment's id. |
Answer: payment
| Name | Type | What it is |
|---|---|---|
| (all the list fields, and these) | ||
| invoice_total | whole number, minor units | The invoice's total. |
| invoice_status | draft, sent, paid or void |
Errors (besides the common ones)
- 404 not_found: No such payment in this organization.
POST /invoices
Create a DRAFT invoice for an existing client.
It never creates a client and never sends anything: a person reviews and sends it in the app. It gets the next invoice number and the organization's tax details, as in the app. Needs the permission to create invoices; the plan's monthly invoice limit applies.
Inputs
| Name | Type | What it is |
|---|---|---|
| Idempotency-Key | header, text up to 120 characters | New for each invoice. A retry with the same key returns the same invoice, so a monthly job can be re-run safely. |
Body (JSON)
| Name | Type | What it is |
|---|---|---|
| client_id* | text | An existing client in this organization: from GET /clients, POST /clients or the sync result. |
| line_items* | list of 1 to 100 | The lines, below. |
| currency | 3 letters | Default INR. |
| issue_date | date, YYYY-MM-DD | Default today. |
| due_date | date, YYYY-MM-DD | |
| payment_terms | text, up to 100 | |
| notes | text, up to 2000 | Shown on the invoice. |
| invoice_type | regular, export_with_tax or export_without_tax | Default regular. |
| place_of_supply | text, up to 10 | GST state code. Default: the client's GSTIN state. |
| line_items[].description* | text, up to 500 | |
| line_items[].unit_price* | whole number, minor units | Price of one unit; 5000000 is 50,000.00. |
| line_items[].quantity | number | Default 1. |
| line_items[].tax_rate | number 0 to 100 | Percent, for example 18. Default 0. |
| line_items[].hsn_sac_code | text, up to 20 |
Answer: invoice
| Name | Type | What it is |
|---|---|---|
| id, invoice_number, status | text | status is draft. |
| subtotal, tax_total, total | whole number, minor units | Worked out from the lines. |
| line_items | list | The saved lines with their ids and line_total. |
| the other invoice fields | As in GET /invoices/{id}. |
Errors (besides the common ones)
- 404 client_not_found: No client with that client_id in this organization. Create it first.
- 400 bad_request: A field is missing or wrong; the message names it (for example line_items[0].unit_price).
- 403 plan_limit: The plan's invoice limit for this month is reached.
Example body
{ "client_id": "cli_5f0c1d52-7c1e-4e55-9a0b-3d7a8f6c2b10", "issue_date": "2026-12-01", "due_date": "2026-12-15", "line_items": [ { "description": "Monthly retainer", "quantity": 1, "unit_price": 5000000, "tax_rate": 18 } ] } Example answer (201)
{ "invoice": { "id": "inv_0b7a6d4e-2f4b-4b0b-8e57-6a9c1f2d3e44", "client_id": "cli_5f0c1d52-7c1e-4e55-9a0b-3d7a8f6c2b10", "invoice_number": "INV-0004", "status": "draft", "currency": "INR", "subtotal": 5000000, "tax_total": 900000, "total": 5900000, "line_items": [ { "description": "Monthly retainer", "quantity": 1, "unit_price": 5000000, "tax_rate": 18, "line_total": 5000000 } ] } }Example
curl -s -H "authorization: Bearer $TOKEN" "https://invoices.sparkyli.com/api/v1/invoices?status=sent" New here? Start with the three things first, then sign in. Errors are explained under errors and limits.