> ## Documentation Index
> Fetch the complete documentation index at: https://docs.yalg.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Upload a custom layout through the frontend contract

> Upload a custom layout through the frontend contract. Requests are scoped to the authenticated API key owner; cross-owner resource IDs return 404.



## OpenAPI

````yaml https://api.yalg.ai/openapi.v1-3.1.json post /v1/carousel/templates/{templateId}/custom-layout
openapi: 3.1.0
info:
  title: YALG Developer API
  description: >-
    Public v1 API for integrating with YALG content workflows. Authenticate
    server-side requests with the `x-api-key` header. Every request is scoped to
    the API key owner.
  version: '1.0'
  contact:
    name: YALG Support
    url: https://yalg.ai
    email: support@yalg.ai
  termsOfService: https://yalg.ai/terms
  license:
    name: Proprietary
    url: https://yalg.ai
servers:
  - url: https://api.yalg.ai
    description: Production
security: []
tags:
  - name: public-v1
    description: Stable public developer API
  - name: anecdotes
    description: Public anecdote endpoints
  - name: posts
    description: Public post endpoints
  - name: post-generation
    description: Public post generation endpoints
  - name: script-jobs
    description: Public script job endpoints
  - name: shorts
    description: Public short-form video endpoints
  - name: carousel
    description: Public carousel endpoints
  - name: carousel-generation
    description: Public carousel generation endpoints
  - name: broll
    description: Public B-roll generation endpoints
  - name: components
    description: Public component library endpoints
  - name: Blog
    description: Public blog endpoints
  - name: youtube-suggestions
    description: Public YouTube suggestion endpoints
  - name: statistics
    description: Public statistics endpoints
