# Push bundles with the Ingest API

URL: /collect/sources/ingest-api

Let test rigs and CI pipelines push signed bundles to Foxborne over HTTPS, with a service token scoped to ingest. The files in each bundle are hashed on arrival and matched to runs like files from any other source.



Test rigs and pipelines produce logs on their own schedule. The **Ingest API** source lets them push those files to Foxborne as signed bundles, so nobody has to import them by hand. In the example dataset, a bench rig pushes its perception soak tests this way.

## Before you start [#before-you-start]

* You need the **Admin** role.
* The **HTTPS ingest API** reads **Listening** on the **Ingest** tab of **Integrations**.
* The sender reaches your deployment over HTTPS on port 443.
* The sender holds a device certificate for mutual TLS. Each push authenticates with that certificate and a short-lived bearer token.
* The sender can write a SHA-256 manifest of the files it sends.

## Add the source [#add-the-source]

<Steps>
  <Step title="Choose Ingest API">
    In **Sources**, select **Add source**. Under **Automation**, pick **Ingest API**: &#x2A;*Your test rigs and pipelines push signed bundles with a scoped service token.** Select **Continue**.
  </Step>

  <Step title="Describe the token">
    | Field          | Example in the console                     | What to enter                                                                   |
    | -------------- | ------------------------------------------ | ------------------------------------------------------------------------------- |
    | **Name**       | For example: Fleet log archive, west range | A name for the sender, such as **Bench rig uploads**.                           |
    | **Token name** | test-rig-ci                                | A name for the sender's service token.                                          |
    | **Scope**      | Ingest only                                | What the token may do. **Ingest only** allows pushing bundles and nothing else. |
    | **Expires**    | 31 Dec 2026                                | The date the token expires.                                                     |
  </Step>

  <Step title="Test and add">
    Select **Test connection**, wait for the fourth check, then select **Add source**. The toast names the source and says &#x2A;*The first sync is running.**
  </Step>
</Steps>

## The endpoint [#the-endpoint]

The source uses the **HTTPS ingest API** endpoint, which the **Ingest** tab of **Integrations** lists at `/app/integrations/ingest`. The **Sources** page points there too: "Endpoints that accept pushed evidence, such as the HTTPS ingest API, are listed under Integrations, Ingest."

| Row in the drawer | Value in the example dataset                            |
| ----------------- | ------------------------------------------------------- |
| **State**         | **Listening**                                           |
| **Listens on**    | HTTPS 443, `/api/fx/v1/ingest`                          |
| **Used by**       | Bench rig uploads                                       |
| **Protocol**      | HTTPS POST, chunked and resumable, Content-Digest       |
| **Auth**          | mTLS device certificates plus short-lived bearer tokens |
| **Air-gapped**    | Yes                                                     |

## What a push carries [#what-a-push-carries]

Senders push to `POST /api/fx/v1/ingest`. Nothing is fetched on a schedule: the source's **Schedule** reads **Push**, and bundles arrive when the sender sends them.

| Part           | What the API expects                                                                                                             |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Endpoint       | `POST /api/fx/v1/ingest`, over HTTPS                                                                                             |
| Body           | A signed multipart bundle                                                                                                        |
| Transfer       | Chunked and resumable, so an interrupted upload picks up where it stopped                                                        |
| Integrity      | A `Content-Digest` header, and the sender's SHA-256 manifest of the files                                                        |
| Authentication | Mutual TLS with the sender's device certificate, plus a short-lived bearer token that can be bound to the certificate (RFC 8705) |
| Token scope    | Ingest only                                                                                                                      |

`Content-Digest` is the HTTP digest field defined in RFC 9530, and each upload carries it as `sha-256`. Foxborne also hashes each file with SHA-256 on arrival and checks it against the sender's manifest before it is parsed.

The IETF "Resumable Uploads for HTTP" specification is still an Internet-Draft, not an RFC. Foxborne therefore "keeps chunk-and-resume behind its own stable API for multi-GB bags."

A sender's "bundle complete" call is one of the triggers for automation A1. A1 closes the sortie out once every source on the test card has arrived, or after 30 minutes with nothing new. See [Automations](/integrate/automations).

## Check the result [#check-the-result]

The example dataset's **Bench rig uploads** source shows what a working push source looks like in its drawer.

| Row            | Value                              |
| -------------- | ---------------------------------- |
| **Location**   | `POST /api/fx/v1/ingest`           |
| **Access**     | Service token, ingest scope only   |
| **Credential** | bench-rig-ci (expires 31 Dec 2026) |
| **Matches**    | Signed multipart bundles           |
| **Schedule**   | Push                               |
| **Files read** | 77, 2.3 GB                         |

Runs built from pushed bundles name the source on the run page. Run R-0908, a perception soak on the bench, came in through **Bench rig uploads** with a companion journal and process samples. The audit log records the new source as **Added source**, with a detail such as **Ingest API, read-only test passed**.

## Troubleshoot [#troubleshoot]

* **Status shows Needs attention or Error.** Open the source's drawer and read the note at the top.
* **Senders cannot connect.** Check that the **HTTPS ingest API** reads **Listening** on the **Ingest** tab, and that the sender presents a device certificate your deployment trusts.
* **Files land on the wrong vehicle.** Check the MAVLink system ID in each flight log the bundle carries.

## Beyond ingest [#beyond-ingest]

The Ingest API accepts bundles and nothing else. <Availability status="planned" /> A general Foxborne API is planned.

## Next [#next]

* [Connect a storage source](/collect/sources) covers every source type, the drawer and its actions.
* [Stream logs to Foxborne](/collect/streaming) lists the streaming paths for companion and ground logs.
* [Supported file formats](/collect/formats) lists the formats a bundle can carry.
