# Inbound EDI

Inbound EDI is EDI that a trading partner sends to your organization. Tediware receives it, translates each transaction set to JSON, and notifies your system via a webhook. In most integrations, your system does nothing until the webhook fires. Tediware handles receiving and processing the EDI automatically.

## How Inbound EDI Arrives

There are three ways EDI can arrive in Tediware:

**SFTP Polling**: Tediware connects to the partner's SFTP server on a configurable schedule and picks up new files. This is the most common method for batch EDI exchanges. Once configured, it runs automatically with no action required from your system.

**AS2**: The partner sends EDI directly to Tediware's AS2 endpoint. Files are processed as they arrive. Like SFTP, this is fully automatic once the connection is configured.

**API Endpoint**: Your system receives EDI through its own channels (e.g., a vendor portal, email attachment, or another protocol) and pushes it to Tediware via the Platform API. This is an alternative for cases where your system is the one receiving the EDI, rather than Tediware. See the endpoint documentation below.

Regardless of how EDI arrives, the processing pipeline is the same. Tediware parses the interchange, splits it into individual transaction sets, translates each one to JSON, and fires your inbound webhook.

## Retrieving Translated Data

When Tediware finishes processing inbound EDI, it calls your inbound webhook with a lightweight notification. The webhook payload contains only identifiers, not the translated document content. Your system then pulls the data through the API:

1. Your inbound webhook receives a POST with a `resultId` and `traceGuid`
2. Your system calls `GET /platform/results/:id` using the `resultId`
3. The result's `artifacts` array contains references to the processed files. The artifact with `"usage": "output"` is the delivered JSON document.
4. Your system calls `GET /platform/artifacts/:id` to download the JSON content
5. Your system processes the JSON: creating orders, updating records, or routing to internal systems

Your webhook endpoint should return a `200` response immediately and handle the data retrieval in a background job. Tediware expects a response within 10 seconds. See the Webhooks page for payload format, signature verification, and retry behavior.

### Test documents

A partner's test interchange (ISA15 `T`) goes through the same flow and to the same webhook as production traffic. The result's `detail.usageIndicator` is `"T"` for a test document and `"P"` for a production one, so check it before creating orders or updating records. The inbound transaction setting's usage indicator says which one you expect from the partner for that set; a document carrying the other value is processed with a `usage_indicator_mismatch` warning.

### Example: Translated 204 (Motor Carrier Load Tender)

Below is an example of the JSON you receive when downloading the output artifact for a 204 transaction set. The document is split into `heading`, `detail`, and `summary` sections. Field names are descriptive: each includes the segment or element name followed by its X12 position code. For the full rules behind this shape, including coded and numeric values, loops, and composite elements, see the [JSON Format reference](/resources/docs/json-format).

