Solavel Solavel Docs
Developer Reference

SolaBooks Finance API

Version: v1 Base URL: https://solavel.com/api/v1 (replace with your domain) Format: JSON 33 endpoints

The SolaBooks Finance API lets you read and write accounting data programmatically — customers, suppliers, invoices, bills, payments, journal entries, and financial reports. All endpoints return JSON. Create an API key →

Authentication

Every protected endpoint requires three headers:

X-API-Key: solabooks_<prefix>.<secret>
X-Organization-Id: 1
X-Client-Id: 4
  • X-API-Key — the full key shown once at creation in Settings → API Access
  • X-Organization-Id — your organization's integer ID (visible in the API Access page)
  • X-Client-Id — your client ID (visible in the API Access page)
Generate a key: Go to Settings → API Access in SolaBooks. The full key is shown exactly once. Each key has scopes — only grant the scopes you need.

The GET /api/v1/health endpoint requires no authentication and can be used to verify connectivity.

Response Format

All responses use a consistent JSON envelope.

Single object

{
  "success": true,
  "data": { ... }
}

Paginated list

{
  "success": true,
  "data": [ ... ],
  "meta": {
    "current_page": 1,
    "per_page": 25,
    "total": 100,
    "last_page": 4
  }
}

Error

{
  "success": false,
  "error": {
    "code": "error_code",
    "message": "Human readable message."
  }
}

Quick Test

Replace your_key, 1, and 4 with your actual API key, organization ID, and client ID.

Health check (no auth required)

curl https://solavel.com/finance/api/v1/health

List invoices

curl https://solavel.com/finance/api/v1/invoices \
  -H "X-API-Key: your_key" \
  -H "X-Organization-Id: 1" \
  -H "X-Client-Id: 4"

Create a customer

curl -X POST https://solavel.com/finance/api/v1/customers \
  -H "X-API-Key: your_key" \
  -H "X-Organization-Id: 1" \
  -H "X-Client-Id: 4" \
  -H "Content-Type: application/json" \
  -d '{"name":"Acme Corp","email":"billing@acme.com"}'

Create a journal entry

curl -X POST https://solavel.com/finance/api/v1/journal-entries \
  -H "X-API-Key: your_key" \
  -H "X-Organization-Id: 1" \
  -H "X-Client-Id: 4" \
  -H "Content-Type: application/json" \
  -d '{
    "date": "2026-06-07",
    "reference": "JE-001",
    "description": "Manual accrual",
    "lines": [
      {"account_id": 101, "debit": 1000, "credit": 0},
      {"account_id": 202, "debit": 0, "credit": 1000}
    ]
  }'

All Endpoints

Click any endpoint to expand its details. 33 endpoints total across 8 resource groups.

Health

GET /api/v1/health no auth

Confirm the API is online and responding. No authentication required.

Response HTTP 200

{"success":true,"data":{"service":"finance-api","status":"ok","timestamp":"2026-06-07T12:00:00+00:00"}}

Customers

GET /api/v1/customers scope: customers.get

List all customers. Supports search and pagination.

Query parameters

ParameterTypeDescription
search string optional Filter by name, email, or phone (partial match)
page integer optional Page number (default: 1)
per_page integer optional Results per page (default: 25, max: 100)

Response HTTP 200

{"success":true,"data":[{"id":1,"name":"Acme Corp","email":"billing@acme.com","phone":"+1-555-0100","is_active":true}],"meta":{"current_page":1,"per_page":25,"total":3,"last_page":1}}
GET /api/v1/customers/{id} scope: customers.get

Retrieve a single customer by ID.

Response HTTP 200

{"success":true,"data":{"id":1,"name":"Acme Corp","email":"billing@acme.com","phone":"+1-555-0100","is_active":true}}
POST /api/v1/customers scope: customers.post

Create a new customer.

Request body (JSON)

FieldTypeDescription
name string required Customer name
email string optional Email address (must be valid email)
phone string optional Phone number (max 50 chars)
is_active boolean optional Active status (default: true)

