# Create a fulfillment route

`POST /fulfillment-routes`

OMS API, Fulfillment Routes.

Create a new fulfillment route. Defaults to "draft" status; call POST /:id/publish to make it live.

**Authentication:** Send `Authorization: Bearer <token>`. Either an identity token issued by the auth service (dashboard flow; requires the selected merchant account id in the `X-Odus-Merchant` header), or a merchant secret key (`sk_...`) for server-to-server calls. Both are sent as `Authorization: Bearer <token>`.

## Request body

Content type `application/json`, required.

Type: object (CreateFulfillmentRouteRequestDTO).

- `name` (string, required): User-friendly name for the fulfillment route. Maximum length 200.
- `countries` (array of string): ISO 3166-1 alpha-2 country codes the rule applies to. Empty = matches any country. Each item: One of `AF`, `AX`, `AL`, `DZ`, `AS`, `AD`, `AO`, `AI`, `AQ`, `AG`, `AR`, `AM`, `AW`, `AU`, `AT`, `AZ`, `BS`, `BH`, `BD`, `BB`, `BY`, `BE`, `BZ`, `BJ`, `BM`, `BT`, `BO`, `BQ`, `BA`, `BW`, `BV`, `BR`, `IO`, `BN`, `BG`, `BF`, `BI`, `KH`, `CM`, `CA`, `CV`, `KY`, `CF`, `TD`, `CL`, `CN`, `CX`, `CC`, `CO`, `KM`, `CG`, `CD`, `CK`, `CR`, `CI`, `HR`, `CU`, `CW`, `CY`, `CZ`, `DK`, `DJ`, `DM`, `DO`, `EC`, `EG`, `SV`, `GQ`, `ER`, `EE`, `ET`, `FK`, `FO`, `FJ`, `FI`, `FR`, `GF`, `PF`, `TF`, `GA`, `GM`, `GE`, `DE`, `GH`, `GI`, `GR`, `GL`, `GD`, `GP`, `GU`, `GT`, `GG`, `GN`, `GW`, `GY`, `HT`, `HM`, `VA`, `HN`, `HK`, `HU`, `IS`, `IN`, `ID`, `IR`, `IQ`, `IE`, `IM`, `IL`, `IT`, `JM`, `JP`, `JE`, `JO`, `KZ`, `KE`, `KI`, `KR`, `KP`, `KW`, `KG`, `LA`, `LV`, `LB`, `LS`, `LR`, `LY`, `LI`, `LT`, `LU`, `MO`, `MK`, `MG`, `MW`, `MY`, `MV`, `ML`, `MT`, `MH`, `MQ`, `MR`, `MU`, `YT`, `MX`, `FM`, `MD`, `MC`, `MN`, `ME`, `MS`, `MA`, `MZ`, `MM`, `NA`, `NR`, `NP`, `NL`, `NC`, `NZ`, `NI`, `NE`, `NG`, `NU`, `NF`, `MP`, `NO`, `OM`, `PK`, `PW`, `PS`, `PA`, `PG`, `PY`, `PE`, `PH`, `PN`, `PL`, `PT`, `PR`, `QA`, `RE`, `RO`, `RU`, `RW`, `BL`, `SH`, `KN`, `LC`, `MF`, `PM`, `VC`, `WS`, `SM`, `ST`, `SA`, `SN`, `RS`, `SC`, `SL`, `SG`, `SX`, `SK`, `SI`, `SB`, `SO`, `ZA`, `GS`, `SS`, `ES`, `LK`, `SD`, `SR`, `SJ`, `SZ`, `SE`, `CH`, `SY`, `TW`, `TJ`, `TZ`, `TH`, `TL`, `TG`, `TK`, `TO`, `TT`, `TN`, `TR`, `TM`, `TC`, `TV`, `UG`, `UA`, `AE`, `GB`, `US`, `UM`, `UY`, `UZ`, `VU`, `VE`, `VN`, `VG`, `VI`, `WF`, `EH`, `YE`, `ZM`, `ZW`.
- `skus` (array of string): SKUs the rule applies to. Empty = matches any SKU. Each item: Maximum length 200.
- `integrationId` (string, required): ID of the integration to route orders to.
- `priority` (number): Lower value = evaluated first. Auto-assigned to next available slot if omitted. Minimum 0.
- `status` (string): Lifecycle status. New routes default to "draft" and only "published" rules are evaluated by the matcher. One of `draft`, `published`.
- `active` (boolean): Whether the route is active. Defaults to true.
- `description` (string): Free-text description of the fulfillment route. Maximum length 1000.

