Skip to content

Partner Integration API (1.9.0)

API specification for freight forwarding system (FFS) partners integrating with BravoTran.

Overview

BravoTran uses an event-driven pull model to stay in sync with your freight forwarding system:

  1. Your system sends an event notification to BravoTran when an entity is created or updated.
  2. BravoTran reads the entity type and identifier from the event.
  3. BravoTran issues a GET request to your API with the entity type and identifier.
  4. Your API returns the entity as JSON per this specification.

BravoTran also POSTs approved invoices to your system for cost posting. When an invoice is approved in BravoTran, it sends a POST /payables-invoices request to your API with the invoice details. Your system must create or record the invoice accordingly.

API Flow Diagram

Polling alternative

If your system cannot reliably push live event notifications, you may instead implement the optional list endpoints GET /orgs, GET /jobs, and GET /payables-invoices. BravoTran will periodically poll them with an updated_after window, get back the identifiers of anything that changed, then call the existing single-entity detail endpoints to fetch each one. Implement either the events API or the list endpoints — not both. All three list endpoints must be implemented together; BravoTran's polling assumes complete coverage of all entity types.

Creates and updates are surfaced through the same filter: when an entity is created, set updated_at to the creation timestamp and it will appear in any subsequent polling response whose updated_after is at or before that timestamp. There is no separate "creation" feed.

Terminology

TermDefinition
FFSFreight Forwarding System — your system
CompanyA country-level entity in the FFS; maps to a BravoTran Account
AccountBravoTran's representation of a company/country
OrgA vendor, carrier, creditor, or debtor
CreditorAn org that issues payables invoices; referenced as creditor on invoices and accruals
JobA consol (consolidation/master) or shipment (house)
AccrualAn expected charge on a job
Charge LineA line item on a payables invoice
Payables InvoiceAP invoice from a creditor
Outbound Payables InvoiceApproved invoice posted from BravoTran to the partner's system for cost posting

BravoTran maps a Company in the FFS to an Account in BravoTran. Often a company represents a country. It is acceptable to have more than one account for a country if that country has multiple companies, but a single account must never represent multiple companies or countries.

Conventions

  • external_id — Unique, immutable identifier for every entity. Used as the foreign key between systems. Typically the primary key in your database. Not visible to end users. The example external_id values throughout these docs are GUIDs, but they don't have to be — an external_id is whatever the stable identifier for the entity is in the FFS.
  • updated_at — ISO 8601 timestamp of when the entity was last modified. BravoTran uses this as a version marker to prevent overwriting with stale data. For jobs, this must update when any child data changes (e.g., accrual added/edited, shipment added to consol). On creation, updated_at must be set to (at least) the creation timestamp so newly created entities appear in polling responses alongside updates — the polling endpoints make no distinction between creates and updates. Charge codes and tax codes are complete lists rather than versioned entities, so updated_at is optional on them.
  • Amounts — Wherever there is an amount, a corresponding currency (ISO 4217) must be provided.
  • Local vs. overseas amounts — local_amount is in the account's local currency (the currency of the country the forwarder operates in). overseas_amount is in the vendor's currency — whatever currency the charge or invoice is denominated in. The two may be the same (e.g., a domestic vendor).
  • Country codes — Country fields use ISO 3166-1 alpha-2 two-letter codes.
  • Nested references — Related entities are represented as nested objects containing at minimum an external_id.
  • Error codes — Error responses use lower_snake codes in both directions. BravoTran keys off the HTTP status rather than partner error bodies.

Staff

Staff data is provided via flat file — typically a spreadsheet export from the FFS. Any format your FFS can export works; field names don't matter as long as the required contents are present. If you don't have an existing export, use the recommended format, which lists each field and whether it is required. Alternatively, implement GET /staff and BravoTran will fetch staff data from your API instead.

Charge Codes

Charge code data is provided via flat file — typically a spreadsheet export from the FFS. Any format your FFS can export works; field names don't matter as long as the required contents are present. If you don't have an existing export, use the recommended format, which lists each field and whether it is required. Alternatively, implement GET /charge-codes and BravoTran will fetch charge code data from your API instead. Accruals and charge lines reference charge codes by external_id; the mapping is maintained through the charge code data.

For a GST/VAT account, API delivery of charge codes also requires GET /tax-codes.

Tax Codes

Tax code data is provided via flat file — typically a spreadsheet export from the FFS. Any format your FFS can export works; field names don't matter as long as the required contents are present. If you don't have an existing export, use the recommended format, which lists each field and whether it is required. Alternatively, implement GET /tax-codes together with GET /charge-codes and BravoTran will fetch both lists from your API.

