Skip to content
Rigel docs
Open Rigel

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#

Make several inventory changes at once: parameters
FieldDescription
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.

Make several inventory changes at once: request body fields
FieldDescription
changesarray of one of 9 shapesrequired

The changes, applied in order.

1 to 200 items
Shape 1, with add
changes[].addobjectrequired

Same input as inventory.item.add.

changes[].add.product_idstringoptional

An existing product, from inventory.product.search. Give this or product_name.

changes[].add.product_namestringoptional

The product by name, ignoring case; it is created when there is none. Give this or product_id.

1 to 120 characters
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".

greater than 0, at most 1,000,000
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.

greater than 0, at most 1,000,000
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.

1 to 80 characters
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.

1 to 80 characters
changes[].add.notestring or nulloptional

A note about this item, as written, such as "for Nia's birthday". Null clears it.

1 to 500 characters
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).

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).

0 to 1
changes[].add.crop.widthnumberrequired

Width of the item in the photo, as a fraction of the image width (more than 0).

0 to 1greater than 0
changes[].add.crop.heightnumberrequired

Height of the item in the photo, as a fraction of the image height (more than 0).

0 to 1greater than 0
Shape 2, with adjust
changes[].adjustobjectrequired

Same input as inventory.item.adjust.

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.

-1,000,000 to 1,000,000
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 inventory.item.set_amount.

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".

greater than 0, at most 1,000,000
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 inventory.item.update.

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.

1 to 80 characters
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.

1 to 80 characters
changes[].update.notestring or nulloptional

A note about this item, as written, such as "for Nia's birthday". Null clears it.

1 to 500 characters
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.

greater than 0, at most 1,000,000
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 inventory.item.move.

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 inventory.item.confirm.

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 inventory.item.remove.

changes[].remove.item_idstringrequired

The item that is gone.

Shape 8, with set_photo
changes[].set_photoobjectrequired

Same input as inventory.item.set_photo.

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).

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).

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).

0 to 1greater than 0
changes[].set_photo.crop.heightnumberrequired

Height of the item in the photo, as a fraction of the image height (more than 0).

0 to 1greater than 0
Shape 9, with confirm_location
changes[].confirm_locationobjectrequired

Same input as inventory.location.confirm.

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.

Make several inventory changes at once: response fields
FieldDescription
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.

Make several inventory changes at once: errors
StatusCodeWhen
400validation_failedA field is missing, misspelled or out of range. details.fields lists each problem.
401unauthenticatedThe request has no valid session.
404item_not_foundA failing change rejects the whole batch.
404product_not_foundA failing add names the product error.
404location_not_foundAn unknown location to confirm.
404file_not_foundAn unknown file for a photo.
404not_foundThe household doesn't exist, or you aren't a member of it.
409no_quantityA delta on an item with no number fails the batch.
409idempotency_key_reusedThe 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

Request, curl
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"
      }
    }
  ]
}'
Request, TypeScript
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();
Request, HTTP
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"
      }
    }
  ]
}
Response, 200
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

Request, curl
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"
      }
    }
  ]
}'
Request, TypeScript
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();
Request, HTTP
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"
      }
    }
  ]
}
Response, 200
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

Request, curl
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
      }
    }
  ]
}'
Request, TypeScript
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();
Request, HTTP
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
      }
    }
  ]
}
Response, 200
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

Request, curl
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
      }
    }
  ]
}'
Request, TypeScript
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();
Request, HTTP
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
      }
    }
  ]
}
Response, 200
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

Request, curl
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"
      }
    }
  ]
}'
Request, TypeScript
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();
Request, HTTP
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"
      }
    }
  ]
}
Response, 200
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

Request, curl
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"
      }
    }
  ]
}'
Request, TypeScript
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();
Request, HTTP
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"
      }
    }
  ]
}
Response, 404
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

Request, curl
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"
      }
    }
  ]
}'
Request, TypeScript
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();
Request, HTTP
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"
      }
    }
  ]
}
Response, 404
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

Request, curl
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
      }
    }
  ]
}'
Request, TypeScript
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();
Request, HTTP
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
      }
    }
  ]
}
Response, 409
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

Request, curl
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"
      }
    }
  ]
}'
Request, TypeScript
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();
Request, HTTP
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"
      }
    }
  ]
}
Response, 404
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

Request, curl
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"
      }
    }
  ]
}'
Request, TypeScript
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();
Request, HTTP
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"
      }
    }
  ]
}
Response, 404
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.

Request, curl
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
      }
    }
  ]
}'
Request, TypeScript
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();
Request, HTTP
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
      }
    }
  ]
}
Response, 200
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.

Rigel is in early access, and sign-up is by invitation. Open Rigel