```json
{
  "heading": {
    "transaction_set_header_ST": {
      "transaction_set_identifier_code_01": "motor_carrier_load_tender_204",
      "transaction_set_control_number_02": "100001"
    },
    "beginning_segment_for_shipment_information_transaction_B2": {
      "standard_carrier_alpha_code_02": "ABCD",
      "shipment_identification_number_04": "50001234",
      "shipment_method_of_payment_06": "collect_CC"
    },
    "set_purpose_B2A": {
      "transaction_set_purpose_code_01": "original_00",
      "application_type_02": "load_tender_truckload_tl_carrier_only_LT"
    },
    "date_time_G62": {
      "date_qualifier_01": "must_respond_by_64",
      "date_02": "2026-04-13",
      "time_qualifier_03": "must_respond_by_1",
      "time_04": "15:55:0",
      "time_code_05": "central_time_CT"
    },
    "business_instructions_and_reference_number_L11": [
      {
        "reference_identification_01": "0",
        "reference_identification_qualifier_02": "region_ACB"
      }
    ],
    "note_special_instruction_NTE": [
      {
        "description_02": "No Comments"
      }
    ],
    "party_identification_N1_loop": [
      {
        "party_identification_N1": {
          "entity_identifier_code_01": "ordered_by_OB",
          "name_02": "Acme Retail Inc",
          "identification_code_qualifier_03": "global_location_number_gln_UL",
          "identification_code_04": "0012345000008"
        }
      }
    ],
    "equipment_details_N7_loop": [
      {
        "equipment_details_N7": {
          "equipment_initial_01": "AR",
          "equipment_number_02": "0",
          "equipment_description_code_11": "trailer_dry_freight_TF",
          "equipment_length_15": 5300
        }
      }
    ]
  },
  "detail": {
    "stop_off_details_S5_loop": [
      {
        "stop_off_details_S5": {
          "stop_sequence_number_01": 1,
          "stop_reason_code_02": "complete_CL",
          "number_of_units_shipped_05": 0,
          "unit_or_basis_for_measurement_code_06": "miles_DH"
        },
        "business_instructions_and_reference_number_L11": [
          {
            "reference_identification_01": "90001234",
            "reference_identification_qualifier_02": "pickup_reference_number_P8"
          }
        ],
        "date_time_G62": [
          {
            "date_qualifier_01": "requested_ship_date_pickup_date_10",
            "date_02": "20260416"
          }
        ],
        "bill_of_lading_handling_requirements_AT5_loop": [
          {
            "bill_of_lading_handling_requirements_AT5": {
              "special_services_code_02": "mutually_defined_ZZ"
            }
          }
        ],
        "party_identification_N1_loop": [
          {
            "party_identification_N1": {
              "entity_identifier_code_01": "ship_from_SF",
              "name_02": "NORTHERN SALT DISTRIBUTION",
              "identification_code_qualifier_03": "code_assigned_by_the_organization_originating_the_transaction_set_93",
              "identification_code_04": "90001234"
            },
            "geographic_location_N4": {
              "city_name_01": "AKRON",
              "state_or_province_code_02": "OH",
              "postal_code_03": "44314"
            },
            "party_location_N3": [
              {
                "address_information_01": "100 INDUSTRIAL PARKWAY"
              }
            ],
            "contact_G61": [
              {
                "contact_function_code_01": "information_contact_IC",
                "name_02": "JANE SMITH",
                "communication_number_qualifier_03": "telephone_TE",
                "communication_number_04": "3305550100"
              },
              {
                "contact_function_code_01": "information_contact_IC",
                "name_02": "JANE SMITH",
                "communication_number_qualifier_03": "electronic_mail_EM",
                "communication_number_04": "shipping@example.com"
              }
            ]
          }
        ],
        "order_information_detail_OID_loop": [
          {
            "order_information_detail_OID": {
              "reference_identification_01": "20",
              "purchase_order_number_02": "5000100200",
              "reference_identification_03": "16",
              "packaging_form_code_04": "case_CAS",
              "quantity_05": 16,
              "weight_unit_code_06": "pounds_L",
              "weight_07": 41360,
              "volume_unit_qualifier_08": "cubic_feet_E",
              "volume_09": 729
            },
            "date_time_G62": [
              {
                "date_qualifier_01": "cancel_after_this_date_01",
                "date_02": "20260418"
              }
            ]
          }
        ]
      },
      {
        "stop_off_details_S5": {
          "stop_sequence_number_01": 2,
          "stop_reason_code_02": "complete_unload_CU",
          "number_of_units_shipped_05": 428,
          "unit_or_basis_for_measurement_code_06": "miles_DH"
        },
        "shipment_weight_packaging_and_quantity_data_AT8": {
          "weight_qualifier_01": "gross_weight_G",
          "weight_unit_code_02": "pounds_L",
          "weight_03": 41360,
          "lading_quantity_04": 16,
          "volume_unit_qualifier_06": "cubic_feet_E",
          "volume_07": 729
        },
        "business_instructions_and_reference_number_L11": [
          {
            "reference_identification_01": "60007890",
            "reference_identification_qualifier_02": "appointment_number_AO"
          }
        ],
        "date_time_G62": [
          {
            "date_qualifier_01": "delivery_appointment_scheduled_date_78",
            "date_02": "20260417",
            "time_qualifier_03": "delivery_appointment_scheduled_time_3",
            "time_04": "1931",
            "time_code_05": "eastern_time_ET"
          }
        ],
        "party_identification_N1_loop": [
          {
            "party_identification_N1": {
              "entity_identifier_code_01": "ship_to_ST",
              "name_02": "DC 7034",
              "identification_code_qualifier_03": "global_location_number_gln_UL",
              "identification_code_04": "0012345045535"
            },
            "geographic_location_N4": {
              "city_name_01": "SMYRNA",
              "state_or_province_code_02": "DE",
              "postal_code_03": "19977"
            },
            "party_location_N3": [
              {
                "address_information_01": "500 WAREHOUSE DRIVE"
              }
            ]
          }
        ],
        "order_information_detail_OID_loop": [
          {
            "order_information_detail_OID": {
              "reference_identification_01": "20",
              "purchase_order_number_02": "5000100200",
              "reference_identification_03": "16",
              "packaging_form_code_04": "case_CAS",
              "quantity_05": 16,
              "weight_unit_code_06": "pounds_L",
              "weight_07": 41360,
              "volume_unit_qualifier_08": "cubic_feet_E",
              "volume_09": 729
            },
            "date_time_G62": [
              {
                "date_qualifier_01": "cancel_after_this_date_01",
                "date_02": "20260418"
              }
            ]
          }
        ]
      }
    ]
  },
  "summary": {
    "total_weight_and_charges_L3": {
      "weight_01": 41360,
      "weight_qualifier_02": "gross_weight_G",
      "freight_rate_03": 1232.64,
      "rate_value_qualifier_04": "per_mile_PM",
      "amount_charged_05": 1232.64,
      "volume_09": 729,
      "volume_unit_qualifier_10": "cubic_feet_E",
      "lading_quantity_11": 16
    },
    "transaction_set_trailer_SE": {
      "number_of_included_segments_01": 30,
      "transaction_set_control_number_02": "100001"
    }
  }
}
```

