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://..."} with Content-Type: application/json. The URL must be fetchable from the public internet; CheckMerch downloads it server-side. A URL it cannot fetch comes back as invalid_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 by external_ref, so it annotates the record rather than retrieving it.
  • context[intended_use]: one of print, sell, display.
  • context[territory]: a two-letter country code, for example US or BR.
  • min_confidence: one of possible, high, exact. Default high. This is the confidence floor: matches weaker than it are still reported in matches[] 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 stays match_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, including authorized: false and a licensing block, so code that reads matches[0].authorized instead of verdict will 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, or possible, 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) plus work with its holder. When authorized is false, licensing says 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 an advisory string instead of authorized, 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 includes valid, authorizing, and works_covered, which name the exact gap when a job the shop expected to pass did not. On an ordered_by call, valid: false means the customer has not authorized the shop; valid: true with an empty works_covered means 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 into match_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

  1. A client for POST /v1/verify and GET /v1/verifications/{id} with the auth, timeout, and error handling above.
  2. 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.
  3. A per-customer field for the customer's CheckMerch account id, empty by default, sent as ordered_by on that customer's jobs whenever it is set.
  4. Persistence for the fields under "Record keeping", joined to the shop's job records.
  5. 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.
  6. Logging of every call: request time, external_ref, outcome (verdict or error code), verification id.

Ask the shop's operator where the hook goes and what each verdict should do; do not decide either yourself.