Make several inventory changes at once
inventory.change.apply
POST/api/v1/households/{household_id}/commands/inventory.change.apply
- Command
- Rigel runs it
- Applies offline
Applies up to 200 item changes in order, in one step, and records them as one command. Each change is an object with exactly one key naming its kind (add, adjust, set_amount, update, move, confirm, remove, set_photo or confirm_location) whose value is the input of the matching command (inventory.item.add and so on; confirm_location takes a location_id). If any change fails, none are applied and the error names the change by number, with change_index in its details. A later change can use a product an earlier add created by naming it. confirmed_at is the default time for every add, adjust, set_amount, confirm and confirm_location that does not give its own: for a photo, set it to when the photo was taken. This is what to run after reading a photo or for a bulk edit. For a photo of a whole shelf: confirm the items you can see, add what is new, remove only what is clearly gone (its spot is visible and empty), and finish with confirm_location for the shelf. Returns one result per change, in order.
Parameters#
| Field | Description |
|---|---|
household_idpathstringrequired | The household's ID. |
Idempotency-Keyheaderstringoptional | The command's ID. Reuse it when retrying the same command. |
Request body#
JSON, as application/json.
| Field | Description |
|---|---|
changesarray of one of 9 shapesrequired | The changes, applied in order. |
| Shape 1, with add | |
changes[].addobjectrequired | Same input as |
changes[].add.product_idstringoptional | An existing product, from |
changes[].add.product_namestringoptional | The product by name, ignoring case; it is created when there is none. Give this or product_id. |
changes[].add.location_idstringrequired | Where the item is kept. |
changes[].add.quantitynumber or nulloptional | How many we have, in quantity_unit, such as 2 (cans) or 1.5 (lb). Leave out or null when there is no number. Never give it together with level. Leave both out for "we have some". |
changes[].add.quantity_unitone of: mg, g, kg, oz, lb, ml, l, tsp, tbsp, fl_oz, cup, pint, quart, gallon, pinch, dash, clove, slice, stick, sprig, head, bunch, stalk, can, jar, bottle, box, bag, package, carton, roll, null or nulloptional | The unit of quantity, such as "lb" or "can". Null or left out is a plain count of the things themselves (3 eggs). Only allowed with quantity. |
changes[].add.levelone of: full, most, half, low, null or nulloptional | A rough amount when there is no number: full, most, half or low. Never give it together with quantity. |
changes[].add.size_valuenumber or nulloptional | The size of one, such as 500 for a 500 g box. Give it together with size_unit; null clears both. |
changes[].add.size_unitone of: mg, g, kg, oz, lb, ml, l, tsp, tbsp, fl_oz, cup, pint, quart, gallon, pinch, dash, clove, slice, stick, sprig, head, bunch, stalk, can, jar, bottle, box, bag, package, carton, roll, null or nulloptional | The unit of size_value, such as g or oz. Give it together with size_value; null clears both. |
changes[].add.for_person_idstring or nulloptional | Who it is kept for, such as Theo's yogurt or a cake for Nia's birthday. Null clears it. |
changes[].add.recipe_idstring or nulloptional | For homemade food and leftovers, the recipe it was made from. Null clears it. |
changes[].add.brandstring or nulloptional | The brand, as written on the package, such as "Barilla". One line, up to 80 characters. Null clears it. |
changes[].add.variantstring or nulloptional | A variation of the product, such as "low sodium" or "whole wheat". One line, up to 80 characters. Null clears it. |
changes[].add.notestring or nulloptional | A note about this item, as written, such as "for Nia's birthday". Null clears it. |
changes[].add.acquired_onstring (date) or nulloptional | The date it was bought or made, YYYY-MM-DD. Null clears it. |
changes[].add.opened_onstring (date) or nulloptional | The date it was opened, if that changes how long it lasts (milk, salsa), YYYY-MM-DD. Null clears it. |
changes[].add.label_best_bystring (date) or nulloptional | The best-by or use-by date printed on the package, as read, YYYY-MM-DD. Null clears it. |
changes[].add.expires_onstring (date) or nulloptional | When to use it by, where it is now, YYYY-MM-DD; the date the app warns about. Give it together with expires_source; null (with a null source) means it does not go bad. |
changes[].add.expires_sourceone of: label, person, estimated, null or nulloptional | Where expires_on came from. Give it together with expires_on; null clears both. |
changes[].add.confirmed_atstring (date-time)optional | When this was known to be true, if not now: for a photo, when it was taken. A time after the command is treated as now, and an item's confirmation never moves backwards. |
changes[].add.photo_file_idstringoptional | A picture of the item, an uploaded file. Optional. |
changes[].add.cropobjectoptional | Where the item is in the photo, as fractions of the image; the picture shows just that part. Leave out for the whole image. |
changes[].add.crop.xnumberrequired | Left edge of the item in the photo, as a fraction of the image width (0 to 1). |
changes[].add.crop.ynumberrequired | Top edge of the item in the photo, as a fraction of the image height (0 to 1). |
changes[].add.crop.widthnumberrequired | Width of the item in the photo, as a fraction of the image width (more than 0). |
changes[].add.crop.heightnumberrequired | Height of the item in the photo, as a fraction of the image height (more than 0). |
| Shape 2, with adjust | |
changes[].adjustobjectrequired | Same input as |
changes[].adjust.item_idstringrequired | The item to change. |
changes[].adjust.deltanumberoptional | How much to add to the quantity, in its own unit; negative to use some (-1, or -0.5 for half a pound). Not 0, at most 1,000,000 either way. Give this or level. |
changes[].adjust.levelone of: full, most, half, lowoptional | Set a rough level instead, which clears any number. Give this or delta. |
changes[].adjust.confirmed_atstring (date-time)optional | When this was known to be true, if not now: for a photo, when it was taken. A time after the command is treated as now, and an item's confirmation never moves backwards. |
| Shape 3, with set_amount | |
changes[].set_amountobjectrequired | Same input as |
changes[].set_amount.item_idstringrequired | The item that was counted. |
changes[].set_amount.quantitynumber or nulloptional | How many we have, in quantity_unit, such as 2 (cans) or 1.5 (lb). Leave out or null when there is no number. Never give it together with level. Leave both out for "we have some". |
changes[].set_amount.quantity_unitone of: mg, g, kg, oz, lb, ml, l, tsp, tbsp, fl_oz, cup, pint, quart, gallon, pinch, dash, clove, slice, stick, sprig, head, bunch, stalk, can, jar, bottle, box, bag, package, carton, roll, null or nulloptional | The unit of quantity, such as "lb" or "can". Null or left out is a plain count of the things themselves (3 eggs). Only allowed with quantity. |
changes[].set_amount.levelone of: full, most, half, low, null or nulloptional | A rough amount when there is no number: full, most, half or low. Never give it together with quantity. |
changes[].set_amount.confirmed_atstring (date-time)optional | When this was known to be true, if not now: for a photo, when it was taken. A time after the command is treated as now, and an item's confirmation never moves backwards. |
| Shape 4, with update | |
changes[].updateobjectrequired | Same input as |
changes[].update.item_idstringrequired | The item to change. |
changes[].update.for_person_idstring or nulloptional | Who it is kept for, such as Theo's yogurt or a cake for Nia's birthday. Null clears it. |
changes[].update.recipe_idstring or nulloptional | For homemade food and leftovers, the recipe it was made from. Null clears it. |
changes[].update.brandstring or nulloptional | The brand, as written on the package, such as "Barilla". One line, up to 80 characters. Null clears it. |
changes[].update.variantstring or nulloptional | A variation of the product, such as "low sodium" or "whole wheat". One line, up to 80 characters. Null clears it. |
changes[].update.notestring or nulloptional | A note about this item, as written, such as "for Nia's birthday". Null clears it. |
changes[].update.size_valuenumber or nulloptional | The size of one, such as 500 for a 500 g box. Give it together with size_unit; null clears both. |
changes[].update.size_unitone of: mg, g, kg, oz, lb, ml, l, tsp, tbsp, fl_oz, cup, pint, quart, gallon, pinch, dash, clove, slice, stick, sprig, head, bunch, stalk, can, jar, bottle, box, bag, package, carton, roll, null or nulloptional | The unit of size_value, such as g or oz. Give it together with size_value; null clears both. |
changes[].update.acquired_onstring (date) or nulloptional | The date it was bought or made, YYYY-MM-DD. Null clears it. |
changes[].update.opened_onstring (date) or nulloptional | The date it was opened, if that changes how long it lasts (milk, salsa), YYYY-MM-DD. Null clears it. |
changes[].update.label_best_bystring (date) or nulloptional | The best-by or use-by date printed on the package, as read, YYYY-MM-DD. Null clears it. |
changes[].update.expires_onstring (date) or nulloptional | When to use it by, where it is now, YYYY-MM-DD; the date the app warns about. Give it together with expires_source; null (with a null source) means it does not go bad. |
changes[].update.expires_sourceone of: label, person, estimated, null or nulloptional | Where expires_on came from. Give it together with expires_on; null clears both. |
| Shape 5, with move | |
changes[].moveobjectrequired | Same input as |
changes[].move.item_idstringrequired | The item that moved. |
changes[].move.location_idstringrequired | Where it is now. |
changes[].move.expires_onstring (date) or nulloptional | A new use-by date for where it is now, YYYY-MM-DD, such as a thawing date; null for none. Give it together with expires_source. Leave both out to keep the date, except that an estimated date is cleared when the new place keeps things differently (frozen, cold or room). |
changes[].move.expires_sourceone of: label, person, estimated, null or nulloptional | Where expires_on came from. Give it together with expires_on; null clears both. |
| Shape 6, with confirm | |
changes[].confirmobjectrequired | Same input as |
changes[].confirm.item_idstringrequired | The item that is still there. |
changes[].confirm.atstring (date-time)optional | When this was known to be true, if not now: for a photo, when it was taken. A time after the command is treated as now. |
| Shape 7, with remove | |
changes[].removeobjectrequired | Same input as |
changes[].remove.item_idstringrequired | The item that is gone. |
| Shape 8, with set_photo | |
changes[].set_photoobjectrequired | Same input as |
changes[].set_photo.item_idstringrequired | The item to give a picture. |
changes[].set_photo.file_idstring or nullrequired | The uploaded image. Null removes the picture and its crop. |
changes[].set_photo.cropobjectoptional | Where the item is in the photo, as fractions of the image; the picture shows just that part. Leave out for the whole image. |
changes[].set_photo.crop.xnumberrequired | Left edge of the item in the photo, as a fraction of the image width (0 to 1). |
changes[].set_photo.crop.ynumberrequired | Top edge of the item in the photo, as a fraction of the image height (0 to 1). |
changes[].set_photo.crop.widthnumberrequired | Width of the item in the photo, as a fraction of the image width (more than 0). |
changes[].set_photo.crop.heightnumberrequired | Height of the item in the photo, as a fraction of the image height (more than 0). |
| Shape 9, with confirm_location | |
changes[].confirm_locationobjectrequired | Same input as |
changes[].confirm_location.location_idstringrequired | The place to confirm: it, the places inside it and every item there. |
changes[].confirm_location.atstring (date-time)optional | When this was known to be true, if not now: for a photo, when it was taken. A time after the command is treated as now. |
confirmed_atstring (date-time)optional | When this was known to be true, if not now: for a photo, when it was taken. A time after the command is treated as now. |
Response#
200 The command was applied.
| Field | Description |
|---|---|
resultobjectrequired | What the command returned, such as the IDs it created. |
result.resultsarray of objectsrequired | One result per change, in the order given. |
result.results[].kindone of: add, adjust, set_amount, update, move, confirm, remove, set_photo, confirm_locationrequired | Which kind of change this was. |
result.results[].item_idstringoptional | The item changed or created; not given for confirm_location. |
result.results[].product_idstringoptional | For add: the item's product. |
result.results[].created_productbooleanoptional | For add: true when the product was created. |
result.results[].removedbooleanoptional | For adjust and remove: true when the item was deleted. |
versionintegerrequired | The household version after the command. |
Errors#
Every error has the same envelope: error.code, error.message, error.details, error.recovery and error.request_id.
| Status | Code | When |
|---|---|---|
400 | validation_failed | A field is missing, misspelled or out of range. details.fields lists each problem. |
401 | unauthenticated | The request has no valid session. |
404 | item_not_found | A failing change rejects the whole batch. |
404 | product_not_found | A failing add names the product error. |
404 | location_not_found | An unknown location to confirm. |
404 | file_not_found | An unknown file for a photo. |
404 | not_found | The household doesn't exist, or you aren't a member of it. |
409 | no_quantity | A delta on an item with no number fails the batch. |
409 | idempotency_key_reused | The Idempotency-Key was already used for a different command. |
Examples#
These are real. Each scenario is one of the command's test cases, run on a household when this site was built, and the response is what the API returned.
Every kind in one change: the batch time applies, the product made once, the shelf confirmed last
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"confirmed_at": "2025-11-06T19:03:20.000Z",
"changes": [
{
"add": {
"product_name": "Orzo",
"location_id": "loc_01JC000000000000000000000A",
"quantity": 1,
"quantity_unit": "box"
}
},
{
"add": {
"product_name": "orzo",
"location_id": "loc_01JC000000000000000000000A",
"quantity": 2,
"quantity_unit": "box"
}
},
{
"adjust": {
"item_id": "inv_01JC000000000000000000000A",
"delta": -1
}
},
{
"set_amount": {
"item_id": "inv_01JC000000000000000000000B",
"level": "half"
}
},
{
"update": {
"item_id": "inv_01JC000000000000000000000C",
"note": "for soup"
}
},
{
"move": {
"item_id": "inv_01JC000000000000000000000D",
"location_id": "loc_01JC000000000000000000000B"
}
},
{
"confirm": {
"item_id": "inv_01JC000000000000000000000E"
}
},
{
"remove": {
"item_id": "inv_01JC000000000000000000000F"
}
},
{
"set_photo": {
"item_id": "inv_01JC000000000000000000000G",
"file_id": "fil_01JC000000000000000000000A",
"crop": {
"x": 0,
"y": 0,
"width": 0.5,
"height": 1
}
}
},
{
"confirm_location": {
"location_id": "loc_01JC000000000000000000000A"
}
}
]
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
confirmed_at: "2025-11-06T19:03:20.000Z",
changes: [
{
add: {
product_name: "Orzo",
location_id: "loc_01JC000000000000000000000A",
quantity: 1,
quantity_unit: "box",
},
},
{
add: {
product_name: "orzo",
location_id: "loc_01JC000000000000000000000A",
quantity: 2,
quantity_unit: "box",
},
},
{
adjust: {
item_id: "inv_01JC000000000000000000000A",
delta: -1,
},
},
{
set_amount: {
item_id: "inv_01JC000000000000000000000B",
level: "half",
},
},
{
update: {
item_id: "inv_01JC000000000000000000000C",
note: "for soup",
},
},
{
move: {
item_id: "inv_01JC000000000000000000000D",
location_id: "loc_01JC000000000000000000000B",
},
},
{
confirm: {
item_id: "inv_01JC000000000000000000000E",
},
},
{
remove: {
item_id: "inv_01JC000000000000000000000F",
},
},
{
set_photo: {
item_id: "inv_01JC000000000000000000000G",
file_id: "fil_01JC000000000000000000000A",
crop: {
x: 0,
y: 0,
width: 0.5,
height: 1,
},
},
},
{
confirm_location: {
location_id: "loc_01JC000000000000000000000A",
},
},
],
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"confirmed_at": "2025-11-06T19:03:20.000Z",
"changes": [
{
"add": {
"product_name": "Orzo",
"location_id": "loc_01JC000000000000000000000A",
"quantity": 1,
"quantity_unit": "box"
}
},
{
"add": {
"product_name": "orzo",
"location_id": "loc_01JC000000000000000000000A",
"quantity": 2,
"quantity_unit": "box"
}
},
{
"adjust": {
"item_id": "inv_01JC000000000000000000000A",
"delta": -1
}
},
{
"set_amount": {
"item_id": "inv_01JC000000000000000000000B",
"level": "half"
}
},
{
"update": {
"item_id": "inv_01JC000000000000000000000C",
"note": "for soup"
}
},
{
"move": {
"item_id": "inv_01JC000000000000000000000D",
"location_id": "loc_01JC000000000000000000000B"
}
},
{
"confirm": {
"item_id": "inv_01JC000000000000000000000E"
}
},
{
"remove": {
"item_id": "inv_01JC000000000000000000000F"
}
},
{
"set_photo": {
"item_id": "inv_01JC000000000000000000000G",
"file_id": "fil_01JC000000000000000000000A",
"crop": {
"x": 0,
"y": 0,
"width": 0.5,
"height": 1
}
}
},
{
"confirm_location": {
"location_id": "loc_01JC000000000000000000000A"
}
}
]
}HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0T9K1G072NGGN49DS
{
"result": {
"results": [
{
"kind": "add",
"item_id": "inv_01JC4N0000VGW1STJ6TGXRB7FY",
"product_id": "prd_01JC4N0000MBGE5RJ6Q7V6V6T8",
"created_product": true
},
{
"kind": "add",
"item_id": "inv_01JC4N0000JVP6PWJ6S8P7WZ6B",
"product_id": "prd_01JC4N0000MBGE5RJ6Q7V6V6T8",
"created_product": false
},
{
"kind": "adjust",
"item_id": "inv_01JC000000000000000000000A",
"removed": false
},
{
"kind": "set_amount",
"item_id": "inv_01JC000000000000000000000B"
},
{
"kind": "update",
"item_id": "inv_01JC000000000000000000000C"
},
{
"kind": "move",
"item_id": "inv_01JC000000000000000000000D"
},
{
"kind": "confirm",
"item_id": "inv_01JC000000000000000000000E"
},
{
"kind": "remove",
"item_id": "inv_01JC000000000000000000000F",
"removed": true
},
{
"kind": "set_photo",
"item_id": "inv_01JC000000000000000000000G"
},
{
"kind": "confirm_location"
}
]
},
"version": 1
}A change's own time beats the batch time
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"confirmed_at": "2025-11-04T23:46:40.000Z",
"changes": [
{
"confirm": {
"item_id": "inv_01JC000000000000000000000A",
"at": "2025-11-06T19:03:20.000Z"
}
},
{
"confirm": {
"item_id": "inv_01JC000000000000000000000B"
}
}
]
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
confirmed_at: "2025-11-04T23:46:40.000Z",
changes: [
{
confirm: {
item_id: "inv_01JC000000000000000000000A",
at: "2025-11-06T19:03:20.000Z",
},
},
{
confirm: {
item_id: "inv_01JC000000000000000000000B",
},
},
],
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"confirmed_at": "2025-11-04T23:46:40.000Z",
"changes": [
{
"confirm": {
"item_id": "inv_01JC000000000000000000000A",
"at": "2025-11-06T19:03:20.000Z"
}
},
{
"confirm": {
"item_id": "inv_01JC000000000000000000000B"
}
}
]
}HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0Y06VFCGVR5K5DJB3
{
"result": {
"results": [
{
"kind": "confirm",
"item_id": "inv_01JC000000000000000000000A"
},
{
"kind": "confirm",
"item_id": "inv_01JC000000000000000000000B"
}
]
},
"version": 1
}Without a time every confirmation is as of now
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"changes": [
{
"adjust": {
"item_id": "inv_01JC000000000000000000000A",
"delta": 1
}
}
]
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
changes: [
{
adjust: {
item_id: "inv_01JC000000000000000000000A",
delta: 1,
},
},
],
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"changes": [
{
"adjust": {
"item_id": "inv_01JC000000000000000000000A",
"delta": 1
}
}
]
}HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0FCDS1F4FCVJ1BQT6
{
"result": {
"results": [
{
"kind": "adjust",
"item_id": "inv_01JC000000000000000000000A",
"removed": false
}
]
},
"version": 1
}Adjust removing an item reports it removed
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"changes": [
{
"adjust": {
"item_id": "inv_01JC000000000000000000000A",
"delta": -1
}
}
]
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
changes: [
{
adjust: {
item_id: "inv_01JC000000000000000000000A",
delta: -1,
},
},
],
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"changes": [
{
"adjust": {
"item_id": "inv_01JC000000000000000000000A",
"delta": -1
}
}
]
}HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01K9D9S2R07XJX7RZ7K38S7XCW
{
"result": {
"results": [
{
"kind": "adjust",
"item_id": "inv_01JC000000000000000000000A",
"removed": true
}
]
},
"version": 1
}Confirm_location confirms the places inside it and their items, but not newer confirmations
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"confirmed_at": "2025-11-04T23:46:40.000Z",
"changes": [
{
"confirm_location": {
"location_id": "loc_01JC000000000000000000000A"
}
}
]
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
confirmed_at: "2025-11-04T23:46:40.000Z",
changes: [
{
confirm_location: {
location_id: "loc_01JC000000000000000000000A",
},
},
],
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"confirmed_at": "2025-11-04T23:46:40.000Z",
"changes": [
{
"confirm_location": {
"location_id": "loc_01JC000000000000000000000A"
}
}
]
}HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01K9D9S2R088GNE0ZYHHJQNPNA
{
"result": {
"results": [
{
"kind": "confirm_location"
}
]
},
"version": 1
}A failing change rejects the whole batch
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"changes": [
{
"adjust": {
"item_id": "inv_01JC000000000000000000000A",
"delta": -1
}
},
{
"remove": {
"item_id": "inv_01JC000000000000000000000Z"
}
}
]
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
changes: [
{
adjust: {
item_id: "inv_01JC000000000000000000000A",
delta: -1,
},
},
{
remove: {
item_id: "inv_01JC000000000000000000000Z",
},
},
],
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"changes": [
{
"adjust": {
"item_id": "inv_01JC000000000000000000000A",
"delta": -1
}
},
{
"remove": {
"item_id": "inv_01JC000000000000000000000Z"
}
}
]
}HTTP/1.1 404 Not Found
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0KGMR26H5CNCZ31P7
{
"error": {
"code": "item_not_found",
"message": "Change 2 (remove): That item isn't in the inventory any more. No changes were applied.",
"details": {
"item_id": "inv_01JC000000000000000000000Z",
"change_index": 1
},
"recovery": "Read the current state, then send a command that fits it.",
"request_id": "req_01K9D9S2R0KGMR26H5CNCZ31P7"
}
}A failing add names the product error
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"changes": [
{
"add": {
"product_name": "Orzo",
"location_id": "loc_01JC000000000000000000000A"
}
},
{
"add": {
"product_id": "prd_01JC000000000000000000000Z",
"location_id": "loc_01JC000000000000000000000A"
}
}
]
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
changes: [
{
add: {
product_name: "Orzo",
location_id: "loc_01JC000000000000000000000A",
},
},
{
add: {
product_id: "prd_01JC000000000000000000000Z",
location_id: "loc_01JC000000000000000000000A",
},
},
],
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"changes": [
{
"add": {
"product_name": "Orzo",
"location_id": "loc_01JC000000000000000000000A"
}
},
{
"add": {
"product_id": "prd_01JC000000000000000000000Z",
"location_id": "loc_01JC000000000000000000000A"
}
}
]
}HTTP/1.1 404 Not Found
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0PMWH20EXNBPXF7AZ
{
"error": {
"code": "product_not_found",
"message": "Change 2 (add): That product isn't there any more. No changes were applied.",
"details": {
"product_id": "prd_01JC000000000000000000000Z",
"change_index": 1
},
"recovery": "Read the current state, then send a command that fits it.",
"request_id": "req_01K9D9S2R0PMWH20EXNBPXF7AZ"
}
}A delta on an item with no number fails the batch
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"changes": [
{
"adjust": {
"item_id": "inv_01JC000000000000000000000A",
"delta": -1
}
}
]
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
changes: [
{
adjust: {
item_id: "inv_01JC000000000000000000000A",
delta: -1,
},
},
],
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"changes": [
{
"adjust": {
"item_id": "inv_01JC000000000000000000000A",
"delta": -1
}
}
]
}HTTP/1.1 409 Conflict
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0KEAJBPZ9JH280JHE
{
"error": {
"code": "no_quantity",
"message": "Change 1 (adjust): That item has no number to change. Use inventory.item.set_amount to give it a quantity, or adjust it with a level. No changes were applied.",
"details": {
"item_id": "inv_01JC000000000000000000000A",
"change_index": 0
},
"recovery": "Read the current state, then send a command that fits it.",
"request_id": "req_01K9D9S2R0KEAJBPZ9JH280JHE"
}
}An unknown location to confirm
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"changes": [
{
"confirm_location": {
"location_id": "loc_01JC000000000000000000000Z"
}
}
]
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
changes: [
{
confirm_location: {
location_id: "loc_01JC000000000000000000000Z",
},
},
],
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"changes": [
{
"confirm_location": {
"location_id": "loc_01JC000000000000000000000Z"
}
}
]
}HTTP/1.1 404 Not Found
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0VEX7QDNAA501W4YQ
{
"error": {
"code": "location_not_found",
"message": "Change 1 (confirm_location): That place isn't in the inventory any more. No changes were applied.",
"details": {
"location_id": "loc_01JC000000000000000000000Z",
"change_index": 0
},
"recovery": "Read the current state, then send a command that fits it.",
"request_id": "req_01K9D9S2R0VEX7QDNAA501W4YQ"
}
}An unknown file for a photo
curl -X POST 'https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA' \
-d '{
"changes": [
{
"set_photo": {
"item_id": "inv_01JC000000000000000000000A",
"file_id": "fil_01JC000000000000000000000Z"
}
}
]
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01JC4N0000AAAAAAAAAAAAAAAA",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
changes: [
{
set_photo: {
item_id: "inv_01JC000000000000000000000A",
file_id: "fil_01JC000000000000000000000Z",
},
},
],
}),
});
const data = await response.json();POST /api/v1/households/hh_01K9BKKP003AHRZ6DMV29GQY5C/commands/inventory.change.apply HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01JC4N0000AAAAAAAAAAAAAAAA
{
"changes": [
{
"set_photo": {
"item_id": "inv_01JC000000000000000000000A",
"file_id": "fil_01JC000000000000000000000Z"
}
}
]
}HTTP/1.1 404 Not Found
Content-Type: application/json
X-Request-Id: req_01K9D9S2R0REG8WE34FM47YN5N
{
"error": {
"code": "file_not_found",
"message": "Change 1 (set_photo): That file isn't there any more. No changes were applied.",
"details": {
"file_id": "fil_01JC000000000000000000000Z",
"change_index": 0
},
"recovery": "Read the current state, then send a command that fits it.",
"request_id": "req_01K9D9S2R0REG8WE34FM47YN5N"
}
}Put away a grocery trip in one step
A list of changes that apply together or not at all.
curl -X POST 'https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/commands/inventory.change.apply' \
-H "Cookie: rigel_session=$RIGEL_SESSION" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: cmd_01M6V9CSD3N3ETDQ8HPMW3SJY0' \
-d '{
"changes": [
{
"add": {
"product_name": "Eggs",
"location_id": "loc_01M6V9CSAACDPHW4NW889QQ2VV",
"quantity": 12
}
},
{
"add": {
"product_name": "Butter",
"location_id": "loc_01M6V9CSAACDPHW4NW889QQ2VV",
"quantity": 1,
"quantity_unit": "lb"
}
},
{
"adjust": {
"item_id": "inv_01M6V9CSAWTFE7ARBQF4CXQ31Q",
"delta": 1
}
}
]
}'const response = await fetch("https://rigel.house/api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/commands/inventory.change.apply", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": "cmd_01M6V9CSD3N3ETDQ8HPMW3SJY0",
Cookie: `rigel_session=${process.env.RIGEL_SESSION}`,
},
body: JSON.stringify({
changes: [
{
add: {
product_name: "Eggs",
location_id: "loc_01M6V9CSAACDPHW4NW889QQ2VV",
quantity: 12,
},
},
{
add: {
product_name: "Butter",
location_id: "loc_01M6V9CSAACDPHW4NW889QQ2VV",
quantity: 1,
quantity_unit: "lb",
},
},
{
adjust: {
item_id: "inv_01M6V9CSAWTFE7ARBQF4CXQ31Q",
delta: 1,
},
},
],
}),
});
const data = await response.json();POST /api/v1/households/hh_01M6V9CS6GGVFCY7NAS8ZTQV95/commands/inventory.change.apply HTTP/1.1
Host: rigel.house
Cookie: rigel_session=<your session>
Content-Type: application/json
Idempotency-Key: cmd_01M6V9CSD3N3ETDQ8HPMW3SJY0
{
"changes": [
{
"add": {
"product_name": "Eggs",
"location_id": "loc_01M6V9CSAACDPHW4NW889QQ2VV",
"quantity": 12
}
},
{
"add": {
"product_name": "Butter",
"location_id": "loc_01M6V9CSAACDPHW4NW889QQ2VV",
"quantity": 1,
"quantity_unit": "lb"
}
},
{
"adjust": {
"item_id": "inv_01M6V9CSAWTFE7ARBQF4CXQ31Q",
"delta": 1
}
}
]
}HTTP/1.1 200 OK
Content-Type: application/json
X-Request-Id: req_01M6V9CSD3CG33Q9HAP1PB7WCQ
{
"result": {
"results": [
{
"kind": "add",
"item_id": "inv_01M6V9CSD3B5AX83KN6N0A4F9N",
"product_id": "prd_01M6V9CSD34WFGC1KN3CBVKEJ8",
"created_product": true
},
{
"kind": "add",
"item_id": "inv_01M6V9CSD313S8F8KNRG1E54XB",
"product_id": "prd_01M6V9CSD32T46X5KN576WM762",
"created_product": true
},
{
"kind": "adjust",
"item_id": "inv_01M6V9CSAWTFE7ARBQF4CXQ31Q",
"removed": false
}
]
},
"version": 51
}For Rigel#
Rigel's tool for this command is inventory_change_apply. Rigel runs this on its own, then says what it did. The agent guide has the rest.