> ## Documentation Index
> Fetch the complete documentation index at: https://docs.withsplendor.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect a source

> Push data to Splendor over HTTP — including from a webhook — or let Splendor read from your own S3 bucket.

A **source** is where a dataset's data comes from. There are two supported ways to get data into a source:

* **Hosted ingest** — you push data to Splendor over HTTP (files or structured records), into storage Splendor manages. This is the simplest path and the one a **webhook** uses.
* **BYOC (bring your own cloud)** — your data stays in your own S3 bucket and Splendor reads it via a cross-account role, auto-ingesting new objects as they land.

This guide covers both, with a webhook as the motivating example.

## Hosted ingest over HTTP

Create a source, mint an ingest token, then push data to it. Creating sources and tokens requires an **admin** role; pushing data uses the **ingest token**.

<Steps>
  <Step title="Create a hosted source">
    A hosted source declares the dataset your data feeds. Splendor detects each file's type automatically, so one source can hold records and images together (see [Sources & images](/concepts/sources-and-assets)).

    ```bash theme={null}
    curl https://api.withsplendor.com/v1/admin/hosted-sources \
      -H "Authorization: Bearer $SPLENDOR_TOKEN" \
      -H "X-Splendor-Tenant-Id: $SPLENDOR_TENANT_ID" \
      -H "Content-Type: application/json" \
      -d '{
        "source_key": "github-events",
        "name": "GitHub events",
        "dataset_id": "github",
        "source_type": "hosted"
      }'
    ```

    The response includes the numeric `source_id` you use to mint a token. The `source_key` (`github-events` here) is what you put in ingest URLs.
  </Step>

  <Step title="Mint an ingest token">
    An **ingest token** authorizes pushing data to one source. It carries the source and tenant itself, so requests to the ingest endpoints need only this token — no tenant header. The plaintext token is returned **once**; store it securely.

    ```bash theme={null}
    curl -X POST https://api.withsplendor.com/v1/admin/sources/$SOURCE_ID/ingest-token \
      -H "Authorization: Bearer $SPLENDOR_TOKEN" \
      -H "X-Splendor-Tenant-Id: $SPLENDOR_TENANT_ID"
    ```

    ```json theme={null}
    { "source_id": 12, "token": "sit_live_8f2c3d4e...", "created_at": "2026-06-16T18:24:59Z" }
    ```

    Re-POST to rotate the token; `DELETE` the same path to revoke it.
  </Step>

  <Step title="Push data">
    Send data to the source with the **ingest token** as a bearer credential. Two shapes:

    * **Structured records / events** — `POST /v1/ingest/sources/{source_key}/logs` takes a **batch** and returns `202 Accepted`. Send either a top-level **JSON array** of event objects with `Content-Type: application/json`, or **newline-delimited JSON** (one object per line) with `Content-Type: application/x-ndjson`. Gzip is allowed.
    * **Files** — for larger objects, use the resumable multipart upload (`POST /v1/ingest/sources/{source_key}/uploads` → sign parts → complete). See [Ingest a dataset](/guides/ingest-a-dataset).

    ```bash theme={null}
    curl https://api.withsplendor.com/v1/ingest/sources/github-events/logs \
      -H "Authorization: Bearer $SPLENDOR_INGEST_TOKEN" \
      -H "Content-Type: application/json" \
      -d '[
        {"action": "opened", "number": 41, "title": "Fix retry backoff", "repo": "acme/api"},
        {"action": "closed", "number": 39, "title": "Bump deps", "repo": "acme/api"}
      ]'
    ```

    <Note>
      The ingest endpoints (`/v1/ingest/sources/...`) authenticate with the **ingest token only** — no `X-Splendor-Tenant-Id` header. The token already names the source and tenant. The response reports how many events were `received` and a `fragment_id` for the batch.
    </Note>
  </Step>

  <Step title="Confirm and search">
    A `202` means accepted, not yet searchable. Track progress with ingest runs, then search once indexed.

    ```bash theme={null}
    curl "https://api.withsplendor.com/v1/ingest/runs?dataset_id=github" \
      -H "Authorization: Bearer $SPLENDOR_TOKEN" \
      -H "X-Splendor-Tenant-Id: $SPLENDOR_TENANT_ID"
    ```
  </Step>
</Steps>

## Connect a webhook

