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:
{
"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_urlis required and may contain up to 8,192 characters.altis optional and may contain up to 512 characters. Use meaningful text when the image communicates information.external_reference_idis 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:
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:
{
"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.
Gallery replacement#
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}/imagesPUT /v1/products/{product_id}/variants/{variant_id}/imagesPUT /v1/bundles/{bundle_id}/imagesPUT /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:
imagesis the authored variant override.effective_imagesis the display-ready gallery.images_inheritedtells you whethereffective_imagescame 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:
{
"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:
| Code | Meaning |
|---|---|
IMAGE_SOURCE_URL_INVALID | The source URL is malformed or violates the HTTPS source rules. |
IMAGE_SOURCE_UNAVAILABLE | Flint could not retrieve the source. Follow the error's retry guidance. |
IMAGE_FORMAT_UNSUPPORTED | The file format or animation is unsupported. |
IMAGE_DIMENSIONS_EXCEEDED | The decoded dimensions exceed the supported limits. |
NORMALIZED_IMAGE_TOO_LARGE | The normalized canonical image exceeds the byte limit. |
IMAGE_GALLERY_LIMIT_EXCEEDED | A gallery contains more than eight images. |
IMAGE_INGESTION_COUNT_EXCEEDED | One mutation contains more than 32 external sources. |
IMAGE_INGESTION_IN_PROGRESS | The image input is still owned by an active processing attempt. Wait briefly, then retry the same request and key. |
IMAGE_INGESTION_RESTART_REQUIRED | The 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_EXPIRED | The previous attempt expired and its storage was cleaned up. Start a fresh attempt with a new idempotency key. |
IMAGE_STORAGE_QUOTA_EXCEEDED | The merchant's image storage quota is full. |
IMAGE_REVISION_MISMATCH | expected_image_revision does not match the current owner revision. |
EXTERNAL_IMAGE_NOT_ALLOWED | A 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#
- Products: create products and variant galleries.
- Bundles: attach images to sellable bundles.
- Subscription plans: add plan imagery.
- Payment links: configure a single hosted-page image.
- Merchants: update merchant branding.
- Idempotency: persist keys and retry writes safely.
