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
customer.shippingAddressobjectNoCustomer's shipping address. Recommended for physical goods — the destination drives US sales-tax sourcing.
customer.shippingAddress.line1stringNoPrimary address line
customer.shippingAddress.line2stringNoSecondary address line
customer.shippingAddress.citystringNoCity name
customer.shippingAddress.statestringConditionalState/province code (required for US and CA)
customer.shippingAddress.postalCodestringNoPostal/ZIP code
customer.shippingAddress.countrystringNoISO 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"
      },
      "refunded": false
    }
  ],
  "evidence": {
    "billingCountry": "NL",
    "ipAddress": "185.23.108.42",
    "paymentMethodCountry": "NL"
  },
  "entity": {
    "companyName": "Outpost Commerce B.V."
  }
}

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
entityobjectThe Outpost legal entity acting as seller / merchant of record (present when Tax of Record applies).
entity.companyNamestringLegal company name of the recording entity.

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",
  "confirmedAt": "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"
  },
  "refundsOnTransaction": [],
  "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"
      },
      "refunded": false
    }
  ],
  "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.

Import Catalogue Product

POST/api/catalogue/products

Imports a single product variant into your catalogue for tax classification. Each call imports one variant. To model a product that comes in multiple options (size, colour, plan tier), send one request per variant and group them under a shared parent product.

Request Headers

ParameterTypeRequiredDescription
AuthorizationYesBearer token for authentication
Content-TypeYesapplication/json

Request Body

{
  "productId": "TSHIRT-001-M",
  "productGroupId": "TSHIRT-001",
  "title": "Classic T-Shirt",
  "requiresShipping": true,
  "taxable": true,
  "description": "100% cotton crew-neck t-shirt",
  "category": "Apparel",
  "imageUrl": "https://cdn.example.com/tshirt-001.png",
  "sku": "TSHIRT-001-M",
  "hsCode": "6109.10",
  "countryOfOrigin": "PT",
  "priceAmount": 24.99,
  "priceCurrency": "USD"
}

Request Body Parameters

ParameterTypeRequiredDescription
productIdstringYesUnique identifier for this variant (the sellable unit). Used as the variant key.
productGroupIdstringNoParent product identifier. Share across variants to group them under one product. Defaults to productId (standalone) when omitted.
titlestringYesProduct title. Product-level — keep identical across variants of a group.
requiresShippingbooleanYesWhether the item ships physically.
taxablebooleanNoWhether the item is taxable (default: true).
descriptionstringNoProduct description. Product-level.
categorystringNoProduct category / type. Product-level.
imageUrlstringNoProduct image URL. Product-level.
skustringNoStock keeping unit. Variant-level.
hsCodestringNoHarmonized System code for customs. Variant-level.
countryOfOriginstringNoISO 3166-1 alpha-2 country of origin. Variant-level.
priceAmountdecimalNoUnit price (default: 0). Variant-level.
priceCurrencystringNoISO 4217 currency code (default: USD). Variant-level.

Response 201 Created

{
  "productId": "TSHIRT-001-M",
  "productGroupId": "TSHIRT-001",
  "title": "Classic T-Shirt",
  "requiresShipping": true,
  "status": "IMPORTED",
  "approvedTaxCode": null,
  "predictedTaxCode": null
}

Response Fields

ParameterTypeRequiredDescription
productIdstringThe variant identifier you supplied.
productGroupIdstring | nullParent product id. null when the product is its own group (standalone).
titlestringProduct title.
requiresShippingbooleanWhether the item ships physically.
statusstringClassification status: IMPORTED, EXTRACTED, READY_FOR_CLASSIFICATION, APPROVED, or ARCHIVED. A fresh import returns IMPORTED.
approvedTaxCodestring | nullOperator-approved tax code. null until a tax code is approved.
predictedTaxCodestring | nullClassifier-predicted tax code. null until classification runs.

Product grouping

Two fields control grouping: productId identifies the variant (the sellable unit), and productGroupId identifies the parent product. There are two ways to structure a product:

  • Standalone product — omit productGroupId. The variant becomes its own group and the response returns productGroupId as null. Use this for single-option items such as an eBook or a one-off SKU.
  • Multi-variant product — send several requests that share one productGroupId, each with a distinct productId. Every call attaches one variant to the same parent product — for example group TSHIRT-001 with variants TSHIRT-001-S, TSHIRT-001-M and TSHIRT-001-L.

Standalone product example:

{
  "productId": "EBOOK-101",
  "title": "Tax Compliance Handbook (eBook)",
  "requiresShipping": false,
  "priceAmount": 9.99,
  "priceCurrency": "USD"
}

Product-level vs variant-level fields

  • Product-level (title, description, category, imageUrl) is keyed by the group. Keep these identical across all variants of one group.
  • Variant-level (sku, priceAmount, priceCurrency, taxable, requiresShipping, hsCode, countryOfOrigin) is specific to each variant.

Re-importing

  • Re-sending a variant with identical content is a no-op.
  • Changing title or description while the variant is APPROVED clears the approval and returns the variant to READY_FOR_CLASSIFICATION for re-review.
  • Changing only priceAmount, hsCode or countryOfOrigin updates the variant silently and keeps any approval.
  • Importing a new productId under an existing productGroupId adds a variant. Tax codes are not inherited; each variant is classified and approved on its own.

Classification lifecycle

The status field moves through IMPORTED EXTRACTED READY_FOR_CLASSIFICATION APPROVED ARCHIVED. predictedTaxCode is set by the classifier after import; approvedTaxCode is set when an operator approves. A fresh import returns IMPORTED with both codes null.

Note: catalogue tax codes are operator-facing today. Tax calculations do not automatically resolve a stored approvedTaxCode; tax is still determined per line item at calculation time.

Error Responses

HTTPCodeDescription
400validation_errorMissing required field — productId, title, or requiresShipping
401Invalid or missing Authorization token
409Conflict — the product could not be imported in its current state

Code Example

# Import a product variant into the catalogue
curl -X POST "https://api.outpostanywhere.com/api/catalogue/products" \
  -H "Authorization: Bearer $OUTPOST_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "productId": "TSHIRT-001-M",
    "productGroupId": "TSHIRT-001",
    "title": "Classic T-Shirt",
    "requiresShipping": true,
    "sku": "TSHIRT-001-M",
    "priceAmount": 24.99,
    "priceCurrency": "USD"
  }' | jq

# Response: 201 Created with the product status and tax codes
# Omit productGroupId to import a standalone product

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.