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

FrontendYour BackendOutpost API
  • 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

VariableValueNotes
API_BASE_URLhttps://api.outpostanywhere.comBase URL for all API endpoints
CLIENT_IDYour client IDNon-sensitive, can be inlined
OUTPOST_CLIENT_SECRETStore 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

POST/api/tax/calculations

Creates 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

ParameterTypeRequiredDescription
AuthorizationYesBearer token for authentication
Content-TypeYesapplication/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

ParameterTypeRequiredDescription
currencystringYesISO 4217 currency code (e.g., EUR, USD, GBP)
customerobjectYesCustomer information object
customer.merchantCustomerReferencestringNoYour internal customer identifier
customer.firstNamestringNoCustomer's first name
customer.lastNamestringNoCustomer's last name
customer.emailstringNoCustomer's email address
customer.billingAddressobjectYesCustomer's billing address
customer.billingAddress.line1stringYesPrimary address line
customer.billingAddress.line2stringNoSecondary address line
customer.billingAddress.citystringYesCity name
customer.billingAddress.statestringConditionalState/province code (required for US and CA)
customer.billingAddress.postalCodestringYesPostal/ZIP code
customer.billingAddress.countrystringYesISO 3166-1 alpha-2 country code
lineItemsarrayYesArray of line items (minimum 1)
lineItems[].merchantLineItemReferencestringYesYour internal line item identifier
lineItems[].productCodestringYesProduct/service code for tax classification
lineItems[].descriptionstringYesHuman-readable item description
lineItems[].unitPricedecimalYesPrice per unit (must be ≥ 0)
lineItems[].quantityintegerNoQuantity (default: 1, must be > 0)
lineItems[].discountAmountdecimalNoDiscount amount for this line item
evidenceobjectConditionalTax evidence (required for non-US billing addresses)
evidence.billingCountrystringConditionalISO 3166-1 alpha-2 billing country code
evidence.ipAddressstringConditionalCustomer's IP address
evidence.paymentMethodCountrystringNoPayment method country code
Note: For non-US billing addresses, 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

ParameterTypeRequiredDescription
taxCalculationIdstringUnique identifier for the calculation
createdAtstringISO 8601 timestamp when calculation was created
expiresAtstringISO 8601 timestamp when calculation expires
currencystringCurrency code
subTotalAmountdecimalSum of line item amounts before discounts
discountAmountdecimalTotal discount amount applied
netAmountdecimalAmount after discounts, before tax
taxAmountdecimalTotal tax amount
totalAmountdecimalFinal amount including tax
customerobjectCustomer information
lineItemsarrayCalculated line items with tax details
evidenceobjectTax evidence used for calculation

Error Responses

HTTPCodeDescription
400invalid_argumentMissing or invalid field — check field requirements
400invalid_statePlease activate Outpost Tax of Record to process transactions
409region_not_activatedMerchant not activated for the customer's tax jurisdiction
409region_not_supportedCustomer location outside supported jurisdictions
409integration_not_configuredTax compliance integration is not configured
401Invalid 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 step

Create Tax Transaction

POST/api/tax/transactions

Converts 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

ParameterTypeRequiredDescription
AuthorizationYesBearer token for authentication
Content-TypeYesapplication/json

Request Body

{
  "taxCalculationId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "merchantTransactionReference": "ORDER-2026-001234",
  "payment": {
    "processor": "stripe",
    "paymentReference": "ch_3Q1234567890abcdef"
  }
}

Request Body Parameters

ParameterTypeRequiredDescription
taxCalculationIdstringYesID of the tax calculation generated by Outpost to convert
merchantTransactionReferencestringYesYour unique order/transaction reference
paymentobjectNoPayment processor information
payment.processorstringNoPayment processor name (e.g., "stripe", "adyen")
payment.paymentReferencestringNoPayment 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

HTTPCodeDescription
400invalid_argumentMissing or invalid field — check field requirements
400invalid_stateTax calculation has expired
400invalid_stateTax calculation has already been used
400invalid_stateTax transactions API can only be used for Tax of Record product
404not_foundTax 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 TaxTransactionResponse

Get Tax Transaction

GET/api/tax/transactions/{transaction_id}

Retrieves the details of an existing tax transaction.

Path Parameters

ParameterTypeRequiredDescription
transaction_idstringYesID of the transaction

Response 200 OK

Returns a TaxTransactionResponse object (same structure as Create Tax Transaction response).

Error Responses

HTTPCodeDescription
404not_foundTransaction not found

Get Transaction Refunds

GET/api/tax/transactions/{transaction_id}/refunds

Retrieves all refunds associated with a transaction.

Path Parameters

ParameterTypeRequiredDescription
transaction_idstringYesID 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

HTTPCodeDescription
404not_foundTransaction not found

Create Refund Transaction

POST/api/tax/transactions/{transaction_id}/refunds

Creates a refund for one or more line items in an existing transaction.

Path Parameters

ParameterTypeRequiredDescription
transaction_idstringYesID 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

ParameterTypeRequiredDescription
merchantRefundReferencestringYesYour unique refund reference
refundDatestringNoISO 8601 date/time of the refund
refundLineItemsarrayYesArray of line items to refund (minimum 1)
refundLineItems[].lineItemIdstringYesID of the original line item
refundLineItems[].refundLineItemReferencestringYesYour unique reference for this refund item
refundLineItems[].refundReasonstringYesReason 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

HTTPCodeDescription
404not_foundTransaction or line items not found

Get Transaction Invoice

GET/api/tax/transactions/{transaction_id}/invoice

Retrieves the invoice file for a transaction.

Path Parameters

ParameterTypeRequiredDescription
transaction_idstringYesID of the transaction

Response 200 OK

{
  "b2cInvoice": {
    "fileName": "invoice-e83a9c47-2b5d-4f8a-9c12-3d4e5f6a7b8c.pdf",
    "url": "https://storage.outpostnow.com/invoices/..."
  }
}

Response Fields

ParameterTypeRequiredDescription
b2cInvoiceobjectB2C invoice information
b2cInvoice.fileNamestringName of the invoice file
b2cInvoice.urlstringURL 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

Phase 1Calculate only (store disabled) to test tax calculation
Phase 2Enable Store for 1% of successful payments
Phase 3Gradually increase to 10%, 50%, 100%
RollbackFeature flags to disable Store calls quickly if needed

Acceptance Checklist

Auth token acquisition works
Token caching and refresh logic implemented
Calculate returns valid taxCalculationId
taxCalculationId properly stored with order
Store includes payment details (paymentReference, processor)
Store only called after successful payment
Timeout and retry logic implemented
Error handling covers 4xx and 5xx cases
Logging includes correlation IDs
Monitoring and alerting configured

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.