# Submitting Outbound EDI

When sending EDI to a trading partner, your system submits JSON data to this endpoint. Tediware converts it to EDI using the partner's mapping and implementation, then delivers the EDI document to the partner's connection. Processing is asynchronous: the API returns immediately with a trace identifier and control numbers. When delivery completes, your outbound webhook is called; if an error occurs, your error webhook is called instead. See the Overview page for the full outbound data flow.

## Prerequisites

Four things must be in place on the partner before a submission can succeed. Each has its own error if it is missing, so you can work through them in order.

1. **An outbound transaction setting** for the code you are sending. This is what declares "this partner receives 856s from us". Add it on the partner's Transaction Sets card.
2. **A mapping, or an implementation on its own,** on that setting. The setting by itself only declares intent. The implementation is the partner's EDI guide, and a mapping converts your JSON shape into it. With a mapping, `contents` is your own shape, the mapping's source. With an implementation alone, `contents` must match the implementation's JSON schema directly, which is workable when this is the only partner you will ever send the document to and becomes per-partner payload code at the second. See [Mappings](/resources/docs/mappings) for the trade-off. Until one or the other is attached, the setting is not ready and no EDI can be produced.
3. **An outbound flow set to Active.** The transaction setting says what to send; the flow says how to build and deliver it. The flow is a separate step, done in the app and not over the API: on the partner's page, click **Build Outbound Flow** on the Flows card. A new outbound flow starts active. One that is pending does not serve the endpoint; activate it from the flow's page with **Edit**. A partner whose `flows` reads `[]` on `GET /platform/partners/:key` has not had this step done yet, and a recreated partner needs it again.
4. **Envelopes assigned to the partner**, internal and external. These supply the ISA sender and receiver identifiers.

After changing any of this, rebuild the flow so the change takes effect. The endpoint runs the flow as it was last built, so a configuration change that has not been rebuilt is invisible to it.

None of this is checked when you create the partner, and a missing piece is not an error until you send. That is deliberate: a partner is usually set up over several sittings. It does mean the first submission for a new partner is where you find out what is left to do.

## Endpoint

```
POST /platform/partners/:key/ts/:code
```

Needs a standard API key. A read-only key is refused with `403`.

- `:key`: the partner's unique key (e.g., `ACME`). Visible on the partner detail page and set when the partner is created. Case-insensitive: `acme` and `ACME` resolve to the same partner.
- `:code`: the X12 transaction set code (e.g., `810`, `850`, `856`). Must match an outbound transaction setting configured on the partner.

## Request Body

The request body is JSON with the following fields:

- `contents` (required): the transaction data to convert. When the transaction setting has a mapping, this is the mapping's source shape: the same JSON for this transaction set no matter which partner key is in the URL. When the setting has an implementation and no mapping, this must match the implementation's JSON schema. See [Which shape to send](#which-shape-to-send).
- `filename` (optional): the name of the file the partner receives. It is used as given, so name it as the EDI file it will be, for example `INV-104305.edi`; a name like `INV-104305.json` reaches the partner under that name too. Must contain only letters, numbers, underscores, periods, and dashes, with a maximum length of 128 characters. If omitted, a random `.edi` filename is generated.
- `overrides` (optional): envelope identifiers to use in place of the ones from the partner's envelope configuration, for this submission only:
  - `interchangeSenderId`: the ISA sender ID
  - `interchangeSenderQualifier`: the ISA sender qualifier
  - `interchangeReceiverId`: the ISA receiver ID
  - `interchangeReceiverQualifier`: the ISA receiver qualifier
  - `usageIndicator`: ISA15, `P` for production or `T` for test. Any other value returns `400 invalid_parameter`

  Overrides apply only to the request they are sent with, and nothing is saved to the partner. The partner's 997 for a document sent to a different receiver ID comes back from that ID, and Tediware recognizes it as the reply to your send, so it does not warn. Anything else the partner sends from that ID arrives with a `sender_mismatch` warning (see [EDI transactions](/resources/api-docs/edi-transactions)), and on a VAN Routing connection nothing from that ID routes, 997s included. If the partner also sends you documents from that identifier, or the connection uses VAN Routing, set the identifier up as its own partner and send those sets through it; see [Partners with Multiple ISA Identifiers](/resources/docs/partners#partners-with-multiple-isa-identifiers). A sender ID override has the same effect on VAN Routing, because the partner's replies are addressed to the overridden ID. Tediware checks only the form of overrides (an ID must be present and a qualifier must be two characters, else `400 invalid_envelope`), not whether they match the partner's configuration. Keys other than these are ignored, and the configured value is used in their place.

  Without a `usageIndicator` override, ISA15 comes from the transaction setting's usage indicator (`usageIndicator` on the partner's `transactionSets`), which defaults to `P`. Use the override to send a single test document for a set that is live, or the reverse; change the setting when the whole set moves between testing and production. Test documents are billed like production ones.

## Which shape to send

`GET /platform/partners/:key` lists the partner's `transactionSets`. Find the entry with your code and `direction` `outbound`:

- **`mapping` is set.** Send your own shape, the mapping's source. `GET /platform/mappings/:id` embeds the source, and its `sample` is an example.
- **`mapping` is `null`.** Send the partner's document directly. It must validate against `GET /platform/implementations/:id/schema`, using the `implementation.id` from the same entry.

In the second case, a few parts of the schema are filled or converted by Tediware, so they read differently from the X12 guide:

