Update multiple inventory levels
Updates quantities on several inventory levels at once, and optionally the variant’s track_quantity and keep_selling flags. A row either sets absolute quantities, or sends available_delta and unavailable_delta, which apply to the row as it is when the request arrives and never change committed. When on_hand is sent with other quantities, on_hand is recalculated as available + committed + unavailable. Levels that fail are listed in errors; the request returns 400 only when every update fails. Requires the inventory.update permission.
curl --request PATCH \
--url https://api.fluid.app/api/inventory_levels/bulk_update \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"inventory_levels": [
{
"id": 48213,
"available": 228,
"committed": 12,
"unavailable": 4,
"on_hand": 244,
"reason": "September cycle count"
},
{
"id": 48214,
"available": 58,
"reason": "September cycle count",
"track_quantity": true,
"keep_selling": true
}
]
}
'import requests
url = "https://api.fluid.app/api/inventory_levels/bulk_update"
payload = { "inventory_levels": [
{
"id": 48213,
"available": 228,
"committed": 12,
"unavailable": 4,
"on_hand": 244,
"reason": "September cycle count"
},
{
"id": 48214,
"available": 58,
"reason": "September cycle count",
"track_quantity": True,
"keep_selling": True
}
] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
inventory_levels: [
{
id: 48213,
available: 228,
committed: 12,
unavailable: 4,
on_hand: 244,
reason: 'September cycle count'
},
{
id: 48214,
available: 58,
reason: 'September cycle count',
track_quantity: true,
keep_selling: true
}
]
})
};
fetch('https://api.fluid.app/api/inventory_levels/bulk_update', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fluid.app/api/inventory_levels/bulk_update",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'inventory_levels' => [
[
'id' => 48213,
'available' => 228,
'committed' => 12,
'unavailable' => 4,
'on_hand' => 244,
'reason' => 'September cycle count'
],
[
'id' => 48214,
'available' => 58,
'reason' => 'September cycle count',
'track_quantity' => true,
'keep_selling' => true
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.fluid.app/api/inventory_levels/bulk_update"
payload := strings.NewReader("{\n \"inventory_levels\": [\n {\n \"id\": 48213,\n \"available\": 228,\n \"committed\": 12,\n \"unavailable\": 4,\n \"on_hand\": 244,\n \"reason\": \"September cycle count\"\n },\n {\n \"id\": 48214,\n \"available\": 58,\n \"reason\": \"September cycle count\",\n \"track_quantity\": true,\n \"keep_selling\": true\n }\n ]\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://api.fluid.app/api/inventory_levels/bulk_update")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"inventory_levels\": [\n {\n \"id\": 48213,\n \"available\": 228,\n \"committed\": 12,\n \"unavailable\": 4,\n \"on_hand\": 244,\n \"reason\": \"September cycle count\"\n },\n {\n \"id\": 48214,\n \"available\": 58,\n \"reason\": \"September cycle count\",\n \"track_quantity\": true,\n \"keep_selling\": true\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fluid.app/api/inventory_levels/bulk_update")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"inventory_levels\": [\n {\n \"id\": 48213,\n \"available\": 228,\n \"committed\": 12,\n \"unavailable\": 4,\n \"on_hand\": 244,\n \"reason\": \"September cycle count\"\n },\n {\n \"id\": 48214,\n \"available\": 58,\n \"reason\": \"September cycle count\",\n \"track_quantity\": true,\n \"keep_selling\": true\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"inventory_levels": [
{
"id": 48213,
"committed": 12,
"available": 228,
"on_hand": 244,
"unavailable": 4,
"adjustments": null,
"unavailable_item_counts_by_category": null,
"variant": {
"id": 70421,
"product_id": 31877,
"sku": "DM-20LB-CASE",
"title": "Case (10 reams)",
"track_quantity": true,
"keep_selling": false
},
"product": {
"image": "https://cdn.fluid.app/dunder-mifflin/products/20lb-copy-paper.jpg",
"title": "Dunder Mifflin 20lb Copy Paper"
}
},
{
"id": 48214,
"committed": 3,
"available": 58,
"on_hand": 61,
"unavailable": 0,
"adjustments": null,
"unavailable_item_counts_by_category": null,
"variant": {
"id": 70455,
"product_id": 31902,
"sku": "DM-MUG-SCR",
"title": "Default",
"track_quantity": true,
"keep_selling": true
},
"product": {
"image": "https://cdn.fluid.app/dunder-mifflin/products/scranton-branch-mug.jpg",
"title": "Scranton Branch Mug"
}
}
],
"errors": [],
"meta": {
"request_id": "5f0c2d9e-8b1a-4c3e-9d7f-2a6b8e4c1f03",
"timestamp": "2026-09-14T15:32:08Z"
}
}{
"error_message": "Updating the inventory levels failed",
"errors": [
{
"inventory_level_id": 48213,
"error_message": "warehouse_id and variant_id of an inventory level cannot change"
}
],
"meta": {
"request_id": "5f0c2d9e-8b1a-4c3e-9d7f-2a6b8e4c1f03",
"timestamp": "2026-09-14T15:32:08Z"
}
}{
"message": "<string>",
"error": "<string>",
"error_message": "<string>",
"errors": "<string>",
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"code": "<string>",
"blocked_date": "2026-11-30",
"blocked_day": 30,
"allowed_date": "2026-11-28",
"scheduled_date": "2026-12-10",
"last_bill_date": "2026-11-10",
"requested_date": "2026-11-30",
"payment_uuid": "<string>",
"error_details": [
{
"code": "<string>",
"field": "<string>",
"payment": "not_attempted",
"message": "<string>"
}
],
"coded_recovery": true
}
}Headers
Optional client-generated key (UUID recommended) that makes a resend
safe. Inventory changes sent as available_delta and unavailable_delta
apply again on a second request, so a client that resends after
losing a response should send the same key. The key is bound to the
exact request that first used it and to the user, for up to 24 hours.
The key must be 1 to 255 printable ASCII characters, or the request answers 422. A blank key
is treated as absent and the request runs unguarded.
Resending that request unchanged replays the first response with
X-Idempotent-Replayed: true and applies nothing. A different
request under the key answers 422, and a resend while the first
request is still running answers 409.
curl --request PATCH \
--url https://api.fluid.app/api/inventory_levels/bulk_update \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"inventory_levels": [
{
"id": 48213,
"available": 228,
"committed": 12,
"unavailable": 4,
"on_hand": 244,
"reason": "September cycle count"
},
{
"id": 48214,
"available": 58,
"reason": "September cycle count",
"track_quantity": true,
"keep_selling": true
}
]
}
'import requests
url = "https://api.fluid.app/api/inventory_levels/bulk_update"
payload = { "inventory_levels": [
{
"id": 48213,
"available": 228,
"committed": 12,
"unavailable": 4,
"on_hand": 244,
"reason": "September cycle count"
},
{
"id": 48214,
"available": 58,
"reason": "September cycle count",
"track_quantity": True,
"keep_selling": True
}
] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
inventory_levels: [
{
id: 48213,
available: 228,
committed: 12,
unavailable: 4,
on_hand: 244,
reason: 'September cycle count'
},
{
id: 48214,
available: 58,
reason: 'September cycle count',
track_quantity: true,
keep_selling: true
}
]
})
};
fetch('https://api.fluid.app/api/inventory_levels/bulk_update', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.fluid.app/api/inventory_levels/bulk_update",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'inventory_levels' => [
[
'id' => 48213,
'available' => 228,
'committed' => 12,
'unavailable' => 4,
'on_hand' => 244,
'reason' => 'September cycle count'
],
[
'id' => 48214,
'available' => 58,
'reason' => 'September cycle count',
'track_quantity' => true,
'keep_selling' => true
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.fluid.app/api/inventory_levels/bulk_update"
payload := strings.NewReader("{\n \"inventory_levels\": [\n {\n \"id\": 48213,\n \"available\": 228,\n \"committed\": 12,\n \"unavailable\": 4,\n \"on_hand\": 244,\n \"reason\": \"September cycle count\"\n },\n {\n \"id\": 48214,\n \"available\": 58,\n \"reason\": \"September cycle count\",\n \"track_quantity\": true,\n \"keep_selling\": true\n }\n ]\n}")
req, _ := http.NewRequest("PATCH", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.patch("https://api.fluid.app/api/inventory_levels/bulk_update")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"inventory_levels\": [\n {\n \"id\": 48213,\n \"available\": 228,\n \"committed\": 12,\n \"unavailable\": 4,\n \"on_hand\": 244,\n \"reason\": \"September cycle count\"\n },\n {\n \"id\": 48214,\n \"available\": 58,\n \"reason\": \"September cycle count\",\n \"track_quantity\": true,\n \"keep_selling\": true\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.fluid.app/api/inventory_levels/bulk_update")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"inventory_levels\": [\n {\n \"id\": 48213,\n \"available\": 228,\n \"committed\": 12,\n \"unavailable\": 4,\n \"on_hand\": 244,\n \"reason\": \"September cycle count\"\n },\n {\n \"id\": 48214,\n \"available\": 58,\n \"reason\": \"September cycle count\",\n \"track_quantity\": true,\n \"keep_selling\": true\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"inventory_levels": [
{
"id": 48213,
"committed": 12,
"available": 228,
"on_hand": 244,
"unavailable": 4,
"adjustments": null,
"unavailable_item_counts_by_category": null,
"variant": {
"id": 70421,
"product_id": 31877,
"sku": "DM-20LB-CASE",
"title": "Case (10 reams)",
"track_quantity": true,
"keep_selling": false
},
"product": {
"image": "https://cdn.fluid.app/dunder-mifflin/products/20lb-copy-paper.jpg",
"title": "Dunder Mifflin 20lb Copy Paper"
}
},
{
"id": 48214,
"committed": 3,
"available": 58,
"on_hand": 61,
"unavailable": 0,
"adjustments": null,
"unavailable_item_counts_by_category": null,
"variant": {
"id": 70455,
"product_id": 31902,
"sku": "DM-MUG-SCR",
"title": "Default",
"track_quantity": true,
"keep_selling": true
},
"product": {
"image": "https://cdn.fluid.app/dunder-mifflin/products/scranton-branch-mug.jpg",
"title": "Scranton Branch Mug"
}
}
],
"errors": [],
"meta": {
"request_id": "5f0c2d9e-8b1a-4c3e-9d7f-2a6b8e4c1f03",
"timestamp": "2026-09-14T15:32:08Z"
}
}{
"error_message": "Updating the inventory levels failed",
"errors": [
{
"inventory_level_id": 48213,
"error_message": "warehouse_id and variant_id of an inventory level cannot change"
}
],
"meta": {
"request_id": "5f0c2d9e-8b1a-4c3e-9d7f-2a6b8e4c1f03",
"timestamp": "2026-09-14T15:32:08Z"
}
}{
"message": "<string>",
"error": "<string>",
"error_message": "<string>",
"errors": "<string>",
"meta": {
"request_id": "<string>",
"timestamp": "2023-11-07T05:31:56Z",
"code": "<string>",
"blocked_date": "2026-11-30",
"blocked_day": 30,
"allowed_date": "2026-11-28",
"scheduled_date": "2026-12-10",
"last_bill_date": "2026-11-10",
"requested_date": "2026-11-30",
"payment_uuid": "<string>",
"error_details": [
{
"code": "<string>",
"field": "<string>",
"payment": "not_attempted",
"message": "<string>"
}
],
"coded_recovery": true
}
}