A small REST API for reading and creating the records you'd otherwise type in by hand, plus signed webhooks so your system hears about things as they happen.
https://books.maya123.com/api/v1Authorization: Bearer <your key>application/json — request and response.
Worth reading before you design against it:
DELETE, no cancel, no un-post,
no delete-draft. Not "restricted" — absent. A key cannot destroy or reverse anything, whatever its scopes./payments means receipt and payment
vouchers — a customer paying an invoice — which is ordinary bookkeeping.PUT/PATCH. Records are created, not edited, through the API.
Edit in the app, where the validation and audit trail live.In MayaBooks: Settings → API & Webhooks (Owner role). Choose Read only or Read and create, and optionally an expiry and a per-minute rate limit.
Authorization header, never in a query string
(URLs end up in logs, history and Referer headers).
curl -s https://books.maya123.com/api/v1/me \
-H "Authorization: Bearer mbk_live_xxxxxxxx_xxxxxxxx…"
{
"data": {
"company": { "id": 1, "name": "Acme Traders", "gstin": "07AAAAA0000A1Z4",
"country": "IN", "base_currency": "INR" },
"key": { "name": "Website sync", "prefix": "mbk_live_ab12cd34", "scopes": "read,write" },
"rate_limit": { "per_minute": 60, "used_last_minute": 1 }
}
}
| Verb | Path | What it does |
|---|---|---|
| GET | /ping | Liveness. The only endpoint that needs no key. |
| GET | /me | Which company and key you are using. |
| GET | /parties | Customers and suppliers. ?type=Customer|Supplier, ?limit= |
| GET | /parties/{id} | One party. |
| POST | /parties | Create a party. |
| GET | /ledgers | Chart of accounts. |
| GET | /ledgers/{id} | One ledger. |
| POST | /ledgers | Create a ledger. |
| GET | /items | Products and services. |
| GET | /items/{id} | One item. |
| POST | /items | Create an item. |
| GET | /sales-invoices | Invoices. ?status=, ?party_id=, ?date_from=, ?date_to= |
| GET | /sales-invoices/{id} | One invoice, with its lines. |
| POST | /sales-invoices | Create an invoice (draft, or posted with "post": true). |
| GET | /payments | Receipt/payment vouchers. ?type=receipt|payment |
| GET | /payments/{id} | One voucher, with its lines. |
| POST | /payments | Record money in or out. |
Omit post (or send false) to leave it as a draft, which is the
default because a draft is reversible and a posted invoice is not. Tax is always recomputed server-side from
quantity, rate, discount and GST rate — amounts you send as totals are ignored.
curl -s -X POST https://books.maya123.com/api/v1/sales-invoices \
-H "Authorization: Bearer $MB_KEY" \
-H "Content-Type: application/json" \
-d '{
"party_id": 12,
"invoice_date": "2026-07-30",
"post": true,
"items": [
{ "description": "Consulting, July", "quantity": 10, "rate": 2500, "gst_rate": 18 },
{ "item_id": 4, "description": "Licence", "quantity": 1, "rate": 9000, "gst_rate": 18 }
]
}'
Give the bank/cash ledger and either the party's ledger or the party id. type is
receipt (money in, the default) or payment (money out).
curl -s -X POST https://books.maya123.com/api/v1/payments \
-H "Authorization: Bearer $MB_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "receipt",
"date": "2026-07-30",
"amount": "29500.00",
"bank_ledger_id": 7,
"party_id": 12,
"reference": "NEFT N123456789",
"post": true
}'
| Status | Meaning |
|---|---|
401 | Missing or invalid key. One generic message for every auth failure, on purpose. |
403 | A read-only key tried to write, or the key's owner lost access to the company. |
404 | No such record in your company, or no such endpoint. |
405 | A verb other than GET/POST. There is no destructive surface. |
422 | The request was understood but the data was rejected — the message says why. |
429 | Rate limited. See Retry-After. |
503 | The API isn't enabled on this installation yet. |
{ "error": { "code": "invalid_request", "message": "An invoice needs at least one line in \"items\"." } }
Every response carries X-RateLimit-Limit and X-RateLimit-Remaining.
Amounts are decimal strings, not floats — a float can't hold 0.1 exactly and an
accounting API shouldn't invite rounding drift.
Subscribe an https endpoint in Settings → API & Webhooks and we POST a signed JSON body when an event happens.
| Event | Fires when |
|---|---|
invoice.created | A sales invoice was created (draft) |
invoice.posted | A sales invoice was posted (financially committed) |
payment.received | A receipt voucher was posted (money in) |
POST /your/endpoint
Content-Type: application/json
Webhook-Id: whd_1234
Webhook-Timestamp: 1785400000
Webhook-Signature: v1=9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
X-MayaBooks-Event: invoice.posted
{
"event": "invoice.posted",
"created_at": "2026-07-30T09:15:00+00:00",
"company_id": 1,
"data": { "invoice": { "id": 88, "invoice_no": "INV/2026-27/0042", "grand_total": "29500.00", … } }
}
The signature is HMAC-SHA256 over the exact string
<Webhook-Id>.<Webhook-Timestamp>.<raw body>, keyed with your signing secret, hex-encoded.
The id and timestamp are inside the signed string so you can reject both a replay of this delivery and an
attempt to pass off a different one as this.
<?php
$secret = getenv('MAYABOOKS_WEBHOOK_SECRET');
$body = file_get_contents('php://input');
$id = $_SERVER['HTTP_WEBHOOK_ID'] ?? '';
$ts = (int) ($_SERVER['HTTP_WEBHOOK_TIMESTAMP'] ?? 0);
$header = $_SERVER['HTTP_WEBHOOK_SIGNATURE'] ?? '';
// Reject anything older than 5 minutes — that is your replay guard.
if (abs(time() - $ts) > 300) { http_response_code(400); exit; }
$expected = hash_hmac('sha256', $id . '.' . $ts . '.' . $body, $secret);
$ok = false;
foreach (explode(' ', trim($header)) as $part) {
// hash_equals, NOT ==, so the comparison is constant-time.
if (str_starts_with($part, 'v1=') && hash_equals($expected, substr($part, 3))) { $ok = true; }
}
if (!$ok) { http_response_code(401); exit; }
http_response_code(200); // 2xx = accepted; anything else is retried
Deliveries can arrive more than once (a retry after your 200 was lost in transit), so treat
Webhook-Id as an idempotency key. Order is not guaranteed.
v1 will not change shape underneath you; anything
incompatible would be v2.MayaBooks — . Questions: contact us.