paths:
  /v1/carousel/templates/{templateId}/custom-layout:
    post:
      tags:
        - Carousel
      summary: Upload a custom layout through the frontend contract
      description: >-
        Upload a custom layout through the frontend contract. Requests are
        scoped to the authenticated API key owner; cross-owner resource IDs
        return 404.
      operationId: CarouselController_uploadCustomLayoutFromFrontend[1]
      parameters:
        - name: templateId
          required: true
          in: path
          schema:
            type: string
            example: 018f9f46-9a0e-7afd-b13f-2f63f07aa4f2
          description: Carousel template identifier.
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                layout:
                  type: string
                  format: binary
                  description: >-
                    ZIP file containing .hbs templates (intro.hbs, content.hbs,
                    outro.hbs)
              required:
                - layout
            examples:
              default:
                summary: Example request
                value:
                  carousel: <PDF file>
                  title: Founder lessons carousel
      responses:
        '200':
          description: Custom layout uploaded successfully
          headers:
            X-RateLimit-Limit:
              description: Maximum number of API requests allowed in the current window.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Number of API requests remaining in the current window.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Unix timestamp, in seconds, when the current rate limit window
                resets.
              schema:
                type: integer
                example: 1766246400
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/YalgCarouselTemplate'
                  - $ref: '#/components/schemas/YalgMutationResult'
              examples:
                success:
                  summary: Example response
                  value:
                    id: 018f9f46-9a0e-7afd-b13f-2f63f07aa4f2
                    name: Minimal red carousel
                    config:
                      primaryColor: '#e00036'
                      slideCount: 6
                    isDefault: false
                    createdAt: '2026-06-20T17:24:39.000Z'
                    updatedAt: '2026-06-20T17:24:39.000Z'
        '201':
          description: >-
            Created successfully. Upload a custom layout through the frontend
            contract.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CarouselTemplate'
              examples:
                success:
                  summary: Example response
                  value:
                    id: 018f9f46-9a0e-7afd-b13f-2f63f07aa4f2
                    name: Minimal red carousel
                    config:
                      primaryColor: '#e00036'
                      slideCount: 6
                    isDefault: false
                    createdAt: '2026-06-20T17:24:39.000Z'
                    updatedAt: '2026-06-20T17:24:39.000Z'
          headers:
            X-RateLimit-Limit:
              description: Maximum number of API requests allowed in the current window.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Number of API requests remaining in the current window.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Unix timestamp, in seconds, when the current rate limit window
                resets.
              schema:
                type: integer
                example: 1766246400
        '400':
          description: Invalid ZIP file or missing required templates
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YalgErrorResponse'
              examples:
                validation_failed:
                  summary: Bad Request
                  value:
                    statusCode: 400
                    message: Validation failed
                    error: Bad Request
                    code: validation_failed
          headers:
            X-RateLimit-Limit:
              description: Maximum number of API requests allowed in the current window.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Number of API requests remaining in the current window.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Unix timestamp, in seconds, when the current rate limit window
                resets.
              schema:
                type: integer
                example: 1766246400
        '401':
          description: >-
            Authentication failed. Provide a valid `x-api-key` header for public
            API requests.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YalgErrorResponse'
              examples:
                unauthorized:
                  summary: Unauthorized
                  value:
                    statusCode: 401
                    message: Invalid or missing API key
                    error: Unauthorized
                    code: unauthorized
          headers:
            X-RateLimit-Limit:
              description: Maximum number of API requests allowed in the current window.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Number of API requests remaining in the current window.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Unix timestamp, in seconds, when the current rate limit window
                resets.
              schema:
                type: integer
                example: 1766246400
        '403':
          description: >-
            Forbidden. The authenticated owner does not have access to this
            feature or resource.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YalgErrorResponse'
              examples:
                forbidden:
                  summary: Forbidden
                  value:
                    statusCode: 403
                    message: Access denied
                    error: Forbidden
                    code: forbidden
          headers:
            X-RateLimit-Limit:
              description: Maximum number of API requests allowed in the current window.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Number of API requests remaining in the current window.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Unix timestamp, in seconds, when the current rate limit window
                resets.
              schema:
                type: integer
                example: 1766246400
        '404':
          description: Template not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YalgErrorResponse'
              examples:
                not_found:
                  summary: Not Found
                  value:
                    statusCode: 404
                    message: Resource not found
                    error: Not Found
                    code: not_found
          headers:
            X-RateLimit-Limit:
              description: Maximum number of API requests allowed in the current window.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Number of API requests remaining in the current window.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Unix timestamp, in seconds, when the current rate limit window
                resets.
              schema:
                type: integer
                example: 1766246400
        '409':
          description: >-
            Conflict. The request cannot be completed because the resource is
            already in another state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YalgErrorResponse'
              examples:
                conflict:
                  summary: Conflict
                  value:
                    statusCode: 409
                    message: Resource conflict
                    error: Conflict
                    code: conflict
          headers:
            X-RateLimit-Limit:
              description: Maximum number of API requests allowed in the current window.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Number of API requests remaining in the current window.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Unix timestamp, in seconds, when the current rate limit window
                resets.
              schema:
                type: integer
                example: 1766246400
        '429':
          description: >-
            Rate limit exceeded. Wait for the window to reset before retrying
            the request.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YalgErrorResponse'
              examples:
                rate_limit_exceeded:
                  summary: Too Many Requests
                  value:
                    statusCode: 429
                    message: Rate limit exceeded
                    error: Too Many Requests
                    code: rate_limit_exceeded
                    retryAfter: 60
          headers:
            X-RateLimit-Limit:
              description: Maximum number of API requests allowed in the current window.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Number of API requests remaining in the current window.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Unix timestamp, in seconds, when the current rate limit window
                resets.
              schema:
                type: integer
                example: 1766246400
            Retry-After:
              description: Seconds to wait before retrying the request.
              schema:
                type: integer
                example: 60
        '500':
          description: Internal server error. An unexpected YALG backend error occurred.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/YalgErrorResponse'
              examples:
                internal_server_error:
                  summary: Internal Server Error
                  value:
                    statusCode: 500
                    message: Internal server error
                    error: Internal Server Error
                    code: internal_server_error
          headers:
            X-RateLimit-Limit:
              description: Maximum number of API requests allowed in the current window.
              schema:
                type: integer
                example: 120
            X-RateLimit-Remaining:
              description: Number of API requests remaining in the current window.
              schema:
                type: integer
                example: 119
            X-RateLimit-Reset:
              description: >-
                Unix timestamp, in seconds, when the current rate limit window
                resets.
              schema:
                type: integer
                example: 1766246400
      security:
        - ApiKeyAuth: []
        - bearer: []
