API reference
Full schema for the public REST API. Available on Pro and Max plans. Issue a key at /account/api.
Prefer Postman? Download our v2.1 collection — paste your gimg_… key into the apiKey variable.
Auth
All endpoints take a Bearer token in the Authorization header. Tokens always begin with gimg_.
Authorization: Bearer gimg_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Rate limits
600 requests / hour / key, max 5 in flight. Every response includes:
X-RateLimit-Limit— fixed at 600X-RateLimit-Remaining— calls left in the current windowX-RateLimit-Reset— epoch seconds when the window resetsRetry-After— seconds until the next allowed call (only on 429)
Idempotency
Pass Idempotency-Key on POST requests. Sending the same key twice returns the existing generation row instead of double-charging credits.
/api/v1/generateSubmit a text-to-image OR image-edit generation. Returns immediately with a generationId; poll /api/v1/generations/:id for the result.
Body
promptstring ≤ 4000 charsrequiredNatural-language description. Photographer terminology (camera, lens, lighting) helps.
type"text2img" | "edit"Defaults to 'text2img'. 'edit' requires inputImageKeys.
size"1024x1024" | "1024x1536" | "1536x1024"Aspect ratio. Defaults to 1024x1024.
quality"low" | "medium" | "high"Defaults to medium. high = HD 2K. low is Fast (1cr).
n1–4Number of variations. Defaults to 1.
inputImageKeysstring[] (max 4)R2 keys returned by /api/v1/upload. Required when type=edit.
Responses
202 Acceptedjson{ "generationId": "...", "status": "queued" }
400 Bad RequestjsonInvalid body. The error includes a Zod validation message.
401 UnauthorizedjsonMissing or invalid Bearer key.
402 Payment RequiredjsonInsufficient credits or monthly compute cap reached.
403 ForbiddenjsonPro/Max plan required.
422 UnprocessablejsonPrompt blocked by content policy.
429 Too Many RequestsjsonRate limit reached. See Retry-After.
/api/v1/uploadGet a presigned R2 PUT URL for uploading an input image. Two-step: this returns the URL, you PUT bytes to it directly.
Body
filenamestring ≤ 200requiredDisplay name; not used for storage.
contentType"image/png" | "image/jpeg" | "image/webp"requiredMust be exactly one of these three.
sizenumber (bytes ≤ 10 MB)requiredFile size you intend to PUT. Used for validation.
Response (200)
urlstringPresigned PUT URL, expires in 5 minutes.
keystringR2 key — pass this in inputImageKeys when calling /api/v1/generate.
/api/v1/generations/:idPoll the status of a generation. Returns immediately with current state.
Response (200)
idstringThe generation id.
status"queued" | "running" | "succeeded" | "failed" | "blocked"'blocked' = content moderation rejected the input or output (credits refunded).
typestringtext2img | edit | upscale | …
outputsArray<{ png, webp, width, height }>Populated when status=succeeded. URLs are public, immutable, CDN-cached.
errorstring | nullPopulated when status=failed or status=blocked.
costCreditsnumberCredits charged (refunded if blocked or failed).
createdAt / completedAtISO 8601Timestamps.
Outbound webhooks
Skip polling: register HTTPS endpoints to receive generation.succeeded and generation.failed events as they happen. Manage via /api/account/webhooks.
Register
curl -X POST https://gptimage2.plus/api/account/webhooks \
-H "Cookie: <your session cookie>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-app.com/hooks/gptimg",
"events": ["generation.succeeded", "generation.failed"],
"label": "Production app"
}'
# → { "id": "...", "secret": "whsec_..." } ← copy this, shown ONCEEvent payload
POST https://your-app.com/hooks/gptimg
X-Gptimg-Event: generation.succeeded
X-Gptimg-Signature: <hex sha256 hmac of body>
X-Gptimg-Delivery: <uuid for dedup>
Content-Type: application/json
{
"event": "generation.succeeded",
"data": {
"generationId": "...",
"type": "text2img",
"status": "succeeded",
"outputs": [{ "png": "...", "webp": "...", "width": 1024, "height": 1024 }],
"createdAt": "2026-04-26T01:23:45.000Z",
"completedAt": "2026-04-26T01:23:50.000Z"
}
}Verify the signature
// Node example
import crypto from "crypto";
const expected = crypto
.createHmac("sha256", process.env.WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
const ok = crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(req.headers["x-gptimg-signature"])
);Timeout: 5s per attempt. We retry non-2xx responses up to 3 times with exponential backoff (30s → 2m → 10m). After 8 consecutive failures across attempts, the webhook is auto-disabled — re-enable from /account/webhooks.