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
- 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 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.
Create Tax Calculation
/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. 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[].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.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 seller / merchant of record (present when Tax of Record 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". |
| 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 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
/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 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
/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
/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
/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
/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 |
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.
| 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
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
Acceptance Checklist
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.