Product image kit
Introduction
Create a consistent product image set from 1–6 original photos: main image, scene, selling points, details, atmosphere and advertisement. Capability key: image.product_kit. All Anycap API requests use Authorization: Bearer <ANYCAP_API_KEY> without browser Cookie or Origin. Provide an existing valid plan directly, or prepare one with the optional text/vision calls below. Each final image uses the existing image generation service.
Requests
| Method | Path | Response |
|---|---|---|
| GET | /api/v1/capabilities/image.product_kit | Permitted vision/image models, exact image specifications and endpoint paths |
| POST | /api/v1/capabilities/image.product_kit/analysis-requests | Free preparation: native_request, execution_path, execution_required=true |
| POST | /v1/chat/completions | Separately billed analysis; JSON plan in choices[0].message.content |
| POST | /api/v1/capabilities/image.product_kit/quotes | 201 persisted image quote manifest; 200 on identical replay |
| POST | /api/v1/capabilities/image.product_kit/executions | 202 original batch and child task states |
| GET | /api/v1/capabilities/image.product_kit/executions/{id}?result=true | Batch and temporary completed image links |
Call sequence: discover image_models → select one priced_combinations entry → provide plan → quote → submit the saved batch id → query result=true → save all completed images. The analysis-requests and chat/completions calls are optional when you already have a plan. The /api/v1/product-kit/workflows paths use browser sessions and are not Bearer integration endpoints.
Optional plan preparation
modelstring
image_urlsstring[1–6]
max_tokensinteger
settings.platform / market / language / stylestring
settings.requirementstring
settings.countsobject
settings.listingboolean
settings.commercial_style_analysisboolean
Preparation does not invoke a model. Send the returned native_request unchanged to execution_path with a stable Idempotency-Key. Accept a plan only when choices[0].finish_reason is stop and content parses as JSON. Preserve the analysis operation key before that paid call; a timeout is not permission to run analysis again. Check the recorded text call when its outcome is unknown.
Image quote input
idempotency_keystring
model / size / resolution / qualitystring
image_urlsstring[1–6]
plan.product / summarystring
plan.selling_points / title / descriptionarray / string
plan.imagesarray[1–12]
You may edit the analyzed plan or provide a valid plan directly. Every child uses n=1 and the same original photos. Quote returns id, amount_microcredits, expires_at and items containing index, id, kind, title, quote_id, resource_id, native_request, amount_microcredits and expires_at. The total is the sum of each independently rounded image quote, with analysis billed separately. Save the complete manifest before executing. Quoting makes no reservations. expires_at is the earliest child quote deadline.
Execution and results
Submit {"id":"BATCH_UUID","max_microcredits":"AUTHORIZED_IMAGE_BUDGET"}. The positive int64 budget bounds the quoted image total. The server validates every quote before claiming the batch and submits the persisted child IDs in parallel. Each child has independent billing; one rejected image does not cancel others. Repeating the submission returns the original batch without a new submission wave.
| Batch state | Meaning |
|---|---|
quoted | No submission wave started |
processing | At least one child is still working |
completed | All images completed |
partial | All children terminal, with both successes and failures |
failed | All children failed or were clearly not submitted |
pending | At least one child outcome requires verification; do not automatically create a replacement set |
Results include id, image_count, items, charged_microcredits, reserved_microcredits, manifest_expires_at and the unexpired manifest. Each item carries index, id, state and, when available, the original ImageJob. charged_microcredits sums known image charges; reserved_microcredits sums known held/pending reservations. Missing children with submission_unconfirmed are unknown, not proof of zero cost. Use result=true for temporary .items[].job.images links; save images promptly. The original child may also be read at GET /api/v1/image-executions/{id}?result=true. The manifest is available for 72 hours; batch identities and existing child financial records remain afterward.
End-to-end example
# Requires bash, curl and jq. Save artifacts privately; the API key stays in the environment.set -euo pipefailumask 077BASE="https://api.anycap.io"KIT="$BASE/api/v1/capabilities/image.product_kit"curl --fail-with-body "$KIT" -H "Authorization: Bearer $ANYCAP_API_KEY" > kit-models.json # Set ORIGINAL_IMAGE_URL to a public HTTPS photo of your own product.# Choose one exact image specification from the returned catalog.# Supply kit-plan.json yourself to skip the optional text/vision call.if [ ! -s kit-plan.json ]; thenANALYSIS_MODEL=$(jq -er '.analysis_models[0].model' kit-models.json)ANALYSIS_TOKENS=$(jq -er '.analysis_models[0].max_output_tokens | if . < 8192 then . else 8192 end' kit-models.json)jq -n --arg model "$ANALYSIS_MODEL" --arg image "$ORIGINAL_IMAGE_URL" --argjson tokens "$ANALYSIS_TOKENS" \ '{model:$model,image_urls:[$image],max_tokens:$tokens,settings:{platform:"Amazon",market:"United States",language:"English",style:"Minimal studio photography",listing:true}}' > analysis-input.jsoncurl --fail-with-body "$KIT/analysis-requests" -H "Authorization: Bearer $ANYCAP_API_KEY" \ -H "Content-Type: application/json" --data-binary @analysis-input.json > analysis-request.jsonjq -e '.native_request' analysis-request.json > analysis-native.json # This next request is a separately billed text/vision call. Run only after authorization.# Set and persist ANALYSIS_OPERATION before calling; never change it to retry an unknown outcome.: "${ANALYSIS_OPERATION:?Set a stable analysis operation key}"curl --fail-with-body "$BASE/v1/chat/completions" -H "Authorization: Bearer $ANYCAP_API_KEY" \ -H "Idempotency-Key: $ANALYSIS_OPERATION" -H "Content-Type: application/json" \ --data-binary @analysis-native.json > analysis-result.jsonjq -e '.choices[0].finish_reason == "stop"' analysis-result.json >/dev/null || exit 1jq -er '.choices[0].message.content' analysis-result.json | jq -e . > kit-plan.jsonfi # Review/edit kit-plan.json. Use a new operation key for each NEW set, never for a lost response.: "${KIT_OPERATION:?Set a stable kit operation key}"jq -n --arg operation "$KIT_OPERATION" --arg image "$ORIGINAL_IMAGE_URL" \ --slurpfile catalog kit-models.json --slurpfile plan kit-plan.json \ '{idempotency_key:$operation,model:$catalog[0].image_models[0].model,image_urls:[$image],plan:$plan[0]} + ($catalog[0].image_models[0].priced_combinations[0] | {size,resolution,quality})' > kit-input.jsoncurl --fail-with-body "$KIT/quotes" -H "Authorization: Bearer $ANYCAP_API_KEY" \ -H "Content-Type: application/json" --data-binary @kit-input.json > kit-quote.json# The complete batch id, child ids and prices are now saved BEFORE the paid POST.jq '{id,amount_microcredits,expires_at,items:[.items[]|{id,kind,amount_microcredits}]}' kit-quote.json # Explicit image budget in integer microcredits; 1 credit = 1000000 microcredits.: "${ANYCAP_MAX_MICROCREDITS:?Set the authorized total image budget}"jq --arg budget "$ANYCAP_MAX_MICROCREDITS" '{id,max_microcredits:$budget}' kit-quote.json > kit-submit.jsoncurl --fail-with-body "$KIT/executions" -H "Authorization: Bearer $ANYCAP_API_KEY" \ -H "Content-Type: application/json" --data-binary @kit-submit.json > kit-execution.json # Also use this GET if the submission response was lost. Poll at 15-second intervals.BATCH_ID=$(jq -er '.id' kit-quote.json)curl --fail-with-body "$KIT/executions/$BATCH_ID?result=true" \ -H "Authorization: Bearer $ANYCAP_API_KEY" > kit-result.jsonjq '.items[]|{id,state,images:(.job.images // []),error_code:(.error_code // .job.error_code)}' kit-result.jsonRegenerate one image
For an explicitly requested new image, create a new quote input using the original image_urls and exact model/size/resolution/quality, keep plan.product and plan.summary, and set plan.images to just the selected saved plan entry. Use a new idempotency_key for that new image, then submit and query its returned batch id. No new text/vision call is needed. Use this for completed/released results, not to replace an unknown pending outcome.
Save the whole set to a folder and ZIP
Save the result=true response as kit-result.json. Run this Python 3 example from that directory; KIT_OUTPUT_DIR optionally selects an output directory. It saves all completed and settled originals together in a new numbered-image folder and creates a ZIP beside it. A partial batch exports its successful images only. Download links are temporary; obtain fresh links by querying the same batch. Directory selection and ZIP creation happen in your client, not through an additional generation endpoint.
# Python 3 standard library. Input: the saved GET .../executions/{id}?result=true response.import json, os, uuid, zipfilefrom pathlib import Pathfrom urllib.parse import urlsplitfrom urllib.request import Request, urlopen result = json.loads(Path("kit-result.json").read_text(encoding="utf-8"))if result.get("state") not in ("completed", "partial"): raise SystemExit("Query the original batch until completed or partial before saving.")files, total = [], 0extensions = {"image/png": "png", "image/jpeg": "jpg", "image/webp": "webp", "image/gif": "gif"}for index, item in enumerate(result["items"]): job = item.get("job", {}) if job.get("state") != "completed" or job.get("billing_status") != "settled": continue for image_index, url in enumerate(job.get("images", [])): parsed = urlsplit(url) if parsed.scheme != "https" or not parsed.hostname or parsed.username or parsed.password: raise SystemExit("Invalid image link. Query result=true again.") # Image downloads do not receive your Anycap key or browser cookies. with urlopen(Request(url), timeout=60) as response: if urlsplit(response.geturl()).scheme != "https": raise SystemExit("Unexpected image redirect.") extension = extensions.get(response.headers.get("Content-Type", "").split(";")[0]) if not extension: raise SystemExit("Image unavailable. Query result=true again.") data = response.read(20 * 1024 * 1024 + 1) total += len(data) if not data or len(data) > 20 * 1024 * 1024 or total > 120 * 1024 * 1024: raise SystemExit("Image set exceeds download limits.") files.append((f"{item.get('index', index) + 1:02d}_{image_index + 1:02d}.{extension}", data))if not files: raise SystemExit("No completed images available. Query result=true again.")output = Path(os.environ.get("KIT_OUTPUT_DIR", "."))output.mkdir(parents=True, exist_ok=True)folder = output / ("anycap-product-kit-" + uuid.uuid4().hex[:12])folder.mkdir() # A new folder prevents overwriting existing images.archive = folder.with_suffix(".zip")with zipfile.ZipFile(archive, "x", compression=zipfile.ZIP_STORED) as bundle: for name, data in files: (folder / name).write_bytes(data) bundle.writestr(folder.name + "/" + name, data)print(f"Saved {len(files)} images: {folder.resolve()}")print(f"ZIP: {archive.resolve()}") Errors
| Status | Action |
|---|---|
| 400 | Correct fields, photo URLs, plan or budget |
| 401 / 403 | Check key, member and source permissions |
| 402 | Budget below the quoted image total, or insufficient credits; inspect child states after a claimed batch |
| 404 | Batch/quote not owned by this connection or full manifest expired |
| 409 | Changed idempotent input or quote binding conflict; retain the original batch and child IDs |
| 429 | Request or concurrency limit reached; inspect existing child results |
| 503 | Unavailable model/source or temporary service failure; query any already submitted batch instead of creating replacements |