# General concepts

## Set up

When you become a fulfillmenttools customer, you'll have access to two environments:

* Pre-production (for example, `ocff-example-pre`)
* Production (for example, `ocff-example-prd`)

## Status codes codes

Standard HTTP status codes are used for success and errors.

| Status code         | Definition                                                |
| ------------------- | --------------------------------------------------------- |
| `200`               | `OK`                                                      |
| `201`               | `Created`                                                 |
| `400`               | `Bad request`                                             |
| `401`, `403`        | `Missing authorization`, `Authentication`                 |
| `404`               | `Not found`                                               |
| `409`               | `Version conflict`                                        |
| `429`               | `Too many requests`                                       |
| `500`, `503`, `504` | `Internal server error`, `Service unavailable`, `Timeout` |

## Formats

### Currencies

When maintaining currencies in the fulfillmenttools platform, the following fields must be defined to support different currencies and decimal precision:

* `value`
* `currency`
* `decimalPlaces`

{% code title="GET response" %}

```json
{
  "value": 1559,
  "currency": "EUR",
  "decimalPlaces": 2
}
```

{% endcode %}

{% hint style="info" %}
In this example, `1559` is interpreted as **15.59 euros**.
{% endhint %}

{% code title="POST body" %}

```json
{
  "value": 1559,
  "currency": "EUR",
  "decimalPlaces": 2
}
```

{% endcode %}

* If no `decimalPlaces` are sent, the backend sets a default of `2`.
* The `decimalPlaces` field is included in the GET response.

### Date and time

