CanopyDocs
Reference

Billing

GET
/api/billing/invoices

Newest first. Unpaginated unless page is sent, in which case limit (default 25, max 250) applies and pagination is returned beside data; without page, limit is ignored and every invoice in scope is returned. A client-pinned credential sees only its own client's invoices; clientId and X-Client-Id are ignored for it.

Authorization

oauth2 billing:read
AuthorizationBearer <token>

OAuth2 client-credentials grant.

In: header

Scope: billing:read

Query Parameters

page?integer
Range0 < value
limit?integer
Range0 < value <= 250
search?string
Lengthlength <= 200
sent?string

Value in

  • "ALL"
  • "SENT"
  • "UNSENT"
clientId?string
billingPeriodId?string

Header Parameters

X-Client-Id?string

Scope results to one client (brand). Resolve a client name to its id via GET /api/clients/lookup. Validated against the credential's tenant; ignored by a client-pinned credential.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/api/billing/invoices"
{  "success": true,  "data": [    {      "id": "string",      "tenantId": "string",      "clientId": "string",      "billingPeriodId": "string",      "invoiceNumber": "string",      "totalAmount": 0,      "currency": "string",      "generatedAt": "2019-08-24T14:15:22Z",      "sentAt": "2019-08-24T14:15:22Z"    }  ],  "pagination": {    "page": 0,    "limit": 0,    "total": 0,    "totalPages": 0  }}
GET
/api/billing/invoices/{id}

The invoice, with time-limited signed download URLs where object storage holds the rendered files. A client-pinned credential gets null URLs: use /download.

Authorization

oauth2 billing:read
AuthorizationBearer <token>

OAuth2 client-credentials grant.

In: header

Scope: billing:read

Path Parameters

id*string

Invoice id

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/api/billing/invoices/string"
{  "success": true,  "data": {    "id": "string",    "tenantId": "string",    "clientId": "string",    "billingPeriodId": "string",    "invoiceNumber": "string",    "totalAmount": 0,    "currency": "string",    "generatedAt": "2019-08-24T14:15:22Z",    "sentAt": "2019-08-24T14:15:22Z",    "csvSignedUrl": "string",    "pdfSignedUrl": "string",    "signedUrlsExpireAt": "2019-08-24T14:15:22Z"  }}
GET
/api/billing/invoices/{id}/signed-urls

Fresh 7-day signed URLs for the stored CSV and PDF. Null for a client-pinned credential and when object storage is not configured; /download always works.

Authorization

oauth2 billing:read
AuthorizationBearer <token>

OAuth2 client-credentials grant.

In: header

Scope: billing:read

Path Parameters

id*string

Invoice id

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/api/billing/invoices/string/signed-urls"
{  "success": true,  "data": {    "invoiceId": "string",    "csvSignedUrl": "string",    "pdfSignedUrl": "string",    "expiresAt": "2019-08-24T14:15:22Z"  }}
GET
/api/billing/invoices/{id}/download

Re-renders the invoice on demand and streams it as an attachment. A client-pinned credential receives the client projection: bin-storage lines read "Storage fee" and name no bin.

Authorization

oauth2 billing:read
AuthorizationBearer <token>

OAuth2 client-credentials grant.

In: header

Scope: billing:read

Path Parameters

id*string

Invoice id

Query Parameters

format*string

Value in

  • "csv"
  • "pdf"

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/api/billing/invoices/string/download?format=csv"
"string"
GET
/api/billing/charges

Every charge in scope, newest occurrence first; filter by billingPeriodId to read one period's lines. A client-pinned credential sees the client view (the schema description lists the staff-only fields it does not receive).

Authorization

oauth2 billing:read
AuthorizationBearer <token>

OAuth2 client-credentials grant.

In: header

Scope: billing:read

Query Parameters

clientId?string
billingPeriodId?string
category?string

Value in

  • "PICK"
  • "LABEL"
  • "RECEIVING_PALLET"
  • "RECEIVING_CARTON"
  • "STORAGE_PALLET"
  • "STORAGE_BIN"
  • "RETURN_PROCESSING"
  • "AD_HOC"
  • "SHIPPING_RECONCILIATION"
  • "ORDER_FEES"
  • "RECEIVING_FEES"
  • "RETURN_FEES"