Authentication

Each direction of communication uses one authentication method, agreed during onboarding.

Sending events to BravoTran

POST /events accepts any of the following. BravoTran issues the credentials during onboarding.

API token

BravoTran issues an API token for each account. Send it in the Authorization header:

Authorization: Token token=<token>

or in an x-api-key header:

x-api-key: <token>

OAuth 2.0 client credentials

Exchange your OAuth client credentials for an access token with POST /oauth/token, then send the access token with each event:

Authorization: Bearer <access_token>

Access tokens expire after expires_in seconds (3600 unless agreed otherwise). There are no refresh tokens: request a new access token when the current one expires. POST /events returns 401 for an expired or invalid access token.

Your OAuth client authenticates to the token endpoint in one of two ways, chosen when BravoTran sets it up.

Client ID and client secret. BravoTran issues a client ID and a client secret. The secret is shown only once, so store it securely. Send the credentials with HTTP Basic authentication or as client_id and client_secret form fields, but not both. For HTTP Basic, form-URL-encode the client ID and the secret before joining them with : and base64-encoding the result (RFC 6749 §2.3.1). Credentials sent in the query string are rejected. When BravoTran replaces a secret, the old secret stops working immediately; access tokens already issued with it stay valid until they expire.

curl -X POST https://api.bravotran.com/oauth/token \
  -u "<client_id>:<client_secret>" \
  -d grant_type=client_credentials

Certificate. Your client authenticates with a JWT signed by your private key (RFC 7523):

  1. Generate an RSA key pair and a certificate signing request (CSR), and send the CSR to BravoTran. Keep the private key in your system; BravoTran never needs it.
  2. BravoTran returns a signed certificate, valid for one year, and your client ID. Send a new CSR before the certificate expires.
  3. For each token request, create a JWT signed with your private key:
PartFieldValue
HeaderalgRS256
Headerx5t#S256Optional. Base64url-encoded SHA-256 thumbprint of your certificate (DER), without padding.
ClaimissYour client ID
ClaimsubYour client ID
Claimaudhttps://api.bravotran.com/oauth/token, exactly
ClaimiatTime the JWT was issued, in seconds since the Unix epoch
ClaimexpExpiry time, in seconds since the Unix epoch. No more than 7 days after iat.
ClaimnbfOptional. Time before which the JWT is not valid
ClaimjtiOptional. A unique ID for the JWT; if sent, each value can be used only once

Send the JWT as client_assertion, with client_assertion_type set to urn:ietf:params:oauth:client-assertion-type:jwt-bearer.

BravoTran calling your API

BravoTran authenticates to your endpoints with one of the following:

  • OAuth 2.0 client credentials — BravoTran requests a token from your token URL with grant_type=client_credentials and your client_id and client_secret as form fields, expects a JSON response with an access_token, and sends it as Authorization: Bearer <access_token>.
  • Bearer token — a static token in the Authorization header, sent as Authorization: Bearer <token>.

Changelog

1.9.0 — 2026-10-30

Backwards compatible: no partner changes required. Every change below loosens a rule, so charge code and tax code data that is valid under 1.8.0 stays valid.

  • Charge codes and tax codes: description is optional and may be null. BravoTran shows the code where a description is missing.
  • Charge codes and tax codes: updated_at is optional. Both lists are complete snapshots, so BravoTran doesn't use it to order them. It stays required on orgs, jobs, payables invoices, and staff.
  • Charge codes: tax_code is the default tax code for charge lines that have no accrual tax code, and may be null on every account, including GST/VAT accounts. The key is still always included. When present it must match an external_id in the tax codes data. GET /tax-codes is still required for GST/VAT accounts that use GET /charge-codes.

1.8.0 — 2026-10-29

Rules that were previously stated only in field descriptions are now part of the schema, so a validator can check them. BravoTran's import behavior is unchanged in this release. A later release will validate the responses BravoTran fetches against this schema and reject payloads that fail; the changelog will say so when that happens.

  • Accruals: cost_invoice_number and cost_invoice_date are required and non-null when cost_posted is true.
  • Identifiers can no longer be empty strings: external_id on every entity and reference, job_number, invoice_number, org name and code, charge code and tax code code, and waybill number.
  • Jobs: parent_jobs holds at most one entry, matching the documented behavior.
  • Orgs: days_to_pay must be zero or greater, and between 1 and 31 when payments_scheduling_rule is day_n_month_after_invoice_date. A null value means BravoTran uses the account's default.
  • POST /payables-invoices: due_date is null when is_credit_note is true. It is also null when BravoTran is not configured to send due dates for the account; earlier wording implied a date was always sent for invoices.
  • Amounts: documented that total equals pre_tax plus tax.

