Agent integration prompt
This page is a prompt, not a guide. Copy everything below the line and paste it into the coding agent that works on your print systems, together with one sentence about where checks should happen in your queue. It stands alone: the agent needs no other CheckMerch documentation. If a person is doing the integration instead, the same material written for people is the supplier quickstart.
You will need an API key first. Email help@checkmerch.io with your company name, what you produce, and who should receive the key. Verification is free.
You are integrating a print shop's job queue with CheckMerch, a rights-verification registry for physical merchandise. Before a job is produced, the shop sends the print file to CheckMerch, which answers whether the image matches artwork registered in the registry and whether authorization covering it was presented on the call. The shop records the answer against the job. This prompt contains the complete API contract; do not invent fields or endpoints beyond it.
The call
One endpoint does the work: POST https://checkmerch.io/v1/verify.
Authentication is a bearer key on every request:
Authorization: Bearer mrc_live_...
The key carries the verify scope. Read it from configuration or an
environment variable (for example CHECKMERCH_API_KEY); never hardcode it.
It is a secret: anyone holding it can run checks as this account.
Send the image one of two ways:
- Multipart upload: field
image, the raster file itself. - JSON body:
{"image_url": "https://..."}withContent-Type: application/json. The URL must be fetchable from the public internet; CheckMerch downloads it server-side. A URL it cannot fetch comes back asinvalid_request, not as a distinct error.
Send exactly one of the two. Send the production raster (PNG, JPEG, or TIFF,
for example). Vector formats such as PDF, AI, EPS, and SVG are not decoded and
return image_unreadable; if the queue holds vectors, rasterize first, the
way the shop already does for proofing.
Optional fields, sent as additional multipart fields or JSON keys:
context[external_ref](string, max 255): the shop's own job, order, or line-item number. Always send this. It is stored with the verification and is what ties the check back to the shop's own records; there is no lookup byexternal_ref, so it annotates the record rather than retrieving it.context[intended_use]: one ofprint,sell,display.context[territory]: a two-letter country code, for exampleUSorBR.min_confidence: one ofpossible,high,exact. Defaulthigh. This is the confidence floor: matches weaker than it are still reported inmatches[]but do not drive the verdict. Leave the default unless the shop decides otherwise.
Set the HTTP client timeout to at least 30 seconds. The call fingerprints the image server-side, and a tight timeout turns normal calls into spurious retries.
Presenting authorization
Without authorization presented, a match against registered artwork comes
back match_unauthorized: the call gave the registry nothing to weigh the
match against. There are three ways to present it, and a call carries at most
one; sending two is invalid_request.
ordered_by is the main one for licensed work. The shop's customers who
hold licenses have CheckMerch accounts of their own, and each will give the
shop their CheckMerch account id, an acct_... string (they may just call it
"our CheckMerch ID"). Treat it like a billing reference: collected once at
customer setup, stored on the customer record, and sent as ordered_by on
every job for that customer. The registry checks that this customer has
authorized the shop as their supplier, then evaluates every license the
customer holds against what the image matched. The shop never handles the
licenses themselves and nothing on the shop's side expires or renews.
Two things to know about ordered_by:
- It works only after the customer has authorized the shop inside CheckMerch,
which is one action on their account (their setup is section 5 of
https://checkmerch.io/docs/supplier, not part of this integration). An id
from a customer who has not done it yet resolves to
"grant": {"valid": false, ...}with an advisory saying no authorization exists, and a matched registered work staysmatch_unauthorized. GET https://checkmerch.io/v1/customers(same bearer auth) lists the customers who have authorized the shop, with their account ids. Use it to confirm an id at customer setup rather than trusting a typed string.
grant_token: a signed authorization token the customer obtained from a
rights holder and passed along with the order. Send it when an order carries
one.
grant_id: a bare license id (grant_...) from paperwork. It makes the
response report what that license covers, but it never changes the verdict,
because it proves nothing about who is calling.
The response
A successful call is HTTP 200 with this shape:
{
"id": "ver_01m009emkpgnn41ngg438ncrc7",
"verdict": "match_unauthorized",
"fingerprint_version": "fp-v1",
"matches": [
{
"id": "mtch_01m009emknc9sg57tjmmqyqyd3",
"tier": 1,
"confidence": "exact",
"scores": {
"phash_distance": 0,
"embedding_similarity": 1.0,
"content_hash_match": true
},
"authorized": false,
"work": {
"id": "wrk_01m009dwhhwj72wqhwccecsq17",
"title": "Haunted Ramen",
"holder": { "id": "rh_01m009dw8zfaa0m8t9ky406th5", "name": "Aster & Vane Studio" }
},
"licensing": {
"license_available": true,
"contact": "licensing@asterandvane.example",
"inquiry_url": "https://checkmerch.io/inquiries/new?work=wrk_01m009dwhhwj72wqhwccecsq17"
}
}
],
"grant": null,
"created_at": "2026-08-14T14:05:12Z"
}
Field by field:
id: the verification's identifier. Store it on the job the moment it arrives. It is how the check is read back later, and there is no other way to find it again.verdict: the answer. One of four values, described below. Branch on this field and nothing else.fingerprint_version: the fingerprint pipeline version the image was processed with. Store it with the record.matches[]: everything the image resembled, including matches below the confidence floor, which carry"below_floor": true. A below-floor match still has full detail, includingauthorized: falseand alicensingblock, so code that readsmatches[0].authorizedinstead ofverdictwill misclassify clean jobs. Do not do that.matches[].tier: 1 means registered artwork with a rights holder account in the registry; 2 means an entry in the registry's reference index of known third-party IP.matches[].confidence:exact,high, orpossible, strongest first.matches[].scores: the raw signals behind the confidence:phash_distance(0 is identical),embedding_similarity(1.0 is identical),content_hash_match(byte-identical content). Keys appear only when that signal fired. Log them; do not re-derive decisions from them.- Tier-1 matches carry
authorized(true, false) plusworkwith itsholder. Whenauthorizedis false,licensingsays whether the holder is open to licensing and how to reach them. - Tier-2 matches carry
reference(ip_name,disputed,provenance_summary,dispute_url) and anadvisorystring instead ofauthorized, because no authorization data exists for them. grant: how the authorization presented on this call resolved, or null if none was presented. When present it includesvalid,authorizing, andworks_covered, which name the exact gap when a job the shop expected to pass did not. On anordered_bycall,valid: falsemeans the customer has not authorized the shop;valid: truewith an emptyworks_coveredmeans they have, but no license of theirs covers this artwork.created_at: ISO 8601 UTC timestamp of the check.
The four verdicts
no_match: nothing in the registry matched at or above the confidence floor. The normal result for original artwork.match_authorized: the image matches registered artwork, and the authorization presented on this call covers it.match_unauthorized: the image matches registered artwork, and no valid authorization covering it was presented on this call. This is a statement about the call, not the world: a license the registry was not shown may exist.match_reference: the image matches known third-party IP in the registry's reference index. The rights holder does not participate in the registry, so no authorization data exists and no token can turn this verdict intomatch_authorized. Treat it as a high-risk signal, not a legal finding.
The verdict is information, not an instruction. CheckMerch never tells the shop what to produce. Map each verdict to an action in the shop's own queue (pass through, hold for a person, notify someone) and make that mapping the shop's configuration, with the shop's owner deciding the defaults. Do not hardcode any verdict to cancel or reject a job on your own initiative.
Errors
Every error is JSON in one envelope:
{ "error": { "code": "image_unreadable", "message": "..." } }
| HTTP | code |
Meaning | Retry? |
|---|---|---|---|
| 503 | fingerprint_unavailable |
Fingerprinting is temporarily down. Says nothing about the artwork. | Yes. Honor the Retry-After header (30s) and back off. |
| 422 | image_unreadable |
The bytes are not a decodable raster image. | No. Fix the file: wrong format or truncated upload. |
| 422 | blank_image |
The image decoded but has no content to fingerprint (fully transparent, or one flat color). | No. Usually a wrong layer or empty export. |
| 413 | image_too_large |
The file is over the byte limit; the message names it. | No. Send a smaller file, or use image_url. |
| 413 | image_too_many_pixels |
The image is over the megapixel limit; the message names it. | No. Export a lower resolution; matching quality is unaffected. |
| 422 | invalid_request |
Malformed request, bad field value, or an image_url that could not be fetched. |
No, not unchanged. The message names the field. |
| 401 | unauthorized |
Missing, malformed, or revoked API key. | No. Fix the credential. |
| 403 | forbidden |
Key lacks the verify scope, or the account is suspended. |
No. A person needs to contact help@checkmerch.io. |
| 404 | not_found |
No such verification under this account. | No. |
| 500 | matcher_version_mismatch |
A misconfiguration on CheckMerch's side, not in the request. | Yes, later; report it if it persists. |
The distinction that matters most: fingerprint_unavailable means the check
did not run, and the two image errors mean this file will never pass as sent.
A failed call is not a verdict. Never mark a job as checked because the call
errored, and never treat an error as a clean result. Record failures in the
shop's own log with the code, so a gap in checks is visible as an outage
rather than reading as jobs that were never sent.
Record keeping
For every call, store on the job: the verification id, the verdict,
created_at, the external_ref you sent, and the SHA-256 of the exact bytes
you submitted (compute it locally at send time). CheckMerch stores the same
digest of what it received, and shows it in the shop's dashboard and exports,
so the shop can later demonstrate which file a given check was about by
hashing the file it still holds. CheckMerch keeps no copy of the image; the
shop's copy of the file plus the digest is the link.
There is no idempotency key: a retried call creates a new verification with a
new id. Record the id on first success and treat any retry as a new check.
A stored answer can be read back at any time:
GET https://checkmerch.io/v1/verifications/{id}
Authorization: Bearer mrc_live_...
It returns the verdict, context, matches, and the grant presented, for this account's own verifications only.
What to build
- A client for
POST /v1/verifyandGET /v1/verifications/{id}with the auth, timeout, and error handling above. - A hook at the point in the queue the shop names, sending the production
raster with
context[external_ref]set to the shop's job number. - A per-customer field for the customer's CheckMerch account id, empty by
default, sent as
ordered_byon that customer's jobs whenever it is set. - Persistence for the fields under "Record keeping", joined to the shop's job records.
- A configurable verdict-to-action mapping owned by the shop, plus a failure path that holds a job as "not yet checked" distinctly from any verdict.
- Logging of every call: request time,
external_ref, outcome (verdict or error code), verificationid.
Ask the shop's operator where the hook goes and what each verdict should do; do not decide either yourself.