status?string

Value in

  • "ACTIVE"
  • "VOIDED"
sourceType?string

Value in

  • "SHIPMENT"
  • "PACK_RECORD"
  • "ORDER"
  • "ORDER_LINE"
  • "INBOUND_SHIPMENT"
  • "PALLET"
  • "BIN"
  • "RETURN"
  • "AD_HOC"
  • "RECURRING"
  • "MANUAL"

Header Parameters

X-Client-Id?string

Scope results to one client (brand). Resolve a client name to its id via GET /api/clients/lookup. Validated against the credential's tenant; ignored by a client-pinned credential.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/api/billing/charges"
{  "success": true,  "data": [    {      "id": "string",      "clientId": "string",      "billingPeriodId": "string",      "category": "PICK",      "description": "string",      "quantity": 0,      "unitPrice": 0,      "multiplier": 0,      "amount": 0,      "currency": "string",      "status": "ACTIVE",      "approvalStatus": "PENDING",      "sourceType": "SHIPMENT",      "sourceId": "string",      "adHocChargeTypeId": "string",      "occurredAt": "2019-08-24T14:15:22Z",      "generatedAt": "2019-08-24T14:15:22Z",      "orderNumber": "string"    }  ]}
GET
/api/billing/adjustments

Signed amounts: a negative adjustment is a credit. A client-pinned credential sees the client view.

Authorization

oauth2 billing:read
AuthorizationBearer <token>

OAuth2 client-credentials grant.

In: header

Scope: billing:read

Query Parameters

clientId?string
billingPeriodId?string
category?string

Value in

  • "PICK"
  • "LABEL"
  • "RECEIVING_PALLET"
  • "RECEIVING_CARTON"
  • "STORAGE_PALLET"
  • "STORAGE_BIN"
  • "RETURN_PROCESSING"
  • "AD_HOC"
  • "SHIPPING_RECONCILIATION"
  • "ORDER_FEES"
  • "RECEIVING_FEES"
  • "RETURN_FEES"
appliesToId?string

Header Parameters

X-Client-Id?string

Scope results to one client (brand). Resolve a client name to its id via GET /api/clients/lookup. Validated against the credential's tenant; ignored by a client-pinned credential.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/api/billing/adjustments"
{  "success": true,  "data": [    {      "id": "string",      "clientId": "string",      "billingPeriodId": "string",      "chargeId": "string",      "category": "PICK",      "amount": 0,      "description": "string",      "appliesToType": "SHIPMENT",      "appliesToId": "string",      "origin": "MANUAL",      "createdAt": "2019-08-24T14:15:22Z"    }  ]}
GET
/api/billing/periods

Newest first, each with its running charges total. A period is OPEN while events accrue, then moves through CLOSING and FINALIZED to INVOICED.

Authorization

oauth2 billing:read
AuthorizationBearer <token>

OAuth2 client-credentials grant.

In: header

Scope: billing:read

Query Parameters

clientId?string
status?string

Value in

  • "OPEN"
  • "CLOSING"
  • "FINALIZED"
  • "INVOICED"

Header Parameters

X-Client-Id?string

Scope results to one client (brand). Resolve a client name to its id via GET /api/clients/lookup. Validated against the credential's tenant; ignored by a client-pinned credential.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/api/billing/periods"
{  "success": true,  "data": [    {      "id": "string",      "tenantId": "string",      "clientId": "string",      "startDate": "2019-08-24T14:15:22Z",      "endDate": "2019-08-24T14:15:22Z",      "status": "OPEN",      "currency": "string",      "finalizedAt": "2019-08-24T14:15:22Z",      "createdAt": "2019-08-24T14:15:22Z",      "updatedAt": "2019-08-24T14:15:22Z",      "chargesTotal": 0    }  ]}
GET
/api/billing/periods/{id}

Authorization

oauth2 billing:read
AuthorizationBearer <token>

OAuth2 client-credentials grant.

In: header

Scope: billing:read

Path Parameters

id*string

