# Push Transactions (BYO data)

**POST** `/users/{user_id}/connections/{connection_id}/transactions`

Base URL: `https://live.savewithsail.com/api/v1`

Ingest a batch of raw transactions into an `external` connection for HSA-eligibility enrichment. Idempotent on `external_transaction_id` within the connection: re-pushed ids update the stored transaction and re-adjudicate. Returns `202`. Enrichment results arrive via `transactions.ingested` webhooks and are read from `/users/{user_id}/expenses`. Requires the `ingest` key scope, a write scope with no product counterpart, so read-only keys can never push data. Calling this on a Sail-managed connection returns `409 connection_not_external`.

## Authorization

- PartnerKey (http, bearer)

## Path parameters

- `user_id` (string, required)
  The Sail user id.
- `connection_id` (string, required)
  The Sail connection id.

## Body

Content type: `application/json`

- `transactions` (object[], required)
  Batch of transactions to ingest, up to 500 per request.
  - `external_transaction_id` (string, required)
    Customer's stable id for this transaction. Idempotency key within the connection.
  - `date` (string<date>, required)
    Transaction date.
  - `amount` (number<float>, required)
    Positive decimal; purchases only.
  - `description` (string, required)
    Raw bank descriptor.
  - `merchant_name` (string)
    Merchant name, if known. Null otherwise.
  - `mcc` (string)
    Merchant category code, if known.
  - `currency` (string)
    ISO 4217 currency code.

Example:

```json
{
  "transactions": [
    {
      "external_transaction_id": "acme-tx-1001",
      "date": "2024-01-01",
      "amount": 0,
      "description": "WALGREENS #4821 RX",
      "merchant_name": "string",
      "mcc": "string",
      "currency": "USD"
    }
  ]
}
```

## Responses

### 202

Batch accepted for enrichment.

- `status` (string)
  The batch's processing status.
- `job_id` (string)
  Identifier for tracking this ingestion job.
- `accepted` (integer)
  New transactions queued for enrichment.
- `duplicates_updated` (integer)
  Rows whose `external_transaction_id` already existed; updated and re-adjudicated.

Example:

```json
{
  "status": "processing",
  "job_id": "string",
  "accepted": 0,
  "duplicates_updated": 0
}
```

### 409

`connection_not_external`: transactions can only be pushed to `external` connections.

### default

Standard error envelope covering 400, 401, 403, 404, 429, and 500.

- `error` (object)
  The error detail.
  - `code` (string)
    Machine-readable code, e.g. `not_found`, `token_scope_mismatch`, `product_not_enabled`, `insufficient_key_scope`, `insufficient_token_scope`, `user_token_required`, `user_token_expired`, `invalid_user_token`, `invalid_key_configuration`, `connection_not_reconnectable`, `rate_limited`.
  - `message` (string)
    Human-readable error message. May change, so match on `error.code` instead.
  - `param` (string)
    The request field that caused the error, when applicable. Null otherwise.

Example:

```json
{
  "error": {
    "code": "string",
    "message": "string",
    "param": "string"
  }
}
```