Response HTTP 201

{"success":true,"data":{"id":42,"name":"Acme Corp","email":"billing@acme.com","phone":null,"is_active":true}}

Suppliers

GET /api/v1/suppliers scope: suppliers.get

List all suppliers. Supports search and pagination.

Query parameters

ParameterTypeDescription
search string optional Filter by name (partial match)
page integer optional Page number
per_page integer optional Results per page (max 100)

Response HTTP 200

{"success":true,"data":[{"id":1,"name":"Global Supply Ltd","email":"ap@globalsupply.com","tax_id":"GB123456789","is_vat_registered":true}],"meta":{"current_page":1,"per_page":25,"total":1,"last_page":1}}
GET /api/v1/suppliers/{id} scope: suppliers.get

Retrieve a single supplier by ID.

Response HTTP 200

{"success":true,"data":{"id":1,"name":"Global Supply Ltd","email":"ap@globalsupply.com","phone":null,"tax_id":"GB123456789","is_overseas":false,"is_vat_registered":true,"reverse_charge":false}}
POST /api/v1/suppliers scope: suppliers.post

Create a new supplier.

Request body (JSON)

FieldTypeDescription
name string required Supplier name
email string optional Email address
phone string optional Phone number
tax_id string optional VAT / tax registration number
is_overseas boolean optional Whether supplier is overseas (default: false)
is_vat_registered boolean optional VAT registered (default: true)
reverse_charge boolean optional Reverse charge VAT applies (default: false)

Response HTTP 201

{"success":true,"data":{"id":15,"name":"Global Supply Ltd","email":"ap@globalsupply.com","tax_id":"GB123456789","is_vat_registered":true}}

Invoices

GET /api/v1/invoices scope: invoices.get

List all invoices with customer relation. Supports status filter and pagination.

Query parameters

ParameterTypeDescription
status string optional Filter: draft | unpaid | paid | void | overdue
page integer optional Page number
per_page integer optional Results per page (max 100)

Response HTTP 200

{"success":true,"data":[{"id":101,"number":"INV-00101","date":"2026-06-01","due_date":"2026-07-01","status":"unpaid","total":5000.00,"customer":{"id":1,"name":"Acme Corp"}}],"meta":{"current_page":1,"per_page":25,"total":48,"last_page":2}}
GET /api/v1/invoices/{id} scope: invoices.get

Retrieve a single invoice with customer and line items.

Response HTTP 200

{"success":true,"data":{"id":101,"number":"INV-00101","date":"2026-06-01","due_date":"2026-07-01","status":"unpaid","subtotal":4347.83,"tax_total":652.17,"total":5000.00,"customer":{"id":1,"name":"Acme Corp"},"lines":[{"id":1,"description":"Consulting services","qty":10,"unit_price":434.78,"total":4347.83}]}}
POST /api/v1/invoices scope: invoices.post

Create a new invoice. Created with status=draft and an auto-generated number.

Request body (JSON)

FieldTypeDescription
customer_id integer required ID of an existing customer
date date required Invoice date (YYYY-MM-DD)
due_date date optional Payment due date (YYYY-MM-DD)
notes string optional Internal or customer-facing notes
subtotal decimal optional Pre-tax total (default: 0)
tax_total decimal optional Tax amount (default: 0)
discount_total decimal optional Discount amount (default: 0)
total decimal optional Grand total (default: 0)

Response HTTP 201

{"success":true,"data":{"id":102,"number":"INV-00102","status":"draft","date":"2026-06-07","due_date":"2026-07-07","total":5000.00,"customer_id":1}}
PUT /api/v1/invoices/{id} scope: invoices.update

Update an existing draft invoice. All fields are optional.

Request body (JSON)

FieldTypeDescription
customer_id integer optional New customer ID
date date optional Invoice date (YYYY-MM-DD)
due_date date optional Payment due date
notes string optional Notes
subtotal decimal optional Pre-tax total
tax_total decimal optional Tax amount
total decimal optional Grand total

Response HTTP 200

