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

# Upsert fulfillment options

> Create or update up to 100 fulfillment options by external id in one request.

Send an array of fulfillment options. Each item creates the option when its `id` does not exist, or updates it when it does. Send only the fields to change. `id` comes from each item and cannot be renamed here.

> [!NOTE]
>
> Requires an access token with the `fulfillment:write` scope. See
> [Token](https://firespark.cloud/docs/integrations-api/oauth/token/post.md) to obtain a token.

An item with only `id` refreshes `updated_at` when the option already exists. A new `id` requires `name` and `type`. Omitted `status` defaults to `ACTIVE` and omitted `channel_ids` defaults to `[]`.

## Request

```bash theme={null}
curl -X PUT "https://firespark.cloud/api/integrations/v1/fulfillment" \
  -H "Authorization: Bearer ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '[
    {
      "id": "delivery",
      "name": { "es": "Domicilio" },
      "channel_ids": ["app", "web"],
      "status": "ACTIVE"
    },
    {
      "id": "pickup",
      "name": { "es": "Retiro" },
      "type": "PICKUP"
    }
  ]'
```

## Body

An array of 1 to 100 items. Each item takes these fields:

| Field | Type | Description |
| - | - | - |
| `id` | string | External fulfillment id. Required. Alphanumeric characters, `_`, and `-`, 1–64 characters. |
| `name` | object | Locale map (`es`, `en-US`, …) to a string of 1–1024 characters. Replaces the whole map. At least one locale is required when the field is sent. |
| `type` | string | Type code, 1–100 characters. One value per merchant. `DELIVERY`, `PICKUP`, and custom codes are allowed. |
| `channel_ids` | string\[] | External channel ids. Replaces the whole array. `[]` clears it. Each id is alphanumeric, `_`, or `-`, 1–64 characters. |
| `status` | string | `ACTIVE` or `INACTIVE`. |

`workflow`, `preset`, `labels`, `uid`, `merchant_id`, and `env` are ignored if sent.

## Response

Zod validates the entire body before saving. If any field is invalid, HTTP `400` is returned and no items are saved. When the body is valid, items are saved one after another. HTTP `200` only when every item is saved. If a later item hits a business error, HTTP is that item's status (`422` or `500`).

| Field | Description |
| - | - |
| `id` | The item `id`. `null` when it was missing or invalid. |
| `status` | `200` when saved. `422` when `name` or `type` is already used by another option, or a new `id` was sent without `name` and `type`. `500` for an unexpected failure. |
| `data` | The saved fulfillment option, including `labels`. Only when `status` is `200`. |
| `error` | Error message. Only when `status` is not `200`. |
| `details` | Error details for item-level failures. |

```json Success theme={null}
{
  "data": [
    {
      "id": "delivery",
      "status": 200,
      "data": {
        "id": "delivery",
        "uid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "merchant_id": "c1d2e3f4-a5b6-7890-cdef-123456789abc",
        "name": { "es": "Domicilio" },
        "type": "DELIVERY",
        "status": "ACTIVE",
        "channel_ids": ["app", "web"],
        "labels": []
      }
    },
    {
      "id": "pickup",
      "status": 422,
      "error": "Fulfillment type already exists",
      "details": null
    }
  ]
}
```

```json Invalid fields theme={null}
{
  "error": "Invalid fields",
  "details": {
    "0.type": "Invalid option"
  }
}
```

## Error responses

| Status | Description |
| - | - |
| `400` | The body is not an array of 1 to 100 items, or any item has invalid fields. No items are saved. |
| `401` | Missing or invalid access token. |
| `403` | Token does not include the `fulfillment:write` scope. |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.