# Save a flow

URL: /docs/api/merchant/flows/save-a-flow

## POST /v1/merchant/flows

```http
POST https://api.elaraone.app/api/v1/merchant/flows
```

### Parameters

| Name | In | Type | Required | Description |
|---|---|---|---|---|
| `X-Client-IP` | header | string |  | Optional. IP address of the shopper (one IPv4/IPv6, no port, no list). Honoured only together with a valid X-Tenant-Secret; ignored otherwise. Used for rate limits and recorded with form submissions. |
| `X-Tenant-Secret` | header | string |  | Optional here. Server secret of the store: server-to-server only, never from a browser. Enables X-Client-IP. |

### Request body (`application/json`)

| Field | Type | Required | Description |
|---|---|---|---|
| `handle` | string | yes |  |
| `name` | string | yes |  |
| `is_enabled` | boolean |  |  |
| `trigger` | string[] | yes | `{event, filters}`: an `event` the engine knows, `filters` an object. |
| `graph` | string[] | yes | `{nodes: [...]}`: exactly one `trigger` node, `next` ids that exist, no cycles, and each `action` node's `config` valid for its `config.action` (the same checks as the flow editor). A `send_email` with `recipient: order` (service mail to the order address) is only allowed when `trigger.event` starts with `order.`. Errors come back in `error.details` keyed `graph.<node id>` or `graph.<node id>.config.<key>`. |

### Responses

- **200**: Success.
- **401**: Unauthenticated
- **402**: Payment required. Branch on `error.code`: - `TENANT_PAYMENT_REQUIRED`: this store is on payment hold. Show a "store temporarily unavailable" page; do not retry the request.
- **403**: Authorization error
- **404**: Not found: `FEATURE_NOT_ENABLED` when this store does not have the CRM feature on.
- **422**: Validation error