## Responses

### `201`

Type: object (FulfillmentRouteDTO).

- `createdAt` (string (date-time), required): The date and time when the entity was created. Example: `2024-02-18T12:00:00Z`.
- `updatedAt` (string (date-time), required, nullable): The date and time when the entity was last updated. Example: `2024-02-21T12:15:00Z`.
- `metadata` (map of string to string, required, nullable): Metadata used by merchants to store additional information about the entity.
- `id` (string, required): Example: `frt_abc123`.
- `name` (string, required): Example: `US Domestic`.
- `priority` (number, required): Example: `0`.
- `status` (string, required): One of `draft`, `published`. Example: `draft`.
- `active` (boolean, required): Example: `true`.
- `countries` (array of string, required)
- `skus` (array of string, required): Empty array = matches any SKU.
- `integration` (string, required): Example: `int_abc123`.
- `description` (string, nullable)

### `202`

The merchant is entitled but its OMS environment is not provisioned yet. Provisioning has been kicked off (exactly once) and is in progress; retry the request — it succeeds once the environment is ready. Returned only for identity-token (dashboard) requests bound to an active-entitled merchant, not for secret-key API calls; any such endpoint can return it while provisioning is underway.

Type: object (ProvisioningInProgress).

- `status` (string, required): Signals the merchant OMS environment is still being provisioned. One of `provisioning`.
- `merchantAccountId` (string, required): The merchant account whose OMS environment is being provisioned. Example: `dAI3aa87ROOeupeBCjNSKCOBOisBCYjB`.

### `400`

The request was rejected. `type` is `invalid_request_error`; when the failure is a validation failure `code` is `validation_failed` and `errors` lists every problem found, with field-attributable entries prefixed by the field’s path.

Type: object (ValidationErrorResponse).

- `statusCode` (number, required): HTTP status code. Example: `400`.
- `requestId` (string, required): Unique identifier for the request. Example: `dfc10de3-36bb-4bcc-b081-316216194033`.
- `message` (string, required): The first error message from the validation errors. Example: `note must be a string`.
- `type` (string, required): Coarse, stable classifier for the failure. Present on every error response including 5xx. This is the field to branch on: `statusCode` is too coarse and `message` is human prose that may be reworded. The set is closed and every member is permanent, so it is safe to handle exhaustively. One of `invalid_request_error`, `authentication_error`, `permission_error`, `not_found_error`, `invalid_state_error`, `conflict_error`, `rate_limit_error`, `payment_error`, `api_error`. Example: `invalid_request_error`.
- `code` (string): Fine-grained identifier for the failure, present only where it adds something `type` does not — for example separating a replayed idempotency key from a held lock, or naming the reason a payment was declined. The set is open and grows over time, so treat an unrecognised value as absent and fall back to `type`. Example: `validation_failed`.
- `errors` (array of string): Complete list of validation errors.

### `401`

No credentials were supplied, or the identity token / secret key is not valid for the target merchant. `type` is `authentication_error`.

Type: object (ErrorResponse).

