Overview
Outpost Tax of Record provides a REST API that automates tax registration, calculation, and filings while you maintain your own checkout and payment service providers (Stripe, Adyen, etc.). The integration is server-to-server — your frontend never calls Outpost directly.
Build with AI
Copy the full integration brief and paste it into your AI coding assistant to scaffold the integration automatically.
One-click brief for your AI agent.
Integration Flow
- Frontend initiates checkout and calls your backend
- Your backend authenticates with Outpost using OAuth2 client_credentials
- Backend calculates tax via Outpost API and displays it at checkout
- After payment succeeds, backend stores the transaction with Outpost
- Outpost handles all tax filing, compliance, and liability
Security & Operational Principles
- Server-only secrets — OUTPOST_CLIENT_SECRET must never be exposed to browsers or logs
- Token management — Cache access tokens with TTL, refresh on expiry or 401 responses
- Idempotency — Prevent double confirm/cancel calls using merchantTransactionReference tracking
- Timeouts & retries — 5s timeout per request, retry transient errors (network/5xx) with bounded backoff
- Production gates — Store calls only after PSP authorization/capture succeeds
Environment Setup
| Variable | Value | Notes |
|---|---|---|
| API_BASE_URL | https://api.outpostanywhere.com | Base URL for all API endpoints |
| CLIENT_ID | Your client ID | Non-sensitive, can be inlined |
| OUTPOST_CLIENT_SECRET | — | Store in environment variables or secret manager |
Authentication
Outpost uses OAuth2 client_credentials flow for server-to-server authentication. Your backend requests an access token using your client ID and secret, then includes it as a Bearer token in all API calls.
Flow
- Request access token using client credentials
- Parse response and extract access_token with expires_in
- Cache token server-side with TTL (recommend expires_in - 300s buffer)
- Refresh token on expiry or 401 responses
- Include Bearer token in all subsequent API requests
Cache access_token with expires_in - 300s buffer; retry once after refresh on 401.
Token Response
{
"access_token": "<JWT>",
"expires_in": 86400,
"token_type": "Bearer"
}Authentication
# Step 1: Obtain an access token
curl -X POST \
"https://access.outpostanywhere.com/oauth2/token" \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=$OUTPOST_CLIENT_SECRET" | jq
# Export the token (example)
# export OUTPOST_ACCESS_TOKEN="eyJhbGciOi..."
# Sample response:
# {
# "access_token": "<JWT>",
# "expires_in": 86400,
# "token_type": "Bearer"
# }Run from your server. Never expose secrets in the browser.
Create Tax Calculation
/api/tax/calculationsCreates a tax calculation for a given set of line items and customer information. The calculation determines applicable tax rates based on the customer's location and can be used to create a transaction after the successful payment.
Request Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| Authorization | — | Yes | Bearer token for authentication |
| Content-Type | — | Yes | application/json |
Request Body
{
"currency": "EUR",
"customer": {
"merchantCustomerReference": "CUST-12345",
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"billingAddress": {
"line1": "Keizersgracht 123",
"line2": "Apt 4B",
"city": "Amsterdam",
"postalCode": "1015 CJ",
"country": "NL"
}
},
"lineItems": [
{
"merchantLineItemReference": "ITEM-001",
"productCode": "TEST-SKU-005",
"description": "Premium Subscription - Annual",
"unitPrice": 99.99,
"quantity": 1,
"discountAmount": 10.00
}
],
"evidence": {
"billingCountry": "NL",
"ipAddress": "185.23.108.42",
"paymentMethodCountry": "NL"
}
}Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| currency | string | Yes | ISO 4217 currency code (e.g., EUR, USD, GBP) |
| customer | object | Yes | Customer information object |
| customer.merchantCustomerReference | string | No | Your internal customer identifier |
| customer.firstName | string | No | Customer's first name |
| customer.lastName | string | No | Customer's last name |
| customer.email | string | No | Customer's email address |
| customer.billingAddress | object | Yes | Customer's billing address |
| customer.billingAddress.line1 | string | Yes | Primary address line |
| customer.billingAddress.line2 | string | No | Secondary address line |
| customer.billingAddress.city | string | Yes | City name |
| customer.billingAddress.state | string | Conditional | State/province code (required for US and CA) |
| customer.billingAddress.postalCode | string | Yes | Postal/ZIP code |
| customer.billingAddress.country | string | Yes | ISO 3166-1 alpha-2 country code |
| lineItems | array | Yes | Array of line items (minimum 1) |
| lineItems[].merchantLineItemReference | string | Yes | Your internal line item identifier |
| lineItems[].productCode | string | Yes | Product/service code for tax classification |
| lineItems[].description | string | Yes | Human-readable item description |
| lineItems[].unitPrice | decimal | Yes | Price per unit (must be ≥ 0) |
| lineItems[].quantity | integer | No | Quantity (default: 1, must be > 0) |
| lineItems[].discountAmount | decimal | No | Discount amount for this line item |
| evidence | object | Conditional | Tax evidence (required for non-US billing addresses) |
| evidence.billingCountry | string | Conditional | ISO 3166-1 alpha-2 billing country code |
| evidence.ipAddress | string | Conditional | Customer's IP address |
| evidence.paymentMethodCountry | string | No | Payment method country code |
evidence.billingCountry must match customer.billingAddress.country.Response 201 Created
{
"taxCalculationId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"createdAt": "2026-01-05T12:00:00Z",
"expiresAt": "2026-01-05T12:30:00Z",
"currency": "EUR",
"subTotalAmount": 99.99,
"discountAmount": 10.00,
"netAmount": 89.99,
"taxAmount": 18.90,
"totalAmount": 108.89,
"customer": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"billingAddress": {
"line1": "Keizersgracht 123",
"line2": "Apt 4B",
"city": "Amsterdam",
"postalCode": "1015 CJ",
"country": "NL"
}
},
"lineItems": [
{
"lineItemId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"merchantLineItemReference": "ITEM-001",
"productCode": "TEST-SKU-005",
"description": "Premium Subscription - Annual",
"unitPrice": 99.99,
"quantity": 1,
"discountAmount": 10.00,
"itemSubTotalAmount": 99.99,
"itemNetAmount": 89.99,
"itemTotalAmount": 108.89,
"tax": {
"rate": 21.00,
"taxAmount": 18.90,
"currency": "EUR"
}
}
],
"evidence": {
"billingCountry": "NL",
"ipAddress": "185.23.108.42",
"paymentMethodCountry": "NL"
}
}Response Fields
| Parameter | Type | Required | Description |
|---|---|---|---|
| taxCalculationId | string | — | Unique identifier for the calculation |
| createdAt | string | — | ISO 8601 timestamp when calculation was created |
| expiresAt | string | — | ISO 8601 timestamp when calculation expires |
| currency | string | — | Currency code |
| subTotalAmount | decimal | — | Sum of line item amounts before discounts |
| discountAmount | decimal | — | Total discount amount applied |
| netAmount | decimal | — | Amount after discounts, before tax |
| taxAmount | decimal | — | Total tax amount |
| totalAmount | decimal | — | Final amount including tax |
| customer | object | — | Customer information |
| lineItems | array | — | Calculated line items with tax details |
| evidence | object | — | Tax evidence used for calculation |
Error Responses
| HTTP | Code | Description |
|---|---|---|
| 400 | invalid_argument | Missing or invalid field — check field requirements |
| 400 | invalid_state | Please activate Outpost Tax of Record to process transactions |
| 409 | region_not_activated | Merchant not activated for the customer's tax jurisdiction |
| 409 | region_not_supported | Customer location outside supported jurisdictions |
| 409 | integration_not_configured | Tax compliance integration is not configured |
| 401 | — | Invalid or missing Authorization token |
Code Example
# Step 2: Calculate tax for the transaction
curl -X POST "https://api.outpostanywhere.com/api/tax/calculations" \
-H "Authorization: Bearer $OUTPOST_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"currency": "EUR",
"customer": {
"merchantCustomerReference": "CUST-001",
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"billingAddress": {
"line1": "123 Main St",
"city": "Berlin",
"postalCode": "10115",
"country": "DE"
}
},
"merchantTransactionReference": "TXN-001",
"lineItems": [
{
"merchantLineItemReference": "ITEM-001",
"productCode": "SKU-123",
"description": "Sample Product",
"unitPrice": 19.99,
"quantity": 1
}
],
"evidence": {
"billingCountry": "DE",
"ipAddress": "192.168.1.1",
"paymentMethodCountry": "DE"
}
}' | jq
# Response: 201 Created with taxCalculationId
# Save the taxCalculationId for the Store stepCreate Tax Transaction
/api/tax/transactionsConverts a valid tax calculation into a confirmed tax transaction. This endpoint should be called when an order is finalized/paid. Only call after payment authorization/capture succeeds.
Request Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| Authorization | — | Yes | Bearer token for authentication |
| Content-Type | — | Yes | application/json |
Request Body
{
"taxCalculationId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"merchantTransactionReference": "ORDER-2026-001234",
"payment": {
"processor": "stripe",
"paymentReference": "ch_3Q1234567890abcdef"
}
}Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| taxCalculationId | string | Yes | ID of the tax calculation generated by Outpost to convert |
| merchantTransactionReference | string | Yes | Your unique order/transaction reference |
| payment | object | No | Payment processor information |
| payment.processor | string | No | Payment processor name (e.g., "stripe", "adyen") |
| payment.paymentReference | string | No | Payment processor transaction ID |
Response 200 OK
{
"transactionId": "e83a9c47-2b5d-4f8a-9c12-3d4e5f6a7b8c",
"refunded": false,
"createdAt": "2026-01-05T12:05:00Z",
"currency": "EUR",
"subTotalAmount": 99.99,
"discountAmount": 10.00,
"netAmount": 89.99,
"taxAmount": 18.90,
"totalAmount": 108.89,
"merchantTransactionReference": "ORDER-2026-001234",
"transactionDate": "2026-01-05T12:05:00Z",
"customer": {
"firstName": "John",
"lastName": "Doe",
"email": "john.doe@example.com",
"billingAddress": {
"line1": "Keizersgracht 123",
"line2": "Apt 4B",
"city": "Amsterdam",
"postalCode": "1015 CJ",
"country": "NL"
}
},
"payment": {
"processor": "stripe",
"paymentReference": "ch_3Q1234567890abcdef"
},
"lineItems": [
{
"lineItemId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"merchantLineItemReference": "ITEM-001",
"productCode": "TEST-SKU-005",
"description": "Premium Subscription - Annual",
"unitPrice": 99.99,
"quantity": 1,
"discountAmount": 10.00,
"itemSubTotalAmount": 99.99,
"itemNetAmount": 89.99,
"itemTotalAmount": 108.89,
"tax": {
"rate": 21.00,
"taxAmount": 18.90,
"currency": "EUR"
}
}
],
"evidence": {
"billingCountry": "NL",
"ipAddress": "185.23.108.42",
"paymentMethodCountry": "NL"
}
}Error Responses
| HTTP | Code | Description |
|---|---|---|
| 400 | invalid_argument | Missing or invalid field — check field requirements |
| 400 | invalid_state | Tax calculation has expired |
| 400 | invalid_state | Tax calculation has already been used |
| 400 | invalid_state | Tax transactions API can only be used for Tax of Record product |
| 404 | not_found | Tax calculation not found |
Code Example
# Step 3: Store the transaction using taxCalculationId from calculate response
curl -X POST "https://api.outpostanywhere.com/api/tax/transactions" \
-H "Authorization: Bearer $OUTPOST_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"taxCalculationId": "'"$TAX_CALCULATION_ID"'",
"merchantTransactionReference": "TXN-001",
"payment": {
"paymentReference": "PAY-001",
"processor": "stripe"
}
}' | jq
# Response: 200 OK with TaxTransactionResponseGet Tax Transaction
/api/tax/transactions/{transaction_id}Retrieves the details of an existing tax transaction.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| transaction_id | string | Yes | ID of the transaction |
Response 200 OK
Returns a TaxTransactionResponse object (same structure as Create Tax Transaction response).
Error Responses
| HTTP | Code | Description |
|---|---|---|
| 404 | not_found | Transaction not found |
Get Transaction Refunds
/api/tax/transactions/{transaction_id}/refundsRetrieves all refunds associated with a transaction.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| transaction_id | string | Yes | ID of the original transaction |
Response 200 OK
[
{
"refundTransactionId": "d1e2f3a4-5b6c-7d8e-9f0a-1b2c3d4e5f6a",
"merchantRefundReference": "REFUND-2026-000456",
"refundDate": "2026-01-10T15:30:00Z",
"refundLineItems": [
{
"lineItemId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"refundLineItemReference": "REFUND-ITEM-001",
"refundReason": "Customer requested cancellation",
"refundItemSubTotalAmount": 99.99,
"refundItemTotalAmount": 108.89,
"refundTax": {
"rate": 21.00,
"taxAmount": 18.90,
"currency": "EUR"
}
}
],
"createdAt": "2026-01-10T15:30:00Z",
"currency": "EUR",
"subTotalAmount": 99.99,
"taxAmount": 18.90,
"totalAmount": 108.89
}
]Error Responses
| HTTP | Code | Description |
|---|---|---|
| 404 | not_found | Transaction not found |
Create Refund Transaction
/api/tax/transactions/{transaction_id}/refundsCreates a refund for one or more line items in an existing transaction.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| transaction_id | string | Yes | ID of the original transaction |
Request Body
{
"merchantRefundReference": "REFUND-2026-000456",
"refundDate": "2026-01-10T15:30:00Z",
"refundLineItems": [
{
"lineItemId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"refundLineItemReference": "REFUND-ITEM-001",
"refundReason": "Customer requested cancellation"
}
]
}Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| merchantRefundReference | string | Yes | Your unique refund reference |
| refundDate | string | No | ISO 8601 date/time of the refund |
| refundLineItems | array | Yes | Array of line items to refund (minimum 1) |
| refundLineItems[].lineItemId | string | Yes | ID of the original line item |
| refundLineItems[].refundLineItemReference | string | Yes | Your unique reference for this refund item |
| refundLineItems[].refundReason | string | Yes | Reason for the refund |
Response 200 OK
{
"refundTransactionId": "d1e2f3a4-5b6c-7d8e-9f0a-1b2c3d4e5f6a",
"merchantRefundReference": "REFUND-2026-000456",
"refundDate": "2026-01-10T15:30:00Z",
"refundLineItems": [
{
"lineItemId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"refundLineItemReference": "REFUND-ITEM-001",
"refundReason": "Customer requested cancellation",
"refundItemSubTotalAmount": 99.99,
"refundItemTotalAmount": 108.89,
"refundTax": {
"rate": 21.00,
"taxAmount": 18.90,
"currency": "EUR"
}
}
],
"createdAt": "2026-01-10T15:30:00Z",
"currency": "EUR",
"subTotalAmount": 99.99,
"taxAmount": 18.90,
"totalAmount": 108.89
}Error Responses
| HTTP | Code | Description |
|---|---|---|
| 404 | not_found | Transaction or line items not found |
Get Transaction Invoice
/api/tax/transactions/{transaction_id}/invoiceRetrieves the invoice file for a transaction.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| transaction_id | string | Yes | ID of the transaction |
Response 200 OK
{
"b2cInvoice": {
"fileName": "invoice-e83a9c47-2b5d-4f8a-9c12-3d4e5f6a7b8c.pdf",
"url": "https://storage.outpostnow.com/invoices/..."
}
}Response Fields
| Parameter | Type | Required | Description |
|---|---|---|---|
| b2cInvoice | object | — | B2C invoice information |
| b2cInvoice.fileName | string | — | Name of the invoice file |
| b2cInvoice.url | string | — | URL to download the invoice |
Response 202 Accepted — Invoice is still being generated. Retry after a short delay.
Integration Flow
The full checkout integration follows a linear sequence: authenticate, calculate tax, process payment, then store the transaction.
Sequence
- Get/refresh access token (cache with TTL)
- Calculate tax → capture taxCalculationId
- Process payment with your PSP (Stripe, Adyen, etc.)
- If payment succeeds → Call Store with taxCalculationId and payment details
Pseudocode
async function processCheckout(orderData) {
// Step 1: Get auth token
const token = await getAccessToken();
// Step 2: Calculate tax
const taxResponse = await calculateTax(orderData, token);
const taxCalculationId = taxResponse.taxCalculationId;
// Persist mapping for idempotency
await persistCalculationMapping(orderData.orderId, taxCalculationId);
// Step 3: Process payment with your PSP
const paymentResult = await processPayment(orderData, taxResponse.taxTotal);
// Step 4: Store transaction only if payment succeeds
if (paymentResult.success) {
await storeTransaction({
taxCalculationId,
merchantTransactionReference: orderData.orderId,
payment: {
paymentReference: paymentResult.paymentReference,
processor: paymentResult.processor // e.g., "stripe", "adyen"
}
}, token);
}
return { success: paymentResult.success, taxResponse };
}Idempotency Controls
- Store merchantTransactionReference → taxCalculationId mappings
- Check existing mappings before creating new calculations
- Implement mutex/locks for concurrent order processing
- Use database constraints to prevent duplicate transactions
- The merchantTransactionReference in Store prevents duplicate storage
Error Handling
Classify errors by HTTP status code and handle them accordingly. Never retry 4xx errors blindly — they indicate client-side issues that need to be fixed.
4xx — Client Errors
Do not retry blindly.
- 401 — Refresh token and retry once
- 400 — Log and fail fast (likely data validation issue)
- 404 — Calculation not found (check taxCalculationId when calling Store)
5xx — Server Errors
Temporary failures, safe to retry.
- Safe to retry with exponential backoff
- Max 3 retries with 1s, 2s, 4s delays
- Include jitter to prevent thundering herd
Token Refresh on 401
// On 401 response: // 1. Clear cached token // 2. Request new access token // 3. Retry the original request once with new token // 4. If still 401, fail and alert
Structured Logging
{
"timestamp": "2024-01-15T10:30:00Z",
"level": "INFO",
"message": "Tax calculated",
"orderId": "order-12345",
"taxCalculationId": "calc-67890",
"currency": "USD",
"taxAmount": 8.25,
"requestId": "req-uuid-123",
"duration": 1200
}Metrics & Alerting
- Success rates — Percentage of successful calculate/store operations
- Latency — P95/P99 response times for each endpoint
- Error rates — 4xx vs 5xx error breakdown by endpoint
- Token refresh frequency — Monitor auth token lifecycle
Alert Conditions
- Success rate drops below 95%
- P95 latency exceeds 2 seconds
- 5xx error rate exceeds 1%
- Store rate significantly lower than calculate rate
- Token refresh failures
Testing & Rollout
Follow a phased rollout strategy to minimize risk. Start with calculate-only mode, then gradually enable store calls as you gain confidence.
Sandbox Testing
Authentication
Verify token acquisition and refresh
Calculate
Test with various customer/product scenarios
Store
Test successful payment flow with valid taxCalculationId
Error handling
Test timeout, retry, and 4xx/5xx scenarios
Rollout Strategy
Acceptance Checklist
Ready to integrate?
Copy the complete integration plan for your AI coding assistant, or contact us for white-glove onboarding.
One-click brief for your AI agent.