Skip to main content

API Conventions

This page documents the common patterns, error model, and status codes used across all Ledgerline API endpoints.

Internal endpoints: GET /info and GET /api/info return deployment metadata (masked env, build info) for ops verification. They are not part of the customer-facing API contract.

Prerequisites​

Admin-only Fields​

Some resource fields are stripped from customer-facing API output. A field marked admin-only will appear in the admin panel and in internal responses, but never in public endpoints.

Current admin-only fields:

  • product.costCents — cost basis. productPublicSchema omits this; any endpoint intended for non-admin consumers must serialize through that schema or explicitly omit the field.

When designing a new resource with sensitive fields, expose a {resource}PublicSchema alongside the main schema so downstream consumers can pick the right shape.

CRUD Pattern​

All data resources follow a consistent CRUD pattern:

OperationMethodPathDescription
CreatePOST/api/data/{resource}Create a new record.
ReadGET/api/data/{resource}/:idRetrieve a single record by ID.
UpdatePATCH/api/data/{resource}/:idPartially update a record.
DeleteDELETE/api/data/{resource}/:idRemove a record.
ListGET/api/data/{resource}List all records for the current tenant.

Example: Create an Order​

POST /api/data/order
Content-Type: application/json
x-tenant-id: default
Cookie: ledgerline_auth=...

{
"orderNumber": "ORD-001",
"items": [
{
"productId": "prod_abc",
"name": "Widget",
"sku": "WDG-001",
"quantity": 2,
"unitPriceCents": 1500
}
],
"currency": "USD",
"customer": {
"name": "Jane Doe",
"discordId": "123456789"
}
}

Response (201):

{
"id": "order_xyz",
"tenantId": "default",
"orderNumber": "ORD-001",
"status": "PENDING_PAYMENT",
"items": [...],
"subtotalCents": 3000,
"totalCents": 3000,
"discountAmountCents": 0,
"currency": "USD",
"customer": { "name": "Jane Doe", "discordId": "123456789" },
"shippingStatus": "PENDING",
"createdAt": "2025-01-15T12:00:00.000Z",
"updatedAt": "2025-01-15T12:00:00.000Z"
}

Example: Update an Order​

PATCH /api/data/order/order_xyz
Content-Type: application/json
x-tenant-id: default
Cookie: ledgerline_auth=...

{
"status": "PAID",
"notes": "Payment verified via Cash App"
}

Example: Delete an Order​

DELETE /api/data/order/order_xyz
x-tenant-id: default
Cookie: ledgerline_auth=...

Response (204): No content.

Request Headers​

HeaderRequiredDescription
Content-TypeYes (for POST/PATCH)Must be application/json.
x-tenant-idYesTenant identifier. See Tenant Scoping.
CookieYesSession cookie (ledgerline_auth).

Server-Side Computed Fields​

Certain fields are computed by the server and should not be provided by the client on create:

  • subtotalCents — Sum of quantity × unitPriceCents for all items.
  • taxAmountCents — Sales tax computed via TaxJar using the shipping address. Defaults to 0 when TaxJar is not configured or no shipping address is present.
  • totalCents — (subtotal − discount) + shipping + tax.
  • discountAmountCents — Calculated from the applied discount.
  • tenantId — Injected from the request context.
  • createdAt / updatedAt — Managed by the server.

Error Model​

Error responses return a JSON object with an error message:

{
"error": "Validation failed",
"details": [
{ "field": "items", "message": "Array must contain at least 1 element(s)" }
]
}

The structure may vary by endpoint, but all errors include at least an error or message field.

Status Codes​

CodeMeaning
200Success (read, update, list).
201Created (successful create).
204No content (successful delete).
400Bad request — validation error, missing fields, or business rule violation.
401Unauthorized — no active session.
404Not found — record does not exist or belongs to another tenant.
500Internal server error.

Validation​

Request bodies are validated using Zod schemas. Invalid input returns 400 with details about which fields failed validation.

Common validation rules:

  • String fields must be non-empty where required.
  • priceCents / amountCents must be non-negative integers.
  • currency must be a 3-character ISO 4217 code.
  • Enum fields (e.g., status, type) must be one of the allowed values.
  • Email fields must be valid email addresses.

Pagination​

List endpoints currently return all records for the tenant without pagination. For tenants with large datasets, use filtering or query parameters if available.

Idempotency​

  • POST /api/data/customer is idempotent by discordId: if a customer with the same Discord ID exists, the existing record is returned with 200 instead of creating a duplicate.
  • POST /api/data/order generates a unique order; callers should avoid submitting the same order payload twice.

Analytics share links use HMAC-signed tokens — not session cookies. They are public endpoints and require no Authorization header.

EndpointAuthNotes
POST /api/admin/analytics/:report/shareAdmin sessionGenerates a token
GET /api/reports/:tokenNone (HMAC)HTML report page
GET /api/reports/:token/csvNone (HMAC)CSV download

Token format: base64url(JSON payload) + '.' + base64url(HMAC-SHA256 sig)

The token is opaque to callers. Do not attempt to parse or forge it. Generate tokens via POST /api/admin/analytics/:report/share from an authenticated admin session.