This is a two-stop shipment: stop 1 is a pickup from a shipper in Akron, OH, and stop 2 is a delivery to a distribution center in Smyrna, DE. The `heading` contains the tender metadata and ordering party. Each entry in the `stop_off_details_S5_loop` describes a stop with its address, contacts, dates, and order details. The `summary` contains total weight and charges.

The exact fields present depend on your partner's implementation guide. Different partners may include additional segments or omit optional ones. Tediware translates whatever is in the EDI, so the JSON output always reflects the actual content of the interchange.

## Inbound Mappings

Because the translation reflects whatever the partner sent, the same document type can arrive shaped differently from different partners. If your organization has assigned an inbound mapping to a partner's transaction setting, Tediware transforms the translation into your organization's canonical shape before delivery, so the output artifact carries the same structure for every mapped partner.

The canonical shape is a canonical X12 implementation your organization owns: one implementation of that transaction set, chosen as the target every mapped partner is normalized into. It is still X12-shaped JSON, with the same segment and element names and the same bare X12 codes (`"00"`, `"SA"`) as an unmapped translation. A mapping removes the variation between partners; the X12 vocabulary stays. Discover the shape the same way as any other, by downloading the output artifact of a processed document.

That leaves the last step to you. Whether or not a mapping is assigned, what arrives is an X12 document expressed as JSON, and turning it into your own domain objects (an order, a shipment, an invoice) is your integration's job. The value of a mapping is that you write that translation once per implementation rather than once per partner.

Two things change for a consumer when a mapping is assigned:

- The output artifact is the mapped document. The untransformed translation is still available on the same result as a JSON artifact with `"usage": "translation"`; the `"input"` artifact remains the partner's EDI.
- If the mapping fails on a document, delivery still happens, and the result's `detail` carries `mappingFailed: true` and a `mappingError` describing the failure. Check the flag before parsing, and route flagged deliveries to an exception path rather than parsing them as the canonical shape. The mapping can fail two ways, distinguished by `mappingError.kind`: `"expression"` means the transformation itself failed to run, and the output is the untransformed translation; `"validation"` means the transformation ran but produced output that does not match the canonical shape, and the output is that mapped document, with `mappingError.errors` listing what failed validation. Either way, `mappingFailed` is the one flag to check: absent means the output is the validated canonical shape, present means it is not guaranteed to be.
- A failed mapping raises a warning. The transaction's `status` and the feed entry's `status` still read as a success and the webhook still fires, because the document was delivered. The mapping step's result carries `detail.warnings` with a `mapping_failed` entry, the transaction's `warningCount` counts it, and `GET /platform/edi_transactions?warnings=true` lists the documents that have one. A consumer that reads transactions rather than results can watch that count instead of inspecting each result's detail.

Without a mapping, none of this applies: the output artifact is always the direct translation shown above.

## Submitting EDI via the API

This endpoint runs a partner's inbound flow on a document you send over HTTP, without a connection in the middle. It exists for testing: a test suite can submit a known 850 and assert on the webhook delivery, and you can check a new inbound mapping against a partner's sample file before the partner sends real traffic.

The document is queued into the partner's active inbound flow exactly as an SFTP or AS2 arrival would be, so translation, any inbound mapping, the notification, and the automatic 997 all behave the same way. That includes the transaction record: a submission shows up in the transaction list like any other inbound document, so keep test submissions on a test partner or a sandbox partner.