{"success":true,"data":{"id":102,"status":"draft","total":6000.00,"customer":{"id":1,"name":"Acme Corp"},"lines":[]}}
POST /api/v1/invoices/{id}/post scope: invoices.post

Post a draft invoice to the general ledger. Requires total > 0 and a date. Changes status to unpaid.

Response HTTP 200

{"success":true,"data":{"id":102,"status":"unpaid","posted_at":"2026-06-07T12:00:00Z","total":5000.00}}

Possible errors: invoice_already_posted, invoice_total_required, invoice_date_required

POST /api/v1/invoices/{id}/void scope: invoices.void

Void a posted invoice. Reverses all ledger entries and marks the invoice void.

Response HTTP 200

{"success":true,"data":{"id":102,"status":"void"}}
POST /api/v1/invoices/{id}/mark-sent scope: invoices.send

Mark a draft invoice as sent to the customer (changes status from draft to unpaid). Idempotent.

Response HTTP 200

{"success":true,"data":{"id":102,"status":"unpaid"}}
DELETE /api/v1/invoices/{id} scope: invoices.delete

Permanently delete a draft invoice and all its lines. Cannot delete posted invoices or invoices with payment allocations.

Response HTTP 200

{"success":true,"data":{"message":"Invoice deleted."}}

Possible errors: posted_invoice_not_deletable, invoice_has_allocations

Bills

GET /api/v1/bills scope: bills.get

List all supplier bills. Supports status filter and pagination.

Query parameters

ParameterTypeDescription
status string optional Filter: draft | unpaid | paid | void
page integer optional Page number
per_page integer optional Results per page (max 100)

Response HTTP 200

{"success":true,"data":[{"id":55,"number":"BILL-00055","date":"2026-06-01","status":"draft","total":2000.00,"supplier":{"id":3,"name":"Global Supply Ltd"}}],"meta":{"current_page":1,"per_page":25,"total":12,"last_page":1}}
GET /api/v1/bills/{id} scope: bills.get

Retrieve a single bill with supplier and line items.

Response HTTP 200

{"success":true,"data":{"id":55,"number":"BILL-00055","date":"2026-06-01","due_date":"2026-07-01","status":"draft","reference":"PO-001","total":2000.00,"supplier":{"id":3,"name":"Global Supply Ltd"},"lines":[]}}
POST /api/v1/bills scope: bills.post

Create a new supplier bill. Created with status=draft and an auto-generated BILL-XXXXX number.

Request body (JSON)

FieldTypeDescription
supplier_id integer required ID of an existing supplier
date date required Bill date (YYYY-MM-DD)
due_date date optional Payment due date
reference string optional Supplier reference / PO number (max 120)
notes string optional Internal notes
subtotal decimal optional Pre-tax total (default: 0)
tax_total decimal optional Tax amount (default: 0)
discount_total decimal optional Discount (default: 0)
total decimal optional Grand total (default: 0)

Response HTTP 201

{"success":true,"data":{"id":56,"number":"BILL-00056","status":"draft","date":"2026-06-07","total":2000.00,"supplier_id":3}}
PUT /api/v1/bills/{id} scope: bills.update

Update an existing draft bill. All fields optional.

Request body (JSON)

FieldTypeDescription
supplier_id integer optional New supplier ID
date date optional Bill date
due_date date optional Payment due date
reference string optional Supplier reference
total decimal optional Grand total

Response HTTP 200

{"success":true,"data":{"id":56,"status":"draft","total":2500.00,"supplier":{"id":3,"name":"Global Supply Ltd"}}}
POST /api/v1/bills/{id}/post scope: bills.post

Post a draft bill to the general ledger, registering the payable.

Response HTTP 200

{"success":true,"data":{"id":56,"status":"unpaid","posted_at":"2026-06-07T12:00:00Z"}}

Possible errors: bill_post_failed

DELETE /api/v1/bills/{id} scope: bills.delete

Permanently delete a draft bill. Bill must be in draft status with no payment allocations.

Response HTTP 200

{"success":true,"data":{"message":"Bill deleted."}}

