Images

Flint stores image bytes for every image attached through the API. You provide an HTTPS source URL on a catalog or branding write. Flint fetches, validates, normalizes, and stores the image, then returns an immutable Flint URL with its dimensions. Buyer-facing responses use that stored URL instead of depending on the source host.

Attach an image#

Use an ImageInput wherever a resource accepts an image or image gallery:

JSON
{
  "source_url": "https://assets.example.com/products/travel-mug.png",
  "alt": "Matte black travel mug with a closed lid",
  "external_reference_id": "media_8421"
}
  • source_url is required and may contain up to 8,192 characters.
  • alt is optional and may contain up to 512 characters. Use meaningful text when the image communicates information.
  • external_reference_id is optional and may contain 1 to 255 characters without leading or trailing whitespace. It is an opaque, case-sensitive value. Use it to associate the attachment with a stable media ID in your system.

The source must use HTTPS on port 443. URLs with credentials or fragments are rejected. Query strings are accepted for signed URLs, but Flint never returns the source URL or includes it in a public error.

JPEG, PNG, and WebP are supported. The source image must be no larger than 5 MB or 40 megapixels, with each dimension between 1 and 12,000 pixels. Animated images are rejected. Delivery variants may still be encoded as AVIF for compatible browsers.

Use an idempotency key for safe retries

Idempotency-Key is optional, including for writes with an external source_url. Generate and persist one key before the first request when you need safe timeout or transport retries. Reuse that key only for the same logical mutation.

When you omit the header, Flint generates an effective key and returns it in the Idempotency-Key response header. That returned key can replay the result after you receive the first response, but it cannot protect a retry when the first response is lost. Supply your own key for that case.

This example replaces a product's gallery:

Bash
curl -X PUT https://api.withflintpay.com/v1/products/prod_01JXYZ1234567890ABCDEFGHJK/images \
  -H "Authorization: Bearer flint_test_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 638d2293-0637-4704-adf0-89c47277e01b" \
  -d '{
    "images": [
      {
        "source_url": "https://assets.example.com/products/travel-mug-front.png",
        "alt": "Front view of a matte black travel mug",
        "external_reference_id": "media_8421"
      },
      {
        "source_url": "https://assets.example.com/products/travel-mug-open.png",
        "alt": "Travel mug with the lid open",
        "external_reference_id": "media_8422"
      }
    ]
  }'

The resource response contains canonical image data:

JSON
{
  "url": "https://images.withflintpay.com/canonical/mer_01JXYZ1234567890ABCDEFGHJK/live/img_01JXYZ1234567890ABCDEFGHJK/original.png",
  "alt": "Front view of a matte black travel mug",
  "external_reference_id": "media_8421",
  "width": 1600,
  "height": 1200
}

Store the returned url when you need to reference the same bytes on a transaction input. Do not reconstruct or modify Flint image URLs.

Products, variants, bundles, and subscription plans accept ordered galleries of up to eight images. Gallery writes use full replacement:

  • Every gallery replacement request must include images. Do not call the gallery endpoint when the gallery should remain unchanged.
  • Send images: [] to clear the gallery.
  • Send a non-empty array to replace the complete gallery in that order.
  • Reorder images by sending the complete array in the new order.

Use these dedicated collection endpoints:

  • PUT /v1/products/{product_id}/images
  • PUT /v1/products/{product_id}/variants/{variant_id}/images
  • PUT /v1/bundles/{bundle_id}/images
  • PUT /v1/subscription-plans/{plan_id}/images

The corresponding parent PATCH endpoints do not accept images or expected_image_revision.

The first image is the primary image used for line-item thumbnails and transaction snapshots.

Payment links and merchant logos are single-image fields. Omit the field to keep it unchanged, send null to clear it, or send an ImageInput to replace it.

Variant inheritance#

A product variant can inherit its product gallery or replace it completely:

  • images is the authored variant override.
  • effective_images is the display-ready gallery.
  • images_inherited tells you whether effective_images came from the product.

A non-empty variant gallery replaces the product gallery. The two galleries are not merged. Send images: [] to PUT /v1/products/{product_id}/variants/{variant_id}/images to remove the override and resume inheritance. Buyer-facing renderers should use effective_images; catalog synchronization should read and write images.

