Tax of Record API for partners

Overview

Outpost Tax of Record provides a REST API that automates tax registration, calculation, and filings for the merchants on your platform. You call it with your own partner credentials on behalf of a named merchant, so your merchants never hold Outpost credentials of their own. The integration is server-to-server — no browser 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 PlatformOutpost API
  • The merchant's checkout calls your platform
  • Your platform authenticates with Outpost using OAuth2 client_credentials
  • Your platform calculates tax for the merchant and returns it to the checkout
  • After payment succeeds, your platform stores the transaction against that merchant
  • 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 and manage your API credentials in the Partner Portal, under Settings → Developer → API Keys. One credential pair covers every merchant linked to your partner account.

Every endpoint below is scoped to a single merchant through the merchantId path parameter. Outpost checks that the merchant is linked to your partner account before the request reaches the tax engine. If it is not — or if the merchant does not exist at all — you get 404, not 403, so that merchant ids cannot be discovered by probing.

Create Tax Calculation

POST/partner/api/merchants/{merchantId}/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.

Path Parameters

ParameterTypeRequiredDescription
merchantIdstringYesOutpost identifier of the merchant you are acting for. Returned when you create the merchant during onboarding.

Request Headers

ParameterTypeRequiredDescription
AuthorizationYesBearer token for authentication
Content-TypeYesapplication/json

Request Body

