openapi: 3.2.0 info: title: thirds.ai Image Packs API version: 1.0.0 description: Turn one design into content at scale. Create branded images and PDFs for your campaigns and clients. Automate each new version through our API or your AI tools. Render saved templates with new data, or send HTML directly. Every error uses one envelope, every response carries an x-request-id header, and every JSON request body rejects fields it does not expect. servers: - url: https://thirds.ai tags: - name: Image Packs paths: /v1/image-packs: post: summary: Create an image pack description: 'Render one saved template version at every requested size: original for the version''s own canvas, a saved size id, a preset slug, or a WxH custom size adapted from the version''s original. Creates one normal image job per size. A size that cannot be adapted fails immediately with resize_source_unsupported; the other sizes still run. The ledger reserves one credit per job, settles it on success, and releases it on failure.' operationId: createImagePack security: - bearerAuth: [] parameters: - name: Idempotency-Key in: header required: true schema: type: string minLength: 1 maxLength: 255 pattern: ^[ -~]+$ requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ImagePackCreate' examples: socialSet: value: template_id: tpl_2f6a2f6a2f6a2f6a2f6a2f6a2f6a2f6a version: 3 data: headline: Autumn sale format: png sizes: - square-post - story - 1200x628 responses: '201': description: The pack was created, or an earlier request with the same idempotency key and body is replayed. headers: x-request-id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ImagePack' '400': $ref: '#/components/responses/Error' '401': $ref: '#/components/responses/Error' '403': $ref: '#/components/responses/AccountSuspended' '404': $ref: '#/components/responses/Error' '409': $ref: '#/components/responses/Error' '413': $ref: '#/components/responses/Error' '415': $ref: '#/components/responses/Error' '429': $ref: '#/components/responses/Error' '500': $ref: '#/components/responses/Error' '503': $ref: '#/components/responses/Error' '405': $ref: '#/components/responses/MethodNotAllowed' '431': $ref: '#/components/responses/RequestHeadersTooLarge' tags: - Image Packs /v1/image-packs/{id}: parameters: - name: id in: path required: true schema: type: string pattern: ^pack_[0-9a-f]{32}$ get: summary: Get an image pack's status description: Returns the pack with each member's own state and, once any member has succeeded, a signed archive_url for the ZIP of every succeeded file. operationId: getImagePack security: - bearerAuth: [] responses: '200': description: The pack's current status. headers: x-request-id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ImagePack' '401': $ref: '#/components/responses/Error' '403': $ref: '#/components/responses/AccountSuspended' '404': $ref: '#/components/responses/Error' '500': $ref: '#/components/responses/Error' '405': $ref: '#/components/responses/MethodNotAllowed' '431': $ref: '#/components/responses/RequestHeadersTooLarge' tags: - Image Packs /v1/image-packs/{id}/retry: post: summary: Retry an image pack's failed members description: Creates one new job for every member whose last attempt failed or that never reached a job. A queued, running, or already succeeded member is left untouched. operationId: retryImagePack security: - bearerAuth: [] parameters: - name: id in: path required: true schema: type: string pattern: ^pack_[0-9a-f]{32}$ responses: '200': description: The pack after retry, with a new job for each member that was retried. headers: x-request-id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ImagePackRetry' '401': $ref: '#/components/responses/Error' '403': $ref: '#/components/responses/AccountSuspended' '404': $ref: '#/components/responses/Error' '429': $ref: '#/components/responses/Error' '500': $ref: '#/components/responses/Error' '405': $ref: '#/components/responses/MethodNotAllowed' '431': $ref: '#/components/responses/RequestHeadersTooLarge' tags: - Image Packs /v1/image-packs/archive/{token}: get: summary: Download an image pack's ZIP archive description: Stream a ZIP of one pack's succeeded members, in requested order with numbered filenames, plus manifest.json recording that order and any failed sizes. The link carries its own authority, so no other credential is read. operationId: getImagePackArchive parameters: - name: token in: path required: true description: The signed archive link. schema: type: string responses: '405': $ref: '#/components/responses/MethodNotAllowed' '431': $ref: '#/components/responses/RequestHeadersTooLarge' '200': description: The ZIP archive of succeeded members plus manifest.json. headers: x-request-id: $ref: '#/components/headers/XRequestId' content: application/zip: schema: type: string format: binary '403': description: The link is genuine but its time is over. headers: x-request-id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '404': description: The link is not valid, or the pack has no succeeded member to archive. headers: x-request-id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' '500': description: An internal error occurred. headers: x-request-id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' tags: - Image Packs components: responses: MethodNotAllowed: description: The method is not allowed on this route. headers: x-request-id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' AccountSuspended: description: The authenticated account is suspended. The code is account_suspended. headers: x-request-id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' RequestHeadersTooLarge: description: The request has more than 64 headers or more than 32 KiB of header names and values. headers: x-request-id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' Error: description: The one error envelope every backend response uses. headers: x-request-id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/ErrorEnvelope' schemas: ErrorEnvelope: type: object description: The one error shape every backend response uses. properties: error: type: object properties: code: type: string enum: - account_suspended - account_concurrency_limited - abuse_limited - ai_failure_limit_reached - ai_needs_paid_credits - already_subscribed - auth_unavailable - batch_row_limit - billing_unavailable - brand_asset_account_limit_reached - brand_asset_invalid - brand_asset_kind_invalid - brand_asset_kind_mismatch - brand_asset_limit_reached - brand_asset_too_large - brand_asset_type_unsupported - brand_data_conflict - brand_font_glyphs_exceeded - brand_font_tables_invalid - brand_image_animated - brand_image_dimensions_invalid - brand_image_pixels_exceeded - brand_kit_colours_invalid - brand_kit_conflict - brand_kit_limit - brand_kit_name_invalid - brand_kit_not_found - brand_kit_patch_empty - brand_kit_tone_invalid - brand_kit_unavailable - captcha_rejected - checkout_superseded - cross_origin_rejected - csrf_rejected - download_expired - email_already_set - gallery_template_not_found - generated_template_invalid - idempotency_conflict - image_asset_header_unsupported - image_asset_invalid - image_asset_limit - image_asset_not_found - image_asset_reference_invalid - image_asset_too_large - image_asset_type_unsupported - image_asset_unavailable - image_url_invalid - image_url_unavailable - insufficient_credits - internal_error - invalid_cursor - invalid_email - invalid_event - invalid_link - invalid_profile - invalid_request - invalid_upload - job_not_finished - key_concurrency_limited - key_limit_reached - method_not_allowed - no_billing_customer - not_found - operation_conflict - operation_limit_exceeded - operation_pending - overage_limit_reached - overage_unavailable - overloaded - playground_busy - playground_request_invalid - playground_selection_invalid - playground_session_limited - playground_unavailable - policy_version_stale - provider_unavailable - rate_limited - render_probe_busy - render_probe_failed - render_probe_not_configured - render_probe_required - render_probe_timeout - request_headers_too_large - request_too_large - resize_timeout - signed_out - spend_cap_reached - template_build_not_found - template_data_collection_limit - template_data_depth_limit - template_data_invalid - template_data_limit - template_depth_limit - template_draft_not_found - template_evaluation_error - template_invalid_filter_input - template_missing_data - template_output_limit - template_not_found - template_schema_complexity - template_schema_draft_unsupported - template_schema_invalid - template_schema_too_large - template_size_canvas_mismatch - template_size_data_overrides_too_large - template_size_duplicate_id - template_size_invalid_dimensions - template_size_invalid_id - template_size_invalid_name - template_sizes_too_large - template_sizes_too_many - template_source_limit - template_syntax_error - template_timeout - template_version_changed - template_work_limit - testimonial_busy - testimonial_invalid - testimonial_rate_limited - testimonial_unavailable - unauthorized - unsupported_media_type - webhook_limit_reached description: A fixed, machine-readable error code. message: type: string description: A fixed, human-readable message. request_id: type: string format: uuid description: The identifier this answer also carries in its x-request-id header. details: type: array description: Present on a validation failure. Schema failures return at most 16 entries with bounded data paths and fixed reasons. Values from the request are never repeated. items: $ref: '#/components/schemas/FieldDetail' retry: $ref: '#/components/schemas/RetryInfo' plan_limit: $ref: '#/components/schemas/PlanLimitInfo' required: - code - message - request_id additionalProperties: false required: - error additionalProperties: false TemplateId: type: string pattern: ^tpl_[0-9a-f]{32}$ RetryInfo: type: object description: How long the caller must wait before it retries. properties: retry_after_seconds: type: integer minimum: 1 maximum: 60 required: - retry_after_seconds additionalProperties: false ImagePack: type: object properties: id: type: string pattern: ^pack_[0-9a-f]{32}$ template_id: $ref: '#/components/schemas/TemplateId' version: type: integer minimum: 1 format: type: string enum: - png - jpeg - webp created_at: type: string format: date-time state: type: string enum: - queued - running - complete - partial - failed members: type: array items: $ref: '#/components/schemas/ImagePackMember' archive_url: type: - string - 'null' description: A signed ZIP download link, present once any member has succeeded. required: - id - template_id - version - format - created_at - state - members - archive_url additionalProperties: false ImagePackRetry: $ref: '#/components/schemas/ImagePack' ImagePackCreate: type: object properties: template_id: $ref: '#/components/schemas/TemplateId' version: type: integer minimum: 1 description: Omitted to use the template's latest version at creation time. data: type: object description: Template data. Each size's own saved data overrides apply over this data. Omitted to use an empty object. format: type: string enum: - png - jpeg - webp sizes: type: array items: type: string minItems: 1 maxItems: 20 description: '1 to 20 different entries: original for the version''s own canvas, a saved size id, a preset slug, or a WxH custom size.' required: - template_id - format - sizes additionalProperties: false FieldDetail: type: object description: One request field that failed, its fixed safe reason, and an optional bounded source location. Details never carry template source, customer values, rendered output, or raw evaluator prose. properties: field: type: string maxLength: 260 description: The path to the field, such as "pdf.scale". Schema errors use data followed by a JSON Pointer, such as data/items/0/count. The pointer is cut at 256 UTF-8 bytes. Empty for a problem with the whole document. reason: type: string enum: - malformed JSON - missing field - unknown field - wrong type - invalid value - A required value is missing. - Use the expected value type. - Declare this variable before using it. - Choose an allowed value. - Use the required format. - Add a value. - Use a shorter value. - Use a number within the allowed range. - Check this value against its data rule. description: A fixed, safe reason. It never repeats the value the caller sent. line: type: integer minimum: 1 maximum: 1000000 description: The one-based template source line when the evaluator provides one within the published bound. column: type: integer minimum: 1 maximum: 1000000 description: The one-based template source column when the evaluator provides one within the published bound. required: - field - reason additionalProperties: false ImagePackMember: type: object properties: position: type: integer minimum: 0 size_id: type: string name: type: string width: type: integer minimum: 1 height: type: integer minimum: 1 state: type: string enum: - queued - running - succeeded - failed error: type: - object - 'null' properties: code: type: string required: - code additionalProperties: false download_url: type: - string - 'null' required: - position - size_id - name - width - height - state - error - download_url additionalProperties: false PlanLimitInfo: type: object description: 'Present when the account''s plan causes the refusal: brand_kit_limit and batch_row_limit. It names the limit and the plan that sets it, so a caller can act without a second request. The message text never names the number.' properties: plan: type: string enum: - scale - growth - starter - pack - free description: The account's plan at the time of the refusal. limit: type: integer minimum: 1 description: 'What that plan allows: brand kits for brand_kit_limit, rows in one run for batch_row_limit.' required: - plan - limit additionalProperties: false headers: XRequestId: description: The UUID that identifies this request and matches error.request_id on an error response. required: true schema: type: string format: uuid securitySchemes: sessionCookie: type: apiKey in: cookie name: __Host-thirds_session description: A browser session. Browser writes also require the matching x-csrf-token header from GET /v1/me. bearerAuth: type: http scheme: bearer description: 'An API key''s secret, sent as "Authorization: Bearer thirds_sk_v1_...".' x-unmatched-v1-responses: description: A request below /v1 that matches no operation receives the shared safe envelope. OpenAPI has no standard path item for an unmatched route, so this extension records the fallback contract without claiming that a catch-all operation exists. '404': $ref: '#/components/responses/NotFound' '431': $ref: '#/components/responses/RequestHeadersTooLarge'