Skip to main content

Case metadata

Metadata are free key-value pairs that you attach to a case. Certificall never interprets them: they are returned to you as-is, on every channel, and let you break down your consumption.

This page covers the topic end to end. The details of each endpoint remain on their own page.

Metadata is not a reference

To find a case, use reportToken: it is the only field the API filters on, and the one the webhook returns as reportId. Metadata is a descriptive complement — site, branch, job type, campaign. Put your identifier in reportToken, the context in metadata.

Setting metadata

Three channels, one metadata field:

ChannelWhere to set itReference
APImetadata field of POST /cases/createCase management
Certilinkcontext.metadata field when creating the linkCertilink
Mobile SDKmetadata option of the capture callSDK reference

Whatever the channel, metadata belongs to the case, not to the photo or the certificate.

Limits

ConstraintValue
Number of keys20 maximum
Key length40 characters
Value length500 characters
Value typetext only
Characters forbidden in a key[ and ]

Exceeding any limit fails the request with a 400, naming the offending key.

Brackets are rejected in keys because they already carry meaning in the filter syntax metadata[key]=value: a key containing one would make the filter ambiguous.

Values are text

Numbers and booleans must be sent as strings ("42", "true"). Nested objects and arrays are rejected: the field is flat, one key holds one value.

Reading them back

Metadata comes back everywhere the case is returned to you:

WhereHow
Case listGET /cases?format=metadatametadata field on each case (details)
Case webhookmetadata field of the body sent on closing (details)
{
"id": 12345,
"cfRef": "CAS-1234_CMP-7",
"reportId": "JOB-2026-018",
"metadata": { "site": "Paris", "zone": "A1" }
}
Key absent rather than empty

A case without metadata has no metadata key in the response. Plan for a default when reading (const { metadata = {} } = case) rather than testing for an empty object.

Updating them

Metadata on an existing case is updated with PATCH /cases/update. Send only the keys that change:

{
"caseId": 12345,
"metadata": { "zone": "B2" }
}

The keys you send are merged into those already set: an existing key has its value overwritten, the others are kept. On a case holding {"site": "Paris", "zone": "A1"}, the call above leaves {"site": "Paris", "zone": "B2"}.

To clear all metadata on a case, pass null:

{ "caseId": 12345, "metadata": null }
The cap applies to the result

Limits are checked on the block after merging. Ten keys added to fifteen existing ones exceed the cap of twenty and the request is rejected with a 400, even though neither payload is at fault on its own.

Removing a single key

There is currently no way to delete one key on its own: null clears the whole block. To remove a key, clear the block and set the keys you want to keep.

To update several cases, use PATCH /cases/bulk-update: a single request against the rate limits.

Filtering your consumption

This is the only place where metadata acts as a filter: GET /companies/consumption counts only the cases carrying all the requested pairs.

curl -X GET \
'https://admin.certificall.app/certificall/api/companies/consumption?startDate=2026-08-01&endDate=2026-08-31&metadata[site]=Paris&metadata[zone]=A1' \
-H 'Authorization: Bearer $TOKEN'

The syntax is metadata[key]=value, repeated as many times as needed. Multiple pairs combine with AND: the example above counts only the cases from the Paris site and zone A1.

See Consumption for the period parameters and the response format.

A misspelled filter is rejected, not ignored

metadata.site=Paris (dot instead of brackets) returns a 400. This is deliberate: otherwise you would receive the unfiltered total while believing it was filtered.

Two things not to confuse

format=metadata is unrelated

The format parameter of GET /cases and POST /reports/report/:reportToken accepts the value metadata. It describes the response format — JSON data rather than a ZIP archive — and has nothing to do with case metadata.

# JSON response format, no filtering on metadata whatsoever
GET /cases?format=metadata&hours=48

# Filtering on case metadata
GET /companies/consumption?startDate=…&endDate=…&metadata[site]=Paris

caseContext.metadata is not filterable

Some older integrations send their metadata inside the case context object (caseContext.metadata). Those values are still returned to you, so existing integrations keep working, but they are not taken into account by the consumption filter.

Only the case metadata field is filterable. If your consumption filter returns nothing although your cases do carry your keys, check that you are sending them in metadata and not in caseContext.

Summary

QuestionAnswer
Find a case by metadata?No — use reportToken
Update metadata after creation?Yes, PATCH /cases/update — keys are merged
Remove a single key?No — null clears the whole block
Filter a case list on it?No — consumption only
Get it in the webhook?Yes, metadata field
Numeric values?As text ("42")