Supplier quickstart

You send an image. CheckMerch tells you whether it matches artwork registered in the rights registry, and whether the account that submitted it holds a license covering that artwork. One HTTP call, before anything goes on a press.

Base URL: https://checkmerch.io. Contact: help@checkmerch.io.

Every request and response on this page is real. The identifiers in them (ver_..., wrk_..., grant_...) are from our demo catalog, so substitute your own where a command takes one. The artwork and rights holders in the examples are invented.

If a coding agent writes your integration, hand it the agent integration prompt instead: the same contract as this page, packaged as one self-contained prompt to paste.

1. Get a key

There is no signup form. Email help@checkmerch.io with your company name, what you produce, and who should receive the key. We reply with an API key that looks like mrc_... and carries the verify scope, which is all you need for everything on this page. Verification is free.

Keep the key server-side. It is a bearer credential: anyone holding it can verify as you.

export CHECKMERCH_KEY=mrc_your_key_here

2. Make the call

curl -X POST https://checkmerch.io/v1/verify \
  -H "Authorization: Bearer $CHECKMERCH_KEY" \
  -F "image=@artwork.png"
{
  "id": "ver_01m009avh5dyfcyjfay7fkekhf",
  "verdict": "no_match",
  "fingerprint_version": "fp-v1",
  "matches": [],
  "grant": null,
  "created_at": "2026-08-14T14:03:08Z"
}

That is a successful call. no_match means nothing in the registry resembled the image, which is the normal result for original artwork.

If you have the artwork on a URL we can fetch instead of a file to upload, send JSON:

curl -X POST https://checkmerch.io/v1/verify \
  -H "Authorization: Bearer $CHECKMERCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"image_url": "https://files.yourshop.example/jobs/40871/art.png"}'

Use one or the other. image is a multipart file upload, image_url must be reachable from the public internet.

3. Read the verdict

verdict is the single field to branch on. It has four values.

Verdict What we found
no_match Nothing in the registry matched above your confidence floor.
match_authorized The image matches registered artwork, and the license presented with the request covers it.
match_unauthorized The image matches registered artwork, and no license covering it was presented on this request.
match_reference The image matches known third-party IP held in our reference index. No authorization data exists for it.

Verdicts are advisory. CheckMerch reports what it found and never tells you what to produce. What you do with each verdict is your policy, and most shops write it down once: which verdicts pass straight through, which pause for a human, who that human is.

match_unauthorized

{
  "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,
        "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"
}

Knowable: which registered work was matched, how closely, who registered it, and whether they are open to licensing. When licensing.license_available is true, contact and inquiry_url are a route to a license rather than a dead end, which is often the fastest way to unblock a customer's order.

Not knowable: whether your customer holds a license we were not shown. match_unauthorized means "no valid grant covering this submitter was presented on this request," not "no license exists." If your customer says they are licensed, section 5 is what to do about it.

match_reference

{
  "verdict": "match_reference",
  "matches": [
    {
      "tier": 2,
      "confidence": "exact",
      "reference": {
        "id": "ref_01m009dwvdcytpbpnygwb8jjqe",
        "ip_name": "Ferro the Tin Sentinel (character artwork)",
        "disputed": false,
        "provenance_summary": "Curated 2026-08 from publicly released catalog imagery.",
        "dispute_url": "https://checkmerch.io/disputes/new?ref=ref_01m009dwvdcytpbpnygwb8jjqe"
      },
      "advisory": "This image matches a reference entry for widely recognized third-party IP. The rights holder does not participate in this registry and no authorization data is available. Treat as high risk."
    }
  ],
  "grant": null
}

The reference index covers widely recognized third-party IP whose owners are not part of CheckMerch. Because they are not part of it, there is no authorization data to check, so this verdict can never become match_authorized no matter what your customer presents. It is a similarity report plus a risk signal, not a legal finding. CheckMerch makes no legal determination about your order, and the named party neither participates in the registry nor has any view on it.

Knowable: the image is a close match for imagery associated with that IP, and how we sourced the entry (provenance_summary).

Not knowable: everything about permission. Your customer may hold a license directly from that owner. We would not see it.

disputed: true means someone has contested the entry and the dispute is open. The entry still matches while that is being worked out. If you believe an entry is wrong, dispute_url is the way to say so.

match_authorized