components:
  schemas:
    YalgCarouselTemplate:
      type: object
      description: Carousel template configuration.
      properties:
        id:
          type: string
          format: uuid
          description: Template identifier.
          example: 018f9f40-7d7b-7712-9b0f-2e5d9fd5df3f
        name:
          type: string
          example: Minimal red
        config:
          type: object
          additionalProperties: true
        isDefault:
          type: boolean
          example: false
        createdAt:
          type: string
          format: date-time
          example: '2026-06-20T17:24:39.000Z'
        updatedAt:
          type: string
          format: date-time
          example: '2026-06-20T17:24:39.000Z'
      additionalProperties: true
    YalgMutationResult:
      type: object
      description: >-
        Generic result returned by mutation endpoints when the endpoint does not
        return a full domain object.
      properties:
        success:
          type: boolean
          example: true
        message:
          type: string
          example: Operation completed successfully
        id:
          type: string
          format: uuid
          description: Identifier of the affected resource when available.
      additionalProperties: true
    CarouselTemplate:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        user:
          $ref: '#/components/schemas/User'
        userId:
          type: string
        config:
          type: object
        isActive:
          type: boolean
        isCustomLayout:
          type: boolean
        createdAt:
          format: date-time
          type: string
        updatedAt:
          format: date-time
          type: string
      required:
        - id
        - name
        - user
        - userId
        - config
        - isActive
        - isCustomLayout
        - createdAt
        - updatedAt
    YalgErrorResponse:
      type: object
      required:
        - statusCode
        - message
      description: >-
        Standard error payload returned by YALG API endpoints. Validation errors
        may return `message` as an array of field-level messages.
      properties:
        statusCode:
          type: integer
          example: 400
        message:
          oneOf:
            - type: string
              example: Bad request
            - type: array
              items:
                type: string
              example:
                - field must be a string
        error:
          type: string
          example: Bad Request
        code:
          type: string
          example: validation_failed
          description: Stable machine-readable error code when the backend can provide one.
        requestId:
          type: string
          example: req_01HX7Y6D3XQ4J8W9Z0A1B2C3D4
          description: >-
            Request correlation identifier. Include this when contacting YALG
            support.
        retryAfter:
          type: integer
          example: 60
          description: >-
            Seconds to wait before retrying. Present on some rate-limit
            responses.
        details:
          type: object
          additionalProperties: true
          description: Optional structured details for validation or domain errors.
    User:
      type: object
      properties:
        id:
          type: string
        email:
          type: string
        password:
          type: string
        firstName:
          type: string
        lastName:
          type: string
        profilePicture:
          type: string
        linkedinUserId:
          type: string
        linkedinUsername:
          type: string
        linkedinAccessToken:
          type: string
        linkedinRefreshToken:
          type: string
        linkedinTokenExpiresAt:
          format: date-time
          type: string
        linkedinProfile:
          type: object
        instagramUserId:
          type: string
        instagramUsername:
          type: string
        instagramAccessToken:
          type: string
        instagramTokenExpiresAt:
          format: date-time
          type: string
        instagramFacebookPageId:
          type: string
        facebookPageId:
          type: string
        facebookPageName:
          type: string
        facebookUserAccessToken:
          type: string
        facebookPageAccessToken:
          type: string
        facebookTokenExpiresAt:
          format: date-time
          type: string
        githubUserId:
          type: string
        githubUsername:
          type: string
        githubAccessToken:
          type: string
        githubTokenExpiresAt:
          format: date-time
          type: string
        githubProfile:
          type: object
        googleUserId:
          type: string
        googleProfile:
          type: object
        language:
          type: string
        emailAlerts:
          type: boolean
        weeklyDigest:
          type: boolean
        newComments:
          type: boolean
        aiReady:
          type: boolean
        marketingEmails:
          type: boolean
        plan:
          type: string
        billingCycle:
          type: string
        nextBillingDate:
          format: date-time
          type: string
        paymentMethod:
          type: object
        postsGeneratedThisMonth:
          type: number
        anecdotesStored:
          type: number
        stripeCustomerId:
          type: string
        stripeSubscriptionId:
          type: string
        stripeDefaultPaymentMethodId:
          type: string
        subscriptionStatus:
          type: string
        trialStartDate:
          format: date-time
          type: string
        trialEndDate:
          format: date-time
          type: string
        subscriptionStartDate:
          format: date-time
          type: string
        subscriptionEndDate:
          format: date-time
          type: string
        currentPeriodStart:
          format: date-time
          type: string
        currentPeriodEnd:
          format: date-time
          type: string
        cancelAtPeriodEnd:
          type: boolean
        paymentMethods:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              type:
                type: string
              last4:
                type: string
              brand:
                type: string
              expiryMonth:
                type: number
              expiryYear:
                type: number
              isDefault:
                type: boolean
            required:
              - id
              - type
              - last4
              - isDefault
        lastInvoiceId:
          type: string
        lastPaymentDate:
          format: date-time
          type: string
        lastPaymentAmount:
          type: number
        failedPaymentAttempts:
          type: number
        lastFailedPaymentDate:
          format: date-time
          type: string
        usageResetDate:
          format: date-time
          type: string
        monthlyUsage:
          type: object
          properties:
            posts:
              type: number
            linkedinPosts:
              type: number
            youtubeScripts:
              type: number
            blogArticles:
              type: number
            carousels:
              type: number
            shorts:
              type: number
            brollJobs:
              type: number
            anecdotes:
              type: number
            apiCalls:
              type: number
          required: []
        usageLimits:
          type: object
          properties:
            posts:
              type: number
              nullable: true
            linkedinPosts:
              type: number
              nullable: true
            youtubeScripts:
              type: number
              nullable: true
            blogArticles:
              type: number
              nullable: true
            carousels:
              type: number
              nullable: true
            shorts:
              type: number
              nullable: true
            brollJobs:
              type: number
              nullable: true
            anecdotes:
              type: number
              nullable: true
            apiCalls:
              type: number
              nullable: true
            apiRateLimitPerMinute:
              type: number
              nullable: true
          required: []
        featureAccess:
          type: object
        stripeCouponId:
          type: string
        discountEnd:
          format: date-time
          type: string
        discountPercent:
          type: number
        posts:
          type: array
          items:
            type: object
        anecdotes:
          type: array
          items:
            type: object
        themes:
          type: array
          items:
            type: object
        defaultThemeId:
          type: string
        styleAnalysis:
          type: object
        isActive:
          type: boolean
        createdAt:
          format: date-time
          type: string
        updatedAt:
          format: date-time
          type: string
      required:
        - id
        - email
        - firstName
        - lastName
        - language
        - emailAlerts
        - weeklyDigest
        - newComments
        - aiReady
        - marketingEmails
        - plan
        - billingCycle
        - postsGeneratedThisMonth
        - anecdotesStored
        - subscriptionStatus
        - cancelAtPeriodEnd
        - failedPaymentAttempts
        - featureAccess
        - posts
        - anecdotes
        - themes
        - isActive
        - createdAt
        - updatedAt
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        YALG Developer API key. Send it in the `x-api-key` header from a
        server-side environment only.
      x-default: yalg_live_your_api_key
    bearer:
      scheme: bearer
      bearerFormat: JWT
      type: http

````