openapi: 3.2.0 info: title: thirds.ai Carousels 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: Carousels paths: /v1/carousels: post: summary: Create a carousel description: Render selected pages of one saved template version as images, in the requested order. Creates one normal image job per page. The ledger reserves one credit per job, settles it on success, and releases it on failure. A successful PDF of the same pages still costs one credit through POST /v1/pdf. operationId: createCarousel 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/CarouselCreate' examples: launchSlides: value: template_id: tpl_2f6a2f6a2f6a2f6a2f6a2f6a2f6a2f6a version: 1 data: brand: Northline Studio format: png pages: - cover - message - offer - proof - action responses: '201': description: The carousel 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/Carousel' '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: - Carousels /v1/carousels/{id}: parameters: - name: id in: path required: true schema: type: string pattern: ^carousel_[0-9a-f]{32}$ get: summary: Get a carousel's status description: Returns the carousel with each member's own state and, once any member has succeeded, a signed archive_url for the ZIP of every succeeded file. operationId: getCarousel security: - bearerAuth: [] responses: '200': description: The carousel's current status. headers: x-request-id: $ref: '#/components/headers/XRequestId' content: application/json: schema: $ref: '#/components/schemas/Carousel' '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: - Carousels /v1/carousels/{id}/retry: post: summary: Retry a carousel'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: retryCarousel security: - bearerAuth: [] parameters: - name: id in: path required: true schema: type: string pattern: ^carousel_[0-9a-f]{32}$ responses: '200': description: The carousel 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/CarouselRetry' '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: - Carousels /v1/carousels/archive/{token}: get: summary: Download a carousel's ZIP archive description: Stream a ZIP of one carousel's succeeded members, in requested order with numbered filenames such as 01-cover.png, plus manifest.json recording that order and any failed pages. The link carries its own authority, so no other credential is read. operationId: getCarouselArchive 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 carousel 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: - Carousels 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}$ CarouselRetry: $ref: '#/components/schemas/Carousel' 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 CarouselMember: type: object properties: position: type: integer minimum: 0 page_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 - page_id - name - width - height - state - error - download_url additionalProperties: false CarouselCreate: 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 applied to every selected page. Omitted to use an empty object. format: type: string enum: - png - jpeg - webp pages: type: array items: type: string minLength: 1 maxLength: 64 pattern: ^[A-Za-z][A-Za-z0-9_-]{0,63}$ minItems: 1 maxItems: 100 description: 1 to 100 different canvas data-thirds-page ids in output order. required: - template_id - format - pages 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 Carousel: type: object properties: id: type: string pattern: ^carousel_[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/CarouselMember' 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 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'