Imgen API v2
Introduction
The Imgen API provides AI-powered image generation with multiple diffusion models, built-in character LoRA support, prompt enhancement, content compliance checking, content credentials, and watermarking. All prompt parsing, workflow construction, and post-processing are handled server-side — clients submit raw prompts and receive signed images hosted on S3.
/llm-docs.Base URL
All endpoints accept POST requests with a Content-Type: application/json body containing an action field.
POST https://imgen.api.efficientstack.com/api/v2
Content-Type: application/json
Authorization: Bearer <API-KEY>
{
"action": "generate",
...
}
Authentication
All requests must include a Bearer token in the Authorization header:
Authorization: Bearer <API-KEY>
Requests without a valid, enabled key receive a 401 Unauthorized response.
Prompt Syntax
The API uses a unified raw prompt format. The server parses the following tokens:
| Token | Syntax | Example | Description |
|---|---|---|---|
| Character | @name | @aya | Activates a character LoRA. One per prompt. |
| Negative | -term | -red dress | Comma-separated term prefixed with - is moved to the negative prompt. |
| Positive | everything else | black lingerie, on bed | Descriptive prompt text. |
Example
@aya black lingerie, sitting on bed, looking_at_viewer, warm lighting, -red dress, -ugly hands
Parsed as: character=aya, positive=black lingerie, sitting on bed, looking_at_viewer, warm lighting, negative=red dress, ugly hands.
Endpoints
| Action | Description |
|---|---|
models | List available characters, models, and style presets |
generate | Submit an image generation job |
status | Poll a generation job for completion |
optimize | Enhance a prompt with LLM |
random | Get a random prompt |
POST models
Returns available characters, models, and styles. No parameters beyond action.
{ "action": "models" }
Response
{
"characters": ["aaliyah", "aya", "bella", "..."],
"models": {
"eros": {
"label": "Eros",
"default_style": "professional",
"styles": {
"professional": { "label": "Professional" },
"boudoir": { "label": "Boudoir" },
"..."
}
}
}
}
POST generate
Submit an image generation job. Returns a job ID for polling.
Request Parameters
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
action | string | yes | — | "generate" |
prompt | string | yes | — | Raw prompt. Supports @character and -negative. |
model | string | no | "eros" | Diffusion model key. |
negative | string | no | "" | Additional negatives. |
optimize | boolean | no | false | LLM-enhance the prompt before generation. |
use_cache | boolean | no | false | Try semantic cache before generating. See Cache Lookup. |
cache_only | boolean | no | false | Only meaningful when use_cache is true. If the cache lookup misses, return 404 instead of falling back to a normal generation. |
cache_force | boolean | no | false | Only meaningful when use_cache is true. Bypass the 0.95 similarity threshold and accept any match (strict model/style/character equality is still enforced). |
cache_pick | integer | no | 5 | Only meaningful when use_cache is true. Size of the top-N pool from which a cached image is randomly selected. Clamped to [1, 10]. |
width | integer | no | 1344 | Image width. Must be a valid pair. |
height | integer | no | 768 | Image height. Must be a valid pair. |
style | string | no | model default | Style preset key for the selected model. |
format | string | no | "jpeg" | jpeg, png, webp, avif. |
quality | integer | no | 95 | Compression quality (1–100). |
lossless | boolean | no | false | Lossless compression (WebP/AVIF). |
compliance_ruleset | string | no | "default" | ACS compliance ruleset to apply for this generation. See Compliance & Rulesets. |
precompliance | string | no | "" | Output of an upstream pre-compliance screening system (e.g. a keyword-based Ban Word Service). Forwarded to ACS as supplementary, non-authoritative context during the image review. Max 2000 characters (excess is truncated). See Pre-compliance Signal. |
wm_image | string | no | "" | Watermark as base64 data URI. |
wm_position | string | no | "bottom-right" | Watermark anchor. |
wm_scale | integer | no | 5 | Watermark scale (1–100). |
wm_transparency | integer | no | 100 | Watermark opacity (0–100). |
wm_rotation | integer | no | 0 | Rotation degrees (-360–360). |
wm_padding_x | integer | no | 10 | Horizontal padding (0–500). |
wm_padding_y | integer | no | 10 | Vertical padding (0–500). |
cs_author | string | no | "" | Override C2PA author. |
cs_title | string | no | "" | Override C2PA title. |
cs_description | string | no | "" | Override C2PA description. |
cs_organization | string | no | "" | Override C2PA organization. |
cs_vendor | string | no | "" | Override C2PA vendor. |
Valid Dimensions
1024×1024 (1:1), 1344×768 (16:9), 768×1344 (9:16), 1216×832 (3:2), 832×1216 (2:3), 1152×896 (4:3), 896×1152 (3:4). Invalid pairs fall back to 1344×768.
Response
{
"id": "run_abc123xyz",
"prompt": "@aya enhanced prompt text, -red dress",
"model": "eros",
"format": "jpeg",
"gen_id": "a1b2c3d4e5f6g7h8i9j0"
}
Response (Cache Hit)
When use_cache is true and a matching cached image is found, the response shape is identical with an additional cache: true field. The polling step still applies and will return immediately.
{
"id": "cache_a1b2c3d4e5f6g7h8",
"prompt": "@aya enhanced prompt text, -red dress",
"model": "eros",
"format": "jpeg",
"gen_id": "a1b2c3d4e5f6g7h8i9j0",
"cache": true
}
Response (Cache Miss with cache_only)
When use_cache and cache_only are both true and the lookup returns no eligible match, the API responds with 404 instead of submitting a generation job.
{ "error": "No cached result found" }
POST status
Poll a generation job. Pass the id, prompt, and model returned by generate.
{
"action": "status",
"id": "run_abc123xyz",
"prompt": "@aya enhanced prompt text, -red dress",
"model": "eros"
}
Pending
{ "status": "IN_QUEUE" }
{ "status": "IN_PROGRESS" }
Completed (Safe)
{
"status": "COMPLETED",
"meta": {
"id": "run_abc123xyz",
"gen_id": "a1b2c3d4e5f6g7h8i9j0",
"time": "2025-03-29T20:02:50Z",
"prompt": "@aya enhanced prompt, -red dress",
"positive": "photo of aya, enhanced prompt, quality tags",
"negative": "base negatives, style negatives, red dress",
"character": "aya",
"model": "eros",
"style": "professional",
"width": 1344,
"height": 768,
"format": "jpeg",
"quality": 95,
"lossless": false,
"optimized": true,
"compliance": "safe",
"compliance_codes": [],
"compliance_ruleset": "default",
"precompliance": "",
"fingerprint": "a4f8e2c1b9d0...",
"images": [
{ "url": "https://s3...signed.jpg?...", "filename": "genid_fingerprint_00001.jpg", "type": "signed" },
{ "url": "https://s3...thumb.jpg?...", "filename": "genid_fingerprint_00001_thumb.jpg", "type": "thumbnail" }
],
"enable_invisimark": true,
"enable_c2pa": true,
"compute_pdq": true,
"wm_position": "bottom-right",
"wm_scale": 5,
"wm_transparency": 100,
"cs_author": "Author",
"cs_title": "AI Image",
"..."
}
}
Completed (Cache Hit)
For cache-served jobs the same shape is returned, with the additional cache: true field. Compliance is always "safe" (compliance review was performed when the image was originally generated and cached).
{
"status": "COMPLETED",
"cache": true,
"meta": {
"id": "cache_a1b2c3d4e5f6g7h8",
"compliance": "safe",
"images": [
{ "url": "...", "filename": "", "type": "signed" },
{ "url": "...", "filename": "", "type": "thumbnail" }
],
"..."
}
}
Completed (Blocked)
{
"status": "COMPLETED",
"compliance": "unsafe",
"error": "Content blocked — flagged for: Category Name.",
"filter_categories": ["id1", "id2"],
"unsafe_url": "aHR0cHM6Ly9zMy5leGFtcGxlLmNvbS9zaWduZWQuanBnP3NpZz0uLi4="
}
| Field | Type | Description |
|---|---|---|
compliance | string | Always "unsafe" when the response is a block. |
error | string | Human-readable summary of the violations. |
filter_categories | string[] | Array of ACS rule IDs that triggered the block. |
unsafe_url | string | Base64-encoded signed S3 URL of the blocked image. Decode with atob() (browser) or base64.b64decode() (Python) to recover the original URL. Provided so moderation tools and trusted clients can still access the asset; not surfaced as a normal images array to discourage casual display. |
Failed
{ "status": "FAILED", "error": "Generation failed" }
POST optimize
Enhance a prompt independently of generation.
{
"action": "optimize",
"prompt": "@aya black lingerie, on bed, -red dress",
"model": "eros"
}
{
"prompt": "@aya elegant black lace lingerie, perched on bed, warm lighting, -red dress"
}
POST random
Returns a random prompt. Omit character for random, set empty "" for none, or specify a name.
{ "action": "random" }
{ "action": "random", "character": "aya" }
{ "action": "random", "character": "" }
{ "prompt": "@aya black lace lingerie, sitting on bed, warm lighting" }
Prompt Construction
Positive Prompt
[trigger], [character_positive], {user prompt}[, style quality tags]
Negative Prompt
{base negatives}[, style negatives][, character negatives][, parsed -terms][, explicit negative]
Cache Lookup
Set use_cache: true on a generate request to opportunistically serve a pre-existing image instead of running a full generation pipeline. The cache is a semantic store: queries are matched on prompt meaning while enforcing strict equality on model, style, and character.
How it works
- After prompt parsing and validation, the API queries the semantic cache with the user's prompt plus the resolved
model,style, andcharacter. - Candidates must strictly match
model,style, andcharacter, and must include aurl. - By default, only candidates whose similarity score is above 0.95 are eligible. Pass
cache_force: trueto disable this floor and accept any returned match. - One of the top
cache_pickeligible candidates (default 5, clamped to[1, 10]) is selected at random. - The response uses a
cache_job ID prefix and includescache: true. - If no eligible candidate exists:
- With
cache_only: false(default), the API silently falls through to a normal generation. - With
cache_only: true, the API responds with404and{"error":"No cached result found"}.
- With
Important
- Bypasses optimization: when a cache hit is returned,
optimizeis not applied (the cache is queried with the user's intent prompt). - Bypasses compliance: cached images were already reviewed when they were generated, so no new ACS check runs.
precomplianceis ignored on cache hits. - Time-limited: cached results are retrievable for up to one hour after the
generatecall. Poll promptly. - Quality vs. variety trade-off: raising
cache_pickincreases response variety but may include lower-similarity matches; lowering it (e.g.1) deterministically returns the single best match. cache_forcecaution: withcache_force: true, matches with very low semantic similarity may be returned. Use only when you need a fallback image at any cost.
Example
{
"action": "generate",
"prompt": "@aya black lingerie, warm lighting",
"model": "eros",
"style": "professional",
"use_cache": true,
"cache_only": false,
"cache_force": false,
"cache_pick": 5
}
Compliance & Rulesets
After generation, every (non-cached) image is automatically reviewed by the ACS compliance service before its URL is released to the client. Reviews happen during status polling — when the underlying job reaches COMPLETED, the API runs the check and either:
- returns
compliance: "safe"with the fullmeta.imagesarray, or - returns
compliance: "unsafe"with a base64-encodedunsafe_url(see status).
Selecting a ruleset
ACS rulesets define the active set of categories applied to a review. Pass compliance_ruleset on the generate request to choose a non-default ruleset for that job:
{
"action": "generate",
"prompt": "@aya black lingerie, warm lighting",
"model": "eros",
"compliance_ruleset": "strict"
}
Rules:
- If
compliance_rulesetis omitted, empty, or whitespace-only, the API sends"default"to ACS. - The selected ruleset is persisted on the generation's metadata and reused when
statustriggers the review — clients do not need to pass it again on poll. - Cache-hit jobs ignore
compliance_rulesetentirely; cached images are pre-cleared.
Pre-compliance Signal
Many clients run an upstream pre-compliance system on the raw text prompt before submitting it to Imgen. The precompliance parameter on generate lets you forward that upstream system's output to Imgen so it can be included in the ACS review of the resulting image as additional context.
Behavior
- Non-authoritative: ACS still makes the final compliance decision. The pre-compliance signal is one input among others.
- Truncated: values longer than 2000 characters are truncated server-side.
- Cache hits ignore it: cached images were already cleared; no new ACS review runs.
Example
{
"action": "generate",
"prompt": "@aya black lingerie, warm lighting",
"model": "eros",
"precompliance": "no banned tokens matched; risk_score=0.04"
}
Content Credentials
All generated images include C2PA manifest signing, invisible watermark embedding, and PDQ fingerprinting. The signed image and thumbnail are delivered as pre-signed S3 URLs in meta.images.
Override C2PA metadata per request: cs_author, cs_title, cs_description, cs_organization, cs_vendor.
Polling Strategy
| Setting | Recommended |
|---|---|
| Interval | 3 seconds |
| Max polls | 120 (6 min timeout) |
| Overlap guard | Wait for each poll to complete before starting the next |
Terminal states: COMPLETED, FAILED, TIMED_OUT, CANCELLED. On COMPLETED, always check compliance before accessing meta.
Error Handling
| Status | Error | Cause |
|---|---|---|
| 400 | Missing action | No action field |
| 400 | Prompt is required | Empty/missing prompt |
| 400 | Invalid character | Unknown @name |
| 400 | Bad ID | Invalid job ID |
| 401 | Unauthorized | Missing or invalid API key |
| 404 | No cached result found | use_cache + cache_only with no eligible match |
| 502 | Generation failed | Upstream error |
| 502 | Enhancement failed | LLM error |
Silent fallbacks: invalid model → first model, invalid dimensions → 1344×768, invalid style → model default, invalid format → jpeg, cache miss/error → normal generation (unless cache_only is set), empty compliance_ruleset → "default", cache_pick out of range → clamped to [1, 10], precompliance longer than 2000 chars → truncated.
Code Examples
const API = 'https://imgen.api.efficientstack.com/api/v2';
const TOKEN = 'your-api-key';
async function api(body) {
const r = await fetch(API, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${TOKEN}` },
body: JSON.stringify(body)
});
return r.json();
}
async function generateImage(prompt, opts = {}) {
const job = await api({
action: 'generate', prompt, model: opts.model || 'eros',
optimize: opts.optimize || false,
use_cache: opts.use_cache || false,
cache_only: opts.cache_only || false,
cache_force: opts.cache_force || false,
cache_pick: opts.cache_pick || 5,
width: opts.width || 1344, height: opts.height || 768,
style: opts.style || '', format: opts.format || 'jpeg',
compliance_ruleset: opts.compliance_ruleset || '',
precompliance: opts.precompliance || ''
});
if (job.error) throw new Error(job.error);
for (let i = 0; i < 120; i++) {
await new Promise(r => setTimeout(r, 3000));
const s = await api({ action: 'status', id: job.id, prompt: job.prompt, model: job.model });
if (s.status === 'IN_QUEUE' || s.status === 'IN_PROGRESS') continue;
if (s.status === 'COMPLETED') {
if (s.compliance === 'unsafe') {
const blockedUrl = s.unsafe_url ? atob(s.unsafe_url) : null;
const err = new Error(s.error);
err.unsafeUrl = blockedUrl;
err.categories = s.filter_categories || [];
throw err;
}
return { meta: s.meta, fromCache: !!s.cache };
}
throw new Error(s.error || 'Failed: ' + s.status);
}
throw new Error('Timeout');
}
const { meta, fromCache } = await generateImage('@aya black lingerie, warm lighting', {
style: 'professional',
use_cache: true, cache_only: false, cache_force: false, cache_pick: 5,
compliance_ruleset: 'default',
precompliance: 'ban-word-service v3.2: no banned tokens matched; risk_score=0.04'
});
const signed = meta.images.find(i => i.type === 'signed');
console.log('Image URL:', signed?.url, fromCache ? '(cached)' : '(generated)');
import requests, time, base64
API = "https://imgen.api.efficientstack.com/api/v2"
TOKEN = "your-api-key"
def api(body):
return requests.post(API, json=body,
headers={"Authorization": f"Bearer {TOKEN}"}).json()
def generate(prompt, model="eros", style="", fmt="jpeg",
use_cache=False, cache_only=False, cache_force=False, cache_pick=5,
ruleset="", precompliance=""):
job = api({"action":"generate","prompt":prompt,"model":model,"style":style,
"format":fmt,
"use_cache":use_cache,"cache_only":cache_only,
"cache_force":cache_force,"cache_pick":cache_pick,
"compliance_ruleset":ruleset,
"precompliance":precompliance})
if "error" in job: raise Exception(job["error"])
for _ in range(120):
time.sleep(3)
s = api({"action":"status","id":job["id"],"prompt":job["prompt"],"model":job["model"]})
if s["status"] in ("IN_QUEUE","IN_PROGRESS"): continue
if s["status"] == "COMPLETED":
if s.get("compliance") == "unsafe":
blocked_url = base64.b64decode(s["unsafe_url"]).decode() if s.get("unsafe_url") else None
raise Exception(f"{s.get('error')} (blocked_url={blocked_url})")
return s["meta"], bool(s.get("cache"))
raise Exception(s.get("error", f"Failed: {s['status']}"))
raise TimeoutError("Polling timed out")
meta, from_cache = generate("@aya black lingerie, warm lighting",
style="professional",
use_cache=True, cache_only=False,
cache_force=False, cache_pick=5,
ruleset="default",
precompliance="ban-word-service v3.2: no banned tokens matched; risk_score=0.04")
signed = next(i for i in meta["images"] if i["type"] == "signed")
print("URL:", signed["url"], "(cached)" if from_cache else "(generated)")
TOKEN="your-api-key"
BASE="https://imgen.api.efficientstack.com/api/v2"
# List models
curl -s -X POST "$BASE" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"action":"models"}'
# Generate with pre-compliance context forwarded from a Ban Word Service
curl -s -X POST "$BASE" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"action":"generate","prompt":"@aya black lingerie","model":"eros","style":"professional","compliance_ruleset":"default","precompliance":"ban-word-service v3.2: no banned tokens matched"}'
# Poll status (replace JOB_ID)
curl -s -X POST "$BASE" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"action":"status","id":"JOB_ID","prompt":"@aya ...","model":"eros"}'
<?php
$api = 'https://imgen.api.efficientstack.com/api/v2';
$token = 'your-api-key';
function apiCall($api, $token, $body) {
$ch = curl_init($api);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($body),
CURLOPT_HTTPHEADER => ['Content-Type: application/json', "Authorization: Bearer $token"],
]);
$r = curl_exec($ch); curl_close($ch);
return json_decode($r, true);
}
$job = apiCall($api, $token, [
'action'=>'generate','prompt'=>'@aya black lingerie','model'=>'eros',
'use_cache'=>true,'cache_only'=>false,'cache_force'=>false,'cache_pick'=>5,
'compliance_ruleset'=>'default',
'precompliance'=>'ban-word-service v3.2: no banned tokens matched'
]);
for ($i = 0; $i < 120; $i++) {
sleep(3);
$s = apiCall($api, $token, ['action'=>'status','id'=>$job['id'],'prompt'=>$job['prompt'],'model'=>$job['model']]);
if (in_array($s['status'], ['IN_QUEUE','IN_PROGRESS'])) continue;
if ($s['status'] === 'COMPLETED') {
if (($s['compliance'] ?? '') === 'unsafe') {
$blockedUrl = !empty($s['unsafe_url']) ? base64_decode($s['unsafe_url']) : null;
die($s['error'] . ($blockedUrl ? " (blocked_url=$blockedUrl)" : ''));
}
$signed = array_values(array_filter($s['meta']['images'], fn($i) => $i['type']==='signed'))[0];
$cached = !empty($s['cache']) ? ' (cached)' : '';
echo "URL: " . $signed['url'] . $cached; break;
}
die($s['error'] ?? 'Failed');
}