Worked examples combining the conventions from API Conventions, API Authentication, and Authorization. All examples use sanitized/placeholder values — substitute your own token and tenant id.
1. Log in and obtain a token
curl -X POST https://bareledger.app/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "user@example.com", "password": "<PASSWORD>"}'
Response (no 2FA enrolled):
{
"token": "<ACCESS_TOKEN>",
"user": { "uuid": "...", "email": "user@example.com" }
}
If the account has 2FA enrolled, the response instead returns a challenge; resubmit with a totp_code field to receive the token.
2. Make an authenticated, tenant-scoped request
curl https://bareledger.app/api/v1/journals?status=submitted \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "X-Tenant: <TENANT_UUID>"
X-Tenant is required on every tenant-scoped endpoint — omitting it, or sending one you don't belong to, returns 409.
3. Create a resource and handle validation errors
curl -X POST https://bareledger.app/api/v1/journals \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "X-Tenant: <TENANT_UUID>" \
-H "Content-Type: application/json" \
-d '{
"transaction_date": "2026-08-01",
"currency": "PHP",
"lines": [
{ "account_id": "<ACCOUNT_UUID_1>", "debit": 1000.00, "credit": 0 },
{ "account_id": "<ACCOUNT_UUID_2>", "debit": 0, "credit": 1000.00 }
]
}'
A validation failure (e.g. unbalanced lines on submit, not on draft save) returns:
422 Unprocessable Entity
{
"message": "The given data was invalid.",
"errors": { "lines": ["Total debits must equal total credits."] }
}
4. Handle pagination
Pass page and per_page to get one page and a meta block; keep requesting the next page until current_page reaches last_page. Without them, most lists return every matching row.
curl "https://bareledger.app/api/v1/invoices?page=2&per_page=25" \
-H "Authorization: Bearer <ACCESS_TOKEN>" \
-H "X-Tenant: <TENANT_UUID>"
{ "data": [ /* 25 items */ ], "meta": { "current_page": 2, "last_page": 6, "per_page": 25, "total": 137 } }
5. Handle a permission denial
403 Forbidden
{ "message": "You do not have permission to perform this action.", "permission": "sales.approve" }
The permission field tells you exactly which key was missing — useful for surfacing a precise "you need X permission" message in a client app rather than a generic error.
6. Handle a lapsed subscription
402 Payment Required
{ "message": "An active subscription is required to perform this action." }
Reads and */billing/* endpoints remain available even in this state — a client can still show data and offer a way to resubscribe.
7. Verify a billing webhook (server-side integration pattern)
Both Paddle and PayMongo webhooks are verified via HMAC signature, not a bearer token — see Integration — Paddle / Integration — PayMongo for the exact header names and algorithm. Pseudocode:
import hmac, hashlib
def verify(raw_body: bytes, signature_header: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature_header)
Never trust a webhook payload whose signature doesn't verify — both gateways fail closed in this system's own implementation, and any external consumer replicating similar logic should do the same.