Possible errors: posted_bill_not_deletable, bill_has_allocations

Payments

GET /api/v1/payments scope: payments.get

List all payments (receipts and disbursements).

Query parameters

ParameterTypeDescription
type string optional Filter: receipt | disbursement
status string optional Filter: draft | posted
page integer optional Page number
per_page integer optional Results per page (max 100)

Response HTTP 200

{"success":true,"data":[{"id":20,"type":"receipt","payment_date":"2026-06-07","amount_total":5000.00,"status":"draft","customer_id":1}],"meta":{"current_page":1,"per_page":25,"total":8,"last_page":1}}
POST /api/v1/payments scope: payments.post

Record a payment receipt (customer paid) or disbursement (you paid supplier). Optionally allocate against an invoice or bill.

Request body (JSON)

FieldTypeDescription
type string required "receipt" (customer payment) or "disbursement" (supplier payment)
payment_date date required Date of payment (YYYY-MM-DD)
amount_total decimal required Total amount paid (must be > 0)
method string optional Payment method e.g. bank_transfer, cash, card
reference string optional Bank reference or cheque number (max 120)
description string optional Description or memo
customer_id integer cond. Required for receipt payments (inferred from invoice if document_type=ar_invoice)
supplier_id integer cond. Required for disbursement payments (inferred from bill if document_type=ap_bill)
document_type string optional "ar_invoice" or "ap_bill" — triggers automatic allocation
document_id integer optional ID of the invoice or bill to allocate against (required when document_type is set)
allocated_amount decimal optional Amount to allocate (defaults to amount_total)

Response HTTP 201

{"success":true,"data":{"id":21,"type":"receipt","payment_date":"2026-06-07","amount_total":5000.00,"status":"draft","allocations":[{"document_type":"ar_invoice","document_id":101,"allocated_amount":5000.00}]}}

Possible errors: document_required, document_type_mismatch, invoice_not_found, bill_not_found, client_required, supplier_required

Journal Entries

GET /api/v1/journal-entries scope: journal_entries.get

List journal entries with lines and accounts. Supports date range filtering.

Query parameters

ParameterTypeDescription
status string optional Filter: draft | posted
from date optional Start date (YYYY-MM-DD)
to date optional End date (YYYY-MM-DD)
page integer optional Page number
per_page integer optional Results per page (max 100)

Response HTTP 200

{"success":true,"data":[{"id":5,"entry_date":"2026-06-07","reference":"JE-001","status":"posted","lines_count":2}],"meta":{"current_page":1,"per_page":25,"total":5,"last_page":1}}
POST /api/v1/journal-entries scope: journal_entries.post

Create and immediately post a balanced journal entry. Total debits must equal total credits (tolerance ±0.01).

Request body (JSON)

FieldTypeDescription
date date required Entry date (YYYY-MM-DD)
reference string optional Reference code e.g. JE-001 (max 120)
description string optional Memo / description (max 2000)
lines array required Minimum 2 lines. Each line: account_id (required), debit (decimal), credit (decimal), description (string)

Response HTTP 201

{"success":true,"data":{"id":6,"entry_date":"2026-06-07","reference":"JE-001","status":"posted","posted_at":"2026-06-07T12:00:00Z","lines":[{"account_id":101,"account":{"code":"1100","name":"Cash"},"debit":1000,"credit":0},{"account_id":202,"account":{"code":"3000","name":"Equity"},"debit":0,"credit":1000}]}}

Possible errors: journal_not_balanced

DELETE /api/v1/journal-entries/{id} scope: journal_entries.delete

Delete an unposted journal entry. Posted entries cannot be deleted through the API.

Response HTTP 200

{"success":true,"data":{"message":"Journal entry deleted."}}

Possible errors: posted_journal_not_deletable

Reports

All report endpoints accept ?from=YYYY-MM-DD&to=YYYY-MM-DD. Balance sheet, trial balance, and aging reports also accept ?as_of=YYYY-MM-DD (equivalent to to).

GET /api/v1/reports/income-statement scope: reports.income_statement

