# Manage stock for agentic commerce When you enable agentic commerce, multiple AI agents can try to buy the same item at the same time. The Inventory API lets you expose real-time stock to those agents, describe your products so agents can present them, and hold stock while an agent completes a purchase. Reservations hold stock for the agent that requested it, so you never sell more than you have, and idempotency support lets you safely retry requests without reserving the same stock twice. The API tracks stock for each product variant. A product variant is a single version of a product that a customer can buy, such as a specific size or color of a shirt. Each variant has its own stock count and its own identifier that you can assign. The API provides: - Stock levels and availability per product variant, so an agent or availability feed always knows what's available to buy. - Reservations – short-lived holds that an agent takes during checkout. If the agent completes the sale, it commits the hold. If the agent abandons the basket, it releases the hold. Holds expire automatically if the agent goes away. - Product knowledge – an optional descriptive layer per variant that agents use to find, present, and recommend a product. For example, the product's title, description, media, price, or attributes. Reservations work with any agentic protocol, including Universal Commerce Protocol (UCP) sessions and Agentic Commerce Protocol (ACP) checkouts. A hold is bound to a reference you provide, rather than to one checkout type. For more information about the payment flows, see: - [Accept a payment via ChatGPT](https://www.checkout.com/docs/payments/accept-payments/accept-a-payment-via-agentic-commerce/accept-a-payment-via-chatgpt.md) for the delegated payments flow. - [Accept a payment via Google AI](https://www.checkout.com/docs/payments/accept-payments/accept-a-payment-via-agentic-commerce/accept-a-payment-via-google-ai.md) for Google's flow. The merchant and the agent have different roles: - Merchants set stock levels and product knowledge, and make stock adjustments. - Agents read availability and product knowledge, then reserve, commit, or release stock during checkout. In practice, your integration often makes both sets of calls: your backend sets stock, and your agentic checkout server reserves and commits it. --- ## Before you start You need: - An OAuth access token, generated with your access key ID and secret using the client credentials grant. For more information, see [OAuth 2.0 client credentials](https://www.checkout.com/docs/resources/api-authentication/oauth-2-0-client-credentials.md). - The `agentic:inventory` scope on the token. Requests performed with an API key that does not have this scope are rejected. The same scope covers stock, reservations, and product knowledge. We resolve your merchant identity from the token. Do not include it in the path or body. A variant or reservation that belongs to another merchant returns a `404` response. --- ## How it works A typical agentic checkout uses the endpoints in this order: 1. The merchant sets stock levels for each variant. 2. Optionally, the merchant adds product knowledge. 3. The agent checks availability. 4. The agent reserves stock for the checkout. 5. The agent commits the reservation when the sale completes, or releases it if the basket is abandoned. --- ## Set stock levels To set absolute levels for a product variant, call the [Set stock levels](https://api-reference.checkout.com/tag/inventory/PUT/inventory/%7Bvariant_id%7D) endpoint, and provide your identifier for the product variant as the `{variant_id}` path parameter. This parameter is the shared key for both stock and product knowledge. > **Information** > > Each variant has a single stock pool. Multi-location inventory is not supported. In your request, provide the following fields: - `on_hand` – The physical stock Checkout.com knows about for the variant. - `safety_stock` – A buffer you withhold from sale, for example to avoid selling the last units online. > **Information** > > Your base URL's `{prefix}` value is unique to your account and environment. To learn how to retrieve your base URLs for the sandbox and production environments, see [API endpoints](https://www.checkout.com/docs/developer-resources/api/api-endpoints.md). **Production** `PUT` `https://{prefix}.api.checkout.com/inventory/var_123` **Sandbox** `PUT` `https://{prefix}.api.sandbox.checkout.com/inventory/var_123` ### Request example ```json { "on_hand": 25, "safety_stock": 2, "reason": "stock take 2026-07" } ``` The API returns a `201` response when it creates a record, or a `200` response when it updates an existing one. ### Response example Availability is always calculated using the following formula, and the result is written as a `set` entry in the ledger: `available = max(0, on_hand - reserved - safety_stock)` ```json { "variant_id": "var_123", "on_hand": 25, "reserved": 0, "safety_stock": 2, "available": 23, "state": "in_stock", "_links": { "self": { "href": "https://{prefix}.api.checkout.com/inventory/var_123" } } } ``` In this example, the variant has 25 units on hand, 0 reserved, and 2 units withheld as safety stock. The API computes 23 available and returns `in_stock` as the state. The following table shows different combinations of stock levels and the resulting availability and state. | on_hand | reserved | safety_stock | available | state | | --- | --- | --- | --- | --- | | 10 | 0 | 0 | 10 | `in_stock` | | 10 | 2 | 1 | 7 | `in_stock` | | 10 | 9 | 1 | 0 | `out_of_stock` | --- ## Adjust stock To apply a relative change instead of an absolute level, call the [Adjust stock](https://api-reference.checkout.com/tag/inventory/POST/inventory/adjustments) endpoint, and provide a required reason, such as damaged or found stock. An adjustment that takes stock below zero is rejected with `invalid_adjustment`. > **Information** > > Your base URL's `{prefix}` value is unique to your account and environment. To learn how to retrieve your base URLs for the sandbox and production environments, see [API endpoints](https://www.checkout.com/docs/developer-resources/api/api-endpoints.md). **Production** `POST` `https://{prefix}.api.checkout.com/inventory/adjustments` **Sandbox** `POST` `https://{prefix}.api.sandbox.checkout.com/inventory/adjustments` ### Request example ```json { "variant_id": "var_123", "delta": -3, "reason": "damaged in warehouse" } ``` --- ## Add product knowledge (optional) You can expose product knowledge so that an agent can present, compare, and recommend specific products from your inventory. You can skip this if the agent already has its own catalog. Product knowledge is stored against the same `variant_id` as stock, but has its own lifecycle. Setting, updating, or deleting it never changes stock. A product variant can have stock with no product knowledge, or product knowledge with no stock. To set product knowledge, call the [Set product knowledge](https://api-reference.checkout.com/tag/inventory/PUT/inventory/%7Bvariant_id%7D/product) endpoint, and provide: - The following required fields: - `title` – The product's title. - `description` – The product's display description. - `product_url` – The canonical URL for the product page. - `image_url` – The URL of the primary product image. Agents don't surface a product without an image. - `price` – The display product's regular price. this field does not include tax, promotions, or authoritative pricing. - `sale_price` – The discounted price, if one exists. If you provide it, `sale_price` must use the same currency as `price` and be less than or equal to it. The charge is settled at checkout. Use `sale_price_starts_at` and `sale_price_ends_at` to define the sale time window. - If the product variant is part of a variant group, you must provide `group_id`, `color`, and `size`. Use the same `group_id` for every variant in the group, so agents can identify them as versions of the same product. ### Request example ```json { "title": "Classic leather belt, brown", "description": "A full-grain leather belt with a brushed nickel buckle.", "product_url": "https://www.example.com/products/classic-leather-belt-brown", "image_url": "https://www.example.com/images/belt-brown-main.jpg", "additional_image_urls": [ "https://www.example.com/images/belt-brown-alt1.jpg" ], "video_url": "https://www.example.com/videos/belt-brown.mp4", "model_3d_url": "https://www.example.com/models/belt-brown.glb", "sku": "BELT-BRN-001", "gtin": "00012345678905", "mpn": "MPN-4471", "brand": "The Online Retailer", "category": "Apparel & Accessories > Belts", "price": { "amount": 1999, "currency": "USD" }, "sale_price": { "amount": 1499, "currency": "USD" }, "sale_price_starts_at": "2026-11-01T00:00:00Z", "sale_price_ends_at": "2026-11-30T23:59:59Z", "group_id": "grp_belt_classic", "group_title": "Classic leather belt", "color": "Brown", "size": "M", "size_system": "US", "gender": "unisex", "condition": "new", "material": "Full-grain leather", "age_group": "adult", "length": 110, "width": 3.5, "height": 0.5, "dimension_unit": "cm", "weight": 0.2, "weight_unit": "kg", "expiration_date": "2027-01-01T00:00:00Z", "harmonized_system_code": "4203.30", "country_of_origin": "IT", "seller_name": "The Online Retailer", "seller_url": "https://www.example.com", "seller_privacy_policy": "https://www.example.com/privacy", "seller_tos": "https://www.example.com/terms" } ``` The API returns a `201` response when it creates a record, or a `200` response when it updates an existing one. ### Read product knowledge with availability To view a product's knowledge together with its live availability, call the [Get stock levels](https://api-reference.checkout.com/tag/inventory/GET/inventory/%7Bvariant_id%7D) endpoint and set the `expand` [query parameter](https://api-reference.checkout.com/tag/inventory/GET/inventory/%7Bvariant_id%7D.query.expand) to `product`. If the variant has no product knowledge, the `product` block is omitted and availability is unaffected. > **Information** > > Your base URL's `{prefix}` value is unique to your account and environment. To learn how to retrieve your base URLs for the sandbox and production environments, see [API endpoints](https://www.checkout.com/docs/developer-resources/api/api-endpoints.md). **Production** `GET` `https://{prefix}.api.checkout.com/inventory/{variant_id}?expand=product` **Sandbox** `GET` `https://{prefix}.api.sandbox.checkout.com/inventory/{variant_id}?expand=product` #### Response example The response includes the derived `available` and `state` values, along with the product knowledge. ```json { "variant_id": "var_123", "on_hand": 10, "reserved": 0, "safety_stock": 1, "available": 9, "state": "in_stock", "product": { "title": "Classic leather belt, brown", "image_url": "https://www.example.com/images/belt-brown-main.jpg", "price": { "amount": 1999, "currency": "USD" }, "sale_price": { "amount": 1499, "currency": "USD" }, "condition": "new" }, "_links": { "self": { "href": "https://{prefix}.api.checkout.com/inventory/var_123" } } } ``` ### Delete product knowledge To remove the descriptive layer, call the [Delete product knowledge](https://api-reference.checkout.com/tag/inventory/DELETE/inventory/%7Bvariant_id%7D/product) endpoint. > **Information** > > Your base URL's `{prefix}` value is unique to your account and environment. To learn how to retrieve your base URLs for the sandbox and production environments, see [API endpoints](https://www.checkout.com/docs/developer-resources/api/api-endpoints.md). **Production** `DELETE` `https://{prefix}.api.checkout.com/inventory/{variant_id}/product` **Sandbox** `DELETE` `https://{prefix}.api.sandbox.checkout.com/inventory/{variant_id}/product` The API returns `204` on success, or `404` if the variant has no product knowledge. Stock for the variant is unchanged. --- ## Check availability An agent or availability feed reads current levels with: - `GET /inventory/var_123` – Availability only. - `GET /inventory/var_123?expand=product` – Availability and product knowledge. The response includes the derived `available` and `state` values. The agent uses them to decide whether, and how, to offer the item. --- ## Reserve stock When an agent starts the checkout process, it takes a hold on the stock. To reserve stock, call the [Create reservation](https://api-reference.checkout.com/tag/inventory/POST/inventory/reservations) endpoint and provide: - `owner_type` and `owner_reference` – The protocol and session that the hold belongs to, for example `ucp_session` and a session ID. The calling protocol adapter sets these, so the same reservation works for any agentic flow. - `ttl_seconds` – How long the hold lasts before it expires automatically. Set a value from `60` to `3600` seconds. The default is `900`. - `items` – The variant IDs and quantities to hold. Each quantity is a positive number that doesn't exceed the variant's available stock. A reservation can span up to 45 variants. > **Information** > > Your base URL's `{prefix}` value is unique to your account and environment. To learn how to retrieve your base URLs for the sandbox and production environments, see [API endpoints](https://www.checkout.com/docs/developer-resources/api/api-endpoints.md). **Production** `POST` `https://{prefix}.api.checkout.com/inventory/reservations` **Sandbox** `POST` `https://{prefix}.api.sandbox.checkout.com/inventory/reservations` ### Request example ```json { "owner_type": "ucp_session", "owner_reference": "cs_8f42", "items": [ { "variant_id": "var_123", "quantity": 2 } ], "ttl_seconds": 900 } ``` ### Response example ```json { "id": "rsv_tkoi5db4hryu5cei5vwoabr7we", "state": "held", "items": [ { "variant_id": "var_123", "quantity": 2 } ], "expires_at": "2026-07-14T08:47:00Z" } ``` Availability now reads `7` for that variant, while `on_hand` is still `10`. If there isn't enough stock, the call returns `409` with `insufficient_stock` and flags the failing variant. Every reservation is processed as a single operation. For a hold across several variants, either all items are reserved or none are. When two agents request the last unit at the same time, exactly one succeeds and the other receives a `409` with `insufficient_stock`. > **Information** > > You can't look up a reservation by owner reference. Keep your own mapping of sessions to reservations. Idempotent retries recover a reservation within the replay window. --- ## Commit or release a reservation A reservation starts as `held` and then moves to one final state. You can't change a reservation after it changes from `held`. To change a basket, release the hold and reserve again. | State | How it happens | Effect on stock | | --- | --- | --- | | `committed` | The order is placed and the agent commits the hold. | Physical stock count is reduced. | | `released` | The agent cancels and releases the hold. | Held units return to availability immediately. | | `expired` | The time-to-live (TTL) elapses with no commit or release. | Held units return to availability automatically. | ### Commit a reservation When the order is placed, commit the hold by calling the [Commit a reservation](https://api-reference.checkout.com/tag/inventory/POST/inventory/reservations/%7Bid%7D/commit) endpoint, setting the reservation ID in the `{id}` path parameter. > **Information** > > Your base URL's `{prefix}` value is unique to your account and environment. To learn how to retrieve your base URLs for the sandbox and production environments, see [API endpoints](https://www.checkout.com/docs/developer-resources/api/api-endpoints.md). **Production** `POST` `https://{prefix}.api.checkout.com/inventory/reservations/{id}/commit` **Sandbox** `POST` `https://{prefix}.api.sandbox.checkout.com/inventory/reservations/{id}/commit` The API decrements physical stock and returns the new availability. A commit on an expired hold is rejected with `409`, so a late commit can never oversell. The agent must reserve again. ### Release a reservation If the agent cancels, release the hold by calling the [Release a reservation](https://api-reference.checkout.com/tag/inventory/POST/inventory/reservations/%7Bid%7D/release) endpoint, setting the reservation ID in the `{id}` path parameter. > **Information** > > Your base URL's `{prefix}` value is unique to your account and environment. To learn how to retrieve your base URLs for the sandbox and production environments, see [API endpoints](https://www.checkout.com/docs/developer-resources/api/api-endpoints.md). **Production** `POST` `https://{prefix}.api.checkout.com/inventory/reservations/{id}/release` **Sandbox** `POST` `https://{prefix}.api.sandbox.checkout.com/inventory/reservations/{id}/release` --- ## Handle errors Errors use the standard Checkout.com format: ```json { "request_id": "0HL80RJLS76I7", "error_type": "request_invalid", "error_codes": ["insufficient_stock"] } ``` For an insufficient-stock conflict, the body also flags the failing variant and its current availability. | HTTP status | Error code | When it happens | | --- | --- | --- | | `401` or `403` | – | The token is missing or invalid, or lacks the `agentic:inventory` scope. | | `404` | - `inventory_item_not_found`
- `reservation_not_found`
- `product_not_found` | The variant, reservation, or product knowledge is unknown, or belongs to another merchant. | | `409` | - `insufficient_stock` | There isn't enough available stock to hold the requested quantity. | | `409` | - `reservation_expired`
- `reservation_released`
- `reservation_committed` | The reservation state doesn't allow the requested transition. | | `409` | - `invalid_adjustment` | An adjustment would take on-hand stock below zero. | | `422` | - `unprocessable_entity` | Request validation failed, for example an out-of-range TTL, a missing required product field, or a `sale_price` above `price`. | | `429` | – | The rate limit is exceeded. |