Docs / API reference
REST API v1
JSON in, signed PDF URLs out. Designed for Shopify apps, freight-forwarder dashboards, and custom internal tooling.
Base URL
https://customs-invoice.com/api/v1All endpoints are HTTPS-only. Requests over HTTP redirect to HTTPS.
CBAM compliance API
Scope check, single-shipment cost calculator, and bulk processor for ERP / TMS integrations are documented separately:
Read the CBAM REST API docs →Authentication
Send your key in the Authorization header as a bearer token:
Authorization: Bearer cik_a3d89ed455260775734e52cd4efd3ace7965a6a0bc12d4f8Keys are 48 hex characters with a cik_ prefix. Mint / revoke at /settings/api-keys. Because we store SHA-256 hashes only, the raw value appears once at creation — copy it into your secret manager immediately.
Create an invoice
Request headers
Authorization: Bearer <your-key>
Content-Type: application/jsonRequest body
| Field | Type | Required | Notes |
|---|---|---|---|
| invoiceData | InvoiceData | yes | See Data types below. |
| documents | string[] | no | Override the documents generated. If omitted we render invoiceData.primaryDocument plus its additionalDocuments. |
| string | no | Delivery email. Defaults to the email on your account. |
Response — 200 OK
{
"id": "e9d1a3b0-...-...",
"shortId": "INV-2026-ABC123",
"documents": [
{
"type": "commercial",
"signedUrl": "https://.../invoices/e9d1.../commercial.pdf?token=...",
"expiresIn": 3600
}
]
}Signed URLs are valid for 1 hour. To refresh, fetch /invoice/<id> in the dashboard or POST again.
Data types
InvoiceData
{
invoiceNumber: string // your reference, e.g. "INV-2026-001"
invoiceDate: string // YYYY-MM-DD
currency: Currency // "USD" | "EUR" | "GBP" | ...
incoterms: Incoterm // "FOB" | "CIF" | "DDP" | ... (Incoterms 2020)
transportMode: string // "air" | "sea" | "road" | "courier"
portOfLoading: string?
portOfDischarge: string?
shipper: Party
consignee: Party
lineItems: LineItem[] // 1..500
freight: number?
insurance: number?
otherCharges: number?
totalDeclaredValue: number // you compute this; must match items + charges
declarationText: string? // commercial/proforma footer
primaryDocument: "commercial" | "proforma"
additionalDocuments: ("packing-list" | "certificate-of-origin")[]
validUntil: string? // proforma only, YYYY-MM-DD
proformaPurpose: string?
manufacturerSameAsShipper: boolean? // CoO only
manufacturer: Party? // CoO only; when not same as shipper
originStatement: string?
}Party
{
name: string
address: string
city: string
state: string?
zip: string
country: string // ISO 3166-1 alpha-2
phone: string?
email: string?
taxId: string? // VAT / EIN / EORI etc.
}LineItem
{
id: string // client-assigned id (we don't validate)
description: string
hsCode: string // e.g. "7318.15"
countryOfOrigin: string // ISO alpha-2
quantity: number
unit: string // PCS, KG, SET, MTR, LTR, BOX, PAL, PKG
unitPrice: number
totalValue: number // quantity * unitPrice
// Packing-list only (ignored otherwise)
packages: number?
netWeightKg: number?
grossWeightKg: number?
dimensionsCm: string? // "L × W × H"
marksAndNumbers: string?
}Code samples
curl
curl -X POST https://customs-invoice.com/api/v1/invoices \
-H "Authorization: Bearer $CUSTOMS_INVOICE_API_KEY" \
-H "Content-Type: application/json" \
-d @shipment.jsonNode.js (native fetch, ≥ 18)
async function createInvoice(invoiceData) {
const res = await fetch(
"https://customs-invoice.com/api/v1/invoices",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.CUSTOMS_INVOICE_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
invoiceData,
documents: ["commercial", "packing-list"],
}),
},
);
if (!res.ok) {
const { error } = await res.json();
throw new Error(`${res.status} ${error}`);
}
return res.json();
}Python (requests)
import os, requests
def create_invoice(invoice_data: dict):
r = requests.post(
"https://customs-invoice.com/api/v1/invoices",
headers={
"Authorization": f"Bearer {os.environ['CUSTOMS_INVOICE_API_KEY']}",
"Content-Type": "application/json",
},
json={
"invoiceData": invoice_data,
"documents": ["commercial", "packing-list"],
},
timeout=120,
)
r.raise_for_status()
return r.json()Go (net/http)
req, _ := http.NewRequest("POST", "https://customs-invoice.com/api/v1/invoices",
bytes.NewReader(payload))
req.Header.Set("Authorization", "Bearer "+os.Getenv("CUSTOMS_INVOICE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil { return err }
defer resp.Body.Close()Limits
- Rate limit: 60 invoices per hour per key. Returns
429on overage. Higher throughput available on request. - Payload size: 1 MB, up to ~500 line items per invoice.
- Document retention: 30 days. Download and persist on your side.
- Signed URL validity: 1 hour. Re-fetch after expiry.
- Request timeout: 120 seconds (enough for ~4 PDFs in one call).
Webhooks
Not yet available on v1. Planned events: invoice.ready (all PDFs uploaded), invoice.expired (30-day retention hit), batch.completed (bulk upload finished).
Let us know what you need first at hello@customs-invoice.com.
Ready to ship?
Mint a key and send your first invoice in under five minutes.
Create a key