Not available

HEIC conversion API (not available)

This API is switched off in production. Every request to /api/v1/* returns 404, and no API keys are being issued. It is waiting on a licensing review and may never launch.

The reference below describes how the API would work if it launches: one HTTP request to turn a HEIC/HEIF photo into JPG, PNG, WebP, AVIF, GIF or PDF. The website converter does not use this API — it runs entirely in your browser and works today.

Quickstart

  1. If the API launches, you would get a key that looks like hk_live_…, shown to you exactly once. No keys are being issued today; tell us through the feedback form if you would use one.
  2. Export it: export HEIC_TO_API_KEY=hk_live_…
  3. Send a multipart request with the file:
curl -X POST https://heic-to.io/api/v1/convert \
  -H "Authorization: Bearer $HEIC_TO_API_KEY" \
  -F "file=@IMG_0001.HEIC" \
  -F "format=jpg" \
  -F "quality=85" \
  -F "max_edge=2048" \
  -o IMG_0001.jpg

Authentication

Requests would send a key as a Bearer token: Authorization: Bearer hk_live_…. Only a SHA-256 hash of a key would be stored, so a lost key could not be recovered and would be replaced. Keys would belong on your server, never in a browser or app bundle.

Planned base URL: https://heic-to.io/api/v1, HTTPS only. It returns 404 today.

POST/api/v1/convert

Body: multipart/form-data. On success the response body would be the converted file (status 200). Each successful conversion would use one unit of a monthly quota; failed requests would not.

Form fields
FieldTypeDefaultNotes
fileHEIC / HEIF binaryrequiredThe image to convert. Up to 50 MB and 50 megapixels. For multi-image files (bursts, collections) the primary image is converted.
formatjpg · png · webp · avif · gif · pdfjpg"jpeg" is accepted as an alias for jpg.
qualityinteger 1–10090Encoder quality for jpg, webp, avif and pdf (the embedded JPEG). Ignored for png and gif.
max_edgeinteger 16–16384noneDownscale so the longest side is at most this many pixels, keeping the aspect ratio. Never upscales.
keep_metadatatrue · falsefalseCopy the original EXIF block (camera, date, and GPS location if present) into jpg, png, webp and avif output. By default all metadata is stripped.

Response headers

Content-Type
MIME type of the output, e.g. image/jpeg or application/pdf.
Content-Disposition
attachment; filename="<original name>.<ext>"
X-Image-Width / X-Image-Height
Pixel size of the output image.
X-Image-Count
Number of top-level images in the source file.
RateLimit-Limit / -Remaining / -Reset
Per-minute window (IETF draft names).
X-Quota-Remaining
Conversions left this calendar month (UTC).
X-Request-Id
Quote this when contacting support.

WebP with metadata, saving headers

curl -X POST https://heic-to.io/api/v1/convert \
  -H "Authorization: Bearer $HEIC_TO_API_KEY" \
  -F "file=@photo.heic" -F "format=webp" -F "keep_metadata=true" \
  --fail-with-body -D headers.txt -o photo.webp

GET/api/v1/usage

Would report a key’s plan, the current rate-limit window and the month’s quota, counting towards the per-minute limit but not the monthly quota.

curl https://heic-to.io/api/v1/usage \
  -H "Authorization: Bearer $HEIC_TO_API_KEY"
{
  "plan": "free",
  "key_prefix": "hk_live_3fA9kQ2z",
  "rate_limit": { "per_minute": 10, "remaining": 9, "reset_seconds": 42 },
  "quota": {
    "month": "2026-09",
    "limit": 100,
    "used": 17,
    "remaining": 83,
    "resets_at": "2026-10-01T00:00:00.000Z"
  }
}

Errors

Every error would have the same JSON shape, so you could branch on error.code and show error.message to humans.

{
  "error": {
    "code": "quota_exceeded",
    "message": "Monthly quota of 100 conversions used up; it resets at 2026-10-01T00:00:00.000Z."
  }
}
Error codes
StatuscodeWhen
400invalid_paramsA form field is missing or out of range.
401invalid_api_keyMissing, malformed, unknown or revoked key.
402quota_exceededMonthly conversion quota used up.
413payload_too_largeFile over 50 MB, or image over 50 megapixels.
415unsupported_media_typeBody is not multipart/form-data, or the file is not HEIC/HEIF.
422unsupported_media_typeLooks like HEIC but could not be decoded (corrupt or unsupported coding).
429rate_limitedPer-minute limit hit — honour Retry-After.
500internalUnexpected server error. Safe to retry once.
503busyAll conversion workers are busy — retry after Retry-After seconds.
504timeoutConversion took longer than 30 seconds.

Planned limits

Free

100

conversions / month

10 requests / minute

Pro

10,000

conversions / month

60 requests / minute

Scale

100,000

conversions / month

300 requests / minute

  • Max file size 50 MB; max image size 50 megapixels; 30 s per conversion.
  • Quotas would reset at 00:00 UTC on the first day of each month.
  • On 429 or 503, clients would wait for the Retry-After seconds before retrying.
  • CORS would be open (no cookies), but keys would belong on your server, not in browsers.

Planned data handling

  • Uploaded images would live only in server memory for the duration of the request, and would not be written to disk, cached, logged or used for anything else.
  • Only request metadata would be logged: client ID, key prefix, output format, byte sizes, duration, status and, when a request fails, an error code or message — never file names, pixels or EXIF.
  • Metadata (including GPS location) would be stripped from output unless you send keep_metadata=true.

See the privacy policy for details.