Most SaaS tools can POST an event to a URL when something happens. The `logs` endpoint expects a **batch** (a JSON array or NDJSON) and an ingest-token `Authorization` header, while a webhook delivers **one event at a time** as a single JSON object — and usually can't attach that header. A tiny **relay** bridges the gap: it wraps each incoming event in a one-element array and forwards it with the token. The relay holds the token, so it never lives in the provider's config.

<Steps>
  <Step title="Create a source and mint a token">
    Follow the two steps above to create a source (say `source_key = "stripe-events"`) and mint its ingest token.
  </Step>

  <Step title="Deploy a small relay">
    Stand up a function (a Cloudflare Worker, Lambda behind a URL, or any HTTP endpoint) that receives the provider's webhook, wraps the body, and forwards it to the `logs` endpoint with the ingest token. About a dozen lines:

    ```js theme={null}
    // Cloudflare Worker — receives a provider webhook, forwards it to Splendor.
    export default {
      async fetch(request, env) {
        const event = await request.json();              // provider's single event
        await fetch(
          "https://api.withsplendor.com/v1/ingest/sources/stripe-events/logs",
          {
            method: "POST",
            headers: {
              "Authorization": `Bearer ${env.SPLENDOR_INGEST_TOKEN}`,
              "Content-Type": "application/json",
            },
            body: JSON.stringify([event]),                // wrap as a 1-element batch
          },
        );
        return new Response("ok");
      },
    };
    ```

    Optionally verify the provider's own signature (Stripe's `Stripe-Signature`, GitHub's `X-Hub-Signature-256`) in the relay before forwarding.
  </Step>

  <Step title="Point the provider's webhook at the relay">
    In the provider's dashboard, set the webhook **payload URL** to your relay's URL. Trigger a test event, then check `GET /v1/ingest/runs` for the dataset and run a search. Events flow in continuously from then on.
  </Step>
</Steps>

<Tip>
  **No relay needed** if your provider (or your own emitter) can both send a JSON **array** body and set a custom `Authorization` header — then point it straight at `https://api.withsplendor.com/v1/ingest/sources/<source_key>/logs` with `Authorization: Bearer <ingest-token>`.
</Tip>

## BYOC — read from your own S3 bucket

With **bring-your-own-cloud**, your data never leaves your account: it stays in your S3 bucket and Splendor reads it in place. New objects are ingested automatically as they land — no uploads.

How it works:

* **Cross-account access.** You grant Splendor a scoped, read-only IAM role on your bucket/prefix, gated by a unique `external_id` (so only Splendor can assume it, and only for your account).
* **Auto-ingest.** An EventBridge rule on your bucket forwards each `Object Created` event to Splendor's ingest queue; the matching source is resolved by bucket and key prefix, and the object is ingested.

You then register the source, pointing it at your bucket with the role and external id:

```bash theme={null}
curl https://api.withsplendor.com/v1/admin/sources \
  -H "Authorization: Bearer $SPLENDOR_TOKEN" \
  -H "X-Splendor-Tenant-Id: $SPLENDOR_TENANT_ID" \
  -H "Content-Type: application/json" \
  -d '{
    "source_key": "warehouse-exports",
    "name": "Warehouse exports",
    "dataset_id": "warehouse",
    "source_type": "generic_jsonl",
    "bucket": "acme-prod-exports",
    "prefix": "splendor/",
    "iam_role_arn": "arn:aws:iam::123456789012:role/acme-splendor-access",
    "external_id": "..."
  }'
```

<Note>
  BYOC requires provisioning the cross-account role and EventBridge wiring in your account (Splendor supplies a Terraform module and the role/external-id values). It is operator-assisted today — **[contact us](https://app.withsplendor.com)** to enable BYOC for a bucket. Once the role and event wiring exist, registering the source above is all that's left.
</Note>

## Related

<Columns cols={2}>
  <Card title="Ingest a dataset" icon="upload" href="/guides/ingest-a-dataset">
    Upload a file through a hosted source with a resumable multipart upload.
  </Card>

  <Card title="Stream logs" icon="scroll" href="/guides/stream-logs">
    Send a continuous stream of records to a source.
  </Card>

  <Card title="Onboard a source with an agent" icon="robot" href="/guides/onboard-a-connector-with-an-agent">
    Have a coding agent wire up a webhook source for you.
  </Card>

  <Card title="Connectors" icon="plug" href="/api-reference/connectors/list-connectors">
    Manage connector instances for third-party systems.
  </Card>
</Columns>