{
  "verdict": "match_authorized",
  "matches": [
    {
      "tier": 1,
      "confidence": "exact",
      "authorized": true,
      "work": {
        "id": "wrk_01m00f68tc9zz52xe1wd58n0jv",
        "title": "Haunted Ramen",
        "holder": { "id": "rh_01m00f68jn5ez7a4wn9ddz7b6f", "name": "Aster & Vane Studio" }
      }
    }
  ],
  "grant": {
    "presented_as": "delegation",
    "valid": true,
    "authorizing": true,
    "ordered_by": { "account": "acct_01m00f68jvv0mp0sd9aceg88ab", "name": "Meridian Merch Co." },
    "unchecked": [],
    "works_covered": ["wrk_01m00f68tc9zz52xe1wd58n0jv"],
    "grants_applied": [
      {
        "id": "grant_01m00f692v7t17tks8cey8fnf6",
        "granted_by": { "id": "rh_01m00f68jn5ez7a4wn9ddz7b6f", "name": "Aster & Vane Studio" },
        "expires_at": "2027-08-14T15:45:29Z"
      }
    ]
  },
  "created_at": "2026-08-14T15:45:48Z"
}

This is what a job for a licensed customer looks like once they have authorized you, which is section 5. works_covered lists which of the matched works the license actually covers, grants_applied names the licenses that did it, and unchecked lists conditions we could not evaluate because they were not sent.

4. The confidence floor, and the matches you did not expect

min_confidence defaults to high. Accepted values, weakest to strongest: possible, high, exact.

The floor decides the verdict only. Matches below it are still returned in matches[], flagged below_floor: true. This surprises people, so it is worth seeing:

