API Reference
The Trailing Paper API gives your store, POS system, or custom application direct access to card payments, invoicing, and payout data for your Trailing Paper merchant account. The API is built on REST principles — all requests and responses use JSON, authentication is via a Bearer token, and standard HTTP status codes indicate success or failure.
This reference documents every available endpoint with full request/response schemas, code examples in four languages, and detailed error descriptions.
Who this is for
This API is for merchants and developers building integrations on top of a Trailing Paper account. Common use cases:
- Charging a customer's card from a custom checkout or e-commerce storefront
- Creating and sending invoices programmatically from ERP or accounting software
- Pulling payout and settlement data into business intelligence dashboards
- Automating customer record creation and payment history lookups
Approved merchant account required. Your Trailing Paper account must be approved by Finix before API charges will succeed. Charges attempted on an unapproved account return 422. Check your account status in your merchant dashboard.
Base URL
API versioning
The current API version is v1. Breaking changes will be introduced only with advance notice via email to the address on your account. Non-breaking additions (new fields, new endpoints) may be made at any time.
Authentication
All API requests are authenticated with an API key passed as a Bearer token in the Authorization header. Your API key provides full access to your merchant account — never expose it in client-side code, public repositories, or browser requests.
Getting your API key
API keys are managed in your dashboard → API Keys. You can create multiple keys with distinct labels (one per integration), see when each was last used, and revoke any key individually without affecting others.
One-time display. Your full key is shown only once, immediately after creation. Copy it and store it in a secret manager (e.g. AWS Secrets Manager, Vault, a .env file outside your repo). If you lose access to a key, revoke it and generate a new one.
Key format
| Prefix | Length | Example |
|---|---|---|
| tp_live_ | 56 chars total | tp_live_a3f8c2d... |
Unauthenticated requests
Any request with a missing, malformed, or revoked API key returns 401 Unauthorized:
{ "error": "Invalid or inactive API key." }
Environments
All API requests currently target the live production environment. A sandbox environment with test card numbers and no real money movement is planned for a future release.
| Environment | Base URL | Status |
|---|---|---|
| Production | https://trailingpaper.com/api |
Active |
| Sandbox | https://sandbox.trailingpaper.com/api |
Coming soon |
Live charges only. Until a sandbox is available, all API charges process real card transactions through Finix and settle real funds. Do not use production cards for testing.
Making Requests
Required headers
| Header | Value | |
|---|---|---|
| Authorization | Required | Bearer tp_live_... |
| Content-Type | POST only | application/json |
| Idempotency-Key | Recommended | Any unique string. UUID v4 recommended. See Idempotency. |
Request body
POST request bodies must be JSON-encoded. application/x-www-form-urlencoded and multipart are not supported. Omitting Content-Type: application/json on a POST will result in a 400 response.
Response format
All responses are JSON. Successful responses return the resource object. Error responses always include an error key with a human-readable description of the problem. Field-level validation errors may return error as an object keyed by field name.
HTTPS only
All API requests must use HTTPS. HTTP requests will be rejected or redirected. TLS 1.2+ is required.
Idempotency
POST requests to the API — especially /api/charge — support idempotent retries via the Idempotency-Key header. If a network failure, timeout, or unexpected disconnect prevents you from receiving a response, you can retry the exact same request with the same key and be guaranteed no duplicate charge is created.
How it works
When you send a request with an Idempotency-Key, the server stores the response against that key. If you retry within 24 hours, the stored response is returned immediately without reprocessing the request. After 24 hours the key expires and can be reused for a new unrelated request.
Tie keys to orders, not requests. Use a stable identifier from your system — such as order_8742 or a UUID stored in your order record — rather than generating a new UUID per attempt. This lets you retry safely regardless of how many times the network failed.
Format
Any string up to 255 characters is accepted. UUID v4 format is recommended to guarantee uniqueness across your system.
Errors & Status Codes
The API uses conventional HTTP status codes. Codes in the 2xx range indicate success. Codes in the 4xx range indicate a problem with your request. Codes in the 5xx range indicate an error on our end.
error field describes what failed.Authorization header.Retry-After response header indicates how many seconds to wait before retrying.Error response shape
All non-2xx responses return a consistent error envelope. Validation errors may return the error value as an object:
{ "error": "Card was declined." }
// Field-level validation
{ "error": { "amount": "The amount field is required." } }
Rate Limits
API requests are rate-limited to 100 requests per minute per API key. Requests that exceed this limit receive 429 Too Many Requests with a Retry-After header.
| Limit | Window | Scope |
|---|---|---|
| 100 requests | Per minute | Per API key |
Handling 429 responses
Implement exponential backoff when you receive a 429. Read the Retry-After header (in seconds) and wait at least that long before retrying. Do not hammer the API — multiple rapid retries will extend your lockout window.
Higher limits
If your integration requires throughput beyond 100 req/min, contact support@trailingpaper.com to discuss a higher-limit arrangement.
Create a Charge
Creates a payment transfer against a tokenized card. The charge flows through Finix and settles to your linked bank account on the standard payout schedule (typically T+2 business days). You must have an approved merchant account for charges to succeed.
Amounts are specified in the smallest currency unit — cents for USD. A charge of $99.99 is "amount": 9999. The minimum chargeable amount is $0.50 (50).
A newly created charge starts in PENDING state. It transitions to SUCCEEDED or FAILED asynchronously, typically within a few seconds. Use webhooks to receive real-time state-change notifications rather than polling.
Card tokenization is handled client-side. Never send raw card numbers (PAN, CVV, expiry) to the Trailing Paper API or your own server. Collect card details using Finix.js in the browser, which returns a payment_instrument_id you can safely pass to this endpoint.
Request
Headers
| Header | Value | |
|---|---|---|
| Authorization | Required | Bearer tp_live_... |
| Content-Type | Required | application/json |
| Idempotency-Key | Recommended | UUID v4 tied to your order ID. Prevents duplicate charges on retry. |
Body parameters
| Parameter | Type | Description |
|---|---|---|
|
amount
Required
|
integer | Charge amount in the smallest currency unit. For USD, this is cents. Min: 50 ($0.50). Max: 99999999 ($999,999.99). Example: 9999 = $99.99. |
|
payment_instrument_id
Required
|
string | Finix Payment Instrument ID representing a tokenized card. Obtained from Finix.js client-side tokenization. Instrument IDs begin with PI. |
|
currency
Optional
|
string | ISO 4217 currency code. Currently only USD is supported. Defaults to USD if omitted. |
|
description
Optional
|
string | Internal note or reference (e.g. "Order #1234"). Visible in your dashboard and included in webhook payloads. Max 255 characters. |
Response — 201 Created
Returns the created transfer object on success.
| Field | Type | Description |
|---|---|---|
| success | boolean | Always true on a 201 response. |
| transfer_id | string | Finix transfer ID (prefixed TR). Use this to identify the payment in your dashboard, dispute lookups, or webhook matching. |
| status | string | Initial state is always PENDING. Transitions asynchronously to SUCCEEDED or FAILED. Subscribe to webhooks to receive state change events. |
| amount | integer | The charged amount in cents, echoed from the request. |
| currency | string | The currency code (USD). |
Error responses
error object will identify the specific field.error message for the specific reason.Retry-After header.List Payments
Returns a paginated list of all charges made through your merchant account, sorted by creation time descending. Supports filtering by status, date range, and amount.
In Development
This endpoint is being built. Email us if you need early access.
Retrieve a Payment
Returns a single payment transfer by Finix transfer ID. Use this to check the current status of a charge after creation.
In Development
Until this endpoint is available, use webhooks to track state transitions from PENDING → SUCCEEDED.
Tokenize a Card
Tokenizes a customer's payment details and returns a payment_instrument_id for use with Create a Charge. Raw card data must never pass through your servers.
Use Finix.js in the meantime. Finix's client-side JS library lets you securely collect and tokenize card details in the browser. The resulting payment_instrument_id is accepted by POST /api/charge today. See finix.com/docs.
In Development
Native Trailing Paper card tokenization is coming. Until then, use Finix.js directly.
Create an Invoice
Creates an invoice and optionally sends it to the customer by email. Returns the invoice object including a unique hosted payment link the customer can use to pay by card.
In Development
Contact us for early access.
List Invoices
Returns a paginated list of all invoices for your account, with filters for status (draft, sent, paid, overdue) and date range.
In Development
Contact us for early access.
Retrieve an Invoice
Returns a single invoice by ID, including line items, payment status, and the hosted payment URL.
In Development
Contact us for early access.
Create a Customer
Creates a customer record in your Trailing Paper account. Customers can be attached to invoices and used to look up payment history and stored payment instruments.
In Development
Contact us for early access.
List Customers
Returns a paginated list of all customers for your account, searchable by name or email.
In Development
Contact us for early access.
Sync API
The Trailing Paper Sync API enables bidirectional data exchange between your Trailing Paper account and external platforms — e-commerce stores, ERPs, marketplaces, and custom systems. Pull your TP data into your store, push your store's data back, or run a full bidirectional sync in a single call.
What you can sync
| Resource | GET (pull from TP) | POST (push to TP) | Full Sync |
|---|---|---|---|
| Products / SKUs | ✓ | ✓ | ✓ |
| Customers / Buyers | ✓ | ✓ | — |
| Vendors | ✓ | ✓ | ✓ |
| Orders → Invoices | — | ✓ | — |
| Purchase Orders | ✓ | ✓ | ✓ |
| Packing Slips | — | ✓ | — |
| Expenses | ✓ | ✓ | ✓ |
| Ledger Entries | ✓ | ✓ | ✓ |
| Credit Memos | ✓ | ✓ | ✓ |
Base URL
https://trailingpaper.com/api/sync
Upsert semantics
All POST endpoints use upsert logic. If a record with the same external_id or matching key field (SKU, email, etc.) already exists, it is updated. Otherwise a new record is created. Every response includes created, updated, and errors counts.
Sync API Authentication
The Sync API uses the same API key as every other Trailing Paper endpoint — no separate credentials. One key gives your external system access to payments, sync pull, and sync push.
Getting a key
Navigate to Settings → API Keys and generate a new key. Keys are automatically scoped to your active company, so the external system syncs data into the correct account.
Copy your key on creation. It is shown once in full. If you lose it, revoke it and generate a new one — existing integrations using the old key will stop working immediately.
Authenticating requests
Pass your key as a standard Bearer token on every request — the same header you use for the payment API:
Authorization: Bearer tp_live_your_key_here
What a key can do
| Endpoint group | Access granted |
|---|---|
POST /api/charge | Create a payment charge |
GET /api/sync/* | Pull any resource from Trailing Paper |
POST /api/sync/* | Push any resource into Trailing Paper |
POST /api/sync/*/full | Bidirectional sync (pull + push in one call) |
Products
Pull all products from your Trailing Paper account, or push products from an external system into TP. Records are matched and upserted by SKU.
GET — Pull products from TP
Returns all active product records for your company, including SKU, name, price, stock quantity, category, and image URL.
Response
{
"items": [
{
"id": 42,
"sku": "SKU-1042",
"name": "Classic Widget Pro",
"description": "Heavy-duty all-purpose widget",
"price": "49.99",
"stock_quantity": 200,
"category": "Hardware",
"image_path": "https://trailingpaper.com/uploads/items/classic-widget-pro.jpg",
"is_active": 1,
"created_at": "2025-11-14T18:32:00Z"
}
]
}
POST — Push products into TP
Send an array of product objects to create or update records. Matched by sku if it exists, otherwise by name.
Request body
| Field | Type | Required | Description |
|---|---|---|---|
items | array | Yes | Array of product objects |
items[].sku | string | No | SKU — used as the upsert key |
items[].name | string | Yes | Product name |
items[].price | number | No | Unit price |
items[].stock_quantity | integer | No | Available stock |
items[].description | string | No | Product description |
items[].category | string | No | Category name |
items[].image_path | string | No | Absolute image URL |
Response
{
"created": 3,
"updated": 12,
"deleted": 0,
"errors": []
}
Customers
Pull or push customer/buyer records. Records are matched by email when available, or by external_id.
GET — Pull customers
Returns all customers for your company including contact details and billing address.
POST — Push customers
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
items[].external_id | string | No | Your platform's customer ID — used as the upsert key |
items[].name | string | Yes | Full name |
items[].email | string | No | Email address — used for deduplication |
items[].phone | string | No | Phone number |
items[].address | string | No | Formatted billing address |
items[].city | string | No | City |
items[].state | string | No | State / province |
items[].postal_code | string | No | ZIP / postal code |
Vendors
Pull or push vendor records. Matched by email or name.
POST — Push vendors
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
items[].name | string | Yes | Vendor / supplier name — upsert key |
items[].email | string | No | Contact email |
items[].phone | string | No | Phone number |
items[].address | string | No | Mailing address |
items[].website | string | No | Website URL |
items[].notes | string | No | Internal notes |
Orders
Push orders from your store into Trailing Paper. Each order is converted into an invoice. Line items, customer lookup, and payment recording are all handled automatically.
POST — Push orders
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
items[].external_id | string | Yes | Your store's order ID — upsert key; prevents duplicates |
items[].buyer_id | string | No | Your store's customer ID (used to resolve TP customer) |
items[].buyer_email | string | No | Customer email — fallback for customer lookup |
items[].buyer_name | string | No | Customer name — created if no match found |
items[].order_date | string | No | ISO 8601 date or datetime |
items[].total | number | No | Order total in dollars |
items[].status | string | No | pending, processing, completed, cancelled, refunded |
items[].payment_method | string | No | e.g. stripe, paypal, card |
items[].line_items | array | No | Array of {sku, name, quantity, unit_price} objects |
items[].shipping_address | object | No | {address, city, state, postal_code, country} |
Response
{
"created": 2,
"updated": 1,
"errors": []
}
Completed orders with a total > 0 automatically generate a payment record and a matching ledger entry in Trailing Paper. Refunded orders trigger a reversal entry.
Purchase Orders
Pull purchase orders from TP or push inbound purchase orders from your procurement system. Matched by source_po_id or po_number.
POST — Push purchase orders
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
items[].source_po_id | string | No | Your system's PO ID — primary upsert key |
items[].po_number | string | No | Human-readable PO number — secondary upsert key |
items[].vendor_name | string | No | Vendor name — resolved to a TP vendor record |
items[].order_date | string | No | ISO 8601 date |
items[].total | number | No | PO total in dollars |
items[].status | string | No | draft, sent, received, cancelled |
items[].line_items | array | No | Array of {sku, name, quantity, unit_cost} |
Expenses
Pull or push expense records. Useful for syncing platform fees, shipping costs, or ad spend from external sources into your TP financials.
POST — Push expenses
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
items[].expense_date | string | Yes | ISO 8601 date — part of upsert key |
items[].amount | number | Yes | Expense amount in dollars — part of upsert key |
items[].description | string | No | Description or memo |
items[].category | string | No | Expense category |
items[].vendor_name | string | No | Vendor / payee name |
items[].external_id | string | No | Your system's expense ID — overrides date+amount key |
Ledger Entries
Pull or push general ledger entries. Use this to sync accounting journal entries from an external ERP or accounting system.
POST — Push ledger entries
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
items[].entry_date | string | Yes | ISO 8601 date |
items[].amount | number | Yes | Entry amount in dollars (positive = debit, negative = credit) |
items[].description | string | No | Entry description / memo |
items[].account_code | string | No | Chart of accounts code |
items[].reference | string | No | Reference number or transaction ID |
items[].external_id | string | No | Your system's entry ID — primary upsert key when provided |
Credit Memos
Pull or push credit memo records for returns, adjustments, or store credit.
POST — Push credit memos
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
items[].external_id | string | No | Your system's memo ID — primary upsert key |
items[].memo_number | string | No | Human-readable memo number |
items[].amount | number | No | Credit amount in dollars |
items[].customer_name | string | No | Customer name — resolved to a TP customer record |
items[].memo_date | string | No | ISO 8601 date |
items[].reason | string | No | Reason for the credit (e.g. return, adjustment) |
Card Processing Config
Discovery endpoint for downstream commerce sites that want to render the in-browser card form and route charges through your Trailing Paper merchant account. Returns the application id, environment, and availability flag your client-side script needs — so you don't have to copy-paste credentials onto every site.
Authenticate with the same X-TP-Sync-Key header used for the rest of the sync API. Cache the response for ~12 hours; invalidate when you rotate the sync key.
Request
GET /api/sync/finix-config HTTP/1.1
Host: trailingpaper.com
X-TP-Sync-Key: tp_live_xxxxxxxxxxxxxxxx
Accept: application/json
Response
{
"success": true,
"available": true,
"application_id": "APij8YKTk1dcUwswCDdc2AtG",
"env": "sandbox",
"platform_merchant_available": true,
"company_merchant_state": "APPROVED"
}
Response fields
| Field | Type | Description |
|---|---|---|
available | boolean | true when card processing is fully wired (API creds set, application id present, and a usable merchant id exists). Fall back to your manual-processing flow if false. |
application_id | string | Pass to Finix.PaymentForm() client-side. Same id is shared across all sites under the same TP application. |
env | string | sandbox or live. Pass to Finix.PaymentForm(). |
platform_merchant_available | boolean | Whether the platform-level merchant id is configured. When true, sites without their own dedicated merchant can still accept cards. |
company_merchant_state | string | Onboarding state of this company's own Finix merchant (PROVISIONING, APPROVED, etc.) or null if not yet onboarded. |
Charge Synced Order
Charges a Trailing Paper invoice that was created via an earlier POST /api/sync/orders push. Designed for downstream checkouts that tokenize cards client-side via Finix.js and want the resulting transfer, payment record, and ledger entry to land on the synced TP invoice.
Authenticate with X-TP-Sync-Key. Idempotent — identical retries return the existing transfer rather than double-charging.
Request
POST /api/sync/charge-order HTTP/1.1
Host: trailingpaper.com
X-TP-Sync-Key: tp_live_xxxxxxxxxxxxxxxx
Content-Type: application/json
{
"external_id": "315",
"external_source": "redacted",
"payment_instrument_id": "TKxxxxxxxxxxxxxxxx",
"amount": 194.65
}
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
external_id | string | Yes | Your store's order id, matching the external_id used in the original POST /api/sync/orders push. |
external_source | string | No | Source key matching the order push (defaults to redacted). TP also falls back to a direct id match if no source-scoped invoice is found. |
payment_instrument_id | string | Yes | Finix token. Accepts both TK… tokens (returned by Finix.js) and PI… payment instruments. TK tokens are exchanged to PI server-side before the transfer. |
amount | number | No | Amount in dollars. Capped server-side to invoice.balance_due. Omit to charge the full balance. Minimum charge is $0.50. |
Success response
{
"success": true,
"transfer_id": "TRxxxxxxxxxxxxxxxx",
"state": "PENDING",
"payment_id": 4421,
"invoice_id": 1293,
"amount": 194.65
}
Response fields
| Field | Type | Description |
|---|---|---|
transfer_id | string | Finix transfer id. Reflects in TP's payment record, ledger entry, and merchant dashboard. |
state | string | Finix transfer state (PENDING initially; SUCCEEDED on settlement; FAILED / CANCELED on rejection). |
payment_id | number | TP payment row id. |
invoice_id | number | TP invoice id this charge was applied to. |
amount | number | Final amount actually charged (after balance-due capping). |
Failure responses
All failures return JSON with success: false and a human-readable error. Common codes:
| HTTP | Meaning |
|---|---|
| 401 | Missing or invalid X-TP-Sync-Key. |
| 404 | No invoice found for this external_id + external_source combination. |
| 422 | Validation failure or Finix decline (card declined, balance already paid, amount below minimum, etc.). The error field carries the underlying message. |
| 500 | Server-side error. Safe to retry — the idempotency key prevents double charges. |
Idempotency: TP forms a Finix idempotency id of inv-<invoice_id>-<sha256_prefix> using the invoice id and the token. Retrying the exact same body returns the original transfer rather than a duplicate charge. Safe to retry on network blips.
Recommended call order
- Tokenize the card client-side via Finix.js (
Finix.PaymentForm— see the integration playbook in/docs/cc-checkout-integration-playbook.md). - Create the order in your store and push it to TP via
POST /api/sync/orders. Wait for200. - Call
POST /api/sync/charge-orderwith the token. - On success: mark the order paid, fire fulfillment hooks, send the receipt email.
- On failure: leave the order unpaid and surface "card declined." Do not send a receipt email.
Create Invoice
Service-style invoice creator for downstream tools (e.g. the Papaya dashboard's rev-share billing) that need to bill arbitrary line items rather than e-commerce orders. Auto-creates the customer if it isn't already in your TP company. Idempotent on external_id.
Authenticate with X-TP-Sync-Key.
Request
POST /api/sync/invoice HTTP/1.1
Host: trailingpaper.com
X-TP-Sync-Key: tp_live_xxxxxxxxxxxxxxxx
Content-Type: application/json
{
"customer": {
"id": 1301,
"external_source": "papaya-dashboard",
"external_id": "site-9",
"name": "Wheyk",
"email": "billing@wheyk.com"
},
"items": [
{ "description": "Revenue share — April 2026", "quantity": 1, "unit_price": 1234.56 }
],
"issue_date": "2026-05-01",
"due_date": "2026-05-15",
"notes": "5% revenue share on $24,691.20 in net sales (Apr 1–30, 2026).",
"payment_methods": ["zelle"],
"external_source": "papaya-dashboard",
"external_id": "rev-share-9-2026-04"
}
Customer matching priority
customer.id— explicit pairing. If supplied, TP uses that customer record. Returns422if the id isn't in your company.customer.external_source+customer.external_id— for callers that maintain their own keying.customer.email— case-sensitive exact match.- Case-insensitive exact match on
customer.namewithin the same company. - If no match, a new customer is created from the provided fields.
Payment method whitelist
Pass payment_methods as an array of method keys to restrict which options appear on the public invoice page. Accepted aliases: credit_card / card, bank / bank_transfer / ach / wire, zelle, venmo, cash. Omit the field to leave all methods enabled (the default allow_* toggles on the invoice).
Idempotency
If external_id is set and an invoice already exists with the same external_source + external_id in your company, TP returns the existing invoice (with "already_existed": true) instead of creating a duplicate. Safe to retry on network blips.
Success response
{
"success": true,
"already_existed": false,
"invoice_id": 1305,
"invoice_number": "INV-00043",
"customer_id": 1301,
"balance_due": 1234.56,
"total": 1234.56,
"status": "unpaid",
"public_link": "https://trailingpaper.com/invoice/view/abc123…",
"public_token": "abc123…",
"external_source": "papaya-dashboard",
"external_id": "rev-share-9-2026-04"
}
Send Invoice Email
Triggers the same email flow that the manual "Send Invoice" button uses on the TP UI — uses the company's email template, refreshes the public invoice link, stamps last_sent_at. Intended for downstream tools that previously created the invoice as a draft via POST /api/sync/invoice and want a separate review-then-send step.
Authenticate with X-TP-Sync-Key.
Request
POST /api/sync/invoice/1305/send HTTP/1.1
Host: trailingpaper.com
X-TP-Sync-Key: tp_live_xxxxxxxxxxxxxxxx
Content-Type: application/json
{
"to": ["billing@wheyk.com"],
"note": "Optional personal message rendered in the email body."
}
Request body fields
| Field | Type | Required | Description |
|---|---|---|---|
to | string or array | No | Override the recipient. Defaults to the customer's email on file. |
note | string | No | Personal note to render above the invoice summary. |
Success response
{
"success": true,
"invoice_id": 1305,
"sent_to": ["billing@wheyk.com"],
"sent_at": "2026-05-01 09:15:42"
}
Customer Search
Lightweight typeahead for downstream UIs that let users link an external entity (a site, a tenant, a user) to an existing TP customer. Matches against name or email within your company. Returns up to 25 customers ordered by name. Auth: X-TP-Sync-Key.
Request
GET /api/sync/customers/search?q=cellular HTTP/1.1
Host: trailingpaper.com
X-TP-Sync-Key: tp_live_xxxxxxxxxxxxxxxx
Accept: application/json
Query params
| Param | Type | Required | Description |
|---|---|---|---|
q | string | No | Substring matched against name OR email. Omit to return the first 25 customers in the company. |
Success response
{
"success": true,
"items": [
{
"id": 1301,
"name": "Amp Cellular Longevity LLC",
"email": "drmike@ampsportsmed.com",
"external_source": "",
"external_id": ""
}
]
}
Pair an external entity with the returned id, then pass it as customer.id when calling POST /api/sync/invoice to bill that customer directly without creating a duplicate.
Full Bidirectional Sync
Full sync endpoints combine a GET (pull from your store) and a POST (push from TP) in a single atomic call. Use these for scheduled nightly syncs or on-demand reconciliation.
| Endpoint | What it does |
|---|---|
POST /api/sync/products/full | Pulls products from your store URL, upserts into TP, then pushes all TP products back to your store |
POST /api/sync/vendors/full | Pulls vendors from your store, upserts into TP, pushes TP vendors back |
POST /api/sync/purchase-orders/full | Bidirectional PO sync |
POST /api/sync/expenses/full | Bidirectional expense sync |
POST /api/sync/ledger/full | Bidirectional ledger sync |
POST /api/sync/credit-memos/full | Bidirectional credit memo sync |
Full sync endpoints require your External Store Base URL to be configured in Settings → Company APIs. They authenticate to your store using your Sync API credentials.
Full sync response
{
"pulled": {
"created": 8,
"updated": 34,
"deleted": 0,
"errors": []
},
"pushed": 42
}
Triggering from the dashboard
You can also trigger full syncs from the Settings → Company APIs page using the sync buttons — no code required.
Webhook Events
Trailing Paper sends webhook notifications to your registered HTTPS endpoint when events occur on your account. Webhooks are the recommended way to track async state changes — particularly transfer.succeeded and transfer.failed, since charges start as PENDING and finalize asynchronously.
Webhook registration is coming soon. You will be able to register your endpoint URL from the dashboard. Until then, poll the retrieve endpoint or use the merchant dashboard to monitor charge status.
Event object structure
| Field | Type | Description |
|---|---|---|
| event | string | Event type, e.g. transfer.succeeded. |
| created_at | string | ISO 8601 timestamp when the event occurred. |
| data | object | The resource that triggered the event. Shape varies by event type. |
Event types
| Event | Description |
|---|---|
| transfer.succeeded | A charge was authorized and will settle on the next payout date. |
| transfer.failed | A charge was declined or failed. The data.failure_code field describes the reason. |
| invoice.paid | An invoice was paid in full. |
| invoice.overdue | An invoice passed its due date without payment. |
| payout.sent | A bank payout was initiated to your linked account. |
| dispute.created | A customer filed a chargeback. You have a limited window to respond. |
Best practices
- Respond with
200 OKwithin 5 seconds. Do heavy processing asynchronously. - Use the
transfer_idin the event payload to look up the charge in your system. - Make your handler idempotent — the same event may be delivered more than once.
- Always verify the webhook signature (see below) before processing.
Verifying Signatures
Every webhook delivery will include a X-TP-Signature header containing an HMAC-SHA256 signature of the raw request body using your webhook secret. Verifying this signature is critical — it proves the request originated from Trailing Paper and not a third party.
Coming with Webhook Registration
Full signature verification documentation will be published when webhook delivery launches. The verification algorithm will follow the industry-standard HMAC-SHA256 pattern used by Stripe and Finix.
Object Reference
Transfer
Returned by POST /api/charge. Represents a single card payment transaction.
| Field | Type | Description |
|---|---|---|
| success | boolean | Always true on success. |
| transfer_id | string | Unique ID. Prefix: TR. Use for dashboard lookups and webhook matching. |
| status | string |
PENDING — created, awaiting finalizationSUCCEEDED — authorized, will settleFAILED — declined or errored
|
| amount | integer | Amount in cents. |
| currency | string | ISO 4217 currency code. Currently always USD. |
Changelog
April 24, 2026 — v1.0
- New:
POST /api/charge— create a card charge via Finix Payment Instrument ID - New: API key management at
/api-keys— generate, label, and revoke keys - New: Bearer token authentication (
tp_live_-prefixed 56-character keys) - New:
Idempotency-Keyheader support on POST endpoints - New: This developer documentation