# OptikAPI — Complete API integration guide API version: v1 Documentation reviewed: 2026-09-23 Website: https://opticalbackgroundremover.com API base URL: https://opticalbackgroundremover.com/api/v1 Human-readable docs: https://opticalbackgroundremover.com/docs LLM documentation index: https://opticalbackgroundremover.com/llms.txt This file can be downloaded and supplied to a coding assistant as API reference. It contains public integration details only; it contains no credentials. ## 1. Setup and authentication 1. Create an account at https://opticalbackgroundremover.com/register. 2. Create an API key at https://opticalbackgroundremover.com/app/api-keys. 3. Store the key in a server-side environment variable named OPTIK_API_KEY. 4. Send Authorization: Bearer YOUR_OPTIK_API_KEY on every /api/v1 request. Keys have an optk_ prefix. Never put a key in client-side JavaScript, a public repository, a URL, logs, or a prompt. For a browser app, call OptikAPI from your own authenticated backend and return the result to the browser. Do not use Dodo payment credentials or an x-api-key header for this public API. ## 2. Endpoint summary POST /remove-background Transparent background, source geometry preserved. POST /prepare-product Background removal plus optical alignment on a 2000 x 2000 white canvas in one request. GET /credits Current account credit balance. GET /jobs/{job_id} Status and result of an existing account-owned job. All paths above are relative to the API base URL. The two POST endpoints each cost 1 credit per successful request. Preparation already includes background removal; calling both endpoints separately costs 2 credits. ## 3. Image uploads Use multipart/form-data with exactly one file field named file. Accepted MIME types: image/jpeg, image/png, image/webp. Maximum file size: 10 MiB (10 * 1024 * 1024 bytes). The declared MIME type must match the file contents. Empty files are rejected. Let your HTTP library generate the multipart Content-Type and boundary. Optional header: Idempotency-Key: a stable unique identifier for one logical image request. The value must contain 1–200 visible ASCII characters with no spaces. Use a UUID generated once for that logical request and persist it for retries. The public endpoints do not expose configurable canvas dimensions, category, subcategory, title, background color, or output_format. Do not build an integration that depends on these fields. The service is selected by URL. ## 4. Processing responses A new image request normally waits for processing and returns HTTP 200 JSON: { "job_id": "example-job-id", "status": "succeeded", "output_url": "data:image/png;base64,...", "credits_consumed": 1, "reserved_credits": 1 } output_url is an inline base64 data URL, not a remote download URL. Parse its MIME type, decode the portion after the comma, and save the bytes. PNG is the normal output; the response MIME prefix is authoritative. The response does not contain product classifications or bounding boxes. reserved_credits is a legacy successful-response field mirroring the job cost; it is not a second charge or a statement that the balance remains reserved. Use credits_consumed and GET /credits to understand charges and balance. An idempotent replay of a still-active job returns HTTP 202 with a job_id and status accepted or processing. It includes Retry-After: 3. Poll GET /jobs/{id} until status becomes succeeded or failed, using a bounded polling deadline. Do not start a new job just because a replay returned 202. A processing failure returns HTTP 422 with a job_id, status failed, and: {"error":{"code":"PROCESSING_FAILED","message":"..."}} ## 5. Idempotency and retries Reusing an Idempotency-Key with the same service, image bytes and MIME type returns the existing job without charging for a second job. Replays include Idempotency-Replayed: true. Using that key with a different image, MIME type, or service returns HTTP 409 IDEMPOTENCY_CONFLICT. If your connection times out and the outcome is unknown, retry with the SAME key and image, or query the job if you already have its ID. Never generate a new key for an uncertain retry. A known failed job remains failed on replay; a deliberate fresh attempt after a terminal failure needs a new key. Honor Retry-After on HTTP 429 and 202. Retry transient network/5xx failures with bounded exponential backoff and jitter. Distinguish a 429 before a job was accepted from a 429 with a failed job_id (queue rejection). A terminal failed job's reservation is released; use a fresh key only for an intentional new attempt after that known failure. Do not repeatedly retry authentication, validation, insufficient-credit, or idempotency-conflict errors unchanged. Processing can take time. Allow an appropriate server request timeout, such as 120 seconds; a client timeout does not cancel or prove failure of the job. ## 6. Credit balance GET https://opticalbackgroundremover.com/api/v1/credits Authorization: Bearer YOUR_OPTIK_API_KEY HTTP 200: {"credits":{"total":100,"reserved":0,"available":100}} One credit is reserved when a new image job is accepted. Successful processing consumes it; failure releases it. New accounts receive 100 free credits once. Paid packs are one-time purchases, not subscriptions. Both services use the same balance. Credits currently have no expiry. Top up through the account's Credits page; payment confirmation is required before purchased credits appear. ## 7. Job lookup GET https://opticalbackgroundremover.com/api/v1/jobs/{job_id} Authorization: Bearer YOUR_OPTIK_API_KEY HTTP 200 for an existing job, including a failed or still-processing job: { "job_id": "example-job-id", "status": "succeeded", "service_type": "prepare-product", "created_at": "2026-09-23T12:00:00.000Z", "output_url": "data:image/png;base64,...", "credits_consumed": 1 } Statuses: accepted, processing, succeeded, failed. output_url and credits_consumed appear on success only. On failure, this GET endpoint's error field is a string (unlike POST's nested error object). Missing jobs and jobs belonging to another account return HTTP 404. There is no public job-cancel endpoint and no customer image-result webhook. ## 8. Limits and errors Default API rate limit: 300 requests/minute per account across /api/v1 routes. Processing concurrency default: 3, with up to 20 waiting requests and a 20-second queue wait. Limits can change; honor returned 429 and Retry-After. Keep batch processing concurrency at 3 or lower rather than sending an unbounded burst. Do not use frequent unbounded job polling. Error responses normally use: {"error":{"code":"INSUFFICIENT_CREDITS","message":"Insufficient credits."}} HTTP 400: BAD_REQUEST or INVALID_IDEMPOTENCY_KEY; fix request format. HTTP 401: UNAUTHORIZED or ACCOUNT_NOT_FOUND; check server-side API key. HTTP 402: INSUFFICIENT_CREDITS; top up the account. HTTP 403: ACCOUNT_SUSPENDED; contact support. HTTP 404: NOT_FOUND; check job ID and account ownership. HTTP 409: IDEMPOTENCY_CONFLICT; key was reused for different input. HTTP 413: PAYLOAD_TOO_LARGE; reduce file size. HTTP 422: UNSUPPORTED_MEDIA_TYPE, INVALID_IMAGE or PROCESSING_FAILED. HTTP 429: RATE_LIMITED, QUEUE_FULL or QUEUE_TIMEOUT; honor Retry-After. HTTP 500: INTERNAL_SERVER_ERROR; retry cautiously with the same key. HTTP 503: SERVICE_UNAVAILABLE; retry cautiously with the same key. Gateways can also return non-JSON errors. Check the status and content type; do not assume every network failure is a JSON API response. ## 9. cURL quickstart Set OPTIK_API_KEY in your local/server environment without committing it. Generate and save a REQUEST_ID once per logical image operation. curl --fail-with-body --max-time 120 \ https://opticalbackgroundremover.com/api/v1/remove-background \ -H "Authorization: Bearer $OPTIK_API_KEY" \ -H "Idempotency-Key: $REQUEST_ID" \ -F "file=@product.jpg;type=image/jpeg" \ -o result.json For optical preparation, replace remove-background with prepare-product. result.json contains JSON, not image bytes. curl --fail-with-body \ https://opticalbackgroundremover.com/api/v1/credits \ -H "Authorization: Bearer $OPTIK_API_KEY" ## 10. Node.js example (Node 20+, server-side) Uses built-in fetch, FormData and Blob. Save as process-image.mjs. Supply a stable REQUEST_ID environment variable; reuse it when retrying the same image. This example uploads one JPEG, polls a pending response, and saves its output. It does not automatically retry terminal failures or rate-limit errors. ```javascript import { readFile, writeFile } from 'node:fs/promises'; const apiKey = process.env.OPTIK_API_KEY; const requestId = process.env.REQUEST_ID; if (!apiKey || !requestId) throw new Error('Set OPTIK_API_KEY and REQUEST_ID'); const base = 'https://opticalbackgroundremover.com/api/v1'; const auth = { Authorization: `Bearer ${apiKey}` }; async function readJSON(response) { const text = await response.text(); let data; try { data = JSON.parse(text); } catch { throw new Error(`Non-JSON response (HTTP ${response.status})`); } if (!response.ok) { const message = typeof data.error === 'string' ? data.error : data.error?.message || 'Request failed'; throw new Error(`HTTP ${response.status}: ${message}`); } return data; } const form = new FormData(); form.append('file', new Blob([await readFile('product.jpg')], { type: 'image/jpeg', }), 'product.jpg'); const response = await fetch(`${base}/prepare-product`, { method: 'POST', headers: { ...auth, 'Idempotency-Key': requestId }, body: form, signal: AbortSignal.timeout(120_000), }); let job = await readJSON(response); const deadline = Date.now() + 120_000; while (['accepted', 'processing'].includes(job.status)) { if (Date.now() >= deadline) throw new Error(`Still pending: ${job.job_id}`); await new Promise(resolve => setTimeout(resolve, 3000)); job = await readJSON(await fetch(`${base}/jobs/${encodeURIComponent(job.job_id)}`, { headers: auth, signal: AbortSignal.timeout(15_000), })); } if (job.status !== 'succeeded') throw new Error(`Job failed: ${job.job_id}`); const match = /^data:(image\/(?:png|webp|jpeg));base64,([A-Za-z0-9+/=]+)$/.exec(job.output_url); if (!match) throw new Error('Unexpected image output'); const extension = { 'image/png': 'png', 'image/webp': 'webp', 'image/jpeg': 'jpg' }[match[1]]; await writeFile(`result.${extension}`, Buffer.from(match[2], 'base64')); console.log(`Saved result.${extension}; credits consumed: ${job.credits_consumed}`); ``` ## 11. Python example (requests) Install requests in your project's virtual environment. Set OPTIK_API_KEY and a stable REQUEST_ID before running. This example intentionally surfaces errors instead of silently creating fresh jobs on retries. ```python import os import re import time import base64 from pathlib import Path import requests BASE = 'https://opticalbackgroundremover.com/api/v1' headers = {'Authorization': 'Bearer ' + os.environ['OPTIK_API_KEY']} request_id = os.environ['REQUEST_ID'] with open('product.jpg', 'rb') as source: response = requests.post( BASE + '/remove-background', headers={**headers, 'Idempotency-Key': request_id}, files={'file': ('product.jpg', source, 'image/jpeg')}, timeout=(10, 120), ) response.raise_for_status() job = response.json() deadline = time.monotonic() + 120 while job['status'] in ('accepted', 'processing'): if time.monotonic() >= deadline: raise TimeoutError('Job still pending: ' + job['job_id']) time.sleep(3) response = requests.get(BASE + '/jobs/' + job['job_id'], headers=headers, timeout=(10, 15)) response.raise_for_status() job = response.json() if job['status'] != 'succeeded': raise RuntimeError('Job failed: ' + job['job_id']) match = re.fullmatch(r'data:(image/(?:png|webp|jpeg));base64,([A-Za-z0-9+/=]+)', job['output_url']) if not match: raise ValueError('Unexpected image output') extension = {'image/png': 'png', 'image/webp': 'webp', 'image/jpeg': 'jpg'}[match[1]] Path('result.' + extension).write_bytes(base64.b64decode(match[2], validate=True)) print('Credits consumed:', job['credits_consumed']) ``` ## 12. Prompt for your coding assistant Use this guide as the API reference to integrate OptikAPI into my application. Inspect my framework and existing server-side authentication first. Implement server-side image upload to /remove-background or /prepare-product, using an OPTIK_API_KEY environment variable. Ask which service I need if unclear. Do not ask me to paste the secret into chat. Validate JPEG/PNG/WebP uploads up to 10 MiB, use multipart file uploads, persist an idempotency key per operation, handle pending jobs and structured errors, and decode output_url as an image data URL. Protect my backend route with my application's auth and usage limits. Show loading, success, insufficient-credit and retry states. Preserve the same idempotency key after uncertain failures. Do not invent API parameters. Verify the implementation with mocked responses before making billable API calls.