{
  "currency": "EUR",
  "taxBehavior": "EXCLUSIVE",
  "customer": {
    "merchantCustomerReference": "CUST-12345",
    "firstName": "John",
    "lastName": "Doe",
    "email": "john.doe@example.com",
    "taxIdentifiers": [
      {
        "type": "EU_VAT",
        "code": "NL123456789B01"
      }
    ],
    "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, BRL). Brazil B2C NFS-e requires BRL.
taxBehaviorstring enumNoAllowed values: EXCLUSIVE, INCLUSIVE. Defaults to EXCLUSIVE. With INCLUSIVE, unitPrice is the gross amount and tax is extracted from it.
customerobjectYesCustomer information object
customer.merchantCustomerReferencestringNoYour internal customer identifier
customer.firstNamestringConditionalCustomer's first name. Required for Brazil B2C NFS-e.
customer.lastNamestringConditionalCustomer's last name. Required for Brazil B2C NFS-e.
customer.emailstringNoCustomer's email address
customer.taxIdentifiersarrayConditionalCustomer tax identifiers, discriminated on type. Required for Brazil NFS-e calculations.
customer.taxIdentifiers[].typestring enumConditionalIdentifier type: BR_CPF, BR_CNPJ, EU_VAT, GB_VAT, or US_EIN. The jurisdiction is part of the type — there is no country field. Unknown types are rejected. Brazil requires exactly one BR_CPF or BR_CNPJ; other types may accompany it.
customer.taxIdentifiers[].codestringConditionalIdentifier value. Validated per type: check digits for BR_CPF and BR_CNPJ, VIES format with a two-letter country prefix for EU_VAT. GB_VAT and US_EIN are informational and recorded as provided.
customer.taxIdentifiers[].buyerTaxRegimestring enumConditionalBR_CNPJ only. Allowed values: SIMPLES_NACIONAL, LUCRO_PRESUMIDO, LUCRO_REAL. Required for Brazil B2B NFS-e calculations.
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.shippingAddressobjectConditionalCustomer's shipping address. Required when any line item is PHYSICAL. The destination drives US sales-tax sourcing.
customer.shippingAddress.line1stringConditionalPrimary address line. Required when the shipping address is present and the country is outside the EU.
customer.shippingAddress.line2stringNoSecondary address line
customer.shippingAddress.citystringConditionalCity name. Required when the shipping address is present and the country is outside the EU.
customer.shippingAddress.statestringConditionalState/province code (required for US and CA)
customer.shippingAddress.postalCodestringConditionalPostal/ZIP code. Required when the shipping address is present and the country is outside the EU.
customer.shippingAddress.countrystringConditionalISO 3166-1 alpha-2 country code. Required whenever the shipping address is present.
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 per unit. For quantity 3 and discountAmount 2.00, the total discount is 6.00; applied before tax calculation.
lineItems[].productTypestring enumNoAllowed values: PHYSICAL, DIGITAL. Defaults to DIGITAL. Physical goods are priced with import duty on the cross-border path.
lineItems[].countryOfOriginstringNoISO 3166-1 alpha-2 country of manufacture. Requires productType PHYSICAL. Needed per line on the cross-border duty path.
lineItems[].hsCodestringNoDestination-qualified HS code used to classify the line for duty. Requires productType PHYSICAL and must not be blank when present.
shipFromobjectNoShip-from origin address for cross-border duty. Requires at least one PHYSICAL line item. When the origin country differs from the destination, the cart routes to the duty provider.
shipFrom.line1stringNoPrimary address line
shipFrom.line2stringNoSecondary address line
shipFrom.citystringNoCity name
shipFrom.statestringNoState/province code
shipFrom.postalCodestringNoPostal/ZIP code
shipFrom.countrystringConditionalISO 3166-1 alpha-2 country code. Required whenever shipFrom is present.
evidenceobjectConditionalTax evidence (required for non-US/non-BR billing addresses; optional for Brazil)
evidence.billingCountrystringConditionalISO 3166-1 alpha-2 billing country code. Required for non-US/non-BR evidence; if provided for Brazil, must be BR.
evidence.ipAddressstringConditionalCustomer's IP address. Required for non-US/non-BR billing addresses; optional for Brazil.
evidence.paymentMethodCountrystringNoPayment method country code
Note: For non-US/non-BR billing addresses, evidence is required and evidence.billingCountry must match customer.billingAddress.country. For Brazil, evidence can be omitted; if provided, evidence.billingCountry must be BR and evidence.ipAddress is optional.
Brazil B2C NFS-e: use currency BRL, include customer first and last name, include exactly one valid BR_CPF identifier in customer.taxIdentifiers and set taxBehavior to INCLUSIVE when the submitted line amount should remain the service value and ISS tax base.

Response 201 Created

{
  "taxCalculationId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "createdAt": "2026-01-05T12:00:00Z",
  "expiresAt": "2026-01-12T12:00: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."
  }
}

Brazil B2C NFS-e Example

Brazil B2C NFS-e calculations require BRL, customer first and last name, and a BR_CPF tax identifier. In inclusive mode, the submitted line amount remains the service value and ISS tax base; tax is not backed out of the line amount. The response keeps unit price, subtotal, net amount, and total amount at the submitted amount and reports ISS separately.

{
  "currency": "BRL",
  "taxBehavior": "INCLUSIVE",
  "customer": {
    "merchantCustomerReference": "BR-CUST-001",
    "firstName": "Beatriz",
    "lastName": "Costa",
    "email": "beatriz.costa@example.com",
    "taxIdentifiers": [
      {
        "type": "BR_CPF",
        "code": "<VALID_CUSTOMER_CPF>"
      }
    ],
    "billingAddress": {
      "line1": "Rua Oscar Freire, 900",
      "city": "Sao Paulo",
      "state": "SP",
      "postalCode": "01426-002",
      "country": "BR"
    }
  },
  "lineItems": [
    {
      "merchantLineItemReference": "BR-VAS-ITEM-001",
      "productCode": "BR-VAS-001",
      "description": "Value-added mobile service",
      "unitPrice": 49.90,
      "quantity": 1
    }
  ],
  "evidence": {
    "billingCountry": "BR",
    "ipAddress": "200.160.2.3",
    "paymentMethodCountry": "BR"
  }
}
{
  "taxCalculationId": "019ef95a-cc77-78da-9041-58908baabaf9",
  "createdAt": "2026-01-05T12:00:00Z",
  "expiresAt": "2026-01-12T12:00:00Z",
  "currency": "BRL",
  "subTotalAmount": "49.90",
  "discountAmount": "0.00",
  "netAmount": "49.90",
  "taxAmount": "1.45",
  "totalAmount": "49.90",
  "customer": {
    "firstName": "Beatriz",
    "lastName": "Costa",
    "email": "beatriz.costa@example.com",
    "billingAddress": {
      "line1": "Rua Oscar Freire, 900",
      "city": "Sao Paulo",
      "state": "SP",
      "postalCode": "01426-002",
      "country": "BR"
    }
  },
  "lineItems": [
    {
      "lineItemId": "83e026ae-b270-4590-926a-03b286fc84d8",
      "merchantLineItemReference": "BR-VAS-ITEM-001",
      "productCode": "BR-VAS-001",
      "description": "Value-added mobile service",
      "unitPrice": "49.90",
      "quantity": 1,
      "discountAmount": "0.00",
      "itemSubTotalAmount": "49.90",
      "itemNetAmount": "49.90",
      "itemTotalAmount": "49.90",
      "tax": {
        "rate": "2.90",
        "taxAmount": "1.45",
        "currency": "BRL"
      }
    }
  ],
  "evidence": {
    "billingCountry": "BR",
    "ipAddress": "200.160.2.3",
    "paymentMethodCountry": "BR"
  },
  "entity": {
    "companyName": "Outpost Brazil Limitada"
  }
}

Brazil CPF Validation Error

Brazil requires exactly one BR_CPF or BR_CNPJ identifier. Use BR_CPF for B2C and BR_CNPJ for B2B. If no Brazilian identifier is present, if more than one is present, or if the value fails check-digit validation, the calculation request is rejected before tax is calculated.

{
  "code": "invalid_argument",
  "errors": [
    {
      "field": "customer.taxIdentifiers[0].code",
      "message": "must be a valid CPF"
    }
  ]
}

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
totalDutyAmountdecimalTotal import duty across all line items. Omitted when no line carries duty.
lineItemsarrayCalculated line items with tax details
lineItems[].tax.ratedecimalEffective tax rate applied to the line
lineItems[].tax.taxAmountdecimalTax amount for the line
lineItems[].tax.dutyAmountdecimalImport duty for the line. Omitted when the line carries no duty.
lineItems[].tax.dutyRatedecimalDuty rate applied to the line
lineItems[].tax.dutyBasisstring enumHow the duty rate was derived: CLASSIFIED (from the HS code) or FALLBACK_ESTIMATE.
lineItems[].tax.appliedHsCodestringHS code the line was actually classified with
lineItems[].tax.chargesarrayJurisdiction-level breakdown. Always present. See the charges note below before using it.
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.
Amount and rate fields in responses are returned as decimal strings to preserve precision. Request examples may send decimal values as JSON numbers.
Fields with no value are omitted from the response rather than returned as null. Check whether a key is present, not whether it equals null.

A calculation is valid for 7 days from createdAt. After expiresAt it can no longer be converted into a transaction — create a new one.

Charge Breakdown

Every line item carries a tax.charges array describing the individual jurisdiction-level charges behind the line totals.

ParameterTypeRequiredDescription
kindstring enumTAX, IMPORT_DUTY, or IMPORT_FEE
namestringCharge name
jurisdictionstringJurisdiction the charge belongs to
jurisdictionLevelstring enumCOUNTRY, STATE, COUNTY, CITY, or DISTRICT
countrystringISO 3166-1 alpha-2 country code
taxTypestring enumVAT, GST, SALES_TAX, IMPORT_VAT, or OTHER
providerTaxTypestringThe tax provider's own type label, passed through unchanged
ratedecimalRate for this charge
amountdecimalAmount for this charge
taxableAmountdecimalAmount this charge was calculated on
Use taxAmount and dutyAmount for money flows. TAX entries sum to taxAmount and IMPORT_* entries sum to dutyAmount, but the breakdown is for display and reconciliation — do not derive amounts by filtering this list.

Error Responses

HTTPCodeDescription
400invalid_argumentMissing or invalid field — check field requirements. A malformed body, or an unknown tax identifier type, is reported on the field "body".
400missing_country_of_originA physical line item on a cross-border order has no countryOfOrigin. The field names the line index.
400shipping_address_requiredThe cart contains a PHYSICAL line item but no customer.shippingAddress
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
409physical_goods_not_supportedPhysical goods cannot be priced for that destination yet
409integration_credentials_invalidThe tax provider rejected the configured integration credentials
409integration_not_configuredTax compliance integration is not configured
422nfse_validation_failedBrazil NFS-e pre-payment validation rejected the customer data. Each reason is returned on the field "customer".
502tax_provider_unavailableThe tax provider is temporarily unavailable. Safe to retry with backoff — this is the only retryable failure on this endpoint.
401Invalid or missing Authorization token
404not_foundThe merchant does not exist, or is not linked to your partner account. Both cases return 404 with an empty errors array — never 403.

Code Example

# Step 2: Calculate tax for the transaction
curl -X POST "https://api.outpostanywhere.com/partner/api/merchants/{merchantId}/tax/calculations" \
  -H "Authorization: Bearer $OUTPOST_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "currency": "EUR",
    "taxBehavior": "EXCLUSIVE",
    "customer": {
      "merchantCustomerReference": "CUST-001",
      "firstName": "John",
      "lastName": "Doe",
      "email": "john.doe@example.com",
      "billingAddress": {
        "line1": "123 Main St",
        "city": "Berlin",
        "postalCode": "10115",
        "country": "DE"
      }
    },
    "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/partner/api/merchants/{merchantId}/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.

Path Parameters

ParameterTypeRequiredDescription
merchantIdstringYesOutpost identifier of the merchant you are acting for. Returned when you create the merchant during onboarding.

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. taxCalculationId must be a valid UUID.
400invalid_stateTax calculation has expired. Returned on the field taxCalculationId — create a new calculation.
400invalid_stateTax calculation has already been used. Returned on the field taxCalculationId.
400invalid_stateTransaction with that merchantTransactionReference already exists. Returned on the field merchantTransactionReference — see Idempotency below.
400invalid_statePlease activate Outpost Tax of Record to process transactions. Returned on the field status.
400invalid_stateThe transaction comes from a customer location that is not activated by Outpost. Note this is a 400 here, not the 409 region_not_activated returned by Create Tax Calculation.
400invalid_stateMoR product is already enabled in this region
404not_foundTax calculation not found
404not_foundThe merchant does not exist, or is not linked to your partner account. Both cases return 404 with an empty errors array — never 403.

Code Example

# Step 3: Store the transaction using taxCalculationId from calculate response
curl -X POST "https://api.outpostanywhere.com/partner/api/merchants/{merchantId}/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/partner/api/merchants/{merchantId}/tax/transactions/{transaction_id}

Retrieves the details of an existing tax transaction.

Path Parameters

ParameterTypeRequiredDescription
merchantIdstringYesOutpost identifier of the merchant you are acting for. Returned when you create the merchant during onboarding.
transaction_idstringYesID of the transaction

Response 200 OK

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

Error Responses

HTTPCodeDescription
404not_foundThe merchant does not exist, or is not linked to your partner account. Both cases return 404 with an empty errors array — never 403.
400invalid_argumenttransaction_id is not a valid transaction id
404not_foundTransaction not found

Get Transaction Refunds

GET/partner/api/merchants/{merchantId}/tax/transactions/{transaction_id}/refunds

Retrieves all refunds associated with a transaction.

Path Parameters

ParameterTypeRequiredDescription
merchantIdstringYesOutpost identifier of the merchant you are acting for. Returned when you create the merchant during onboarding.
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_foundThe merchant does not exist, or is not linked to your partner account. Both cases return 404 with an empty errors array — never 403.
400invalid_argumenttransaction_id is not a valid transaction id
404not_foundTransaction not found

Create Refund Transaction

POST/partner/api/merchants/{merchantId}/tax/transactions/{transaction_id}/refunds

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

Path Parameters

ParameterTypeRequiredDescription
merchantIdstringYesOutpost identifier of the merchant you are acting for. Returned when you create the merchant during onboarding.
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_foundThe merchant does not exist, or is not linked to your partner account. Both cases return 404 with an empty errors array — never 403.
400invalid_argumentMissing or invalid field. refundDate must match yyyy-MM-ddTHH:mm:ssZ and cannot be later than today.
400invalid_stateRefund with that merchantRefundReference already exists. Returned on the field merchantRefundReference.
404not_foundTransaction or line items not found

Get Transaction Invoice

GET/partner/api/merchants/{merchantId}/tax/transactions/{transaction_id}/invoice

Retrieves the invoice file for a transaction.

Path Parameters

ParameterTypeRequiredDescription
merchantIdstringYesOutpost identifier of the merchant you are acting for. Returned when you create the merchant during onboarding.
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.

The url is a signed link that expires one hour after the response. Download the file rather than storing the link.

Error Responses

HTTPCodeDescription
404not_foundThe merchant does not exist, or is not linked to your partner account. Both cases return 404 with an empty errors array — never 403.
400invalid_argumenttransaction_id is not a valid transaction id
404not_foundTransaction not found

Integration Flow

The full checkout integration follows a linear sequence: authenticate, calculate tax, process payment, then store the transaction.

Sequence

  • Resolve the Outpost merchantId for the merchant you are acting for
  • 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

One access token covers every merchant linked to your partner account. The merchant is chosen per request through the merchantId path parameter, so cache the token once and reuse it across merchants. You get the merchantId when you create the merchant during onboarding.

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
  • Reusing a merchantTransactionReference returns 400 invalid_state on that field, not the original transaction — treat it as "already stored" and read the transaction you recorded
  • A calculation can be converted once. Reusing it returns 400 invalid_state on taxCalculationId

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)
  • 409 — Configuration or coverage problem, not a client bug. Do not retry; the region, product or provider integration needs changing
  • 422 — Brazil NFS-e rejected the customer data. Fix the data before retrying
  • 404 not_found — The merchant does not exist, or is not linked to your partner account. Both cases return 404 with an empty errors array — never 403.

5xx — Server Errors

Usually temporary. Retrying a read is always safe; retrying a write needs a duplicate check first.

  • 502 tax_provider_unavailable — the provider is temporarily down. Retry with exponential backoff
  • Max 3 retries with 1s, 2s, 4s delays
  • Include jitter to prevent thundering herd
  • 504 request_timeout on Store — the transaction may still have been created. Do not blind-retry; check by merchantTransactionReference first

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.

Environments

There are two environments, staging and production, each with its own credentials. Build and test against staging, then switch both base URLs and the credential pair to go live. There is no separate sandbox environment and no test mode within production.

EnvironmentAPI base URLAuth base URL
Staginghttps://api.outpostnow.techhttps://access.outpostnow.tech
Productionhttps://api.outpostanywhere.comhttps://access.outpostanywhere.com

What to test

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
Merchant is linked to your partner account before the first tax call

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.