Avoid fetching an unchanged source#

Flint must fetch and decode an external URL before it can determine whether the bytes already exist. If your media record has not changed, skip the gallery replacement request. external_reference_id gives your synchronization process a stable value to compare without relying on signed URL text.

Within one gallery, both source_url and external_reference_id must be unique. One API mutation may ingest at most 32 external images and 100 megapixels in total. For larger catalog updates, create or update resources in smaller requests.

Images on orders and invoices#

Catalog-backed line items automatically snapshot the resolved primary image. Later catalog edits do not change an existing order, invoice, or subscription snapshot.

Transaction creation does not fetch external image hosts. An ad hoc line item may omit its image or use ImageReferenceInput with a canonical Flint URL previously returned for the same merchant and environment:

JSON
{
  "image": {
    "url": "https://images.withflintpay.com/canonical/mer_01JXYZ1234567890ABCDEFGHJK/live/img_01JXYZ1234567890ABCDEFGHJK/original.png",
    "alt": "Front view of a matte black travel mug"
  }
}

An external URL on an order or invoice input is rejected. Attach it to a catalog or branding resource first, then use the returned Flint URL.

Concurrent updates#

Every image owner returns an image_revision. Gallery replacement is last-write-wins by default. To prevent one editor from overwriting another, send the revision you read as expected_image_revision. A stale value returns 409 with error.code: IMAGE_REVISION_MISMATCH and does not change the gallery.

Errors and retries#

Image failures use the standard error envelope. The param points to the exact input, including nested paths such as variants[3].images[1].source_url. Each error includes remediation that indicates whether retrying is appropriate.

Common codes include:

CodeMeaning
IMAGE_SOURCE_URL_INVALIDThe source URL is malformed or violates the HTTPS source rules.
IMAGE_SOURCE_UNAVAILABLEFlint could not retrieve the source. Follow the error's retry guidance.
IMAGE_FORMAT_UNSUPPORTEDThe file format or animation is unsupported.
IMAGE_DIMENSIONS_EXCEEDEDThe decoded dimensions exceed the supported limits.
NORMALIZED_IMAGE_TOO_LARGEThe normalized canonical image exceeds the byte limit.
IMAGE_GALLERY_LIMIT_EXCEEDEDA gallery contains more than eight images.
IMAGE_INGESTION_COUNT_EXCEEDEDOne mutation contains more than 32 external sources.
IMAGE_INGESTION_IN_PROGRESSThe image input is still owned by an active processing attempt. Wait briefly, then retry the same request and key.
IMAGE_INGESTION_RESTART_REQUIREDThe prior attempt ended or lost a duplicate-content race. Wait briefly for cleanup, then start a new ingestion with a new idempotency key.
IMAGE_INGESTION_EXPIREDThe previous attempt expired and its storage was cleaned up. Start a fresh attempt with a new idempotency key.
IMAGE_STORAGE_QUOTA_EXCEEDEDThe merchant's image storage quota is full.
IMAGE_REVISION_MISMATCHexpected_image_revision does not match the current owner revision.
EXTERNAL_IMAGE_NOT_ALLOWEDA transaction input tried to fetch an external image.

Storage quota errors include machine-readable byte totals and, when available, the next ready-but-unattached image expiration time. Do not loop on quota failures. Remove unused attachments or wait for the stated expiration before retrying.

An idempotent retry can replay a completed image result, but it does not take ownership of an attempt that is still processing. IMAGE_INGESTION_IN_PROGRESS is the only image-ingestion error that asks you to retry the same key. Once an owned attempt returns an error, that key replays the stable error. When remediation.next_actions contains start_new_image_ingestion, wait for any stated retry delay and submit the logical mutation again with a new idempotency key.

Environment isolation#

Catalog and checkout images belong to one merchant and one environment. A test or sandbox image URL cannot attach to a live resource, and one merchant cannot attach another merchant's URL. Merchant logos are global to that merchant and are accepted only by the logo field.

Environment copies do not carry image assets across the boundary. Attach images separately in the target environment.

Next steps#

Rate this doc