# Run data sources

> Run one or more data source adapters in parallel and return each source result.

`POST /data` runs every source in `config.sources` in parallel. Each source
receives the fields from `inputs` that its input schema accepts. The response
returns after all selected sources finish.

## Authorization

Requires an access token and a workflow belonging to the token organization.
See [Authentication](/docs/api/data-serve/authentication/).

## Headers

| Header | Required | Description |
| --- | --- | --- |
| `X-Datastruct-Access-Token` | Conditional | Datastruct token |
| `X-Datastruct-Workflow` | Conditional | Header-token workflow |
| `Authorization` | Conditional | Alternative token header: `Bearer <token>` |
| `Content-Type` | Recommended | `application/json`; omission is accepted |

## Request body

```json
{
  "access_token": "<access-token>",
  "workflow": "wfl_01jykdqt8yejf9hz2yvcqwb4gd",
  "config": {
    "context": "prod",
    "sources": {
      "ip-api__geolocation": true
    }
  },
  "inputs": {
    "query": "8.8.8.8"
  }
}
```

| Field | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `access_token` | string or null | No | `null` | Body token |
| `workflow` | string or null | No | `null` | Body-token workflow |
| `config` | object | Yes | — | Run configuration |
| `config.context` | string | No | `dev` | One of `prod`, `test`, or `dev` |
| `config.sources` | object | Yes | — | Non-empty source map |
| `inputs` | object | No | `{}` | Fields shared by all selected sources |

Unknown top-level fields are accepted and ignored. `inputs` is one flat object;
it is not namespaced by source. See
[Source configuration](/docs/api/data-serve/data-sources/) for the accepted
source values and credential fields.

### Input filtering

Each source declares an input JSON Schema. Data Serve filters `inputs` to the
property names in that schema before the source runs. Properties from every
`oneOf`, `anyOf`, or `allOf` branch are included.

A request can therefore contain the union of fields needed by all selected
sources. A misspelled or undeclared field is silently dropped rather than
rejected.

## Request example

```bash
curl --request POST \
  --url https://api.us.datastruct.co/data \
  --header 'Content-Type: application/json' \
  --header 'X-Datastruct-Access-Token: <access token>' \
  --header 'X-Datastruct-Workflow: wfl_01jykdqt8yejf9hz2yvcqwb4gd' \
  --data '{
    "config": {
      "context": "prod",
      "sources": {
        "ip-api__geolocation": true,
        "dsmock__person_alpha": {}
      }
    },
    "inputs": {
      "query": "8.8.8.8",
      "first_name": "John",
      "last_name": "Doe",
      "state": "CA"
    }
  }'
```

## Successful response

```http
HTTP/1.1 200 OK
Content-Type: application/json
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none'
```

```json
{
  "transaction_id": "trx_01k2v8q9m7f3zb6cw4h5n0j1xd",
  "config": {
    "context": "prod",
    "sources": {
      "ip-api__geolocation": true,
      "dsmock__person_alpha": {}
    }
  },
  "outputs": {
    "ip-api__geolocation": {
      "status": "success",
      "country": "United States",
      "city": "Ashburn",
      "query": "8.8.8.8"
    },
    "dsmock__person_alpha": {
      "first_name": "John",
      "last_name": "Doe",
      "state": "CA"
    }
  },
  "status": {
    "ip-api__geolocation": {
      "success": true,
      "message": "Success",
      "match": true,
      "response_time_ms": 96
    },
    "dsmock__person_alpha": {
      "success": true,
      "message": "Success",
      "match": true,
      "response_time_ms": 12
    }
  },
  "response_time_ms": 104
}
```

## Response fields

| Field | Type | Description |
| --- | --- | --- |
| `transaction_id` | string | Opaque `trx_` transaction identifier |
| `config` | object | Validated configuration with defaults |
| `outputs` | object | One entry per requested source |
| `outputs.<source>` | object | Source output, or `{}` after failure |
| `status` | object | One entry per requested source |
| `status.<source>.success` | boolean | Source completion outcome |
| `status.<source>.message` | string | Outcome text; do not parse |
| `status.<source>.match` | boolean | Whether the vendor found a record |
| `status.<source>.response_time_ms` | integer | Source time |
| `response_time_ms` | integer | Total service time |

The response `config` includes the default context when the request omits it.
Each source defines its own output schema. Source output fields can be added;
clients must read the fields they need and ignore unrecognized fields.

> **Confirmation needed:** Confirm whether response configuration should echo
> request-supplied credential fields. Current behavior can return plaintext
> credential fields when a caller sends them.

## Outcome combinations

| `success` | `match` | Meaning |
| --- | --- | --- |
| `true` | `true` | The source completed and returned a record |
| `true` | `false` | Source completed without a record (valid result) |
| `false` | `false` | The source did not complete; its output is `{}` |

All sources start at the same time. One source failure does not stop another.
Total `response_time_ms` is close to the slowest source time, not the sum.

## Per-source failures

Every failure below appears in an HTTP `200` response.

| Message prefix | Cause |
| --- | --- |
| `Adapter not found for` | Unknown source identifier |
| `Adapter error:` | Credential, input, timeout, or vendor failure |
| `Unexpected error:` | Data Serve failed while collecting the result |

Message wording can change. Branch on `success` and `match`, not `message`.

## Request errors and retries

This endpoint can return `401`, `422`, or `500`. An empty or missing
`config.sources` currently returns `500`. See
[Errors](/docs/api/data-serve/errors/) for error bodies.

Data Serve has no idempotency key. A retry runs each selected vendor call again
and can be billable. Retry only sources that failed because of a timeout or
vendor `5xx` response.
