Vai al contenuto
imgmarker

API reference

Label AI images from your own software. One call in, one finished image out, with the badge drawn in and the metadata written.

This reference is available in English only.

Introduction

The API does what the website does, through the same code. An image goes in, the badge is drawn into one of the four corners, and the IPTC digital source type is written to XMP and IPTC. Calls are synchronous: labelling takes milliseconds, so there is no job id and nothing to poll.

Base URL
https://imgmarker.net/api/v1
Also reachable at
https://api.imgmarker.net/v1
Request
multipart/form-data
Response
the image itself, errors as JSON
Max file size
25 MB
Max pixels
50 megapixels
Formats
JPEG, PNG, WebP
Authentication
Bearer token

Authentication

Create a key in your account and send it as a bearer token. We keep only a hash of it, the same way we keep passwords, so a key is shown once at creation and can never be looked up again. Lose it and you make a new one.

Authorization: Bearer 7|kJ3fQ2m…
Accept: application/json
  • Keep keys on your server. A key in browser JavaScript is a key you have given away.
  • One key per integration, so revoking one does not stop the others.
  • Revoking takes effect on the next call, with no delay.

Quickstart

Label one image and write the result next to the original.

curl -X POST https://imgmarker.net/api/v1/label \
  -H "Authorization: Bearer YOUR_KEY" \
  -F image=@photo.jpg \
  -F type=generated \
  -F corner=br \
  -o photo-aimarked.jpg
POST

/api/v1/label

Labels one image and returns it. A successful response carries the image file, not JSON. The filename keeps its stem and gains -aimarked before the extension.

Request fields

Field Type Required Description
image file yes JPEG, PNG or WebP. Up to 25 MB and 50 megapixels.
type string yes generated for fully AI generated images, modified for AI edited ones. Decides which source type is written.
corner string yes tl, tr, bl or br. Where the badge goes.
badge_locale string no en, de, es, it or fr. Language of the badge caption. Defaults to en.
creator string no Written to XMP-dc:Creator and IPTC By-line. Up to 200 characters.
description string no Written to XMP-dc:Description and IPTC Caption-Abstract. Up to 2000 characters.
credit string no Written to XMP-photoshop:Credit. Up to 200 characters.
c2pa_acknowledged boolean no Needed only when the image carries Content Credentials. See below.
response string no file returns the image itself, the default. url stores the result and returns a link.

Response headers

X-Imgmarker-Credits-Remaining Images left in the current period, after this call.
Content-Type image/jpeg, image/png or image/webp, matching the input.
Content-Disposition attachment, with the new filename.

Full example, with metadata

curl -X POST https://imgmarker.net/api/v1/label \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Idempotency-Key: article-88-hero" \
  -F image=@hero.png \
  -F type=modified \
  -F corner=tl \
  -F badge_locale=de \
  -F "creator=Redaktion Beispielblatt" \
  -F "description=Retuschiert mit generativer KI" \
  -F "credit=Beispielblatt / imgmarker" \
  -D headers.txt \
  -o hero-aimarked.png

A link instead of the image

Send response=url and you get JSON with a download link instead of the file. Useful when the URL is passed on to something else, or when you want the metadata without reading headers.

curl -X POST https://imgmarker.net/api/v1/label \
  -H "Authorization: Bearer YOUR_KEY" \
  -F image=@photo.jpg \
  -F type=generated \
  -F corner=br \
  -F response=url

The trade is storage. With the default the image passes through and nothing is kept; with a link it sits on our disk until expires_at, 60 minutes after the call. The link is the entire authorisation, so treat it as a secret. Download once, store the file yourself, and use the default when you have nowhere to pass a URL.

POST

/api/v1/label/batch

Several images in one call, answered with links. Included in the plans that list the batch endpoint; the others get 403 batch_not_available.

Parallel single calls are usually faster

Twenty concurrent calls to /label finish sooner than one batch worked through in sequence, and a failure costs you one image rather than the wait for the whole set. Use the batch endpoint when fanning out is awkward on your side.

Fields are the same as for a single call, with images[] instead of image. One type and one corner apply to the whole batch. How many images fit in one call comes from your plan.

curl -X POST https://imgmarker.net/api/v1/label/batch \
  -H "Authorization: Bearer YOUR_KEY" \
  -F "images[]=@one.jpg" \
  -F "images[]=@two.png" \
  -F "images[]=@three.webp" \
  -F type=generated \
  -F corner=br
  • Only images that came out the other end are charged. A rejected one costs nothing.
  • If your remaining allowance is smaller than the batch, nothing is processed and the call answers 402. Half a batch you cannot account for is worse than none.
  • zip_url hands you everything that worked as one archive.
  • Every link expires with the batch, 60 minutes after the call.
GET

/api/v1/usage

What is left, and when it refills. Cheap to call before a large run, though the credits header on every label call usually makes it unnecessary.

curl https://imgmarker.net/api/v1/usage \
  -H "Authorization: Bearer YOUR_KEY"