To ensure consistency across fulfillmenttools, all date and time attributes in API requests must follow a defined schema. The platform requires [ISO 8601/RFC 3339](https://en.wikipedia.org/wiki/ISO_8601)–based timestamps for all date and time values.

`date-time` is used when the specific time (including timezone) is relevant. Valid ISO 8601 date-time strings in UTC include:

* `2025-03-21T13:00:12Z`
* `2025-03-21T13:00:12.123Z`
* `2025-03-21T13:00:12.123456Z`
* `2025-03-21T13:00:12.123456789Z`

#### Dates

If the time component isn't required, `date` is used. The format `YYYY-MM-DD` is used. A valid date string would be, for example:

```
2025-02-03
```

#### Times

For attributes such as facility opening times, the `HH:MM` format is used. If higher precision is needed (seconds or milliseconds), use the pattern:

```
HH:MM:ss:SSS
```

#### Time duration

Durations must be defined using ISO 8601 duration notation (for example, `P30D` for 30 days). Negative values move the date backwards.

## Resource timestamps

All entities in fulfillmenttools include two primary timestamps to track their lifecycle:

* `created`: The date and time when the entity was created.
* `lastModified`: The date and time when the entity was last changed.

## Updates without modifications

When an entity is updated through the fulfillmenttools APIs and no data has changed, the system does not perform a database write. In this case:

* No version bump occurs.
* The `lastModified` field is not updated.
* No platform event is triggered.

This behavior reduces unnecessary cascading updates.<br>

## Mandatory parameters

In the fulfillmenttools APIs, all mandatory string parameters must have a minimum length of **one**. An empty string (`""`) isn't a valid value.

{% hint style="info" %}
Mandatory string parameters must contain at least one character.
{% endhint %}

For example, the fields `title` and `titleLocalized` in a listing must contain at least one character:

```json
"titleLocalized": {
  "en_US": "US title"
}
```

Requests that include mandatory parameters with fewer than one character will fail.

## Pagination

Pagination in fulfillmenttools enables efficient handling of large datasets when searching for entities, such as stocks. Instead of returning all results in a single response, the system divides them into smaller, manageable pages. Each search response includes a `pageInfo` object with cursors and flags (for example, `hasNextPage` and `endCursor`) that indicate whether additional results are available. These values can be applied to request subsequent pages until all records are retrieved.

### Iterating over multiple pages

When reading several entities that don't fit on a single page, it's necessary to iterate over multiple pages of search results to collect all required information. An example is iterating over pages to get the `Id` and `version` of all entities for an update.

#### Search entities with pagination

The search endpoints return paginated responses. Each response includes a `pageInfo` object that indicates whether additional pages are available.

For example:

{% tabs %}
{% tab title="POST request" %}

```http
POST https://{YOUR_TENANT_NAME}.api.fulfillmenttools.com/api/stocks/search
```

{% endtab %}

{% tab title="Body request" %}

```json
{
  "query": {
    "tenantArticleId": {
      "eq": "4711"
    }
  }
}
```

{% endtab %}

{% tab title="JSON response" %}

```json
{
    "total": 28,
    "pageInfo": {
        "hasNextPage": true,
        "hasPreviousPage": false,
        "startCursor": "MDBhZjE1NzEtMjg5MC00ZTE0LTg1OTEtOWVjZjEyODYzMjIw",
        "endCursor": "YjljYTkwZDMtYWEyMC00MmIyLTlmMmItMDk5ZTIxNWFhZGU0"
    },
    "stocks": [
        {
            "created": "2025-11-06T13:11:35.738Z",
            "facilityRef": "d083f631-9f4c-463b-85d4-9414fb19239f",
            "id": "00af1571-2890-4e14-8591-9ecf12863220",
            "lastModified": "2025-11-26T07:54:35.624Z",
            "tenantArticleId": "4711",
            "value": 100,
            "locationRef": "SL-02",
            "scannableCodes": [],
            "scores": [...],
            "tenantStockId": null,
            "reserved": 0,
            "facilityWideReserved": 0,
            "available": 111,
            "traits": [
                "PICKABLE",
                "ACCESSIBLE"
            ],
            "traitConfig": null,
            "conditions": null,
            "properties": {},
            "serializedProperties": "{}",
            "receiptDate": "2025-11-16T07:46:16.215Z",
            "version": 2,
            "customAttributes": null,
            "availableUntil": null,
            "reservations": [],
            "combinedId": "d083f631-9f4c-463b-85d4-9414fb19239f_11111",
            "facility": {
                "facilityRef": "d083f631-9f4c-463b-85d4-9414fb19239f"
            }
        },
        {
            "created": "2025-11-10T13:19:15.624Z",
            "facilityRef": "d083f631-9f4c-463b-85d4-9414fb19239f",
            "id": "019a6deb-b698-7510-b7a2-ee891d09b1a7",
            "lastModified": "2025-11-10T13:19:15.624Z",
            "tenantArticleId": "4711",
            "value": 10,
            "locationRef": "SL-01",
            "scannableCodes": [],
            "scores": [...],
            "tenantStockId": null,
            "reserved": 0,
            "facilityWideReserved": 0,
            "available": 10,
            "traits": [
                "PICKABLE",
                "ACCESSIBLE"
            ],
            "traitConfig": null,
            "conditions": null,
            "properties": {},
            "serializedProperties": "{}",
            "receiptDate": "2023-06-16T07:42:54.646Z",
            "version": 1,
            "customAttributes": null,
            "availableUntil": null,
            "reservations": [],
            "combinedId": "d083f631-9f4c-463b-85d4-9414fb19239f_11111",
            "facility": {
                "facilityRef": "d083f631-9f4c-463b-85d4-9414fb19239f"
            }
        },
        ...
    ]
}
```

{% endtab %}
{% endtabs %}

* The entity array (in this example, `stocks`) contains the records, including `id` and `version`.
* The `pageInfo` object provides cursors and flags (`hasNextPage`, `hasPreviousPage`) to navigate through results.

#### Page iteration

To retrieve all entities, iterate through the pages until `hasNextPage` is `false`.

Example workflow:

{% stepper %}
{% step %}
**Send the initial search request**
{% endstep %}

{% step %}
**Collect the `id` and `version` values from the entity array**
{% endstep %}

{% step %}
**Check the `pageInfo.hasNextPage`**

* If `true`, send another request using the `endCursor` value as the starting point for the next page
* If `false`, all results have been retrieved
  {% endstep %}
  {% endstepper %}

## Data update guarantees

### Strong consistency

Each resource provides **read-after-write consistency** for **the same entity endpoint**. This ensures that when a client updates an entity, the system makes the changes immediately available in the response and for subsequent queries to the **same endpoint** for that entity.

{% tabs %}
{% tab title="REST API" %}
**Example:** The fulfillmenttools `REST API` provides this guarantee. If a client updates facility details via `POST /api/facilities/{ID}` and waits for the response, a subsequent `GET /api/facilities/{ID}` query will immediately reflect the updated state. However, a read of all facilities using `GET /api/facilities/` does **not guarantee** that the updated facility's details will be instantly reflected, as list views or aggregated views of data may rely on eventual consistency. This restriction explicitly includes reads with specific filters or pagination parameters.
{% endtab %}

{% tab title="GraphQL API" %}
**Example**: In the GraphQL API, read-after-write consistency is guaranteed for **root-level fields** when querying an entity by its ID. For instance, if a facility's details are updated using a mutation:

```graphql
mutation {
  updateFacility(input: { id: "123", ... }) { ... }
}
```

A subsequent query for the same facility will immediately reflect the updated state:

```graphql
query {
  facilityV2(id: "123") { ... }
}
```

However, a query for all facilities does **not guarantee** that the updated facility's details will be instantly reflected, as list views or aggregated views of data may rely on eventual consistency. This restriction explicitly includes reads with specific filters or pagination parameters.
{% endtab %}
{% endtabs %}

### Eventual consistency

Following an update, some changes are not immediately visible in client applications (especially mobile and web clients). For example, shipment creation is an asynchronous process. While updating the shipment resource itself provides read-after-write consistency, the platform delays updating its visibility in the respective client applications. Additionally, eventual consistency is guaranteed for any list views of data, such as a list of facilities.

These delayed updates provide an eventual consistency guarantee, meaning the changes become visible after a short delay. The length of this delay can vary depending on the amount of data to be processed. The system is designed to keep these delays to a minimum.

### Optimistic concurrency control (optimistic locking)

Many API resources use optimistic concurrency control to prevent lost updates when multiple clients concurrently modify data. These resources have a version attribute. When sending an update, the client must include the resource's last known version in the request. After a successful update, the HTTP response body contains the new version of the resource.

Background processes and other system events can update a resource and change its version number without an API client sending a request to that resource. The version number is not guaranteed to increase sequentially.

{% hint style="warning" %}
To ensure that updates are applied to the most recent state, the latest version of an entity should be retrieved before each update.
{% endhint %}

The API doesn't use `ETag` and `If-Match` HTTP headers to control optimistic concurrency. Instead, it returns a `409 HTTP status (Conflict)` error if a version mismatch occurs.