Rules that depend on other data stay as descriptions and are not checked by the schema: references must match an external_id in the related data, invoice numbers are unique per creditor, and tax codes are required only for GST/VAT accounts.

1.7.0 — 2026-10-09

  • Documented the authentication options for POST /events: an API token, or an OAuth 2.0 access token obtained with a client ID and client secret or with a certificate.
  • OAuth clients can now authenticate to the token endpoint with a client ID and client secret, using HTTP Basic authentication or form fields. Certificate (signed JWT) authentication is unchanged.
  • Added POST /oauth/token, BravoTran's token endpoint, with its request fields, response, and errors.
  • Documented that BravoTran sends client_id and client_secret as form fields when requesting a token from your token URL.

The following entries correct earlier documentation; BravoTran's behavior is unchanged:

  • Corrected the tax notes for GET /jobs/{external_id} and GET /payables-invoices/{external_id} to match the schemas: always include numeric tax values, using 0.0 for non-tax accounts. Job accruals must also include tax_code, using null for non-tax accounts. Earlier wording incorrectly allowed these fields to be omitted.
  • Corrected the POST /payables-invoices tax note and schemas: tax is always present and numeric; charge-line tax_code and tax_rate are always present and are null for accounts without GST/VAT.
  • Corrected the POST /payables-invoices credit-note description: credit-note totals are positive, but individual lines retain their sign relative to the document, so discount lines can be negative.
  • Clarified that GST/VAT accounts using GET /charge-codes also require GET /tax-codes, and that tax-code data is fetched together with charge-code data.
  • Aligned the job and invoice examples across submission and posting, and corrected the event URL in the flow diagram to /events.

1.6.0 — 2026-09-28

  • BravoTran now rejects an org returned by GET /orgs/{external_id} when country is missing or isn't a valid ISO 3166-1 alpha-2 code, and the import of that org fails. country was already required with a two-letter uppercase format; values such as us or USA are not corrected.
  • Added the optional address_line_2 field to orgs.
  • Documented how BravoTran uses the org address_line_1, address_line_2, city, state, and postal_code fields, and that sending null for one of them leaves BravoTran's existing value unchanged.

1.5.0 — 2026-08-28

  • Charge codes returned by GET /charge-codes now include a required charge_type field classifying the charge as freight, origin, destination, or other.

1.4.0 — 2026-07-17

  • Renamed the org reference to creditor on job accruals (GET /jobs/{external_id}), payables invoices (GET /payables-invoices/{external_id}), and outbound payables invoices (POST /payables-invoices). The referenced entity is still an org fetched via the /orgs endpoints; the key now reflects the role the org plays on these records.

1.3.0 — 2026-07-16

  • Payables invoice responses now include a status field (draft or posted) — in both GET /payables-invoices/{external_id} and the POST /payables-invoices response body.
  • POST /payables-invoices charge lines now always include an accrual reference whose external_id matches an accrual returned by GET /jobs/{external_id} (null when the charge line has no matching accrual).
  • Job accruals now include a tax_code reference. Like other tax fields, it is optional for non-tax / US-based accounts.
  • Added supporting data endpoints — GET /staff, GET /charge-codes, and GET /tax-codes — as an alternative to flat file delivery of staff, charge code, and tax code data.
  • POST /payables-invoices now documents a 409 response with error code duplicate_invoice, returned when an invoice with the same invoice_number already exists for the org.

1.2.0 — 2026-07-08

  • POST /payables-invoices requests no longer include an external_id. Your system generates its own identifier for the created invoice and returns it as external_id in the 200 response. BravoTran stores that identifier and uses it for GET /payables-invoices/{external_id}.
  • POST /events error responses now use lower_snake error codes (validation_error, invalid_json, unauthorized), matching the partner-hosted endpoints.

1.1.0 — 2026-07-07

  • tax is now required on invoice-level and charge-line amounts returned by GET /payables-invoices/{external_id} for tax-enabled accounts. Non-tax / US-based accounts are unaffected.
  • Zero-tax lines must state tax: 0.0 explicitly rather than omitting the field or sending null.
Download OpenAPI description
Languages
Servers
Mock server
https://apidocs.bravotran.com/_mock/openapi
https://api.yourserver.com