- `statusCode` (number, required): HTTP status code. Example: `400`.
- `requestId` (string, required): Unique identifier for the request. Example: `dfc10de3-36bb-4bcc-b081-316216194033`.
- `message` (string, required): Human-readable description of the failure. Example: `An error occurred`.
- `type` (string, required): Coarse, stable classifier for the failure. Present on every error response including 5xx. This is the field to branch on: `statusCode` is too coarse and `message` is human prose that may be reworded. The set is closed and every member is permanent, so it is safe to handle exhaustively. One of `invalid_request_error`, `authentication_error`, `permission_error`, `not_found_error`, `invalid_state_error`, `conflict_error`, `rate_limit_error`, `payment_error`, `api_error`. Example: `invalid_request_error`.
- `code` (string): Fine-grained identifier for the failure, present only where it adds something `type` does not — for example separating a replayed idempotency key from a held lock, or naming the reason a payment was declined. The set is open and grows over time, so treat an unrecognised value as absent and fall back to `type`. Example: `validation_failed`.

### `403`

The credentials are valid but lack the permission this operation requires. `type` is `permission_error`.

Type: object (ErrorResponse).

- `statusCode` (number, required): HTTP status code. Example: `400`.
- `requestId` (string, required): Unique identifier for the request. Example: `dfc10de3-36bb-4bcc-b081-316216194033`.
- `message` (string, required): Human-readable description of the failure. Example: `An error occurred`.
- `type` (string, required): Coarse, stable classifier for the failure. Present on every error response including 5xx. This is the field to branch on: `statusCode` is too coarse and `message` is human prose that may be reworded. The set is closed and every member is permanent, so it is safe to handle exhaustively. One of `invalid_request_error`, `authentication_error`, `permission_error`, `not_found_error`, `invalid_state_error`, `conflict_error`, `rate_limit_error`, `payment_error`, `api_error`. Example: `invalid_request_error`.
- `code` (string): Fine-grained identifier for the failure, present only where it adds something `type` does not — for example separating a replayed idempotency key from a held lock, or naming the reason a payment was declined. The set is open and grows over time, so treat an unrecognised value as absent and fall back to `type`. Example: `validation_failed`.

### `413`

The request exceeded a limit on what this API accepts and was refused before the operation ran, so nothing was created or changed. The body must be under 4 MB. `type` is `invalid_request_error`, and `message` names the limit that was met. Reduce the request below the limit and send it again.

Type: object (ErrorResponse).

- `statusCode` (number, required): HTTP status code. Example: `400`.
- `requestId` (string, required): Unique identifier for the request. Example: `dfc10de3-36bb-4bcc-b081-316216194033`.
- `message` (string, required): Human-readable description of the failure. Example: `An error occurred`.
- `type` (string, required): Coarse, stable classifier for the failure. Present on every error response including 5xx. This is the field to branch on: `statusCode` is too coarse and `message` is human prose that may be reworded. The set is closed and every member is permanent, so it is safe to handle exhaustively. One of `invalid_request_error`, `authentication_error`, `permission_error`, `not_found_error`, `invalid_state_error`, `conflict_error`, `rate_limit_error`, `payment_error`, `api_error`. Example: `invalid_request_error`.
- `code` (string): Fine-grained identifier for the failure, present only where it adds something `type` does not — for example separating a replayed idempotency key from a held lock, or naming the reason a payment was declined. The set is open and grows over time, so treat an unrecognised value as absent and fall back to `type`. Example: `validation_failed`.

### `429`

Too many requests. The rate limit is applied per client across all operations. `type` is `rate_limit_error`.

Type: object (ErrorResponse).

- `statusCode` (number, required): HTTP status code. Example: `400`.
- `requestId` (string, required): Unique identifier for the request. Example: `dfc10de3-36bb-4bcc-b081-316216194033`.
- `message` (string, required): Human-readable description of the failure. Example: `An error occurred`.
- `type` (string, required): Coarse, stable classifier for the failure. Present on every error response including 5xx. This is the field to branch on: `statusCode` is too coarse and `message` is human prose that may be reworded. The set is closed and every member is permanent, so it is safe to handle exhaustively. One of `invalid_request_error`, `authentication_error`, `permission_error`, `not_found_error`, `invalid_state_error`, `conflict_error`, `rate_limit_error`, `payment_error`, `api_error`. Example: `invalid_request_error`.
- `code` (string): Fine-grained identifier for the failure, present only where it adds something `type` does not — for example separating a replayed idempotency key from a held lock, or naming the reason a payment was declined. The set is open and grows over time, so treat an unrecognised value as absent and fall back to `type`. Example: `validation_failed`.

