Tax of Record API for partners

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_SECRET-Store in environment variables or secret manager

Authentication

Outpost uses OAuth2 client_credentials for server-to-server calls. Your backend exchanges a client_id and api_token for an access_token, then sends it as a Bearer token on every API request.

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.

How-to and operational notes live on the Authentication page - the shapes below are the reference copy.

Flow

  1. Request an access_token with your client_id and api_token
  2. Read access_token and expires_in from the response
  3. Cache the token server-side with TTL expires_in - 300s
  4. Refresh on expiry or on a 401, then retry the request once
  5. Send Authorization: Bearer <access_token> on every API call

Get access token

POST/oauth2/token

Host: https://access.outpostanywhere.com

Do not send token requests to the API host (https://api.outpostanywhere.com).

Send your api_token as the OAuth client_secret form field - that is the wire name for this grant.

Request

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_API_TOKEN" | jq

Response

{
  "access_token": "<JWT>",
  "expires_in": 86400,
  "token_type": "Bearer"
}

Run from your server. Never expose your api_token in the browser.

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
Authorization-YesBearer token for authentication
Content-Type-Yesapplication/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. Format/check-digit validated for BR_CPF and BR_CNPJ (not against Receita Federal registration). VIES format with a two-letter country prefix for EU_VAT. GB_VAT and US_EIN are informational and recorded as provided. Formatted or bare codes accepted.
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.

NFS-e vs NFCom: regular (non-telecom) services use NFS-e / ISS. Telecom or communication services (for example eSIM) need NFCom / ICMS instead - do not run them through the NFS-e path.

CPF/CNPJ checks are format and check-digit only. A format-valid ID can still fail when the e-invoice is submitted to the authority.

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
taxCalculationIdstring-Unique identifier for the calculation
createdAtstring-ISO 8601 timestamp when calculation was created
expiresAtstring-ISO 8601 timestamp when calculation expires
currencystring-Currency code
subTotalAmountdecimal-Sum of line item amounts before discounts
discountAmountdecimal-Total discount amount applied
netAmountdecimal-Amount after discounts, before tax
taxAmountdecimal-Total tax amount
totalAmountdecimal-Final amount including tax
customerobject-Customer information
totalDutyAmountdecimal-Total import duty across all line items. Omitted when no line carries duty.
lineItemsarray-Calculated line items with tax details
lineItems[].tax.ratedecimal-Effective tax rate applied to the line
lineItems[].tax.taxAmountdecimal-Tax amount for the line
lineItems[].tax.dutyAmountdecimal-Import duty for the line. Omitted when the line carries no duty.
lineItems[].tax.dutyRatedecimal-Duty rate applied to the line
lineItems[].tax.dutyBasisstring enum-How the duty rate was derived: CLASSIFIED (from the HS code) or FALLBACK_ESTIMATE.
lineItems[].tax.appliedHsCodestring-HS code the line was actually classified with
lineItems[].tax.chargesarray-Jurisdiction-level breakdown. Always present. See the charges note below before using it.
evidenceobject-Tax evidence used for calculation
entityobject-The Outpost legal entity acting as the recording seller (present when Tax of Record / liability transfer applies).
entity.companyNamestring-Legal 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 enum-TAX, IMPORT_DUTY, or IMPORT_FEE
namestring-Charge name
jurisdictionstring-Jurisdiction the charge belongs to
jurisdictionLevelstring enum-COUNTRY, STATE, COUNTY, CITY, or DISTRICT
countrystring-ISO 3166-1 alpha-2 country code
taxTypestring enum-VAT, GST, SALES_TAX, IMPORT_VAT, or OTHER
providerTaxTypestring-The tax provider's own type label, passed through unchanged
ratedecimal-Rate for this charge
amountdecimal-Amount for this charge
taxableAmountdecimal-Amount 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". Includes legacy tax-identifier shapes (e.g. type "CPF" without the BR_ prefix).
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 Taxes / 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.
401-Invalid 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.

On create, Outpost validates structure and required fields synchronously and rejects malformed requests immediately. Authority clearance (signed submission / e-invoice authorization) is asynchronous. Retrieve invoices and e-invoice documents from Invoicing, and explicit pending/cleared/rejected status and webhooks are on the roadmap.

Path Parameters

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

Request Headers

ParameterTypeRequiredDescription
Authorization-YesBearer token for authentication
Content-Type-Yesapplication/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 Taxes / 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
b2cInvoiceobject-B2C invoice information
b2cInvoice.fileNamestring-Name of the invoice file
b2cInvoice.urlstring-URL 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

Checkout sequence

Authenticate, calculate, charge on your PSP, then commit. The critical handoff is round-tripping taxCalculationId through the PSP so the payment webhook can reconcile the paid order to the quote before you commit. Auth details live on Authentication, and endpoint schemas live in the API reference.

Steps

  • Resolve the Outpost merchantId for the merchant you are acting for
  • Authenticate - get/refresh an access token (cache with TTL)
  • Calculate - POST /partner/api/merchants/{merchantId}/tax/calculations, capture taxCalculationId, show taxAmount at checkout
  • Round-trip - attach taxCalculationId to the PSP payment as metadata/custom field
  • Pay - charge via your PSP (Stripe, Adyen, Nuvei, …)
  • Commit - on payment success webhook, POST /partner/api/merchants/{merchantId}/tax/transactions with taxCalculationId and payment details
  • Clear - e-invoice / authority clearance runs asynchronously after commit

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.

PSP round-trip

After Calculate, attach taxCalculationId to the payment request (e.g. Stripe metadata, Nuvei customField1). When the payment webhook fires, read that field back and call commit. Without the round-trip you cannot reliably connect a successful charge to the tax shown at checkout.

Pseudocode

async function processCheckout(orderData) {
  const token = await getAccessToken();

  const taxResponse = await calculateTax(orderData, token);
  const taxCalculationId = taxResponse.taxCalculationId;
  await persistCalculationMapping(orderData.orderId, taxCalculationId);

  const paymentResult = await processPayment(orderData, {
    amount: taxResponse.totalAmount,
    metadata: { taxCalculationId },
  });

  // Often from the PSP webhook - only after payment succeeds
  if (paymentResult.success) {
    await storeTransaction({
      taxCalculationId,
      merchantTransactionReference: orderData.orderId,
      payment: {
        paymentReference: paymentResult.paymentReference,
        processor: paymentResult.processor, // e.g. "stripe", "adyen", "nuvei"
      },
    }, token);
  }

  return { success: paymentResult.success, taxResponse };
}

Idempotency

  • Persist merchantTransactionReference → taxCalculationId before charging
  • Reuse an existing mapping instead of creating a second calculation for the same order
  • 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 by HTTP status. Do not retry 4xx blindly - they are client mistakes. Token refresh on 401 is covered under Authentication.

4xx - client

  • 401 - refresh the token and retry once (see Authentication)
  • 400 invalid_argument - fail fast (malformed fields, legacy tax-id shapes like type "CPF" without a jurisdiction prefix)
  • 400 invalid_state - calculation expired, already used, or product not activated
  • 404 - calculation not found (check taxCalculationId on commit)
  • 409 - configuration or coverage problem, not a client bug. Do not retry; the region, product or provider integration needs changing
  • 422 nfse_validation_failed - 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

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. Safe to retry with exponential backoff (e.g. 1s, 2s, 4s, with jitter)
  • Cap retries - typically three attempts - then fail and alert
  • 504 request_timeout on commit - the transaction may still have been created. Do not blind-retry; check by merchantTransactionReference first

Tax identifiers

  • Identifier types are jurisdiction-prefixed: BR_CPF, BR_CNPJ, EU_VAT, GB_VAT, US_EIN - shapes without the prefix return 400 invalid_argument
  • CPF/CNPJ checks are format and check-digit only - a format-valid ID can still fail later at NFS-e issuance
  • Malformed calculate requests return a structured 400 with field-level errors - not a 500

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/commit 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%
  • Commit rate significantly lower than calculate rate
  • Token refresh failures

Testing & rollout

Prove calculate → round-trip → commit against staging, then enable commit gradually in production.

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

Format-valid Brazilian IDs still do not guarantee NFS-e issuance - registration is confirmed only when the e-invoice is submitted.

  • Auth - token acquisition and refresh
  • Calculate - include a valid-format tax identifier for Brazil (BR_CPF / BR_CNPJ) - omitting it can silently return zero tax
  • Commit - only after a successful payment, with the same taxCalculationId
  • Errors - exercise timeout, retry, and representative 4xx/5xx cases

Rollout

Phase 1Calculate only (commit disabled) until quotes look right
Phase 2Enable commit for 1% of successful payments
Phase 3Gradually increase to 10%, 50%, 100%
RollbackFeature flags to disable commit calls quickly if needed

Acceptance checklist

  • Auth token acquisition works
  • Token caching and refresh logic implemented
  • Calculate returns a taxCalculationId
  • taxCalculationId round-trips through the PSP and is stored with the order
  • Commit includes paymentReference and processor
  • Commit runs only after successful payment
  • Brazil cases include a valid-format CPF/CNPJ
  • Timeout and retry logic implemented
  • 4xx fail fast; 5xx retry with backoff
  • 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.