Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Partner Portal - Invoice API Contract

Base URL: /api/partner/v1
Authentication: Bearer token (JWT via Knock or Doorkeeper OAuth)
Required Header: Authorization: Bearer <token>
Authorization: All endpoints require the authenticated staff to have owner role for the restaurant. Staff with admin, contributor, viewer, or staff roles will receive 403 Forbidden.

Common Error Responses

StatusDescription
401 UnauthorizedMissing or invalid Bearer token
403 ForbiddenAuthenticated staff does not have owner role for the restaurant

1. List Invoices

GET /invoices

Returns a paginated list of invoices for the restaurant, ordered by latest billing period. Includes total outstanding balance.

Query Parameters

ParamTypeRequiredDefaultDescription
pageintegerNo1Page number
per_pageintegerNo15Items per page
statusstringNoFilter by status: pending, partial, paid, cancelled
restaurant_idintegerNoFilter by specific restaurant (for multi-branch staff)
sort_bystringNobilling_periodColumn to sort by. Allowed values: billing_period, external_invoice_code, issue_date, due_date, total_amount, status
sort_directionstringNodescSort direction. Allowed values: asc, desc

Note: Invalid sort_by or sort_direction values are silently ignored and fall back to the defaults (billing_period desc).

Response 200 OK

{
  "data": [
    {
      "id": "123",
      "type": "accounting_invoice",
      "attributes": {
        "external_invoice_id": "INV-500",
        "external_invoice_code": "INV001",
        "billing_period": "2026-05-01",
        "issue_date": "2026-05-01",
        "due_date": "2026-06-01",
        "status": "pending",
        "total_amount": "3000.0",
        "remain_amount": "3000.0",
        "currency": "THB",
        "document_url": "https://doc.peaksandbox.com/invoice?emi=MjkxNQ%3D%3D&eti=MTc5NTU0",
        "created_at": "2026-05-01T10:00:00.000Z",
        "restaurant_id": 456,
        "restaurant_name": "My Restaurant",
        "is_overdue": false,
        "total_amount_format": "฿3,000",
        "remain_amount_format": "฿3,000"
      }
    }
  ],
  "meta": {
    "total": 25,
    "page": 1
  },
  "links": {
    "self": "/api/partner/v1/invoices?page=1",
    "first": "/api/partner/v1/invoices?page=1",
    "next": "/api/partner/v1/invoices?page=2",
    "prev": null,
    "last": "/api/partner/v1/invoices?page=3"
  },
  "outstanding": {
    "total_balance": 4500.0,
    "total_balance_format": "฿4,500",
    "currency": "THB"
  }
}

2. Invoice Detail

GET /invoices/:id

Returns detail of a single invoice.

Path Parameters

ParamTypeRequiredDescription
idintegerYesInvoice ID

Response 200 OK

{
  "data": {
    "id": "123",
    "type": "accounting_invoice",
    "attributes": {
      "external_invoice_id": "INV-500",
      "external_invoice_code": "INV001",
      "billing_period": "2026-05-01",
      "issue_date": "2026-05-01",
      "due_date": "2026-06-01",
      "status": "pending",
      "total_amount": "3000.0",
      "remain_amount": "3000.0",
      "currency": "THB",
      "document_url": "https://doc.peaksandbox.com/invoice?emi=MjkxNQ%3D%3D&eti=MTc5NTU0",
      "created_at": "2026-05-01T10:00:00.000Z",
      "restaurant_id": 456,
      "restaurant_name": "My Restaurant",
      "is_overdue": false,
      "total_amount_format": "฿3,000",
      "remain_amount_format": "฿3,000"
    }
  }
}

Response 404 Not Found

{
  "data": null,
  "success": false,
  "message": "Invoice not found"
}

3. Invoice by Period

GET /invoices/period_detail

Returns the invoice for a specific billing period (year & month).

Query Parameters

ParamTypeRequiredDescription
yearintegerYesBilling year (e.g. 2026)
monthintegerYesBilling month (1-12)
restaurant_idintegerNoFilter by specific restaurant

Response 200 OK (invoice found)

{
  "data": {
    "id": "123",
    "type": "accounting_invoice",
    "attributes": {
      "external_invoice_id": "INV-500",
      "external_invoice_code": "INV001",
      "billing_period": "2026-05-01",
      "issue_date": "2026-05-01",
      "due_date": "2026-06-01",
      "status": "paid",
      "total_amount": "15000.0",
      "remain_amount": "0.0",
      "currency": "THB",
      "document_url": "https://doc.peaksandbox.com/invoice?emi=MjkxNQ%3D%3D&eti=MTc5NTU0",
      "created_at": "2026-05-01T10:00:00.000Z",
      "restaurant_id": 456,
      "restaurant_name": "My Restaurant",
      "is_overdue": false,
      "total_amount_format": "฿15,000",
      "remain_amount_format": "฿0"
    }
  }
}

