openapi: 3.2.0 info: title: thirds.ai Image Assets 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 Assets paths: /v1/image-assets/preview: post: summary: Preview a URL image description: Read a public HTTP or HTTPS PNG, JPEG, or WebP through the checked asset transport. Credentials, private destinations, unsafe DNS answers, and redirects to them are refused. At most five redirects and 15 seconds total. The same upload decoder checks the 10 MiB, dimension, pixel, and memory limits. Returns verified bytes without storage or a render charge. Browser sessions require CSRF. operationId: previewImageUrl security: - bearerAuth: [] requestBody: required: true content: application/json: schema: type: object properties: url: type: string maxLength: 4096 required: - url additionalProperties: false responses: '200': description: The verified image bytes and dimensions after orientation. headers: x-request-id: $ref: '#/components/headers/XRequestId' x-image-width: schema: type: integer x-image-height: schema: type: integer content: image/png: schema: type: string format: binary image/jpeg: schema: type: string format: binary image/webp: schema: type: string format: binary '400': $ref: '#/components/responses/Error' '401': $ref: '#/components/responses/Error' '403': $ref: '#/components/responses/Error' '413': $ref: '#/components/responses/Error' '422': $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 Assets /v1/image-assets: post: summary: Upload an image description: Store one private, immutable PNG, JPEG, or WebP image. Send the raw bytes with the matching Content-Type. Maximum 10 MiB, 7680 by 4320 stored pixels, 32 million pixels, and 64 MiB decoded memory. Animated images are refused. Images and brand assets share an account limit of 100 files and 100 MiB. Use the returned reference in an HTML body img src, or in template data for an img src. Render creation checks ownership after template expansion and captures the exact bytes. References are not public URLs. PDF header and footer templates do not support image references. No render credit is charged for an upload. operationId: uploadImage security: - bearerAuth: [] requestBody: required: true content: image/png: schema: type: string format: binary image/jpeg: schema: type: string format: binary image/webp: schema: type: string format: binary responses: '201': description: The image is stored. headers: x-request-id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/UploadedImage' '400': $ref: '#/components/responses/Error' '401': $ref: '#/components/responses/Error' '403': $ref: '#/components/responses/Error' '404': $ref: '#/components/responses/Error' '413': $ref: '#/components/responses/Error' '422': $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 Assets /v1/image-assets/{id}/content: parameters: - name: id in: path required: true schema: type: string pattern: ^asset_[a-f0-9]{32}$ get: summary: Read an owned image description: Return verified image or captured brand logo/font bytes to their owner. Missing and foreign assets return the same 404 error. The response uses private, no-store caching and nosniff. operationId: readImageContent security: - bearerAuth: [] responses: '200': description: The exact image bytes. headers: x-request-id: $ref: '#/components/headers/XRequestId' content: font/woff2: schema: type: string format: binary image/png: schema: type: string format: binary image/jpeg: schema: type: string format: binary image/webp: schema: type: string format: binary '400': $ref: '#/components/responses/Error' '401': $ref: '#/components/responses/Error' '403': $ref: '#/components/responses/Error' '404': $ref: '#/components/responses/Error' '413': $ref: '#/components/responses/Error' '422': $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 Assets 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' 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 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 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 UploadedImage: type: object additionalProperties: false required: - id - reference - media_type - byte_size - width - height properties: id: type: string pattern: ^asset_[a-f0-9]{32}$ reference: type: string pattern: ^asset://asset_[a-f0-9]{32}$ description: Private template reference. This is not a fetchable URL. media_type: type: string enum: - image/png - image/jpeg - image/webp byte_size: type: integer minimum: 1 maximum: 10485760 width: type: integer minimum: 1 maximum: 7680 description: Display width after image orientation. height: type: integer minimum: 1 maximum: 7680 description: Display height after image orientation. 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'