v1.0.0

API reference

A small, synchronous API. Send an image, get an optimized one back. Everything on this page is generated from the same document your code generator reads.

OpenAPI document

Point a client generator at this URL, or import it into your API tool of choice. It is public: you do not need a key to read it.

https://ecommerceimageoptimizer.com/v1/openapi.json

Getting a key

Keys are created from your account page. A live key spends your allowance; a test key runs the same image pipeline and spends nothing, so you can build against real output before you commit to anything.

Authentication

Send the key as a bearer token on every request. Keep it on your server: this API sends no CORS headers, because a key in browser JavaScript is a published key.

Authorization: Bearer iok_live_R3kP...

Your first call

Optimize one image and print the URL of the result. The URL is signed and short lived, so fetch it as part of handling the response rather than storing it.

curl -X POST https://ecommerceimageoptimizer.com/v1/images/optimize \
  -H "Authorization: Bearer $IOK_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d "{
        \"image_base64\": \"$(base64 -w0 product.jpg)\",
        \"file_name\": \"product.jpg\",
        \"config\": { \"outputFormat\": \"webp\", \"quality\": 78 }
      }"

Limits and cost

  • 500 calls a month are free per account. After that each call spends one credit.
  • Sending the same image again within 24 hours is free. The credit unit is a unique source image, not a request, so retries and reprocessing are never billed twice. The billable field in the response tells you which happened.
  • 120 requests a minute per key. Every response carries the real remaining count, so backing off on X-RateLimit-Remaining works.
  • Images up to 3 MB.

Retrying safely

Send an Idempotency-Key header and a retry cannot charge you twice. For 24 hours the same key returns the same result with a freshly signed URL. The same key with a different body is refused with 422 rather than quietly treated as a new request.

Errors

Every failure is an RFC 9457 problem document. Switch on type, which is stable; title is written for people and may change.

{
  "type": "https://ecommerceimageoptimizer.com/problems/insufficient_credits",
  "title": "The free monthly allowance is spent and this account has no credits left.",
  "status": 402
}

Endpoints

get/v1/account

Balance, allowance and key details

Read-only and unmetered: asking how much allowance you have never spends any of it. This is the call to make first to check a key works.

A test key sees the account's real balance and real usage, with mode saying which world it is in. Hiding them would make the endpoint useless for checking an integration.

Scope: account:read

Responses

200
The account state.
401
No key, or a key that is not valid.
403
The key lacks the account:read scope.
429
Over the per-key rate limit.
503
The API is temporarily unavailable.
get/v1/presets

List the built-in presets and your own

Built-in presets are shared by every account and cannot be edited. Your own are the ones saved through this endpoint or in the web app: an API key and a signed-in browser session on the same account see the same list.

Scope: presets:read

Responses

200
Every preset available to this account.
401
No key, or a key that is not valid.
403
The key lacks the presets:read scope.
429
Over the per-key rate limit.
503
The API is temporarily unavailable.
post/v1/presets

Save a preset

Send only the fields you care about. The stored preset is your config layered over the defaults, so reading it back gives a complete config rather than a fragment whose meaning depends on which build answered.

Scope: presets:write

Request body

namestringrequired
descriptionstringoptional
tagsstring[]optional
product_typegeneral | fashion | jewelry | electronics | food | furnitureoptional
configPresetConfigrequired
Image settings. Every field is optional and unknown fields are ignored, so a client written against an older version keeps working. GET /v1/presets returns complete configs, which is the easiest way to see every field.

Responses

201
The preset as stored.
400
The body is not valid. errors names the fields.
401
No key, or a key that is not valid.
403
The key lacks the presets:write scope.
409
This account has reached the preset limit.
429
Over the per-key rate limit.
503
The API is temporarily unavailable.
post/v1/images/optimize

Optimize one image

Synchronous. Send base64 image bytes, get a signed URL to the result.

The URL is valid for 60 seconds, so fetch it as part of handling the response rather than storing it. Sending Idempotency-Key makes a retry safe: for 24 hours the same key returns the same result with a freshly signed URL, and the same key with a different body is 422 rather than a silent second charge.

Configuration is layered: defaults, then preset_id, then config. So preset_id plus config: {"quality": 90} means "that preset, at 90".

Scope: images:write

Headers

Idempotency-Keystringoptional
Any string up to 255 characters. Honoured for 24 hours.

Request body

image_base64stringrequired
Raw base64, or a data URL. Up to 3 MB decoded.
file_namestringoptional
Used for the output name. Defaults to image.
preset_idstringoptional
A built-in preset id, or one of yours from GET /v1/presets.
configPresetConfigoptional
Image settings. Every field is optional and unknown fields are ignored, so a client written against an older version keeps working. GET /v1/presets returns complete configs, which is the easiest way to see every field.

Responses

200
The optimized image.
400
The body is not valid.
401
No key, or a key that is not valid.
402
The free allowance is spent and there are no credits left.
403
The key lacks the images:write scope.
404
No preset with that id belongs to this account.
409
A request with this Idempotency-Key is still running.
413
The image is over the size limit for this tier.
422
Either the image could not be processed, or this Idempotency-Key was already used for a different request.
429
Over the per-key rate limit.
500
The image could not be optimized. You were not charged.
503
The API is temporarily unavailable.