# Create External Connection

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

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

Creates a customer-managed connection for pushing your own transaction data (bring-your-own-data). Only `type: external` may be created directly. `card`, `store`, and `benefit_account` connections are established through a `connect_account` hosted session. Requires the `connections` key scope. External connections support only the `expenses` product.

## Authorization

- PartnerKey (http, bearer)

## Path parameters

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

## Body

Content type: `application/json`

- `type` ("external", required)
  Must be `external` — the only type creatable directly via this endpoint.
- `provider_label` (string, required)
  Customer-declared label for the upstream source, shown wherever a provider name would appear (e.g. "Acme Budgeting · Plaid feed").
- `products` ("expenses"[])
  Products enabled on this connection. Currently only `expenses` is supported.

Example:

```json
{
  "type": "external",
  "provider_label": "Acme Budgeting · Plaid feed",
  "products": [
    "expenses"
  ]
}
```

## Responses

### 201

Connection created.

- `id` (string)
  The Sail connection id.
- `user_id` (string)
  The Sail user this connection belongs to.
- `type` ("card" | "store" | "benefit_account" | "external")
  The kind of data source this connection links to.
- `status` ("active" | "aggregating" | "user_input_required" | "error" | "disconnected")
  The connection's current sync/authentication status.
- `products` ("expenses" | "benefit_account" | "account_numbers" | "identity"[])
  Capabilities enabled on this connection.
- `accounts` (object[])
  Embedded summaries of the accounts under this connection, so account ids are discoverable without a second call. Populated for `benefit_account` connections (a single administrator login can hold multiple benefit accounts, e.g. HSA + LPFSA); empty for connection types whose accounts are not exposed. Full detail — balances, numbers, contributions — lives at /users/{user_id}/accounts/{account_id}.
  - `id` (string)
    The Sail account id.
  - `type` ("hsa" | "fsa" | "hra" | "checking" | "savings" | "credit_card")
    The account type.
  - `name` (string)
    The account's display name.
- `provider` (object)
  The external party this connection links to.
  - `id` (string)
    The provider's identifier in Sail's directory (merchant or administrator id).
  - `name` (string)
    The provider's display name.
  - `kind` ("administrator" | "financial_institution" | "merchant" | "external")
    What kind of external party this provider is.
  - `icon_url` (string)
    URL of the provider's icon/logo.
- `error` (object)
  Present when the connection is in an error state. Null otherwise.
  - `code` (string)
    Machine-readable error code.
  - `message` (string)
    Human-readable error message.
  - `recoverable` (boolean)
    Whether the user can resolve this error via a `reconnect` hosted session.
- `last_sync_at` (string<date-time>)
  When this connection last completed a sync. Null if it has never synced.
- `created_at` (string<date-time>)
  When the connection was created.
- `updated_at` (string<date-time>)
  When the connection was last updated.

Example:

```json
{
  "id": "conn_9f2c",
  "user_id": "string",
  "type": "card",
  "status": "active",
  "products": [
    "expenses"
  ],
  "accounts": [
    {
      "id": "acct_77b1",
      "type": "hsa",
      "name": "HealthEquity HSA"
    }
  ],
  "provider": {
    "id": "string",
    "name": "HealthEquity",
    "kind": "administrator",
    "icon_url": "string"
  },
  "error": {
    "code": "INVALID_CREDENTIALS",
    "message": "string",
    "recoverable": true
  },
  "last_sync_at": "1970-01-01T00:00:00.000Z",
  "created_at": "1970-01-01T00:00:00.000Z",
  "updated_at": "1970-01-01T00:00:00.000Z"
}
```

### 400

`invalid_request`: `type` must be `external`.

### 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"
  }
}
```