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
- 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
| Variable | Value | Notes |
|---|---|---|
| API_BASE_URL | https://api.outpostanywhere.com | Base URL for all API endpoints |
| CLIENT_ID | Your client ID | Non-sensitive, can be inlined |
| OUTPOST_CLIENT_SECRET | - | Store in environment variables or secret manager |
Authentication
Outpost uses OAuth2 client_credentials 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
- Request an
access_tokenwith yourclient_idandapi_token - Read
access_tokenandexpires_infrom the response - Cache the token server-side with TTL
expires_in - 300s - Refresh on expiry or on a 401, then retry the request once
- Send
Authorization: Bearer <access_token>on every API call
Get access token
POST/oauth2/tokenHost: 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" | jqResponse
{
"access_token": "<JWT>",
"expires_in": 86400,
"token_type": "Bearer"
}Run from your server. Never expose your api_token in the browser.
Create Tax Calculation
POST/partner/api/merchants/{merchantId}/tax/calculationsCreates a tax calculation for a given set of line items and customer information. The calculation determines applicable tax rates based on the customer's location and can be used to create a transaction after the successful payment.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| merchantId | string | Yes | Outpost identifier of the merchant you are acting for. Returned when you create the merchant during onboarding. |
Request Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| Authorization | - | Yes | Bearer token for authentication |
| Content-Type | - | Yes | application/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
| Parameter | Type | Required | Description |
|---|---|---|---|
| currency | string | Yes | ISO 4217 currency code (e.g., EUR, USD, GBP, BRL). Brazil B2C NFS-e requires BRL. |
| taxBehavior | string enum | No | Allowed values: EXCLUSIVE, INCLUSIVE. Defaults to EXCLUSIVE. With INCLUSIVE, unitPrice is the gross amount and tax is extracted from it. |
| customer | object | Yes | Customer information object |
| customer.merchantCustomerReference | string | No | Your internal customer identifier |
| customer.firstName | string | Conditional | Customer's first name. Required for Brazil B2C NFS-e. |
| customer.lastName | string | Conditional | Customer's last name. Required for Brazil B2C NFS-e. |
| customer.email | string | No | Customer's email address |
| customer.taxIdentifiers | array | Conditional | Customer tax identifiers, discriminated on type. Required for Brazil NFS-e calculations. |
| customer.taxIdentifiers[].type | string enum | Conditional | Identifier 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[].code | string | Conditional | Identifier 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[].buyerTaxRegime | string enum | Conditional | BR_CNPJ only. Allowed values: SIMPLES_NACIONAL, LUCRO_PRESUMIDO, LUCRO_REAL. Required for Brazil B2B NFS-e calculations. |
| customer.billingAddress | object | Yes | Customer's billing address |
| customer.billingAddress.line1 | string | Yes | Primary address line |
| customer.billingAddress.line2 | string | No | Secondary address line |
| customer.billingAddress.city | string | Yes | City name |
| customer.billingAddress.state | string | Conditional | State/province code (required for US and CA) |
| customer.billingAddress.postalCode | string | Yes | Postal/ZIP code |
| customer.billingAddress.country | string | Yes | ISO 3166-1 alpha-2 country code |
| customer.shippingAddress | object | Conditional | Customer's shipping address. Required when any line item is PHYSICAL. The destination drives US sales-tax sourcing. |
| customer.shippingAddress.line1 | string | Conditional | Primary address line. Required when the shipping address is present and the country is outside the EU. |
| customer.shippingAddress.line2 | string | No | Secondary address line |
| customer.shippingAddress.city | string | Conditional | City name. Required when the shipping address is present and the country is outside the EU. |
| customer.shippingAddress.state | string | Conditional | State/province code (required for US and CA) |
| customer.shippingAddress.postalCode | string | Conditional | Postal/ZIP code. Required when the shipping address is present and the country is outside the EU. |
| customer.shippingAddress.country | string | Conditional | ISO 3166-1 alpha-2 country code. Required whenever the shipping address is present. |
| lineItems | array | Yes | Array of line items (minimum 1) |
| lineItems[].merchantLineItemReference | string | Yes | Your internal line item identifier |
| lineItems[].productCode | string | Yes | Product/service code for tax classification |
| lineItems[].description | string | Yes | Human-readable item description |
| lineItems[].unitPrice | decimal | Yes | Price per unit (must be ≥ 0) |
| lineItems[].quantity | integer | No | Quantity (default: 1, must be > 0) |
| lineItems[].discountAmount | decimal | No | Discount amount per unit. For quantity 3 and discountAmount 2.00, the total discount is 6.00; applied before tax calculation. |
| lineItems[].productType | string enum | No | Allowed values: PHYSICAL, DIGITAL. Defaults to DIGITAL. Physical goods are priced with import duty on the cross-border path. |
| lineItems[].countryOfOrigin | string | No | ISO 3166-1 alpha-2 country of manufacture. Requires productType PHYSICAL. Needed per line on the cross-border duty path. |
| lineItems[].hsCode | string | No | Destination-qualified HS code used to classify the line for duty. Requires productType PHYSICAL and must not be blank when present. |
| shipFrom | object | No | Ship-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.line1 | string | No | Primary address line |
| shipFrom.line2 | string | No | Secondary address line |
| shipFrom.city | string | No | City name |
| shipFrom.state | string | No | State/province code |
| shipFrom.postalCode | string | No | Postal/ZIP code |
| shipFrom.country | string | Conditional | ISO 3166-1 alpha-2 country code. Required whenever shipFrom is present. |
| evidence | object | Conditional | Tax evidence (required for non-US/non-BR billing addresses; optional for Brazil) |
| evidence.billingCountry | string | Conditional | ISO 3166-1 alpha-2 billing country code. Required for non-US/non-BR evidence; if provided for Brazil, must be BR. |
| evidence.ipAddress | string | Conditional | Customer's IP address. Required for non-US/non-BR billing addresses; optional for Brazil. |
| evidence.paymentMethodCountry | string | No | Payment method country code |
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.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
| Parameter | Type | Required | Description |
|---|---|---|---|
| taxCalculationId | string | - | Unique identifier for the calculation |
| createdAt | string | - | ISO 8601 timestamp when calculation was created |
| expiresAt | string | - | ISO 8601 timestamp when calculation expires |
| currency | string | - | Currency code |
| subTotalAmount | decimal | - | Sum of line item amounts before discounts |
| discountAmount | decimal | - | Total discount amount applied |
| netAmount | decimal | - | Amount after discounts, before tax |
| taxAmount | decimal | - | Total tax amount |
| totalAmount | decimal | - | Final amount including tax |
| customer | object | - | Customer information |
| totalDutyAmount | decimal | - | Total import duty across all line items. Omitted when no line carries duty. |
| lineItems | array | - | Calculated line items with tax details |
| lineItems[].tax.rate | decimal | - | Effective tax rate applied to the line |
| lineItems[].tax.taxAmount | decimal | - | Tax amount for the line |
| lineItems[].tax.dutyAmount | decimal | - | Import duty for the line. Omitted when the line carries no duty. |
| lineItems[].tax.dutyRate | decimal | - | Duty rate applied to the line |
| lineItems[].tax.dutyBasis | string enum | - | How the duty rate was derived: CLASSIFIED (from the HS code) or FALLBACK_ESTIMATE. |
| lineItems[].tax.appliedHsCode | string | - | HS code the line was actually classified with |
| lineItems[].tax.charges | array | - | Jurisdiction-level breakdown. Always present. See the charges note below before using it. |
| evidence | object | - | Tax evidence used for calculation |
| entity | object | - | The Outpost legal entity acting as the recording seller (present when Tax of Record / liability transfer applies). |
| entity.companyName | string | - | Legal company name of the recording entity. |
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.
| Parameter | Type | Required | Description |
|---|---|---|---|
| kind | string enum | - | TAX, IMPORT_DUTY, or IMPORT_FEE |
| name | string | - | Charge name |
| jurisdiction | string | - | Jurisdiction the charge belongs to |
| jurisdictionLevel | string enum | - | COUNTRY, STATE, COUNTY, CITY, or DISTRICT |
| country | string | - | ISO 3166-1 alpha-2 country code |
| taxType | string enum | - | VAT, GST, SALES_TAX, IMPORT_VAT, or OTHER |
| providerTaxType | string | - | The tax provider's own type label, passed through unchanged |
| rate | decimal | - | Rate for this charge |
| amount | decimal | - | Amount for this charge |
| taxableAmount | decimal | - | Amount this charge was calculated on |
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
| HTTP | Code | Description |
|---|---|---|
| 400 | invalid_argument | Missing 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). |
| 400 | missing_country_of_origin | A physical line item on a cross-border order has no countryOfOrigin. The field names the line index. |
| 400 | shipping_address_required | The cart contains a PHYSICAL line item but no customer.shippingAddress |
| 400 | invalid_state | Please activate Outpost Taxes / Tax of Record to process transactions |
| 409 | region_not_activated | Merchant not activated for the customer's tax jurisdiction |
| 409 | region_not_supported | Customer location outside supported jurisdictions |
| 409 | physical_goods_not_supported | Physical goods cannot be priced for that destination yet |
| 409 | integration_credentials_invalid | The tax provider rejected the configured integration credentials |
| 409 | integration_not_configured | Tax compliance integration is not configured |
| 422 | nfse_validation_failed | Brazil NFS-e pre-payment validation rejected the customer data. Each reason is returned on the field "customer". |
| 502 | tax_provider_unavailable | The 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 |
| 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. |
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 stepCreate Tax Transaction
POST/partner/api/merchants/{merchantId}/tax/transactionsConverts a valid tax calculation into a confirmed tax transaction. This endpoint should be called when an order is finalized/paid. Only call after payment authorization/capture succeeds.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| merchantId | string | Yes | Outpost identifier of the merchant you are acting for. Returned when you create the merchant during onboarding. |
Request Headers
| Parameter | Type | Required | Description |
|---|---|---|---|
| Authorization | - | Yes | Bearer token for authentication |
| Content-Type | - | Yes | application/json |
Request Body
{
"taxCalculationId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"merchantTransactionReference": "ORDER-2026-001234",
"payment": {
"processor": "stripe",
"paymentReference": "ch_3Q1234567890abcdef"
}
}Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| taxCalculationId | string | Yes | ID of the tax calculation generated by Outpost to convert |
| merchantTransactionReference | string | Yes | Your unique order/transaction reference |
| payment | object | No | Payment processor information |
| payment.processor | string | No | Payment processor name (e.g., "stripe", "adyen") |
| payment.paymentReference | string | No | Payment processor transaction ID |
Response 200 OK
{
"transactionId": "e83a9c47-2b5d-4f8a-9c12-3d4e5f6a7b8c",
"refunded": false,
"createdAt": "2026-01-05T12:05:00Z",
"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
| HTTP | Code | Description |
|---|---|---|
| 400 | invalid_argument | Missing or invalid field - check field requirements. taxCalculationId must be a valid UUID. |
| 400 | invalid_state | Tax calculation has expired. Returned on the field taxCalculationId - create a new calculation. |
| 400 | invalid_state | Tax calculation has already been used. Returned on the field taxCalculationId. |
| 400 | invalid_state | Transaction with that merchantTransactionReference already exists. Returned on the field merchantTransactionReference - see Idempotency below. |
| 400 | invalid_state | Please activate Outpost Taxes / Tax of Record to process transactions. Returned on the field status. |
| 400 | invalid_state | The 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. |
| 400 | invalid_state | MoR product is already enabled in this region |
| 404 | not_found | Tax calculation not found |
| 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. |
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 TaxTransactionResponseGet Tax Transaction
GET/partner/api/merchants/{merchantId}/tax/transactions/{transaction_id}Retrieves the details of an existing tax transaction.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| merchantId | string | Yes | Outpost identifier of the merchant you are acting for. Returned when you create the merchant during onboarding. |
| transaction_id | string | Yes | ID of the transaction |
Response 200 OK
Returns a TaxTransactionResponse object (same structure as Create Tax Transaction response).
Error Responses
| HTTP | Code | Description |
|---|---|---|
| 404 | not_found | The merchant does not exist, or is not linked to your partner account. Both cases return 404 with an empty errors array - never 403. |
| 400 | invalid_argument | transaction_id is not a valid transaction id |
| 404 | not_found | Transaction not found |
Get Transaction Refunds
GET/partner/api/merchants/{merchantId}/tax/transactions/{transaction_id}/refundsRetrieves all refunds associated with a transaction.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| merchantId | string | Yes | Outpost identifier of the merchant you are acting for. Returned when you create the merchant during onboarding. |
| transaction_id | string | Yes | ID of the original transaction |
Response 200 OK
[
{
"refundTransactionId": "d1e2f3a4-5b6c-7d8e-9f0a-1b2c3d4e5f6a",
"merchantRefundReference": "REFUND-2026-000456",
"refundDate": "2026-01-10T15:30:00Z",
"refundLineItems": [
{
"lineItemId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"refundLineItemReference": "REFUND-ITEM-001",
"refundReason": "Customer requested cancellation",
"refundItemSubTotalAmount": "99.99",
"refundItemTotalAmount": "108.89",
"refundTax": {
"rate": "21.00",
"taxAmount": "18.90",
"currency": "EUR"
}
}
],
"createdAt": "2026-01-10T15:30:00Z",
"currency": "EUR",
"subTotalAmount": "99.99",
"taxAmount": "18.90",
"totalAmount": "108.89"
}
]Error Responses
| HTTP | Code | Description |
|---|---|---|
| 404 | not_found | The merchant does not exist, or is not linked to your partner account. Both cases return 404 with an empty errors array - never 403. |
| 400 | invalid_argument | transaction_id is not a valid transaction id |
| 404 | not_found | Transaction not found |
Create Refund Transaction
POST/partner/api/merchants/{merchantId}/tax/transactions/{transaction_id}/refundsCreates a refund for one or more line items in an existing transaction.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| merchantId | string | Yes | Outpost identifier of the merchant you are acting for. Returned when you create the merchant during onboarding. |
| transaction_id | string | Yes | ID of the original transaction |
Request Body
{
"merchantRefundReference": "REFUND-2026-000456",
"refundDate": "2026-01-10T15:30:00Z",
"refundLineItems": [
{
"lineItemId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"refundLineItemReference": "REFUND-ITEM-001",
"refundReason": "Customer requested cancellation"
}
]
}Request Body Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| merchantRefundReference | string | Yes | Your unique refund reference |
| refundDate | string | No | ISO 8601 date/time of the refund |
| refundLineItems | array | Yes | Array of line items to refund (minimum 1) |
| refundLineItems[].lineItemId | string | Yes | ID of the original line item |
| refundLineItems[].refundLineItemReference | string | Yes | Your unique reference for this refund item |
| refundLineItems[].refundReason | string | Yes | Reason for the refund |
Response 200 OK
{
"refundTransactionId": "d1e2f3a4-5b6c-7d8e-9f0a-1b2c3d4e5f6a",
"merchantRefundReference": "REFUND-2026-000456",
"refundDate": "2026-01-10T15:30:00Z",
"refundLineItems": [
{
"lineItemId": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
"refundLineItemReference": "REFUND-ITEM-001",
"refundReason": "Customer requested cancellation",
"refundItemSubTotalAmount": "99.99",
"refundItemTotalAmount": "108.89",
"refundTax": {
"rate": "21.00",
"taxAmount": "18.90",
"currency": "EUR"
}
}
],
"createdAt": "2026-01-10T15:30:00Z",
"currency": "EUR",
"subTotalAmount": "99.99",
"taxAmount": "18.90",
"totalAmount": "108.89"
}Error Responses
| HTTP | Code | Description |
|---|---|---|
| 404 | not_found | The merchant does not exist, or is not linked to your partner account. Both cases return 404 with an empty errors array - never 403. |
| 400 | invalid_argument | Missing or invalid field. refundDate must match yyyy-MM-ddTHH:mm:ssZ and cannot be later than today. |
| 400 | invalid_state | Refund with that merchantRefundReference already exists. Returned on the field merchantRefundReference. |
| 404 | not_found | Transaction or line items not found |
Get Transaction Invoice
GET/partner/api/merchants/{merchantId}/tax/transactions/{transaction_id}/invoiceRetrieves the invoice file for a transaction.
Path Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| merchantId | string | Yes | Outpost identifier of the merchant you are acting for. Returned when you create the merchant during onboarding. |
| transaction_id | string | Yes | ID of the transaction |
Response 200 OK
{
"b2cInvoice": {
"fileName": "invoice-e83a9c47-2b5d-4f8a-9c12-3d4e5f6a7b8c.pdf",
"url": "https://storage.outpostnow.com/invoices/..."
}
}Response Fields
| Parameter | Type | Required | Description |
|---|---|---|---|
| b2cInvoice | object | - | B2C invoice information |
| b2cInvoice.fileName | string | - | Name of the invoice file |
| b2cInvoice.url | string | - | URL to download the invoice |
Response 202 Accepted - Invoice is still being generated. Retry after a short delay.
The url is a signed link that expires one hour after the response. Download the file rather than storing the link.
Error Responses
| HTTP | Code | Description |
|---|---|---|
| 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. |
| 400 | invalid_argument | transaction_id is not a valid transaction id |
| 404 | not_found | Transaction 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.
| Environment | API base URL | Auth base URL |
|---|---|---|
| Staging | https://api.outpostnow.tech | https://access.outpostnow.tech |
| Production | https://api.outpostanywhere.com | https://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
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.