Sapientia ERP

Developers

A plain JSON API over HTTPS. Create a key under Settings → API keys in the app, choose its scopes and optionally bind it to one company. Keys look like sk_live_erp_… (or sk_test_erp_… for test keys, which are accepted but flagged).

Authentication

curl https://erp.sapientiainformationsolutions.com/api/v1/companies \
  -H "Authorization: Bearer sk_live_erp_…"

Every response is { "data": … } or { "error": { "code", "message" } }. Codes: unauthenticated (401), forbidden (403, missing scope or wrong company), not_found, invalid_request (400/422), query_failed (500). Dates are YYYY-MM-DD; timestamps ISO 8601; money is PHP with two decimals.

Scopes

Idempotency

Daily summaries are keyed by company + app + date; channel orders by channel + external_id; tickets by channel + external_ref; journals accept an idempotency_key. Re-sending returns the original with replayed: true and HTTP 200.

Companies and accounts

GET/api/v1/companiesscope accounts:read

Companies the key can post to.

{ "data": [{ "id", "name", "tin", "base_currency" }] }
GET/api/v1/accountsscope accounts:read

Chart of accounts of a company.

Query: company (required unless the key is bound to one)

Journals

GET/api/v1/journalsscope journals:read

List journals.

Query: company · status=draft|submitted|posted|cancelled · from, to (posting_date) · limit (≤500)

POST/api/v1/journalsscope journals:write

Create a journal (draft, or posted when post=true and the key may post).

{ "company": "<uuid>", "posting_date": "2026-10-12", "memo": "…", "reference": "…", "idempotency_key": "…", "post": true, "lines": [{ "account": "1010", "debit": 1000 }, { "account": "4010", "credit": 1000, "memo": "…" }] }
GET/api/v1/journals/{id}scope journals:read

One journal with lines.

POST/api/v1/journals/{id}/transitionscope journals:write

Move a journal through its workflow.

{ "action": "submit" | "post" | "reverse", "note": "…" }

App feeds (postings)

POST/api/v1/postings/daily-summaryscope postings:write

One call per business day: movements by bucket become one posted journal through the company's mapping; balances are kept for the sub-ledger report. Same day twice returns the same journal.

{ "company": "<uuid>", "app": "sugoa_daily", "date": "2026-10-11", "movements": { "sales": 12500, "fees": 1250, "payouts": 9800 }, "balances": { "escrow_held": 431, "wallet_balances": 3500 } }
POST/api/v1/postings/shift-closescope postings:write

POS shift close: sales by payment method, discounts, tips and cash drawer variance posted as one journal.

Orders and catalogue (e-commerce)

POST/api/v1/ordersscope orders:write

Push a shop order to a sales channel. Deduped on channel + external_id (replayed: true). Becomes customer, sales order, invoice and receipt per the channel's settings.

{ "company": "<uuid>", "channel": "shopify", "order": { "external_id": "1001", "number": "#1001", "ordered_at": "2026-10-12T10:00:00+08:00", "customer": { "name": "Ana Cruz", "email": "ana@example.com", "phone": "0917…" }, "ship_to": "12 Mango Ave, Cebu City", "payment_method": "gcash", "paid": true, "discount": 50, "shipping_fee": 80, "platform_fee": 0, "lines": [{ "sku": "BEANS-250", "name": "House blend 250g", "qty": 2, "unit_price": 450 }] } }
{ "data": { "channel_order_id", "status": "invoiced", "sales_order_id", "invoice_id", "payment_id", "total", "warning" }, "replayed": false }  (422 with status "failed" and error when a SKU is unknown)
GET/api/v1/ordersscope orders:read

List channel orders.

Query: company · channel (key) · status · from · limit

GET/api/v1/orders/{id}scope orders:read

One channel order with its raw payload and links.

POST/api/v1/orders/{id}/shipscope orders:write

Mark shipped; creates the shipment (and the delivery when stock has not left yet).

{ "tracking_no": "JT123", "carrier": "J&T Express" }
GET/api/v1/itemsscope items:read

Catalogue for a storefront: sku, barcode, name, price (party price list when party is given), stock on hand.

Query: company · party (optional customer id for its price list) · updated_since (ISO timestamp)

Support

POST/api/v1/ticketsscope tickets:write

Open a helpdesk ticket from an app. Same external_ref returns the existing ticket.

{ "company": "<uuid>", "subject": "Order late", "body": "…", "priority": "high", "channel": "api", "external_ref": "sugoa-ord-77", "contact": { "name", "email", "phone" }, "category": "Delivery", "tags": ["sugoa"] }

Reports

GET/api/v1/reports/trial-balancescope reports:read

Trial balance for a range.

Query: company · from · to

Webhooks

GET/api/v1/webhooksscope webhooks:manage

Your subscriptions.

POST/api/v1/webhooksscope webhooks:manage

Subscribe a URL to events. Deliveries are signed with HMAC-SHA256 of the body using the subscription secret (header X-Sapientia-Signature) and retried with back-off.

{ "url": "https://example.com/hook", "events": ["journal.posted", "order.received", "order.shipped", "order.cancelled"], "description": "…" }
DELETE/api/v1/webhooks/{id}scope webhooks:manage

Remove a subscription.

Webhook events

journal.posted, order.received, order.shipped, order.cancelled. Each delivery is a POST with JSON { "event", "payload", "delivered_at" }; verify X-Sapientia-Signature = hex HMAC-SHA256(body, secret). Deliveries retry every 5 minutes with back-off for a day.