In production, your partner's documents arrive through the partner's connection. You do not need this endpoint for that.

### Endpoint

```
POST /platform/partners/:key/edi
```

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

- `:key`: the partner's unique key (e.g., `ACME`). Case-insensitive: `acme` and `ACME` resolve to the same partner.

### Request Body

The request body is JSON with the following fields:

- `contents` (required): the raw EDI string to process. This should be a complete EDI interchange starting with an ISA segment.
- `filename` (optional): a label for this submission. Must contain only letters, numbers, underscores, periods, and dashes, with a maximum length of 128 characters. If omitted, a random filename is generated.

Tediware checks the document before queueing anything, so a document that is not X12 comes back on this request instead of failing later on a trace. Contents that are not a string return `400 invalid_parameter`. Two structural checks return `422 invalid_edi` with the reason in the message: the ISA header has to be readable, and the trailers have to be there with plausible counts (`SE` closing its transaction set, `GE` its group, `IEA` last). The trailer check is what catches a truncated file, which used to pass the header check, pass every step in the flow, and fail quietly in a mapping.

Both checks are structural and use no reference data, so a document that passes them can still fail the full parse downstream, which surfaces on the trace rather than here.

The interchange is limited to 1 MB. Anything larger returns `413 content_too_large` without being queued.

### Success Response

On success, the API returns `200` with a trace identifier:

```json
{
  "message": "Processing queued",
  "traceGuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}
```

- `traceGuid`: a unique identifier that links all processing results for this submission. Use it to query results, or follow it with [`GET /platform/traces/:traceGuid`](/resources/api-docs/traces), which carries a `processing` flag so a poll has a defined end.

A duplicate is processed, not refused. This endpoint exists partly to replay documents, so a repeat runs the flow again and issues its own 997. When the interchange control number and sender match an inbound document already received, the transaction the replay creates names the earlier one in `duplicateOf` (see [EDI transactions](/resources/api-docs/edi-transactions)). Processing runs after this request returns, so read it from the transaction, not the receipt.

The bytes you submit are stored as the `input` artifact on the entry step's result, exactly as an SFTP arrival's are, so you can download and re-inspect what was actually sent.

### Example Request

```bash
curl -X POST "https://tediware.com/platform/partners/ACME/edi" \
  -H "Authorization: Key your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "contents": "ISA*00*          *00*          *ZZ*SENDER         *ZZ*RECEIVER       *260330*1200*U*00401*000001042*0*P*>~GS*IN*SENDER*RECEIVER*20260330*1200*1043*X*004010~ST*810*0001~BIG*20260330*INV-001~SE*3*0001~GE*1*1043~IEA*1*000001042~"
  }'
```

To submit EDI from a file:

```bash
EDI_CONTENTS=$(cat my-document.edi)
curl -X POST "https://tediware.com/platform/partners/ACME/edi" \
  -H "Authorization: Key your-api-key-here" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg contents "$EDI_CONTENTS" '{"contents": $contents}')"
```

### Error Responses

```
| Status | Code                | reason                  | Cause                                                              |
|--------|---------------------|-------------------------|--------------------------------------------------------------------|
| 400    | missing_parameter   |                         | The contents field is missing or null                               |
| 400    | invalid_parameter   |                         | The contents field is not a string                                  |
| 400    | invalid_filename    |                         | The filename contains invalid characters or exceeds 128 characters  |
| 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     |
| 413    | content_too_large   |                         | The interchange is larger than the 1 MB processing limit            |
| 422    | invalid_edi         |                         | The contents do not begin with a readable ISA header, or the trailers are missing or implausible |
| 422    | configuration_error | flow_inactive           | The partner has no inbound flow, or its inbound flow is pending. Built and activated in the app |
| 422    | configuration_error | missing_endpoint_node   | The flow has no EDI endpoint node                                   |
| 422    | configuration_error | ambiguous_endpoint_node | The flow has more than one matching node                            |
```

As on the outbound endpoint, 400, 404, and 422 `invalid_edi` will never succeed on an unchanged resend, while a 422 `configuration_error` will once the flow is set up. The flow is built in the app, from the partner's page (**Build Inbound Flow** on the Flows card), and a new inbound flow starts pending, so activating it from the flow's page is a second step; the `flow_inactive` message says which of the two is missing. See [Errors](/resources/api-docs/errors) for the full scheme.