Profit and loss report. Returns revenue, expenses, and net income for the specified period.

Query parameters

ParameterTypeDescription
from date optional Period start (YYYY-MM-DD)
to date optional Period end (YYYY-MM-DD)

Response HTTP 200

{"success":true,"data":{"revenue":150000,"expenses":85000,"net_income":65000}}
GET /api/v1/reports/balance-sheet scope: reports.balance_sheet

Balance sheet snapshot. Returns assets, liabilities, and equity as of the specified date.

Query parameters

ParameterTypeDescription
to date optional As-of date (YYYY-MM-DD). Also accepted as ?as_of=

Response HTTP 200

{"success":true,"data":{"assets":{"total":250000},"liabilities":{"total":80000},"equity":{"total":170000}}}
GET /api/v1/reports/cash-flow scope: reports.cash_flow

Cash flow statement for the specified period (direct method).

Query parameters

ParameterTypeDescription
from date optional Period start
to date optional Period end

Response HTTP 200

{"success":true,"data":{"operating":45000,"investing":-12000,"financing":-8000,"net_change":25000}}
GET /api/v1/reports/trial-balance scope: reports.trial_balance

Trial balance showing all accounts with debit and credit totals as of a date.

Query parameters

ParameterTypeDescription
to date optional As-of date (YYYY-MM-DD)

Response HTTP 200

{"success":true,"data":[{"account_code":"1100","account_name":"Cash","debit":50000,"credit":0},{"account_code":"2000","account_name":"Accounts Payable","debit":0,"credit":15000}]}
GET /api/v1/reports/ar-aging scope: reports.ar_aging

Accounts receivable aging. Outstanding customer invoices bucketed by how overdue they are.

Query parameters

ParameterTypeDescription
to date optional Aging as-of date (YYYY-MM-DD)

Response HTTP 200

{"success":true,"data":{"current":12000,"1_30":3500,"31_60":800,"61_90":0,"over_90":500,"total":16800}}
GET /api/v1/reports/ap-aging scope: reports.ap_aging

Accounts payable aging. Outstanding supplier bills bucketed by how overdue they are.

Query parameters

ParameterTypeDescription
to date optional Aging as-of date (YYYY-MM-DD)

Response HTTP 200

{"success":true,"data":{"current":8000,"1_30":1200,"31_60":0,"61_90":0,"over_90":0,"total":9200}}
GET /api/v1/reports/tax scope: reports.tax

VAT / tax summary. Returns tax collected on sales and tax paid on purchases for the period.

Query parameters

ParameterTypeDescription
from date optional Period start
to date optional Period end

Response HTTP 200

{"success":true,"data":{"tax_collected":15000,"tax_paid":4200,"net_vat_payable":10800}}

Error Reference

Error Code HTTP Meaning
invoice_already_posted 422 Invoice is already posted to the ledger
invoice_total_required 422 Cannot post an invoice with zero or negative total
invoice_date_required 422 Document date is required before posting
posted_invoice_not_deletable 422 Void or unpost the invoice before deleting
invoice_has_allocations 422 Remove payment allocations before deleting
posted_bill_not_deletable 422 Cannot delete a posted or non-draft bill
bill_has_allocations 422 Remove payment allocations before deleting
bill_post_failed 422 Bill failed internal post validation
journal_not_balanced 422 Total debits do not equal total credits
posted_journal_not_deletable 422 Only draft (unposted) entries can be deleted
document_required 422 document_id is required when document_type is set
document_type_mismatch 422 ar_invoice requires receipt; ap_bill requires disbursement
invoice_not_found 404 Invoice with that ID does not exist
bill_not_found 404 Bill with that ID does not exist
client_required 422 customer_id is required for receipt payments
supplier_required 422 supplier_id is required for disbursement payments

Pagination

All list endpoints return paginated results. Control pagination with query parameters:

ParameterDefaultMaxDescription
page1Page number to retrieve
per_page25100Number of results per page

The meta object in every list response contains current_page, per_page, total, and last_page.