Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.closient.com/llms.txt

Use this file to discover all available pages before exploring further.

Every Closient API resource accepts a metadata field — a flat dict of string keys and string values you can attach to bridge our objects to your own systems (internal IDs, workflow state, audit info, A/B-test buckets, feature flags, anything). Closient never reads metadata for behavior. It’s opaque developer storage, by design — the same trust contract Stripe makes about its metadata. We won’t key any feature, billing, search ranking, or default behavior off the contents.

Shape

  • Type: flat dict, string keys mapped to string values. No nested objects, no arrays, no booleans, no numbers. If you want structured data, serialize it yourself (e.g. JSON-encode and store the string).
  • Default: {} — never null. The field is always present in responses.

Limits

LimitValue
Max keys per object50
Max key length40 characters
Max value length500 characters
Validation runs on every write. Exceeding any limit returns 422 Unprocessable Entity with a descriptive error_code: validation_error envelope.

Reading metadata

Every read response includes metadata:
{
  "id": "prod_abc123",
  "name": "Acme Widget",
  "metadata": {
    "internal_sku": "AW-12345",
    "warehouse": "east"
  }
}
If you’ve never set metadata, the field is {}.

Writing metadata

Send metadata to the regular PATCH /v1/{resource}/{id} endpoint. There’s no separate metadata sub-endpoint — that’s how Stripe does it and we match.

Set or overwrite a key

curl -X PATCH https://www.closient.com/api/v1/products/prod_abc123 \
  -H "Authorization: Bearer ck_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{"metadata": {"internal_sku": "AW-12345"}}'

Merge semantics — only changed keys are sent

metadata updates are always merge, never replace. Send only the keys you’re changing; everything else is preserved.
{"metadata": {"warehouse": "west"}}
This sets warehouse=west and leaves any other existing keys untouched.

Delete a key — empty-string value

{"metadata": {"deprecated_field": ""}}
An empty-string value is the delete sentinel. The key is removed from the object.

Clear all metadata

Two equivalent forms:
{"metadata": {}}
{"metadata": null}
Both wipe every key on the object.

Combined: set, delete, leave-alone in one request

{
  "metadata": {
    "internal_sku": "AW-67890",
    "warehouse": "",
    "campaign_id": "summer-2026"
  }
}
This sets internal_sku to a new value, deletes warehouse, and adds campaign_id. Any other existing key is left alone.

Form-encoded shorthand

For curl-style usage, the metadata[key]=value form-encoded shorthand is also accepted:
curl -X PATCH https://www.closient.com/api/v1/products/prod_abc123 \
  -H "Authorization: Bearer ck_live_xxx" \
  -d "metadata[internal_sku]=AW-67890" \
  -d "metadata[warehouse]="
A bare metadata= (no [key]) clears all metadata.

Webhooks

{resource}.updated webhook events include the full updated metadata. Setting/deleting keys triggers the standard updated event — there’s no separate metadata.updated event type.

Resources that support metadata

Every CRUD-API resource in the Closient API supports metadata as of C-2699:
  • Products, Brands, Substances
  • Retailers, Offers (in-store + online), Promotions
  • Webhook Endpoints, Resolver Rules
  • Organizations, Users, API Keys, Service Accounts
  • Locations, Campaigns, Scan Sessions, Voice Feedback Sessions
  • Certifications and Claims
If you find one that doesn’t, file an issue — we missed it.

Common patterns

Stamp every object with your internal ID

client.products.update("prod_abc123", metadata={"erp_id": "ERP-12345"})
Then look up the original product later by checking metadata["erp_id"].

Track migration progress

client.products.update(product_id, metadata={"migrated_to_v2": "true"})
(Use a string, not a boolean — metadata values must be strings.)

A/B test bucketing

client.products.update(
    product_id,
    metadata={"experiment_2026q2_pricing": "control"},
)

Cleanup of stale keys

client.products.update(
    product_id,
    metadata={"old_experiment_id": "", "new_experiment_id": "treatment"},
)

What metadata is NOT for

  • Identifiers Closient should look up. If you want us to find an object by your ID, that’s a different feature (deep search) — we don’t index metadata for behavioral lookups.
  • Sensitive data. While transport is HTTPS, metadata payloads are stored in our database with the rest of the resource and visible to anyone who can read the resource. Don’t put secrets, PII you don’t already share with us, or anything you wouldn’t put in our name field.
  • Schema-heavy data. If you find yourself building rich structures inside metadata, that’s a sign you want a real schema — talk to us about a first-class field on the resource.