GET

/api/v1/health

Reachability, no key required. Meant for uptime monitoring, so it never touches your allowance.

curl https://imgmarker.net/api/v1/health

Idempotency

When a connection drops mid-call you cannot tell whether the image was labelled and charged. Send an Idempotency-Key header with a value you choose, up to 128 characters, and a repeat of that call is not charged again.

  • A repeat with a used key answers 409 already_processed. The image is not sent a second time, because we no longer hold it.
  • Failed calls are not recorded, so the key stays usable after an error.
  • Keys belong to your account. Two customers may use the same value without colliding.
  • A file hash makes a good key: same input, same key, no accidental double charge.
curl -X POST https://imgmarker.net/api/v1/label \
  -H "Authorization: Bearer YOUR_KEY" \
  -H "Idempotency-Key: order-4711-image-3" \
  -F image=@photo.jpg -F type=generated -F corner=br

Quota and rate limits

Two separate limits, answering with different codes so you can tell them apart without guessing.

402 quota_exceeded

The monthly allowance is used up. Waiting will not help before the reset date, a larger plan will. We do not bill for overage, so nothing runs up an invoice you did not expect.

429 Too Many Requests

Too many calls per minute for your plan. Read Retry-After and back off. The allowance itself is untouched.

The rate limit counts per key rather than per account, so one runaway script does not throttle your other integrations. The monthly allowance resets on your billing date, not on the first of the month, so a subscription taken out on the 28th still gets a full period.

A rejected image costs nothing. Only calls that return an image are counted.

Content Credentials

Labelling re-encodes the image, which breaks the pixel hash inside an existing C2PA manifest. A broken signature is worse than none, because a verifier reports it as tampered. So when we find credentials, the call is refused rather than quietly destroying them.

HTTP/1.1 409 Conflict

{
  "error": "c2pa_consent_required",
  "message": "This image carries Content Credentials. Labelling invalidates them. Repeat the request with c2pa_acknowledged=true to proceed."
}

Once acknowledged, the invalidated manifest is removed instead of being left in place broken. The same rule applies on the website, so the API cannot be used to sidestep a consent the interface insists on.

What ends up in the file

Two things: the badge drawn into the pixels, and the machine readable declaration. The declaration is the IPTC digital source type, written to XMP and to legacy IPTC so old and new readers agree.

type DigitalSourceType
generated http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia
modified http://cv.iptc.org/newscodes/digitalsourcetype/compositeWithTrainedAlgorithmicMedia

Note the http: the IPTC vocabulary uses that form, and a reader matching the exact string would miss anything else.

Checking the result

exiftool -G1 -s \
  -XMP-iptcExt:DigitalSourceType \
  -IPTC:Caption-Abstract \
  -XMP-dc:Creator \
  photo-aimarked.jpg

Error reference

Every error is JSON with a stable error field. Branch on that, not on the human readable message, which may be reworded.

{
  "error": "too_many_pixels",
  "message": "The image has too many pixels."
}
Status error What to do
401 Missing, malformed or revoked key. Create a new one in your account.
402 quota_exceeded Wait for the reset date, or move to a larger plan.
403 api_not_available Your plan does not include API access.
403 batch_not_available Your plan does not include the batch endpoint.
409 c2pa_consent_required Repeat with c2pa_acknowledged=true if you accept losing the credentials.
409 already_processed This Idempotency-Key was used before. Use a new one to label again.
400 invalid_idempotency_key Shorten the key to 128 characters or fewer.
422 too_large The file exceeds 25 MB.
422 too_many_pixels More than 50 megapixels. Downscale first.
422 unsupported_type Only JPEG, PNG and WebP are accepted.
422 corrupt The file is not a readable image.
422 Validation failed. The response lists the fields under "errors".
429 Rate limit for your plan. Read Retry-After and back off.
500 processing_failed Something broke on our side. Safe to retry with a new Idempotency-Key.

Retrying sensibly

Retry on 429 and 500, with a growing delay. Never retry 402 or 422: the answer will not change until you change something.

import time

RETRYABLE = {429, 500, 502, 503}


def with_retries(call, attempts=4):
    for attempt in range(attempts):
        response = call()

        if response.status_code not in RETRYABLE:
            return response

        # Honour Retry-After when the server sends one
        wait = float(response.headers.get("Retry-After", 2 ** attempt))
        time.sleep(wait)

    return response

Retention

Nothing is kept. The image exists on our server for the duration of the call and is deleted once the response has gone out. We record the call itself for billing and your usage statistics: timestamp, label type, file format, status. No filename, no image data.

Responsibility for labelling correctly stays with you. We write what you ask us to write. Deciding whether an image needs a label, and which one, is yours.

Versioning

The version sits in the path. Within v1 we add fields and error codes but never remove or rename them, so parsing by field name stays safe. Anything that would break your integration goes into a new version, and v1 keeps running.

Questions about the API: info@fezznrw.de