openapi: 3.1.0
info:
  title: Partner Integration API
  version: 1.5.0
  description: >
    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](images/api-flow.png)


    ### 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


    | Term | Definition |

    |------|-----------|

    | **FFS** | Freight Forwarding System — your system |

    | **Company** | A country-level entity in the FFS; maps to a BravoTran
    **Account** |

    | **Account** | BravoTran's representation of a company/country |

    | **Org** | A vendor, carrier, creditor, or debtor |

    | **Creditor** | An org that issues payables invoices; referenced as
    `creditor` on invoices and accruals |

    | **Job** | A consol (consolidation/master) or shipment (house) |

    | **Accrual** | An expected charge on a job |

    | **Charge Line** | A line item on a payables invoice |

    | **Payables Invoice** | AP invoice from a creditor |

    | **Outbound Payables Invoice** | Approved 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](https://en.wikipedia.org/wiki/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.

    - **Amounts** — Wherever there is an amount, a corresponding `currency`
    ([ISO 4217](https://en.wikipedia.org/wiki/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](https://en.wikipedia.org/wiki/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](https://docs.bravotran.com/File-Definitions-166c5c207d858086b3d0dd89c1ae252b#166c5c207d8580f09c40f841992ebfe5),
    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](https://docs.bravotran.com/File-Definitions-166c5c207d858086b3d0dd89c1ae252b#166c5c207d8580699bdbc831d58ba770),
    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.


    ## 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](https://docs.bravotran.com/File-Definitions-166c5c207d858086b3d0dd89c1ae252b#166c5c207d85807c9a6ac4ed15211d2e),
    which lists each field and whether it is required. Alternatively, implement
    `GET /tax-codes` and BravoTran will fetch tax code data from your API
    instead.


    ## Authentication


    Each direction of communication supports one of the following authentication
    methods, agreed upon during onboarding:


    **BravoTran → Partner** (partner-hosted endpoints):


    - **OAuth 2.0** — Client credentials flow

    - **Bearer Token** — Static token in the `Authorization` header


    **Partner → BravoTran** (`POST /events`):


    - **OAuth 2.0** — Client credentials flow

    - **Token** — API token in the `Authorization` header, formatted as `Token
    token=<token>`

    - **API Key** — API token in the `x-api-key` header


    ## Changelog


    ### 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`.
servers:
  - url: https://api.yourserver.com
    description: >-
      Partner-hosted API — where BravoTran fetches entity data and posts
      approved invoices
paths:
  /events:
    post:
      operationId: sendEvent
      summary: Send event notification to BravoTran
      description: >
        **This endpoint is hosted by BravoTran**, not the partner. **Required
        unless you implement the polling alternative** (`GET /orgs`, `GET
        /jobs`, `GET /payables-invoices`). Implement either the events API or
        the list endpoints — not both.


        Your system calls this endpoint to notify BravoTran that an entity has
        been created or updated.

        BravoTran will then fetch the entity details from your API.


        BravoTran validates `entity_type`, `external_id`, and `updated_at`.
        Additional JSON fields are rejected.
      tags:
        - Events
      security:
        - OAuth2.0-BravoTran: []
        - Token-Auth-BravoTran: []
        - X-API-Key-BravoTran: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EventNotification'
            example:
              entity_type: job
              external_id: db3b1f33-2335-4fc5-a874-f8284a95d539
              updated_at: '2025-02-17T20:32:39Z'
      responses:
        '200':
          description: Event accepted for processing
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventAccepted'
              example:
                int_req_id: 12345
        '400':
          description: Invalid event payload
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EventError'
              examples:
                validation_error:
                  summary: Missing required field
                  value:
                    int_req_id: 12345
                    error:
                      code: validation_error
                      message: Request validation failed
                      details:
                        - field: external_id
                          message: is required
                invalid_json:
                  summary: Malformed JSON
                  value:
                    int_req_id: 12345
                    error:
                      code: invalid_json
                      message: Request body must be valid JSON
                      details:
                        - field: null
                          message: unexpected token at '{ invalid json'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UnauthorizedError'
              example:
                error:
                  code: unauthorized
                  message: Token was incorrect
      servers:
        - url: https://api.bravotran.com
          description: BravoTran-hosted event ingestion endpoint
  /orgs:
    get:
      operationId: listOrgs
      summary: List updated orgs
      description: >
        Returns a list of org identifiers, optionally filtered to those updated
        within a time window.


        **Optional.** Provided as an alternative to `POST /events` for partners
        that cannot push live event notifications. Implement either the events
        API or the list endpoints — not both.


        Response items contain only `external_id`. To fetch full org details,
        call `GET /orgs/{external_id}` for each entry.
      tags:
        - Orgs
      parameters:
        - $ref: '#/components/parameters/UpdatedAfter'
        - $ref: '#/components/parameters/UpdatedBefore'
      responses:
        '200':
          description: List of org identifiers updated in the given window.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/EntityRef'
              example:
                - external_id: feaa09da-9551-47f8-b30d-6e303b6ad6d5
                - external_id: 9b2d1ac4-71f2-4f52-9b62-8e3e6c4a1d33
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /orgs/{external_id}:
    get:
      operationId: getOrg
      summary: Get an org
      description: >
        Returns a single org by its external ID. **Implementation is required.**


        If multiple source tables are combined into the orgs entity, prefix the
        `external_id` with the source type to maintain uniqueness (e.g.,
        `vendor-123`).
      tags:
        - Orgs
      parameters:
        - $ref: '#/components/parameters/ExternalId'
      responses:
        '200':
          description: Org details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Org'
              example:
                external_id: feaa09da-9551-47f8-b30d-6e303b6ad6d5
                updated_at: '2024-12-24T19:04:29Z'
                name: Mediterranean Shipping Company S.A.
                code: MSC
                is_active: true
                is_creditor: true
                is_debtor: false
                country: CH
                days_to_pay: 30
                payments_scheduling_rule: n_days_after_invoice_date
                address_line_1: Chemin Rieu 12
                city: Geneva
                state: null
                postal_code: '1202'
                contacts:
                  - name: Marco Rossi
                    email_address: mrossi@msc.com
                  - name: Elena Bianchi
                    email_address: ebianchi@msc.com
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /jobs:
    get:
      operationId: listJobs
      summary: List updated jobs
      description: >
        Returns a list of job identifiers, optionally filtered to those updated
        within a time window.


        **Optional.** Provided as an alternative to `POST /events` for partners
        that cannot push live event notifications. Implement either the events
        API or the list endpoints — not both.


        A job's `updated_at` reflects changes to nested data as well (accruals,
        child shipments linked, etc.), so this endpoint surfaces any change that
        should trigger a re-sync. Response items contain only `external_id`. To
        fetch full job details, call `GET /jobs/{external_id}` for each entry.
      tags:
        - Jobs
      parameters:
        - $ref: '#/components/parameters/UpdatedAfter'
        - $ref: '#/components/parameters/UpdatedBefore'
      responses:
        '200':
          description: List of job identifiers updated in the given window.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/EntityRef'
              example:
                - external_id: db3b1f33-2335-4fc5-a874-f8284a95d539
                - external_id: 5f13f433-5228-4ce0-bb23-765285682111
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
  /jobs/{external_id}:
    get:
      operationId: getJob
      summary: Get a job
      description: >
        Returns a single job by its external ID. **Implementation is required.**


        > **Tax note:** Tax-related fields (e.g., `tax` on amounts, tax code
        references) are marked as required but are **optional for non-tax /
        US-based accounts**. BravoTran determines the tax mode based on the
        authenticated account.


        Jobs contain nested accruals, container numbers, waybills, and
        additional references.

        When a job is updated (including changes to accruals, child shipments
        added to a consol, etc.),

        the `updated_at` timestamp must reflect the most recent change.
      tags:
        - Jobs
      parameters:
        - $ref: '#/components/parameters/ExternalId'
      responses:
        '200':
          description: Job details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
              examples:
                shipment:
                  summary: A shipment with accruals
                  value:
                    external_id: db3b1f33-2335-4fc5-a874-f8284a95d539
                    updated_at: '2025-02-17T20:32:39Z'
                    job_number: S631234
                    job_type: shipment
                    status: open
                    parent_jobs:
                      - external_id: 5f13f433-5228-4ce0-bb23-765285682111
                    child_jobs: []
                    operator:
                      external_id: a7ed2209-b471-4c37-90f5-bfea1e7bb464
                    accruals:
                      - external_id: 62292d0a-3334-4ecb-9267-5101e1b06c70
                        operator:
                          external_id: a7ed2209-b471-4c37-90f5-bfea1e7bb464
                        creditor:
                          external_id: feaa09da-9551-47f8-b30d-6e303b6ad6d5
                        charge_code:
                          external_id: eda74483-b7d9-4168-9a20-82155e59395f
                        tax_code:
                          external_id: 940dca09-88d4-403e-be23-79a23d37fffd
                        description: International Freight
                        local_amount:
                          currency: AUD
                          pre_tax: 1570
                          tax: 0
                          total: 1570
                        overseas_amount:
                          currency: USD
                          pre_tax: 1000
                          tax: 0
                          total: 1000
                        cost_posted: true
                        cost_invoice_number: SYD923891
                        cost_invoice_date: '2025-02-15'
                      - external_id: 438e506b-6114-4e2e-bb06-a3df6c18811a
                        operator:
                          external_id: a7ed2209-b471-4c37-90f5-bfea1e7bb464
                        creditor:
                          external_id: feaa09da-9551-47f8-b30d-6e303b6ad6d5
                        charge_code:
                          external_id: ff815915-de16-4c63-8248-4757aea906e9
                        tax_code:
                          external_id: 3c01e205-aecd-4d34-ac19-fe4dea96a195
                        description: Customs Clearance Fee
                        local_amount:
                          currency: AUD
                          pre_tax: 100
                          tax: 10
                          total: 110
                        overseas_amount:
                          currency: AUD
                          pre_tax: 100
                          tax: 10
                          total: 110
                        cost_posted: false
                        cost_invoice_number: null
                        cost_invoice_date: null
                    container_numbers:
                      - MEDU5338992
                      - SEGU2901100
                    way_bills:
                      - type: mwb
                        number: MEDUUJ111111
                    additional_references:
                      - label: Supplier Cost Ref
                        value: ABC1234
                consol:
                  summary: A consol with a child shipment
                  value:
                    external_id: 5f13f433-5228-4ce0-bb23-765285682111
                    updated_at: '2025-02-17T20:35:22Z'
                    job_number: C585234
                    job_type: consol
                    status: closed
                    parent_jobs: []
                    child_jobs:
                      - external_id: db3b1f33-2335-4fc5-a874-f8284a95d539
                    operator:
                      external_id: a7ed2209-b471-4c37-90f5-bfea1e7bb464
                    accruals: []
                    container_numbers: []
                    way_bills: []
                    additional_references: []
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /payables-invoices:
    get:
      operationId: listPayablesInvoices
      summary: List updated payables invoices
      description: >
        Returns a list of payables invoice identifiers, optionally filtered to
        those updated within a time window.


        **Optional.** Provided as an alternative to `POST /events` for partners
        that cannot push live event notifications. Implement either the events
        API or the list endpoints — not both.


        Response items contain only `external_id`. To fetch full invoice
        details, call `GET /payables-invoices/{external_id}` for each entry.
      tags:
        - Payables Invoices
      parameters:
        - $ref: '#/components/parameters/UpdatedAfter'
        - $ref: '#/components/parameters/UpdatedBefore'
      responses:
        '200':
          description: List of payables invoice identifiers updated in the given window.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/EntityRef'
              example:
                - external_id: b6d1fdec-81ff-43b9-b422-ae27513047ce
                - external_id: 4a1f2ec9-2c9e-4f16-9f70-d7e5d3c1a982
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
    post:
      operationId: postPayablesInvoice
      summary: Receive an approved payables invoice from BravoTran
      description: >
        **This endpoint is hosted by the partner**, not BravoTran.
        **Implementation is required.**


        BravoTran calls this endpoint when an invoice has been approved and is
        ready for cost posting.

        Your system must create or record the invoice accordingly, then respond
        with the `external_id`

        your system assigned to the created invoice and its `status` (`draft` or
        `posted`). Do not derive

        the identifier from the request — BravoTran does not send one.


        > **Tax note:** Tax-related fields (`tax` on amounts, `tax_code`,
        `tax_rate`) are only present for tax-enabled accounts. For non-tax /
        US-based accounts these fields will be absent or null. BravoTran
        determines the tax mode based on the authenticated account.
      tags:
        - Outbound Payables Invoices
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/OutboundPayablesInvoice'
            example:
              creditor:
                external_id: feaa09da-9551-47f8-b30d-6e303b6ad6d5
              invoice_number: BT-2025-0042
              invoice_date: '2025-03-15'
              is_credit_note: false
              overseas_amount:
                currency: AUD
                pre_tax: 1200
                tax: 20
                total: 1220
              due_date: '2025-04-14'
              files:
                - doc_type: payables_invoice
                  file_type: application/pdf
                  file_data: JVBERi0xLjQKJcfs...
              charge_lines:
                - job:
                    external_id: db3b1f33-2335-4fc5-a874-f8284a95d539
                  accrual: null
                  charge_code:
                    external_id: eda74483-b7d9-4168-9a20-82155e59395f
                  description: International Freight
                  overseas_amount:
                    currency: AUD
                    pre_tax: 1000
                    tax: 0
                    total: 1000
                  tax_code:
                    external_id: 940dca09-88d4-403e-be23-79a23d37fffd
                  tax_rate: 0
                - job:
                    external_id: db3b1f33-2335-4fc5-a874-f8284a95d539
                  accrual:
                    external_id: 438e506b-6114-4e2e-bb06-a3df6c18811a
                  charge_code:
                    external_id: ff815915-de16-4c63-8248-4757aea906e9
                  description: Customs Clearance Fee
                  overseas_amount:
                    currency: AUD
                    pre_tax: 200
                    tax: 20
                    total: 220
                  tax_code:
                    external_id: 3c01e205-aecd-4d34-ac19-fe4dea96a195
                  tax_rate: 10
      responses:
        '200':
          description: Invoice accepted
          content:
            application/json:
              schema:
                type: object
                required:
                  - external_id
                  - status
                properties:
                  external_id:
                    type: string
                    description: >-
                      The unique, immutable identifier your system generated for
                      the created invoice. BravoTran stores this identifier and
                      uses it to fetch the invoice via `GET
                      /payables-invoices/{external_id}`.
                  status:
                    type: string
                    enum:
                      - draft
                      - posted
                    description: |
                      Status of the created invoice in your system:
                      - `draft` — created but not yet posted to the ledger
                      - `posted` — posted to the ledger
              example:
                external_id: 7f3c9a2e-5b81-4d64-a0c7-1e92d8f4b6a3
                status: posted
        '400':
          description: Bad request
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: bad_request
                  message: Request validation failed.
                  details:
                    - field: invoice_number
                      message: is required
        '401':
          $ref: '#/components/responses/Unauthorized'
        '409':
          description: >
            Duplicate invoice. Returned when an invoice with the same
            `invoice_number`

            already exists for the creditor in your system.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: duplicate_invoice
                  message: >-
                    An invoice with invoice_number BT-2025-0042 already exists
                    for this creditor.
  /payables-invoices/{external_id}:
    get:
      operationId: getPayablesInvoice
      summary: Get a payables invoice
      description: >
        Returns a single payables invoice by its external ID. **Implementation
        is required.**


        > **Tax note:** Tax-related fields (e.g., `tax` on amounts, tax code
        references) are marked as required but are **optional for non-tax /
        US-based accounts**. BravoTran determines the tax mode based on the
        authenticated account.


        Payables invoices contain nested charge lines, each referencing a job
        and optionally an accrual.
      tags:
        - Payables Invoices
      parameters:
        - $ref: '#/components/parameters/ExternalId'
      responses:
        '200':
          description: Payables invoice details
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PayablesInvoice'
              example:
                external_id: b6d1fdec-81ff-43b9-b422-ae27513047ce
                updated_at: '2025-02-15T03:18:13Z'
                creditor:
                  external_id: feaa09da-9551-47f8-b30d-6e303b6ad6d5
                invoice_number: SYD923891
                invoice_date: '2025-02-15'
                status: posted
                overseas_amount:
                  currency: AUD
                  pre_tax: 1770
                  tax: 20
                  total: 1790
                charge_lines:
                  - job:
                      external_id: db3b1f33-2335-4fc5-a874-f8284a95d539
                    accrual:
                      external_id: 62292d0a-3334-4ecb-9267-5101e1b06c70
                    charge_code:
                      external_id: eda74483-b7d9-4168-9a20-82155e59395f
                    description: International Freight
                    overseas_amount:
                      currency: AUD
                      pre_tax: 1570
                      tax: 0
                      total: 1570
                  - job:
                      external_id: db3b1f33-2335-4fc5-a874-f8284a95d539
                    accrual:
                      external_id: 438e506b-6114-4e2e-bb06-a3df6c18811a
                    charge_code:
                      external_id: ff815915-de16-4c63-8248-4757aea906e9
                    description: Customs Clearance Fee
                    overseas_amount:
                      currency: AUD
                      pre_tax: 200
                      tax: 20
                      total: 220
        '401':
          $ref: '#/components/responses/Unauthorized'
        '404':
          $ref: '#/components/responses/NotFound'
  /staff:
    get:
      operationId: listStaff
      summary: List staff
      description: >
        Returns the full list of staff members.


        **Optional.** Provided as an alternative to flat file delivery of staff
        data.

        Jobs and accruals reference staff (operators) by `external_id`.
      tags:
        - Supporting Data
      responses:
        '200':
          description: List of staff members.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/StaffMember'
              example:
                - external_id: a7ed2209-b471-4c37-90f5-bfea1e7bb464
                  updated_at: '2024-11-03T08:12:45Z'
                  name: Sarah Nguyen
                  email_address: snguyen@yourcompany.com
                  is_active: true
                - external_id: 3c1f0b7a-8d24-4e59-9a41-2f6b7c8d9e0a
                  updated_at: '2024-09-18T22:47:10Z'
                  name: James Porter
                  email_address: jporter@yourcompany.com
                  is_active: false
        '401':
          $ref: '#/components/responses/Unauthorized'
  /charge-codes:
    get:
      operationId: listChargeCodes
      summary: List charge codes
      description: >
        Returns the full list of charge codes.


        **Optional.** Provided as an alternative to flat file delivery of charge
        code data.

        Accruals and charge lines reference charge codes by `external_id`.
      tags:
        - Supporting Data
      responses:
        '200':
          description: List of charge codes.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ChargeCode'
              example:
                - external_id: eda74483-b7d9-4168-9a20-82155e59395f
                  updated_at: '2024-08-21T11:30:02Z'
                  code: FRT
                  description: International Freight
                  charge_type: freight
                  tax_code:
                    external_id: 940dca09-88d4-403e-be23-79a23d37fffd
                  is_active: true
                - external_id: ff815915-de16-4c63-8248-4757aea906e9
                  updated_at: '2024-08-21T11:30:02Z'
                  code: CCL
                  description: Customs Clearance Fee
                  charge_type: destination
                  tax_code:
                    external_id: 3c01e205-aecd-4d34-ac19-fe4dea96a195
                  is_active: true
        '401':
          $ref: '#/components/responses/Unauthorized'
  /tax-codes:
    get:
      operationId: listTaxCodes
      summary: List tax codes
      description: >
        Returns the full list of tax codes.


        **Optional.** Provided as an alternative to flat file delivery of tax
        code data.

        Charge codes, accruals, and charge lines reference tax codes by
        `external_id`.
      tags:
        - Supporting Data
      responses:
        '200':
          description: List of tax codes.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/TaxCode'
              example:
                - external_id: 3c01e205-aecd-4d34-ac19-fe4dea96a195
                  updated_at: '2024-07-02T09:15:33Z'
                  code: GST
                  description: Goods and services tax (10%)
                  tax_rate: 10
                  is_active: true
                - external_id: 940dca09-88d4-403e-be23-79a23d37fffd
                  updated_at: '2024-07-02T09:15:33Z'
                  code: FRE
                  description: GST free
                  tax_rate: 0
                  is_active: true
        '401':
          $ref: '#/components/responses/Unauthorized'
components:
  parameters:
    ExternalId:
      name: external_id
      in: path
      required: true
      description: The unique, immutable identifier for the entity in the FFS.
      schema:
        type: string
    UpdatedAfter:
      name: updated_after
      in: query
      required: false
      description: >
        Inclusive lower bound on `updated_at`. ISO 8601 UTC datetime (e.g.,
        `2025-02-17T20:32:39Z`).

        Date-only values are not accepted. Optional.
      schema:
        type: string
        format: date-time
        examples:
          - '2025-02-17T20:32:39Z'
    UpdatedBefore:
      name: updated_before
      in: query
      required: false
      description: >
        Exclusive upper bound on `updated_at`. ISO 8601 UTC datetime (e.g.,
        `2025-02-18T20:32:39Z`).

        Date-only values are not accepted. Optional, but if provided,
        `updated_after` is required.
      schema:
        type: string
        format: date-time
        examples:
          - '2025-02-18T20:32:39Z'
  responses:
    NotFound:
      description: Entity not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: not_found
              message: Entity with the given external_id does not exist.
    BadRequest:
      description: >
        Invalid request. For list endpoints, returned when query parameters are
        malformed,

        when `updated_before` is provided without `updated_after`, or when a
        date-only value

        is supplied where a UTC datetime is required.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: bad_request
              message: updated_before requires updated_after to be provided.
    Unauthorized:
      description: Authentication credentials were missing or invalid.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error:
              code: unauthorized
              message: Missing or invalid credentials.
  schemas:
    EventNotification:
      type: object
      additionalProperties: false
      required:
        - entity_type
        - external_id
        - updated_at
      properties:
        entity_type:
          type: string
          enum:
            - org
            - job
            - payables-invoice
          description: The entity type that was affected.
        external_id:
          type: string
          description: The external_id of the affected entity.
        updated_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of the change.
    EventAccepted:
      type: object
      required:
        - int_req_id
      properties:
        int_req_id:
          type: integer
          description: BravoTran request record ID for this event notification.
    EventError:
      type: object
      required:
        - int_req_id
        - error
      properties:
        int_req_id:
          type: integer
          description: BravoTran request record ID for this event notification.
        error:
          type: object
          required:
            - code
            - message
            - details
          properties:
            code:
              type: string
              enum:
                - validation_error
                - invalid_json
            message:
              type: string
            details:
              type: array
              items:
                $ref: '#/components/schemas/EventErrorDetail'
    EventErrorDetail:
      type: object
      required:
        - field
        - message
      properties:
        field:
          type:
            - string
            - 'null'
          description: >-
            Field that failed validation, or null for request-level errors such
            as malformed JSON.
        message:
          type: string
    UnauthorizedError:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              enum:
                - unauthorized
            message:
              type: string
    EntityRef:
      type: object
      required:
        - external_id
      properties:
        external_id:
          type: string
          description: The external_id of the referenced entity.
    Amount:
      type: object
      required:
        - currency
        - pre_tax
        - total
      properties:
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          description: ISO 4217 currency code.
          examples:
            - AUD
            - USD
        pre_tax:
          type: number
          description: Amount before tax.
        tax:
          type:
            - number
            - 'null'
          description: >-
            Tax amount. Optional on outbound invoice-level and charge line
            amounts.
        total:
          type: number
          description: Total amount including tax.
    AmountWithTax:
      type: object
      required:
        - currency
        - pre_tax
        - tax
        - total
      properties:
        currency:
          type: string
          pattern: ^[A-Z]{3}$
          description: ISO 4217 currency code.
          examples:
            - AUD
            - USD
        pre_tax:
          type: number
          description: Amount before tax.
        tax:
          type: number
          description: Tax amount.
        total:
          type: number
          description: Total amount including tax.
    Contact:
      type: object
      required:
        - name
        - email_address
      properties:
        name:
          type: string
          description: Contact person's name.
        email_address:
          type: string
          format: email
          description: Contact person's email address.
    Org:
      type: object
      required:
        - external_id
        - updated_at
        - name
        - code
        - is_active
        - is_creditor
        - is_debtor
        - country
      properties:
        external_id:
          type: string
          description: Unique, immutable identifier for this org.
        updated_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when this org was last updated.
        name:
          type: string
          description: Full legal name of the org.
        code:
          type: string
          description: Short code used to identify this org in the FFS UI.
        is_active:
          type: boolean
          description: Whether this org is currently active.
        is_creditor:
          type: boolean
          description: Whether this org can submit payables invoices.
        is_debtor:
          type: boolean
          description: Whether this org will receive receivables invoices.
        country:
          type: string
          pattern: ^[A-Z]{2}$
          description: ISO 3166-1 alpha-2 country code.
        days_to_pay:
          type:
            - integer
            - 'null'
          description: >
            Payment scheduling parameter. For `n_days_after_invoice_date`, the
            number of days

            after the invoice date that payment is due. For
            `day_n_month_after_invoice_date`,

            the day of the month (in the month after the invoice date) that
            payment is due.

            Not used by the other rules.
        payments_scheduling_rule:
          type:
            - string
            - 'null'
          enum:
            - n_days_after_invoice_date
            - eom_following_receipt
            - day_n_month_after_invoice_date
            - always_ignore
            - null
          description: >
            Rule governing when payments are scheduled when this org is a
            creditor:

            - `n_days_after_invoice_date` — payment is due `days_to_pay` days
            after the invoice date (BravoTran may snap the date to the account's
            configured payment day of week)

            - `day_n_month_after_invoice_date` — payment is due on day
            `days_to_pay` of the month after the invoice date

            - `eom_following_receipt` — payment is due at the end of the month
            following the invoice date (despite the name, this is computed from
            the invoice date, not the receipt date)

            - `always_ignore` — payments for this creditor are always marked
            ignored and never scheduled
        address_line_1:
          type:
            - string
            - 'null'
        city:
          type:
            - string
            - 'null'
        state:
          type:
            - string
            - 'null'
        postal_code:
          type:
            - string
            - 'null'
        contacts:
          type: array
          items:
            $ref: '#/components/schemas/Contact'
          description: >-
            Contact persons associated with this org. Helps BravoTran identify
            the creditor.
    StaffMember:
      type: object
      required:
        - external_id
        - updated_at
        - name
        - email_address
        - is_active
      properties:
        external_id:
          type: string
          description: Unique, immutable identifier for this staff member.
        updated_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when this staff member was last updated.
        name:
          type: string
          description: Staff member's full name.
        email_address:
          type: string
          format: email
          description: Staff member's email address.
        is_active:
          type: boolean
          description: Whether this staff member is currently active.
    ChargeCode:
      type: object
      required:
        - external_id
        - updated_at
        - code
        - description
        - charge_type
        - tax_code
        - is_active
      properties:
        external_id:
          type: string
          description: Unique, immutable identifier for this charge code.
        updated_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when this charge code was last updated.
        code:
          type: string
          description: Short code the user might know for this charge code.
        description:
          type: string
          description: Human-readable description of the charge code.
        charge_type:
          type: string
          enum:
            - freight
            - origin
            - destination
            - other
          description: |
            Classification of the charge:
            - `freight` — freight / carriage charges
            - `origin` — origin-side charges
            - `destination` — destination-side charges
            - `other` — anything else
        tax_code:
          oneOf:
            - $ref: '#/components/schemas/EntityRef'
            - type: 'null'
          description: >-
            Reference to the default tax code for this charge code. Must match
            an `external_id` from the tax codes data. Required for GST/VAT
            accounts; may be null for non-tax / US-based accounts.
        is_active:
          type: boolean
          description: Whether this charge code is currently active.
    TaxCode:
      type: object
      required:
        - external_id
        - updated_at
        - code
        - description
        - tax_rate
        - is_active
      properties:
        external_id:
          type: string
          description: Unique, immutable identifier for this tax code.
        updated_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when this tax code was last updated.
        code:
          type: string
          description: Short code the user might know for this tax code.
        description:
          type: string
          description: Human-readable description of the tax code.
        tax_rate:
          type: number
          minimum: 0
          maximum: 100
          description: >-
            Tax rate percentage between 0.0 and 100.0 (e.g., `10.0` for 10%
            GST).
        is_active:
          type: boolean
          description: Whether this tax code is currently active.
    Accrual:
      type: object
      required:
        - external_id
        - operator
        - creditor
        - charge_code
        - tax_code
        - local_amount
        - overseas_amount
        - cost_posted
      properties:
        external_id:
          type: string
          description: Unique identifier for this accrual.
        operator:
          $ref: '#/components/schemas/EntityRef'
          description: Reference to the staff member (operator) for this accrual.
        creditor:
          $ref: '#/components/schemas/EntityRef'
          description: >-
            Reference to the creditor org (the vendor this accrual is payable
            to).
        charge_code:
          $ref: '#/components/schemas/EntityRef'
          description: >-
            Reference to the charge code for this accrual. Must match an
            `external_id` from the charge codes data.
        tax_code:
          oneOf:
            - $ref: '#/components/schemas/EntityRef'
            - type: 'null'
          description: >-
            Reference to the tax code applied to this accrual. Must match an
            `external_id` from the tax codes data. Required for tax-enabled
            accounts; may be null for non-tax / US-based accounts.
        description:
          type:
            - string
            - 'null'
          description: Human-readable description of the accrual.
        local_amount:
          $ref: '#/components/schemas/AmountWithTax'
          description: Amount in the local (account) currency.
        overseas_amount:
          $ref: '#/components/schemas/AmountWithTax'
          description: Amount in the overseas (vendor's) currency.
        cost_posted:
          type: boolean
          description: Whether the cost has been posted.
        cost_invoice_number:
          type:
            - string
            - 'null'
          description: Invoice number for the cost. Required if `cost_posted` is true.
        cost_invoice_date:
          type:
            - string
            - 'null'
          format: date
          description: >-
            ISO 8601 date of the cost invoice. Required if `cost_posted` is
            true.
    WayBill:
      type: object
      required:
        - type
        - number
      properties:
        type:
          type: string
          enum:
            - mwb
            - hwb
            - mawb
            - hawb
          description: >
            Type of waybill:

            - `mwb` — Master Bill of Lading (ocean)

            - `hwb` — House Bill of Lading (ocean)

            - `mawb` — Master Air Waybill (air)

            - `hawb` — House Air Waybill (air)


            Ocean entries (`mwb`, `hwb`) carry bill of lading numbers; air
            entries (`mawb`, `hawb`) carry air waybill numbers — the `way_bills`
            field holds the job's primary transport documents in either mode.
        number:
          type: string
          description: The waybill number.
    AdditionalReference:
      type: object
      required:
        - label
        - value
      properties:
        label:
          type: string
          description: Label for this reference (e.g., "Supplier Cost Ref").
        value:
          type: string
          description: Value of this reference.
    Job:
      type: object
      required:
        - external_id
        - updated_at
        - job_number
        - job_type
        - status
        - operator
        - accruals
      properties:
        external_id:
          type: string
          description: Unique, immutable identifier for this job.
        updated_at:
          type: string
          format: date-time
          description: >
            ISO 8601 timestamp of when this job was last updated.

            Must update whenever any nested data changes (accrual added/edited,
            shipment linked, etc.).
        job_number:
          type: string
          description: Human-friendly identifier for the job.
        job_type:
          type: string
          enum:
            - consol
            - shipment
          description: |
            Type of job:
            - `consol` — Consolidation of child jobs (master)
            - `shipment` — Stand-alone job or child of a consol (house)
        status:
          type: string
          description: Current status of the job in the FFS.
        parent_jobs:
          type: array
          items:
            $ref: '#/components/schemas/EntityRef'
          description: >-
            References to parent jobs. Currently contains 0 or 1 parent job, but
            is modeled as an array to support multiple parent jobs in the
            future.
        child_jobs:
          type: array
          items:
            $ref: '#/components/schemas/EntityRef'
          description: References to child jobs. Typically present on consols.
        operator:
          $ref: '#/components/schemas/EntityRef'
          description: >-
            Reference to the staff member responsible for this job. BravoTran
            notifies this operator about inaccurate or missing accruals so they
            can fix them.
        accruals:
          type: array
          items:
            $ref: '#/components/schemas/Accrual'
          description: >-
            Expected charges on this job. Send even if the job has no accruals
            (empty array).
        container_numbers:
          type: array
          items:
            type: string
          description: Container numbers associated with this job.
        way_bills:
          type: array
          items:
            $ref: '#/components/schemas/WayBill'
          description: Waybills associated with this job.
        additional_references:
          type: array
          items:
            $ref: '#/components/schemas/AdditionalReference'
          description: Any additional reference data for this job.
    ChargeLine:
      type: object
      required:
        - job
        - charge_code
        - description
        - overseas_amount
      properties:
        job:
          $ref: '#/components/schemas/EntityRef'
          description: Reference to the job this charge line applies to.
        accrual:
          oneOf:
            - $ref: '#/components/schemas/EntityRef'
            - type: 'null'
          description: Reference to the matching accrual from the job data, if applicable.
        charge_code:
          $ref: '#/components/schemas/EntityRef'
          description: >-
            Reference to the charge code for this line. Must match an
            `external_id` from the charge codes data.
        description:
          type: string
          description: Human-readable description of the charge.
        overseas_amount:
          $ref: '#/components/schemas/AmountWithTax'
          description: Amount in the overseas (invoice) currency — the vendor's currency.
    PayablesInvoice:
      type: object
      required:
        - external_id
        - updated_at
        - creditor
        - invoice_number
        - invoice_date
        - status
        - overseas_amount
        - charge_lines
      properties:
        external_id:
          type: string
          description: Unique, immutable identifier for this invoice.
        updated_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when this invoice was last updated.
        creditor:
          $ref: '#/components/schemas/EntityRef'
          description: Reference to the creditor org that issued this invoice.
        invoice_number:
          type: string
          description: Invoice number. Must be unique scoped to the creditor.
        invoice_date:
          type: string
          format: date
          description: ISO 8601 date of the invoice.
        status:
          type: string
          enum:
            - draft
            - posted
          description: |
            Current status of the invoice in the FFS:
            - `draft` — entered but not yet posted to the ledger
            - `posted` — posted to the ledger
        overseas_amount:
          $ref: '#/components/schemas/AmountWithTax'
          description: Total invoice amount in the overseas (invoice) currency.
        charge_lines:
          type: array
          items:
            $ref: '#/components/schemas/ChargeLine'
          minItems: 1
          description: Line items on the invoice.
    OutboundChargeLine:
      type: object
      required:
        - job
        - accrual
        - charge_code
        - description
        - overseas_amount
      properties:
        job:
          $ref: '#/components/schemas/EntityRef'
          description: Reference to the job this charge line applies to.
        accrual:
          oneOf:
            - $ref: '#/components/schemas/EntityRef'
            - type: 'null'
          description: >
            Reference to the matching accrual. The `external_id` matches an
            accrual returned by `GET /jobs/{external_id}`.


            Null when the charge line has no matching accrual. A null accrual
            represents a "scratch" charge line that your system should create on
            the job. Scratch charge lines are enabled on a per-integration
            basis, so BravoTran may never send them to your integration; when
            not enabled, `accrual` always references an existing accrual.
        charge_code:
          $ref: '#/components/schemas/EntityRef'
          description: >-
            Reference to the charge code for this line. Must match an
            `external_id` from the charge codes data.
        description:
          type: string
          description: Human-readable description of the charge.
        overseas_amount:
          $ref: '#/components/schemas/Amount'
          description: Amount in the overseas (invoice) currency — the vendor's currency.
        tax_code:
          oneOf:
            - $ref: '#/components/schemas/EntityRef'
            - type: 'null'
          description: >-
            Tax code applied to this charge line. Only present for tax-enabled
            accounts.
        tax_rate:
          type:
            - number
            - 'null'
          description: >-
            Tax rate percentage applied to this charge line. Only present for
            tax-enabled accounts.
    OutboundPayablesInvoiceFile:
      type: object
      required:
        - doc_type
        - file_type
        - file_data
      properties:
        doc_type:
          type: string
          enum:
            - payables_invoice
            - other
          description: The document category for this attached file.
        file_type:
          type: string
          enum:
            - application/pdf
            - application/xml
          description: MIME type of the attached file.
        file_data:
          type: string
          format: byte
          description: Base64-encoded file contents.
    OutboundPayablesInvoice:
      type: object
      required:
        - creditor
        - invoice_number
        - invoice_date
        - is_credit_note
        - overseas_amount
        - due_date
        - files
        - charge_lines
      properties:
        creditor:
          $ref: '#/components/schemas/EntityRef'
          description: Reference to the creditor org for this invoice.
        invoice_number:
          type: string
          description: Invoice number. Unique scoped to the creditor.
        invoice_date:
          type: string
          format: date
          description: ISO 8601 date of the invoice.
        is_credit_note:
          type: boolean
          description: >-
            Whether this invoice is a credit note. Amounts are always positive
            regardless.
        overseas_amount:
          $ref: '#/components/schemas/Amount'
          description: Total invoice amount in the overseas (invoice) currency.
        due_date:
          type:
            - string
            - 'null'
          format: date
          description: Payment due date. Null when `is_credit_note` is true.
        files:
          type: array
          items:
            $ref: '#/components/schemas/OutboundPayablesInvoiceFile'
          minItems: 1
          description: Files attached to the approved invoice.
        charge_lines:
          type: array
          items:
            $ref: '#/components/schemas/OutboundChargeLine'
          minItems: 1
          description: Line items on the invoice.
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: >-
                Machine-readable error code in lower_snake_case (e.g.,
                `not_found`, `bad_request`, `unauthorized`).
            message:
              type: string
              description: Human-readable explanation of the error.
            details:
              type: array
              items:
                $ref: '#/components/schemas/ErrorDetail'
              description: Optional field-level validation errors.
    ErrorDetail:
      type: object
      required:
        - field
        - message
      properties:
        field:
          type:
            - string
            - 'null'
          description: Field that failed validation, or null for request-level errors.
        message:
          type: string
  securitySchemes:
    OAuth2.0-Partner:
      type: oauth2
      description: 'BravoTran → Partner: OAuth 2.0 client credentials flow.'
      flows:
        clientCredentials:
          tokenUrl: https://api.yourserver.com/oauth/token
          scopes: {}
    Bearer-Token-Partner:
      type: http
      scheme: bearer
      description: 'BravoTran → Partner: Bearer token in the Authorization header.'
    OAuth2.0-BravoTran:
      type: oauth2
      description: 'Partner → BravoTran: OAuth 2.0 client credentials flow.'
      flows:
        clientCredentials:
          tokenUrl: https://api.bravotran.com/oauth/token
          scopes: {}
    Token-Auth-BravoTran:
      type: apiKey
      in: header
      name: Authorization
      description: >-
        Partner → BravoTran: API token in the `Authorization` header, formatted
        as `Token token=<token>`.
    X-API-Key-BravoTran:
      type: apiKey
      in: header
      name: x-api-key
      description: 'Partner → BravoTran: API token in the `x-api-key` header.'
security:
  - OAuth2.0-Partner: []
  - Bearer-Token-Partner: []
tags:
  - name: Events
    description: Event notifications sent from the FFS to BravoTran.
  - name: Orgs
    description: >-
      Vendors, carriers, creditors, and debtors that BravoTran fetches from the
      FFS.
  - name: Jobs
    description: >-
      Consols and shipments, including their accruals, that BravoTran fetches
      from the FFS.
  - name: Payables Invoices
    description: AP invoices from creditors that BravoTran fetches from the FFS.
  - name: Outbound Payables Invoices
    description: >-
      Approved invoices that BravoTran posts to the partner's system for cost
      posting.
  - name: Supporting Data
    description: >-
      Staff, charge codes, and tax codes that BravoTran fetches from the FFS as
      an alternative to flat file delivery.