Response 200 OK (no invoice for period)

{
  "data": null
}

Response 422 Unprocessable Entity

{
  "success": false,
  "message": "Invalid year or month"
}

4. Overdue Alert

GET /invoices/overdue_alert

Returns overdue payment alert information. Use this on the dashboard to show a warning banner.

Query Parameters

ParamTypeRequiredDescription
restaurant_idintegerNoFilter by specific restaurant

Response 200 OK (has overdue)

{
  "data": {
    "has_overdue": true,
    "type": "warning",
    "message": "Payment Overdue: You have an outstanding invoice of ฿4,500 due on May 14, 2026. Please settle this balance to avoid service interruption.",
    "invoice_id": 123,
    "document_url": "https://doc.peaksandbox.com/invoice?emi=MjkxNQ%3D%3D&eti=MTc5NTU0"
  }
}

Response 200 OK (no overdue)

{
  "data": {
    "has_overdue": false,
    "type": null,
    "message": null,
    "invoice_id": null,
    "document_url": null
  }
}

5. Refresh Payment Status

POST /invoices/:id/refresh_payment_status

Manually re-fetches the payment status for a specific invoice from the upstream accounting provider (PEAK, etc.) and enqueues a background job to update it.

When to use: A restaurant partner pays their invoice in the PEAK dashboard and wants to see the updated status immediately, without waiting for the nightly sync that runs at 09:00 ICT.

The update is asynchronous — call GET /invoices/:id a few seconds after this request to see the refreshed status and remain_amount.

Path Parameters

ParamTypeRequiredDescription
idintegerYesInvoice ID

Request

No request body required.

Response 200 OK

{
  "success": true,
  "message": "Payment status refresh has been queued. Please check back shortly."
}

Response 404 Not Found

Returned when the :id does not exist or belongs to a different restaurant.

{
  "success": false,
  "message": "Invoice not found"
}

Response 422 Unprocessable Entity

Returned when the invoice has not been submitted to PEAK yet (no external_invoice_id).

{
  "success": false,
  "message": "This invoice has not been synced to the accounting provider yet."
}

Response 500 Internal Server Error

{
  "success": false,
  "message": "Failed to queue payment status refresh. Please try again."
}

Data Types Reference

Invoice Status

ValueDescription
pendingInvoice created, no payment received
partialPartial payment received
paidFully paid
cancelledInvoice cancelled

Invoice Attributes

AttributeTypeDescription
idstringInvoice ID
external_invoice_idstringPeak invoice ID
external_invoice_codestringPeak invoice code (e.g. “INV001”)
billing_periodstringFirst day of billing month (YYYY-MM-DD)
issue_datestringDate invoice was issued (YYYY-MM-DD)
due_datestringPayment due date (YYYY-MM-DD)
statusstringOne of: pending, partial, paid, cancelled
total_amountstringTotal invoice amount (decimal as string)
remain_amountstringRemaining unpaid amount (decimal as string)
currencystringCurrency code (e.g. “THB”)
document_urlstringPEAK invoice view URL (null if not yet synced)
created_atstringISO 8601 timestamp
restaurant_idintegerRestaurant ID
restaurant_namestringRestaurant name
is_overduebooleanWhether invoice is past due and unpaid
total_amount_formatstringFormatted total amount (e.g. “฿15,000”)
remain_amount_formatstringFormatted remaining amount (e.g. “฿3,000”)

Usage Notes

  • Outstanding balance (total_balance) is calculated from remain_amount of all pending + partial invoices.
  • Overdue means due_date < today AND status is pending or partial.
  • invoice_id in overdue alert points to the oldest overdue invoice — use it to navigate to invoice detail via GET /invoices/:id.
  • document_url in overdue alert is the PEAK invoice view URL. Open it directly in a browser tab. null when there is no overdue invoice or invoice is not yet synced to PEAK.
  • document_url on invoice attributes is the PEAK-hosted invoice page. Open it directly — no additional API call needed.
  • Refresh Payment Status (POST /invoices/:id/refresh_payment_status) is asynchronous — poll GET /invoices/:id after a few seconds to confirm the updated status.
  • All endpoints are scoped to the authenticated staff’s restaurants.
  • Owner-only access: All endpoints require the staff to have owner role (via staff_roles table) for the restaurant. Other roles (admin, contributor, viewer, staff) receive 403 Forbidden. Admin panel OAuth (partner-portal-admin Doorkeeper token) bypasses this check.
  • Sorting by status: Use sort_by=status to group invoices by their payment status alphabetically.