curl -X POST https://checkmerch.io/v1/verify \
  -H "Authorization: Bearer $CHECKMERCH_KEY" \
  -F "image=@recolored-crop.png"
{
  "verdict": "no_match",
  "matches": [
    {
      "tier": 1,
      "confidence": "possible",
      "scores": { "embedding_similarity": 0.8937 },
      "below_floor": 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
}

The verdict is no_match and matches[0].authorized is false. Those two facts are not in conflict, and the first one is the answer.

Branch on verdict. Do not branch on matches[0].authorized. A below-floor match carries authorized: false and a full licensing block exactly like a real one, so an integration that reads the first match instead of the verdict will flag clean jobs.

Lowering the floor to possible on the same image turns that entry into a match_unauthorized and drops the below_floor flag. Raising it to exact narrows to near-identical files only. high is the default because it is the level at which recolored, resized, and re-encoded copies of registered artwork still match.

Keep the below-floor entries out of your operator's way, but they are useful in a log: a run of them on one customer's orders is worth a look.

5. When your customer is licensed

Your customer holds a license from the rights holder. For you to get a green answer on their work, they authorize you once, and from then on you verify by saying who the order came from.

They do this, once, on their own account, either on the Printers page of their CheckMerch dashboard (they enter the email your account is under) or with one call:

curl -X POST https://checkmerch.io/v1/suppliers \
  -H "Authorization: Bearer $THEIR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"supplier_account": "acct_01m00f68jy5qhffb6vj0mjf8rx", "external_ref": "VENDOR-118"}'

You can see who has done it on the Customers page of your dashboard, or over the API, which is also where you get the account ids you will be sending:

curl https://checkmerch.io/v1/customers \
  -H "Authorization: Bearer $CHECKMERCH_KEY"
{
  "data": [
    {
      "id": "lsl_01m00f6kgex6b10t61172r9eab",
      "ordered_by": { "account": "acct_01m00f68jvv0mp0sd9aceg88ab", "name": "Meridian Merch Co." },
      "external_ref": "VENDOR-118",
      "active": true,
      "started_at": "2026-08-14T15:45:40Z"
    }
  ]
}

Then every job for that customer carries ordered_by:

curl -X POST https://checkmerch.io/v1/verify \
  -H "Authorization: Bearer $CHECKMERCH_KEY" \
  -F "image=@artwork.png" \
  -F "ordered_by=acct_01m00f68jvv0mp0sd9aceg88ab" \
  -F "context[intended_use]=print" \
  -F "context[external_ref]=JOB-40871"
{
  "id": "ver_01m00f6vf9ghe9mpr71ysxefdw",
  "verdict": "match_authorized",
  "matches": [
    {
      "tier": 1,
      "confidence": "exact",
      "authorized": true,
      "work": {
        "id": "wrk_01m00f68tc9zz52xe1wd58n0jv",
        "title": "Haunted Ramen",
        "holder": { "id": "rh_01m00f68jn5ez7a4wn9ddz7b6f", "name": "Aster & Vane Studio" }
      }
    }
  ],
  "grant": {
    "presented_as": "delegation",
    "valid": true,
    "authorizing": true,
    "ordered_by": { "account": "acct_01m00f68jvv0mp0sd9aceg88ab", "name": "Meridian Merch Co." },
    "unchecked": [],
    "works_covered": ["wrk_01m00f68tc9zz52xe1wd58n0jv"],
    "grants_applied": [
      {
        "id": "grant_01m00f692v7t17tks8cey8fnf6",
        "granted_by": { "id": "rh_01m00f68jn5ez7a4wn9ddz7b6f", "name": "Aster & Vane Studio" },
        "expires_at": "2027-08-14T15:45:29Z"
      }
    ]
  },
  "created_at": "2026-08-14T15:45:48Z"
}

Note what you did not have to do. You did not hold anything of your customer's, and you did not have to know which of their licenses was the relevant one. You sent an account id that is already in your system, mapped once when you set the customer up. The registry checked that they authorized you, looked across every license they hold, and reported in grants_applied which one covered the work and who issued it.

That is the whole integration for licensed work: one mapping per customer, not one token per order.

Your customer can end the authorization at any time, and the rights holder can revoke the underlying license at any time. Either one takes effect on your very next call, so nothing to expire on your side and nothing to renew when a license rolls over.

If a job comes back match_unauthorized on a customer you expected to be covered, the grant object says which of the two is missing. No relationship gives you "valid": false and an advisory saying so. A live relationship with no covering license gives you "valid": true with "works_covered": [], which means your customer is authorized for other things but not this design.

Checking a grant id

Your customer may also send you a bare grant id on the paperwork. You can look it up, and it will tell you what that license covers and who holds it:

curl -X POST https://checkmerch.io/v1/tokens/introspect \
  -H "Authorization: Bearer $CHECKMERCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"grant_id": "grant_01m00f692v7t17tks8cey8fnf6"}'

You can also send grant_id on a verify call and it will report coverage the same way. What it will not do is change the verdict:

{
  "id": "grant_01m00f692v7t17tks8cey8fnf6",
  "valid": true,
  "presented_as": "id",
  "granted_by": { "id": "rh_01m00f68jn5ez7a4wn9ddz7b6f", "name": "Aster & Vane Studio" },
  "granted_to": { "account": "acct_01m00f68jvv0mp0sd9aceg88ab", "name": "Meridian Merch Co." },
  "works_covered": ["wrk_01m00f68tc9zz52xe1wd58n0jv"],
  "authorizing": false,
  "advisory": "A grant id identifies a license and reports what it covers. It does not establish that this account may verify on the licensee's behalf, so on a verification it does not change the verdict. The licensee authorizes the suppliers who print for them with POST /v1/suppliers; those suppliers then verify by sending ordered_by."
}

A grant id travels on purchase orders and job tickets and is not a secret, so it identifies a license rather than proving anything about who is calling. Treat it as useful context at intake, and get your customer to authorize you for the answer that counts.

There is also a status-only check that needs no key and no account:

curl https://checkmerch.io/v1/grants/grant_01m00f692v7t17tks8cey8fnf6/status
{ "status": "active", "revoked_at": null }

6. Tie a verification to your job

context is optional. It sharpens the answer and connects it to your paperwork:

  • external_ref: your order number, job ticket, or line item, up to 255 characters.
  • intended_use: print, sell, or display.
  • territory: a two-letter country code.
curl -X POST https://checkmerch.io/v1/verify \
  -H "Authorization: Bearer $CHECKMERCH_KEY" \
  -F "image=@artwork.png" \
  -F "context[external_ref]=JOB-40871" \
  -F "context[intended_use]=print"

Store the returned id against your job. It is how you read the verification back later, and it is what a rights holder will ask for if a job is ever questioned:

curl https://checkmerch.io/v1/verifications/ver_01m00dg3ztf8d739t89rbexycv \
  -H "Authorization: Bearer $CHECKMERCH_KEY"
{
  "id": "ver_01m00dg3ztf8d739t89rbexycv",
  "verdict": "match_authorized",
  "fingerprint_version": "fp-v1",
  "min_confidence": "high",
  "context": { "external_ref": "JOB-40871", "intended_use": "print" },
  "grant": { "id": "grant_01m00dfk2gmq2qr80b7c52t61m", "presented_as": "id" },
  "created_at": "2026-08-14T15:15:55Z",
  "matches": [
    {
      "tier": 1,
      "confidence": "exact",
      "authorized": true,
      "work": { "id": "wrk_01m00dfjt096cmkekpeff96zb2", "title": "Haunted Ramen" }
    }
  ]
}

That record is the answer to "prove you checked": the verdict, your own job reference, and which grant it was made against.

Two limits worth designing around. There is no lookup by external_ref, so if you lose the id you cannot find the verification again: write it down at the moment you get it. And there is no idempotency key, so a retried call creates a second verification with a new id rather than returning the first. Both are survivable if you record the id on first success and treat retries as new checks.

7. Errors

Every error uses the same envelope, so one handler covers all of them:

{ "error": { "code": "image_unreadable", "message": "The submitted image could not be decoded." } }
Status Code What it means What to do
503 fingerprint_unavailable The registry is up, the image is fine, and fingerprinting is temporarily unavailable. Retry. The response carries Retry-After: 30. Back off and try again; do not treat the job as checked.
422 image_unreadable The bytes we received are not a decodable image. Do not retry. Wrong file, wrong format, or a truncated upload. See section 8.
422 blank_image The image decoded but has nothing to fingerprint: fully transparent, or one flat color. Do not retry. Usually the wrong layer or an empty export.
422 invalid_request The request is malformed: no image, an image_url we could not fetch, or a bad field value. Do not retry unchanged. message names the field.
401 unauthorized Missing, malformed, or revoked key. Check the Authorization header. Do not retry.
403 forbidden The key is valid but lacks the verify scope, or the account is suspended. Contact help@checkmerch.io.
404 not_found No such verification under your account. Check the id.
500 matcher_version_mismatch A misconfiguration on our side. Not your request. Retry later, and tell us if it persists.

The important distinction is between fingerprint_unavailable and the two image errors. 503 says nothing about the artwork. A queue that treats it as "this image is bad" will reject clean jobs whenever we have a brief outage, and a queue that treats it as a pass has not checked anything. Retry it, and if it does not clear, hold the job in whatever state your policy uses for "not yet verified."

An unfetchable image_url currently comes back as invalid_request, the same code as a genuinely malformed request. If you use the URL form, check that your files are reachable before you conclude the request was wrong.

8. What to send

Send the production artwork, the file that goes to the press. Raster formats decode: PNG, JPEG, TIFF, GIF, and BMP are all accepted. Format and resolution are not matched on, so a downscaled JPEG export of a PNG-registered design still comes back exact. Send whatever you already have.

PDF, AI, EPS, and SVG are not accepted and return image_unreadable. Most shops already rasterize for proofing, so the usual fix is to verify the proof raster rather than the vector file. Flatten to a single image first: a transparent or empty layer exported on its own returns blank_image.

Uploads are capped at 100 MB. The image_url form has a lower cap of 50 MB, and a file over it comes back as invalid_request rather than a size error, so prefer the upload form for large production files.

Matching survives ordinary production edits. Recolors, resizes, re-encodes, mirroring, and single-ink versions of registered artwork still match, which is the point. Cropped artwork, and artwork recomposed into a larger layout, can fall to possible or below the default floor, which is where section 4 matters: those entries still show up in matches[], and a run of them on one customer's orders is worth a look.

Set your client timeout to at least 30 seconds. A verify call does real work on the image, and a tight timeout turns a normal call into a retry that costs more than the wait.

9. Webhooks

You can register an HTTPS endpoint:

curl -X POST https://checkmerch.io/v1/webhooks \
  -H "Authorization: Bearer $CHECKMERCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://yourshop.example/hooks/checkmerch", "events": ["match.created"]}'

The response includes a secret shown exactly once. Deliveries are signed with it in a CheckMerch-Signature: t=<timestamp>,v1=<hmac> header, where the HMAC is SHA-256 over <timestamp>.<raw body>.

Be aware of what this does and does not give a supplier today. Events are delivered to the account they concern: match.created goes to the rights holder whose work was matched, and grant.revoked goes to the license holder and the rights holder. An account that only verifies is not the subject of either, so registering a webhook on a verify-only key will not produce deliveries. Verification is synchronous: the answer you need is in the response to your own call. Register an endpoint if you also hold grants, and talk to us if you want notification for something else.

10. Getting help

Email help@checkmerch.io. Include the verification id if you have one, or the request you sent if you do not. If a verdict looks wrong, send us the id and the artwork and we will tell you what matched and why.