Billing period id

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/api/billing/periods/string"
{  "success": true,  "data": {    "id": "string",    "tenantId": "string",    "clientId": "string",    "startDate": "2019-08-24T14:15:22Z",    "endDate": "2019-08-24T14:15:22Z",    "status": "OPEN",    "currency": "string",    "finalizedAt": "2019-08-24T14:15:22Z",    "createdAt": "2019-08-24T14:15:22Z",    "updatedAt": "2019-08-24T14:15:22Z",    "chargesTotal": 0  }}
GET
/api/billing/rate-cards

Tenant-wide credentials only. A credential pinned to one client is refused (403), as that client's portal users are; use a tenant-wide credential with X-Client-Id. Drafts (isActive: false) are included; deleted cards are not.

Authorization

oauth2 rate-cards:read
AuthorizationBearer <token>

OAuth2 client-credentials grant.

In: header

Scope: rate-cards:read

Header Parameters

X-Client-Id?string

Scope results to one client (brand). Resolve a client name to its id via GET /api/clients/lookup. Validated against the credential's tenant; ignored by a client-pinned credential.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/api/billing/rate-cards"
{  "success": true,  "data": [    {      "id": "string",      "tenantId": "string",      "clientId": "string",      "name": "string",      "effectiveFrom": "2019-08-24T14:15:22Z",      "effectiveTo": "2019-08-24T14:15:22Z",      "chargeShippingLabels": true,      "isActive": true,      "deletedAt": "2019-08-24T14:15:22Z",      "createdAt": "2019-08-24T14:15:22Z",      "updatedAt": "2019-08-24T14:15:22Z"    }  ]}
GET
/api/billing/rate-cards/{id}

Tenant-wide credentials only. A credential pinned to one client is refused (403), as that client's portal users are; use a tenant-wide credential with X-Client-Id. Rules are ordered by category, then priority.

Authorization

oauth2 rate-cards:read
AuthorizationBearer <token>

OAuth2 client-credentials grant.

In: header

Scope: rate-cards:read

Path Parameters

id*string

Rate card id

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/api/billing/rate-cards/string"
{  "success": true,  "data": {    "id": "string",    "tenantId": "string",    "clientId": "string",    "name": "string",    "effectiveFrom": "2019-08-24T14:15:22Z",    "effectiveTo": "2019-08-24T14:15:22Z",    "chargeShippingLabels": true,    "isActive": true,    "deletedAt": "2019-08-24T14:15:22Z",    "createdAt": "2019-08-24T14:15:22Z",    "updatedAt": "2019-08-24T14:15:22Z",    "rules": [      {        "id": "string",        "rateCardId": "string",        "category": "PICK",        "shape": "FLAT",        "name": "string",        "config": null,        "selector": null,        "selectorKey": "string",        "priority": 0,        "isActive": true,        "sharedRuleId": "string",        "deletedAt": "2019-08-24T14:15:22Z",        "createdAt": "2019-08-24T14:15:22Z",        "updatedAt": "2019-08-24T14:15:22Z"      }    ]  }}
GET
/api/billing/rate-cards/{id}/versions

Tenant-wide credentials only. A credential pinned to one client is refused (403), as that client's portal users are; use a tenant-wide credential with X-Client-Id. The published cards that succeed one another without a gap, around the requested card.

Authorization

oauth2 rate-cards:read
AuthorizationBearer <token>

OAuth2 client-credentials grant.

In: header

Scope: rate-cards:read

Path Parameters

id*string

Rate card id

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/api/billing/rate-cards/string/versions"
{  "success": true,  "data": {    "cardId": "string",    "clientId": "string",    "versions": [      {        "id": "string",        "name": "string",        "effectiveFrom": "2019-08-24T14:15:22Z",        "effectiveTo": "2019-08-24T14:15:22Z",        "isCurrent": true,        "isRequestedCard": true,        "activeRuleCount": 0,        "mintedByCorrection": true      }    ]  }}
GET
/api/billing/rate-cards/{id}/rules/{ruleId}/history

Tenant-wide credentials only. A credential pinned to one client is refused (403), as that client's portal users are; use a tenant-wide credential with X-Client-Id. The last 100 edits to one rule, newest first, with the unit price before and after each.

Authorization

oauth2 rate-cards:read
AuthorizationBearer <token>

OAuth2 client-credentials grant.

In: header

Scope: rate-cards:read

Path Parameters

id*string

Rate card id

ruleId*string