### `500`

The request could not be completed because of an unexpected error. `type` is `api_error`.

Type: object (ErrorResponse).

- `statusCode` (number, required): HTTP status code. Example: `400`.
- `requestId` (string, required): Unique identifier for the request. Example: `dfc10de3-36bb-4bcc-b081-316216194033`.
- `message` (string, required): Human-readable description of the failure. Example: `An error occurred`.
- `type` (string, required): Coarse, stable classifier for the failure. Present on every error response including 5xx. This is the field to branch on: `statusCode` is too coarse and `message` is human prose that may be reworded. The set is closed and every member is permanent, so it is safe to handle exhaustively. One of `invalid_request_error`, `authentication_error`, `permission_error`, `not_found_error`, `invalid_state_error`, `conflict_error`, `rate_limit_error`, `payment_error`, `api_error`. Example: `invalid_request_error`.
- `code` (string): Fine-grained identifier for the failure, present only where it adds something `type` does not — for example separating a replayed idempotency key from a held lock, or naming the reason a payment was declined. The set is open and grows over time, so treat an unrecognised value as absent and fall back to `type`. Example: `validation_failed`.

### `503`

A dependency needed to serve the request is temporarily unavailable — for every operation the auth service that verifies credentials and resolves entitlement, and on the provisioning routes also the ODUS API the OMS reconciles against. `type` is `api_error`. Unlike a plain 500 the condition is transient rather than a fault in the request, so it is worth retrying with backoff.

Type: object (ErrorResponse).

- `statusCode` (number, required): HTTP status code. Example: `400`.
- `requestId` (string, required): Unique identifier for the request. Example: `dfc10de3-36bb-4bcc-b081-316216194033`.
- `message` (string, required): Human-readable description of the failure. Example: `An error occurred`.
- `type` (string, required): Coarse, stable classifier for the failure. Present on every error response including 5xx. This is the field to branch on: `statusCode` is too coarse and `message` is human prose that may be reworded. The set is closed and every member is permanent, so it is safe to handle exhaustively. One of `invalid_request_error`, `authentication_error`, `permission_error`, `not_found_error`, `invalid_state_error`, `conflict_error`, `rate_limit_error`, `payment_error`, `api_error`. Example: `invalid_request_error`.
- `code` (string): Fine-grained identifier for the failure, present only where it adds something `type` does not — for example separating a replayed idempotency key from a held lock, or naming the reason a payment was declined. The set is open and grows over time, so treat an unrecognised value as absent and fall back to `type`. Example: `validation_failed`.

### `504`

The request exceeded the processing time limit and was abandoned. `type` is `api_error` and `code` is `timeout` — unlike a plain 500 the request may still have taken effect.

Type: object (ErrorResponse).

- `statusCode` (number, required): HTTP status code. Example: `400`.
- `requestId` (string, required): Unique identifier for the request. Example: `dfc10de3-36bb-4bcc-b081-316216194033`.
- `message` (string, required): Human-readable description of the failure. Example: `An error occurred`.
- `type` (string, required): Coarse, stable classifier for the failure. Present on every error response including 5xx. This is the field to branch on: `statusCode` is too coarse and `message` is human prose that may be reworded. The set is closed and every member is permanent, so it is safe to handle exhaustively. One of `invalid_request_error`, `authentication_error`, `permission_error`, `not_found_error`, `invalid_state_error`, `conflict_error`, `rate_limit_error`, `payment_error`, `api_error`. Example: `invalid_request_error`.
- `code` (string): Fine-grained identifier for the failure, present only where it adds something `type` does not — for example separating a replayed idempotency key from a held lock, or naming the reason a payment was declined. The set is open and grows over time, so treat an unrecognised value as absent and fall back to `type`. Example: `validation_failed`.

---

Generated from the OMS API OpenAPI document, https://sandbox-oms-api.odus.com/openapi.json. Interactive page: https://docs.odus.com/docs/api/oms/fulfillment-route-controller-create
