Image Translate
Replace text inside product images with a translation that preserves the original visual style — font weight, colour, layout, and badge backdrops.
Endpoint
POST /v1/image-translate/translateSend a product image as raw bytes; receive the same image with the source text removed and replaced with a translation. Font, weight, and render mode are caller-controlled via query string.
- Input languages — Chinese (Simplified or Traditional), Japanese, English. Pass
autoto skip the script gate. - Output languages — any ISO-639-style code. Visual fidelity depends on whether the chosen
fontcovers the script. Korean, Simplified Chinese, Japanese, and Latin targets are first-class. Call/supported-fontsfor the live list of fonts and the source-language codes accepted.
Request Format
The endpoint accepts raw image bytes in the HTTP body. JSON-with-base64 is not accepted.
| Header | Required | Notes |
|---|---|---|
Content-Type | Yes | image/jpeg, image/png, image/webp, or application/octet-stream |
x-api-key | Yes | Your API key |
All other parameters travel in the query string:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
source_language | string | Yes | — | One of ch, zh, zh-CN, zh-TW, chinese_cht, ja, japan, en, or auto. auto skips the script gate — every region with a translation is rendered. |
target_language | string (2–8) | Yes | — | ISO-639-style target code. Free-form. |
font | string | No | Pretendard | One of the values returned by /supported-fonts under allFonts. URL-encode if it contains non-ASCII characters. Pretendard is the default — modern Korean variable sans with full Hangul, CJK punctuation, and Latin Extended-A coverage. The renderer also runs a built-in fallback chain so any glyph the chosen font is missing (e.g. CJK Han ideographs) is filled from Pretendard and NotoSansSC automatically. |
font_weight | string | No | ExtraBold | One of auto, Thin, Light, Regular, Medium, SemiBold, Bold, ExtraBold, Black. auto matches the original per-region weight where possible; any explicit weight overrides every region uniformly. |
render | boolean | No | true | true returns a final translated image (binary). false returns the inpainted image plus per-region metadata as JSON so you can draw the overlay yourself. |
Response
render=true (default) — binary image
When render=true the response body is the rendered JPEG bytes with Content-Type: image/jpeg. Metadata flows through response headers:
| Header | Description |
|---|---|
X-Apinoa-Request-Id | Request id (echo this when filing a ticket) |
X-Apinoa-Regions-Count | Number of text regions detected and replaced |
If no text is detected, the response is the original image bytes with X-Apinoa-Regions-Count: 0.
render=false — JSON with regions
When render=false the response is JSON:
{
"success": true,
"requestId": "550e8400e29b41d4a716446655440000",
"data": {
"inpainted_base64": "<base64 JPEG with text removed>",
"regions": [
{
"polygon": [[10, 20], [210, 20], [210, 60], [10, 60]],
"bbox": { "x": 10, "y": 20, "w": 200, "h": 40 },
"text": "免费送货",
"text_kr": "무료 배송",
"confidence": 0.98,
"text_color": { "r": 255, "g": 255, "b": 255 },
"bg_color": { "r": 200, "g": 50, "b": 50 },
"font_weight": "Bold",
"is_badge": true
}
],
"regions_count": 5
}
}| Field | Type | Description |
|---|---|---|
inpainted_base64 | string | Base64-encoded JPEG with the source text removed. |
regions[].polygon | number[][] | 4-point polygon in original image coordinates. |
regions[].bbox | object | Axis-aligned bounding box in original coordinates. |
regions[].text | string | Detected source text. |
regions[].text_kr | string | The translation, in the requested target_language. |
regions[].confidence | number | Recognition confidence, 0–1. |
regions[].text_color / bg_color | object | RGB colour estimates. |
regions[].font_weight | string | Detected weight: Thin, Regular, Bold. |
regions[].is_badge | boolean | true for filled-rectangle text labels (price tags, sale badges). |
Examples
Basic translation (cURL)
curl -X POST "https://gateway.apinoa.com/v1/image-translate/translate?source_language=zh-CN&target_language=ko" \
-H "x-api-key: $APINOA_API_KEY" \
-H "Content-Type: image/jpeg" \
--data-binary @product.jpg \
-o translated.jpgPick a font (Python)
import requests
with open("product.jpg", "rb") as f:
img_bytes = f.read()
r = requests.post(
"https://gateway.apinoa.com/v1/image-translate/translate",
headers={
"x-api-key": "YOUR_API_KEY",
"Content-Type": "image/jpeg",
},
params={
"source_language": "zh-CN",
"target_language": "ko",
"font": "Pretendard",
"font_weight": "Bold",
},
data=img_bytes,
timeout=300, # the server timeout is 5 minutes — see "Timeouts" below
)
r.raise_for_status()
with open("translated.jpg", "wb") as f:
f.write(r.content)
print("regions:", r.headers.get("X-Apinoa-Regions-Count"))Inpaint-only, render client-side (Node.js / TypeScript)
import { readFileSync, writeFileSync } from "node:fs";
const image = readFileSync("product.jpg");
const url = new URL("https://gateway.apinoa.com/v1/image-translate/translate");
url.searchParams.set("source_language", "zh-CN");
url.searchParams.set("target_language", "ko");
url.searchParams.set("render", "false");
const res = await fetch(url, {
method: "POST",
headers: {
"x-api-key": process.env.APINOA_API_KEY!,
"Content-Type": "image/jpeg",
},
body: image,
});
const { data } = (await res.json()) as {
data: { inpainted_base64: string; regions: any[] };
};
writeFileSync("inpainted.jpg", Buffer.from(data.inpainted_base64, "base64"));Limits
| Limit | Value |
|---|---|
| Max image dimensions | 8192 × 8192 px |
| Max request body | 20 MB |
| Per-API-key QPS | matches your plan's per-second limit |
| Image format | JPEG, PNG, WebP |
Oversize images return 413 Payload Too Large. Excessive QPS returns 429 Too Many Requests with retry-after set.
Errors
| Code | HTTP | When | What to do |
|---|---|---|---|
BAD_REQUEST | 400 | Empty body or invalid query params | Check the request shape |
UNAUTHORIZED | 401 | Missing or invalid API key | Check the x-api-key header |
UNSUPPORTED_MEDIA_TYPE | 415 | Content-Type is not a binary image type | Set Content-Type: image/jpeg (or png/webp) and POST raw bytes |
PAYLOAD_TOO_LARGE | 413 | Body > 20 MB | Downscale client-side |
INSUFFICIENT_BALANCE | 402 | Your balance does not cover the call | Top up under Dashboard > Balance; retrying will not clear it |
UPSTREAM_ERROR | 502 | Image processing temporarily unavailable | Retry; usually recovers in < 60 s |
SERVICE_UNAVAILABLE | 503 | Temporarily over capacity | Retry with backoff |
Timeouts
A request can take up to 5 minutes (300 s) under heavy load. Set your HTTP client's timeout to at least 300 seconds so a slow response is not aborted on your side.
| Latency | Typical | Worst case |
|---|---|---|
| Normal load | 1.5 – 3 s | < 10 s |
| Heavy load | 5 – 30 s | 5 min |
Most requests finish in a few seconds; the 5-minute window only matters under sustained burst traffic. If you reliably approach it, lower your concurrency or contact support.
Pricing
Each call is charged against your balance at the image-translate rate on the pricing page.
Support
If you hit repeated 5xx errors or see obvious quality regressions, include the requestId from the X-Apinoa-Request-Id response header when you open a ticket at apinoa.com/contact.
Image Translate
Translate the text inside product images and keep the design — the four Image Translate endpoints, what each returns, and how they are billed.
OCR Only
Detect and read text in a product image. Returns recognised regions with polygons, bounding boxes, text, and confidence — no inpainting, no translation, no rendering.