Rule id

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X GET "https://example.com/api/billing/rate-cards/string/rules/string/history"
{  "success": true,  "data": [    {      "id": "string",      "action": "string",      "sharedRuleId": "string",      "performedByEmail": "string",      "createdAt": "2019-08-24T14:15:22Z",      "previousUnitPrice": 0,      "newUnitPrice": 0,      "previousValue": null,      "newValue": null    }  ]}
POST
/api/billing/ad-hoc-charges

Tenant-wide credentials only. A credential pinned to one client is refused (403), as that client's portal users are; use a tenant-wide credential with X-Client-Id. category must be AD_HOC (any other category is 400). The charge lands in the client's OPEN period covering occurredAt (or the named OPEN billingPeriodId), else 409. amount = unitPrice × quantity (default 1, at most 1,000,000); money is a JSON number with at most two decimals, and amount must not exceed 9,999,999,999.99 (400 past either bound). Every API-posted charge lands PENDING on the Review Board whatever its charge type says, and bills only once a tenant admin approves it. Idempotency-Key is REQUIRED for API callers (400 without it). There is no edit: to correct a charge, void it and post again. The tenant's API access must list billing:write in its allowed scopes before a credential can hold it; no tenant has it by default.

Authorization

oauth2 billing:write
AuthorizationBearer <token>

OAuth2 client-credentials grant.

In: header

Scope: billing:write

Header Parameters

Idempotency-Key?string

Optional client-supplied key that makes this write safe to retry. A replay of a completed key returns the original response; an in-flight duplicate gets 409. A key is bound to the operation — method, URL and request body — and to the caller — the credential and its token scopes; reusing it with any of those changed gets 422.

Lengthlength <= 255

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/api/billing/ad-hoc-charges" \  -H "Content-Type: application/json" \  -d '{    "clientId": "string",    "category": "AD_HOC",    "unitPrice": 0,    "description": "string",    "occurredAt": "2019-08-24T14:15:22Z"  }'
{  "success": true,  "data": {    "id": "string",    "clientId": "string",    "billingPeriodId": "string",    "category": "PICK",    "description": "string",    "quantity": 0,    "unitPrice": 0,    "multiplier": 0,    "amount": 0,    "currency": "string",    "status": "ACTIVE",    "approvalStatus": "PENDING",    "sourceType": "SHIPMENT",    "sourceId": "string",    "adHocChargeTypeId": "string",    "occurredAt": "2019-08-24T14:15:22Z",    "generatedAt": "2019-08-24T14:15:22Z"  }}
POST
/api/billing/charges/{id}/void

Tenant-wide credentials only. A credential pinned to one client is refused (403), as that client's portal users are; use a tenant-wide credential with X-Client-Id. A credential may void only the charges it posted itself; any other charge reads as 404. Refused (409) once the charge's period is no longer OPEN, or when the charge is already voided.

Authorization

oauth2 billing:write
AuthorizationBearer <token>

OAuth2 client-credentials grant.

In: header

Scope: billing:write

Path Parameters

id*string

Charge id

Header Parameters

Idempotency-Key?string

Optional client-supplied key that makes this write safe to retry. A replay of a completed key returns the original response; an in-flight duplicate gets 409. A key is bound to the operation — method, URL and request body — and to the caller — the credential and its token scopes; reusing it with any of those changed gets 422.

Lengthlength <= 255

Request Body

application/json

TypeScript Definitions

Use the request body type in TypeScript.

Response Body

application/json

application/json

application/json

application/json

application/json

application/json

application/json

application/json

curl -X POST "https://example.com/api/billing/charges/string/void" \  -H "Content-Type: application/json" \  -d '{    "reason": "string"  }'
{  "success": true,  "data": {    "id": "string",    "clientId": "string",    "billingPeriodId": "string",    "category": "PICK",    "description": "string",    "quantity": 0,    "unitPrice": 0,    "multiplier": 0,    "amount": 0,    "currency": "string",    "status": "ACTIVE",    "approvalStatus": "PENDING",    "sourceType": "SHIPMENT",    "sourceId": "string",    "adHocChargeTypeId": "string",    "occurredAt": "2019-08-24T14:15:22Z",    "generatedAt": "2019-08-24T14:15:22Z"  }}