Why updating customer metadata fails with a 422 value_already_exist error

Last updated: June 30, 2026

When you update a customer through the Lago API and include a metadata item that already exists on that customer, the call can fail with a 422 value_already_exist error. The cause is that the metadata field is a full replacement rather than a key-based upsert: Lago matches each item by its internal ID, so an item sent without that ID is treated as a new entry, and the new entry collides with the existing key.

How customer metadata updates work

Customer metadata is identified by an internal ID, not by its key. When you send a metadata array on a customer update, Lago matches each item to an existing one by that ID and treats any item without an ID as a new entry to create. So if you send a metadata item by key and value alone, Lago does not read it as an edit of the existing item. It tries to add a new one with the same key, the per-customer key uniqueness check fails, and the call returns the 422 value_already_exist error.

The array you send is treated as the complete desired state for the customer, which produces two related behaviors:

  • Sending metadata: [] clears all metadata on the customer.

  • Omitting an existing item from the payload deletes that item.

How to confirm the cause

Check the metadata array in the failing update request. If an item whose key already exists on the customer is being sent without that item's ID, this is the cause. Fetch the customer with GET /customers/{external_id} and compare: any metadata key in your payload that also appears on the customer, but without its ID attached, will be treated as a create and will trigger the 422.

How to resolve the issue

Read the existing metadata first and send each item's ID back on the update. The detail to watch is the field name: the customer GET response returns each item's ID as lago_id, but the update payload accepts it as id. Read lago_id from the response and send it back as id. Sending it under the name lago_id in the update payload does not work, because the update only recognizes id. The item is then treated as having no ID, Lago tries to create it, and you get the same 422.

The steps:

  1. Fetch the customer with GET /customers/{external_id} and read the lago_id of each metadata item you want to keep or update.

  2. Build your update with one entry per item you want on the customer, setting id to the value you read from lago_id, along with key, value, and display_in_invoice.

  3. For a genuinely new item not yet on the customer, leave id out so Lago creates it.

Because the array is the full desired state, include every item you want the customer to keep, not only the one you are changing. Anything you omit is deleted.

If reading the IDs back is inconvenient, you can instead replace all metadata in two calls: send a first update with metadata: [] to clear what is there, then a second update with the full array you want, with no id on any item since none exist anymore. This avoids the fetch but makes two calls and leaves a brief window where the customer has no metadata. Prefer the ID approach when consistency matters or when other items must be preserved.

Field reference

The update payload identifies each metadata item by id, which is the value returned as lago_id on the customer GET response.

Field

Where it comes from

Notes

id

The lago_id of the item on the GET response

Required to update an existing item. Omit to create a new one.

key

Your data

Unique per customer. A duplicate key on a create is what triggers value_already_exist.

value

Your data

The metadata value.

display_in_invoice

Your data

Whether the item shows on the invoice.


This article reflects guidance drawn from customer case resolutions. It is not officially supported documentation and may not apply to all situations.