{
  "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.\n\n## Overview\n\nBravoTran uses an event-driven pull model to stay in sync with your freight forwarding system:\n\n1. Your system sends an **event notification** to BravoTran when an entity is created or updated.\n2. BravoTran reads the entity type and identifier from the event.\n3. BravoTran issues a `GET` request to your API with the entity type and identifier.\n4. Your API returns the entity as JSON per this specification.\n\nBravoTran 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.\n\n![API Flow Diagram](images/api-flow.png)\n\n### Polling alternative\n\nIf 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.\n\nCreates 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.\n\n## Terminology\n\n| Term | Definition |\n|------|-----------|\n| **FFS** | Freight Forwarding System — your system |\n| **Company** | A country-level entity in the FFS; maps to a BravoTran **Account** |\n| **Account** | BravoTran's representation of a company/country |\n| **Org** | A vendor, carrier, creditor, or debtor |\n| **Creditor** | An org that issues payables invoices; referenced as `creditor` on invoices and accruals |\n| **Job** | A consol (consolidation/master) or shipment (house) |\n| **Accrual** | An expected charge on a job |\n| **Charge Line** | A line item on a payables invoice |\n| **Payables Invoice** | AP invoice from a creditor |\n| **Outbound Payables Invoice** | Approved invoice posted from BravoTran to the partner's system for cost posting |\n\nBravoTran 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.\n\n## Conventions\n\n- **`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.\n- **`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.\n- **Amounts** — Wherever there is an amount, a corresponding `currency` ([ISO 4217](https://en.wikipedia.org/wiki/ISO_4217)) must be provided.\n- **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).\n- **Country codes** — Country fields use [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2) two-letter codes.\n- **Nested references** — Related entities are represented as nested objects containing at minimum an `external_id`.\n- **Error codes** — Error responses use `lower_snake` codes in both directions. BravoTran keys off the HTTP status rather than partner error bodies.\n\n## Staff\n\nStaff 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.\n\n## Charge Codes\n\nCharge 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.\n\n## Tax Codes\n\nTax 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.\n\n## Authentication\n\nEach direction of communication supports one of the following authentication methods, agreed upon during onboarding:\n\n**BravoTran → Partner** (partner-hosted endpoints):\n\n- **OAuth 2.0** — Client credentials flow\n- **Bearer Token** — Static token in the `Authorization` header\n\n**Partner → BravoTran** (`POST /events`):\n\n- **OAuth 2.0** — Client credentials flow\n- **Token** — API token in the `Authorization` header, formatted as `Token token=<token>`\n- **API Key** — API token in the `x-api-key` header\n\n## Changelog\n\n### 1.5.0 — 2026-08-28\n\n- Charge codes returned by `GET /charge-codes` now include a required `charge_type` field classifying the charge as `freight`, `origin`, `destination`, or `other`.\n\n### 1.4.0 — 2026-07-17\n\n- 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.\n\n### 1.3.0 — 2026-07-16\n\n- 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.\n- `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).\n- Job accruals now include a `tax_code` reference. Like other tax fields, it is optional for non-tax / US-based accounts.\n- 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.\n- `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.\n\n### 1.2.0 — 2026-07-08\n\n- `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}`.\n- `POST /events` error responses now use `lower_snake` error codes (`validation_error`, `invalid_json`, `unauthorized`), matching the partner-hosted endpoints.\n\n### 1.1.0 — 2026-07-07\n\n- `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.\n- Zero-tax lines must state `tax: 0.0` explicitly rather than omitting the field or sending `null`.\n"
  },
  "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.\n\nYour system calls this endpoint to notify BravoTran that an entity has been created or updated.\nBravoTran will then fetch the entity details from your API.\n\nBravoTran validates `entity_type`, `external_id`, and `updated_at`. Additional JSON fields are rejected.\n",
        "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.\n\n**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.\n\nResponse items contain only `external_id`. To fetch full org details, call `GET /orgs/{external_id}` for each entry.\n",
        "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.**\n\nIf multiple source tables are combined into the orgs entity, prefix the `external_id` with the source type to maintain uniqueness (e.g., `vendor-123`).\n",
        "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.\n\n**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.\n\nA 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.\n",
        "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.**\n\n> **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.\n\nJobs contain nested accruals, container numbers, waybills, and additional references.\nWhen a job is updated (including changes to accruals, child shipments added to a consol, etc.),\nthe `updated_at` timestamp must reflect the most recent change.\n",
        "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.\n\n**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.\n\nResponse items contain only `external_id`. To fetch full invoice details, call `GET /payables-invoices/{external_id}` for each entry.\n",
        "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.**\n\nBravoTran calls this endpoint when an invoice has been approved and is ready for cost posting.\nYour system must create or record the invoice accordingly, then respond with the `external_id`\nyour system assigned to the created invoice and its `status` (`draft` or `posted`). Do not derive\nthe identifier from the request — BravoTran does not send one.\n\n> **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.\n",
        "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:\n- `draft` — created but not yet posted to the ledger\n- `posted` — posted to the ledger\n"
                    }
                  }
                },
                "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`\nalready exists for the creditor in your system.\n",
            "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.**\n\n> **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.\n\nPayables invoices contain nested charge lines, each referencing a job and optionally an accrual.\n",
        "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.\n\n**Optional.** Provided as an alternative to flat file delivery of staff data.\nJobs and accruals reference staff (operators) by `external_id`.\n",
        "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.\n\n**Optional.** Provided as an alternative to flat file delivery of charge code data.\nAccruals and charge lines reference charge codes by `external_id`.\n",
        "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.\n\n**Optional.** Provided as an alternative to flat file delivery of tax code data.\nCharge codes, accruals, and charge lines reference tax codes by `external_id`.\n",
        "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`).\nDate-only values are not accepted. Optional.\n",
        "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`).\nDate-only values are not accepted. Optional, but if provided, `updated_after` is required.\n",
        "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,\nwhen `updated_before` is provided without `updated_after`, or when a date-only value\nis supplied where a UTC datetime is required.\n",
        "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\nafter the invoice date that payment is due. For `day_n_month_after_invoice_date`,\nthe day of the month (in the month after the invoice date) that payment is due.\nNot used by the other rules.\n"
          },
          "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- `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)\n- `day_n_month_after_invoice_date` — payment is due on day `days_to_pay` of the month after the invoice date\n- `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)\n- `always_ignore` — payments for this creditor are always marked ignored and never scheduled\n"
          },
          "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:\n- `freight` — freight / carriage charges\n- `origin` — origin-side charges\n- `destination` — destination-side charges\n- `other` — anything else\n"
          },
          "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:\n- `mwb` — Master Bill of Lading (ocean)\n- `hwb` — House Bill of Lading (ocean)\n- `mawb` — Master Air Waybill (air)\n- `hawb` — House Air Waybill (air)\n\nOcean 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.\n"
          },
          "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.\nMust update whenever any nested data changes (accrual added/edited, shipment linked, etc.).\n"
          },
          "job_number": {
            "type": "string",
            "description": "Human-friendly identifier for the job."
          },
          "job_type": {
            "type": "string",
            "enum": [
              "consol",
              "shipment"
            ],
            "description": "Type of job:\n- `consol` — Consolidation of child jobs (master)\n- `shipment` — Stand-alone job or child of a consol (house)\n"
          },
          "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:\n- `draft` — entered but not yet posted to the ledger\n- `posted` — posted to the ledger\n"
          },
          "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}`.\n\nNull 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.\n"
          },
          "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."
    }
  ]
}