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.