- **ST02** is filled in by Tediware with the control number it assigns, before validation, so you can leave it out. The downloaded schema still marks it required, so if you validate on your side first, put in any value that fits, such as `"0001"`. ST01 is the transaction set code, for example `"810"`.
- **Numbers are real values.** An element with implied decimals (X12 type N2, for example) takes the decimal amount in the JSON: send `175.00`, and Tediware writes `17500`. A partner note such as "2 decimals are implied" describes the EDI element, not the JSON value. Each numeric element's `description` in the schema says what to send.
- **Round before sending.** A value with more decimal places than the element allows is rejected: `19.999` on an N2 element fails. The one exception is floating-point noise within 0.0001 of a valid value, such as `19.98999`, which is accepted and rounded (written `1999`). Round to the element's precision, 2 places for N2.

To check a document against the schema before sending, set `multipleOfPrecision: 2` if you use ajv. That is what Tediware's own validator does. A stock validator rejects valid values such as `19.99` and `0.07` against `multipleOf: 0.01`, because of floating-point division.

## Success Response

On success, the API returns `200` with the control numbers assigned to the transmission and a trace identifier:

```json
{
  "message": "Processing queued",
  "interchangeControlNumber": 1042,
  "groupControlNumber": 1042,
  "traceGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "ediTransactionId": "4c2021f8-8310-4628-80f9-b578d4d68e11"
}
```

- `interchangeControlNumber`: the ISA control number assigned to this interchange.
- `groupControlNumber`: the GS control number assigned to this functional group. This will also be the transaction set (TS) control number.
- `traceGuid`: a unique identifier that links all processing results for this submission. Use it to query results or look up the trace in the application UI.
- `ediTransactionId`: the EDI transaction this submission created. Tediware records it before answering, so you can fetch it with [`GET /platform/edi_transactions/:id`](/resources/api-docs/edi-transactions) without searching by trace first.

**Queued means queued.** Translation, validation and delivery all run after this response. A submission that will fail still answers `200` here and fails on the trace seconds later, so the response is a receipt, not a verdict. Poll [`GET /platform/traces/:traceGuid`](/resources/api-docs/traces) until `processing` is `false` when you need the outcome in line, or read it from your outbound and error webhooks or the feed when you do not.

Implementation validation is one of those deferred steps, by design, and there is no pre-send validation call. When it fails, the transaction's `status` becomes `error` and the failing result's `errors` array lists one violation per entry, for example `/heading/f_o_b_related_instructions_FOB must have required property 'shipment_method_of_payment_01'`. The paths are JSON pointers into the implementation-shaped document, which with a mapping is the mapping's output rather than the `contents` you sent, so the fix is in the mapping or in the source value it reads. A missing top-level property has no path prefix.

`contents` has to be a JSON object. A string, an array or `null` returns `400 invalid_parameter`.

## Example Request

```bash
curl -X POST "https://tediware.com/platform/partners/ACME/ts/810" \
  -H "Authorization: Key your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "contents": {
      "invoiceNumber": "INV-2026-001",
      "invoiceDate": "2026-03-30",
      "totalAmount": 1500.00,
      "lineItems": [
        {
          "quantity": 10,
          "unitPrice": 150.00,
          "description": "Widget A"
        }
      ]
    },
    "filename": "invoice-001.edi"
  }'
```

The `contents` here is a mapping source: field names the sender chose, with the partner's 810 mapping responsible for turning them into the segments the partner wants. The same request body goes to `/partners/OTHERPARTNER/ts/810` unchanged, and that partner's mapping produces its EDI. The alternative, sending the implementation's own JSON (a `heading` object holding a `beginning_segment_for_invoice_BIG` and so on), is what a mapping produces for you.

## Error Responses

```
| Status | Code                | reason                    | Cause                                                              |
|--------|---------------------|---------------------------|--------------------------------------------------------------------|
| 400    | missing_parameter   |                           | The contents field is missing or null                               |
| 400    | invalid_filename    |                           | The filename contains invalid characters or exceeds 128 characters  |
| 400    | invalid_envelope    |                           | The envelope configuration is incomplete or invalid                 |
| 401    | unauthorized        |                           | The API key is missing or invalid                                   |
| 403    | forbidden           | read_only_key             | The key is a read-only key                                          |
| 404    | not_found           | partner                   | The partner key does not match any partner in your organization     |
| 404    | not_found           | transaction_set           | The transaction set code is not configured for outbound on this partner |
| 422    | configuration_error | transaction_set_not_ready | The setting exists but has no implementation or mapping             |
| 422    | configuration_error | flow_inactive             | The partner has no outbound flow, or its outbound flow is pending. Built and activated in the app |
| 422    | configuration_error | missing_endpoint_node     | The flow has no partner endpoint node                               |
| 422    | configuration_error | ambiguous_endpoint_node   | The flow has more than one matching node                            |
| 422    | configuration_error | missing_external_envelope | The partner has no external envelope assigned                       |
| 422    | configuration_error | missing_internal_envelope | The partner has no internal envelope assigned                       |
```

Every error answers as `{"error": {"message", "code", "reason"?}}`; the full scheme is on the [Errors](/resources/api-docs/errors) page. `code` names the class of failure and `reason` names the specific condition, so you can branch on either. The code tells you whether to retry: **400 and 404 will never succeed on an unchanged resend**, because the payload or an identifier is wrong. **A 422 `configuration_error` will succeed** once someone completes the prerequisite it names, with no change to your request. Branch on the code rather than the status, since other endpoints return `422` under codes that no amount of retrying will clear. The `invalid_envelope` 400 is the one judgment call in the table: it can be caused by the `overrides` you sent, so it is treated as a problem with the request.

## Resending a Document You Already Sent

This endpoint always builds and sends a new document with new control numbers. To deliver a document you already sent a second time, byte for byte and under its original control numbers, use `POST /platform/edi_transactions/:id/resend` instead. See [EDI Transactions](/resources/api-docs/edi-transactions) for when a resend is possible and what it does to the transaction record.
