openapi: 3.1.0
info:
  title: Rendra API
  version: 1.0.0
  description: |
    Rendra is an OG image generation API powered by AI. Generate beautiful, customized images for social media, blogs, and marketing materials.

    ## Authentication
    All API requests require authentication using an API key. Include your API key in the request headers:
    - `Authorization: Bearer YOUR_API_KEY` or
    - `X-API-Key: YOUR_API_KEY`

    ## Rate Limits
    - **Free tier**: 100 generations/month
    - **Pro tier**: 1000 generations/month

    ## Getting Started
    1. Sign up to get your API key
    2. Install the SDK: `npm install @rendra/client`
    3. Start generating images!

  contact:
    email: support@alphabros.eu
  license:
    name: MIT

servers:
  - url: https://rendra.alphabros.eu/api/v1
    description: Production server
  - url: http://localhost:3000/api/v1
    description: Development server

tags:
  - name: Generation
    description: Image generation endpoints
  - name: Delivery
    description: Signed image delivery for crawlers and social platforms
  - name: Dashboard
    description: User dashboard and analytics
  - name: Users
    description: User management

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: API key for authentication
    BearerAuth:
      type: http
      scheme: bearer
      description: Bearer token authentication

  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
          description: Error message
      required:
        - error

    User:
      type: object
      properties:
        id:
          type: string
          description: User ID
        email:
          type: string
          format: email
          description: User email
        plan:
          type: string
          enum: [free, pro]
          description: Current subscription plan
        credits_used:
          type: integer
          description: Total credits used
        created_at:
          type: string
          format: date-time
          description: Account creation timestamp

    Generation:
      type: object
      properties:
        id:
          type: string
          description: Generation ID
        user_id:
          type: string
          description: User ID who created the generation
        prompt:
          type: string
          description: Text prompt used for generation
        status:
          type: string
          enum: [pending, processing, completed, failed]
          description: Generation status
        image_url:
          type: string
          format: uri
          nullable: true
          description: URL to the generated image (null if not completed)
        credits_used:
          type: integer
          description: Credits consumed for this generation
        created_at:
          type: string
          format: date-time
          description: Generation creation timestamp
      required:
        - id
        - user_id
        - prompt
        - status
        - credits_used
        - created_at

    GenerationRequest:
      type: object
      properties:
        prompt:
          type: string
          description: Text description of the image to generate
          example: "A futuristic cityscape at sunset with flying cars"
        template_id:
          type: string
          description: Optional template ID to use
          default: "default"
        width:
          type: integer
          description: Image width in pixels
          default: 1024
          minimum: 256
          maximum: 2048
        height:
          type: integer
          description: Image height in pixels
          default: 1024
          minimum: 256
          maximum: 2048
      required:
        - prompt

    GenerationResponse:
      type: object
      properties:
        id:
          type: string
          description: Generation ID
        status:
          type: string
          enum: [processing, completed, failed]
          description: >-
            Generation status at response time. The serverless runtime resolves
            the generation before responding, so this is usually already
            "completed" or "failed"; "processing" means keep polling
            GET /api/v1/generate/{id}.
        message:
          type: string
          description: Status message
        template_id:
          type: string
          description: Template ID used
        prompt:
          type: string
          description: The prompt used
        dimensions:
          type: object
          properties:
            width:
              type: integer
            height:
              type: integer

    DashboardStats:
      type: object
      properties:
        total_generations:
          type: integer
          description: Total number of generations
        credits_used:
          type: integer
          description: Total credits consumed
        credits_remaining:
          type: integer
          description: Credits remaining in current plan
        plan:
          type: string
          enum: [free, pro]
          description: Current subscription plan
        plan_limit:
          type: integer
          description: Monthly generation limit for current plan

    ApiKey:
      type: object
      properties:
        id:
          type: string
          description: API key ID
        name:
          type: string
          description: Human-readable name for the API key
        key:
          type: string
          description: The actual API key (only shown once at creation)
        last_used_at:
          type: string
          format: date-time
          nullable: true
          description: Last time this key was used
        created_at:
          type: string
          format: date-time
          description: Key creation timestamp

paths:
  /i/{template}:
    get:
      summary: Render an image from a signed URL
      description: |
        Crawler-compatible image rendering for `og:image` and similar consumers
        that cannot send authentication headers. The `sig` value is
        `<API_KEY_ID>.<HMAC_SHA256>`. Compute the HMAC with the API key as the
        secret over the request path, `?`, and all query parameters except
        `sig`, sorted by name. An `expires` Unix timestamp is required and is
        covered by the signature. All non-control parameters become template
        data; control parameters are `expires`, `format`, `width`, and `height`.

        `width`/`height` size the rendered card itself, clamped to 200-2400 px
        per axis.

        The `og_brand` template additionally accepts `bg`, `bg2`, `bgAngle`,
        `fg`, `muted`, `accent`, `font`, `logo`, `align` and `theme`. Every one
        of those is validated before it reaches CSS: colours must be `#rgb`,
        `#rrggbb`, `#rrggbbaa` or a strict `rgb()`/`rgba()`; `bgAngle` an
        integer 0-360; `font` one of `system`, `apple`, `arial`, `helvetica`,
        `serif`, `mono`; `align` one of `center`, `left`; `theme` one of
        `light`, `dark`; `logo` an absolute `https:` URL on an allowlisted host
        (`*.alphabros.eu` by default). An invalid value is rejected with 400
        naming the parameter — it is never silently dropped. Passing no colour
        at all renders a neutral, unbranded card.
      tags:
        - Delivery
      security: []
      servers:
        - url: https://rendra.alphabros.eu
          description: Production server
        - url: http://localhost:3000
          description: Development server
      parameters:
        - name: template
          in: path
          required: true
          schema:
            type: string
            enum: [og_standard, announcement, og_brand]
        - name: expires
          in: query
          required: true
          description: Unix timestamp after which the URL is rejected
          schema:
            type: integer
        - name: sig
          in: query
          required: true
          description: API key ID and lowercase SHA-256 HMAC separated by a dot
          schema:
            type: string
        - name: format
          in: query
          schema:
            type: string
            enum: [png, jpeg]
            default: png
        - name: width
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 4096
            default: 1200
        - name: height
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 4096
            default: 630
      responses:
        "200":
          description: Rendered image
          headers:
            Cache-Control:
              description: Public cache lifetime capped at 24 hours and URL expiry
              schema:
                type: string
            X-Cached:
              description: Whether Rendra's renderer cache supplied the image
              schema:
                type: string
                enum: ["true", "false"]
          content:
            image/png:
              schema:
                type: string
                format: binary
            image/jpeg:
              schema:
                type: string
                format: binary
        "400":
          description: >-
            Invalid image dimensions, format, or template parameter. The body
            names the offending parameter.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/Error"
                  - type: object
                    properties:
                      parameter:
                        type: string
                        description: The template parameter that failed validation
                        example: bg
        "403":
          description: Signature is invalid or expired
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "402":
          description: Account credit limit reached

  /api/v1/design/og-params:
    post:
      summary: Derive og_brand parameters from a DESIGN.md
      description: |
        DESIGN.md is an OPTIONAL brand input. Supply the document body
        (`designMd`) or a URL to a raw file on an allowlisted host
        (`designUrl` — the same host allowlist as `logo`, because fetching a
        customer URL is the same egress concern). Tokens map as:
        `colors.background` → `bg`, `colors.surface-gradient` → `bg2`,
        `colors.foreground` → `fg`, `colors.text-muted` → `muted`,
        `colors.accent`/`colors.primary` → `accent`,
        `typography.heading.fontFamily` → `font` (through the font allowlist,
        never interpolated raw), plus the extended `tagline`, `logo` and
        `social:` keys. Anything the document does not declare stays unset, so
        a colourless design system still yields a neutral card. Explicit
        `params` always win over anything derived from the file. No image is
        rendered, so no credit is consumed.
      tags:
        - Delivery
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                designMd:
                  type: string
                  description: DESIGN.md document body
                designUrl:
                  type: string
                  description: https URL to a raw DESIGN.md on an allowlisted host
                params:
                  type: object
                  additionalProperties:
                    type: string
                  description: Explicit og_brand params; these win over the file
      responses:
        "200":
          description: Derived template parameters
          content:
            application/json:
              schema:
                type: object
                properties:
                  template:
                    type: string
                    example: og_brand
                  params:
                    type: object
                    additionalProperties:
                      type: string
                  sources:
                    type: object
                    additionalProperties:
                      type: string
                  notes:
                    type: array
                    items:
                      type: string
                  lint:
                    type: object
                    properties:
                      errors:
                        type: integer
                      warnings:
                        type: integer
                      infos:
                        type: integer
        "400":
          description: Invalid document or parameter; the body names the parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Missing or invalid API key

  /api/v1/design/design-md:
    post:
      summary: Generate a DESIGN.md
      description: |
        For a customer who has no design system yet: returns a spec-valid
        DESIGN.md (frontmatter with `name`, `version`, `colors`, `typography`,
        `spacing`, `rounded`, plus the `logo`, `tagline` and `social:` keys the
        fleet needs and the spec tolerates). No brand colour is invented — omit
        `colors.primary` and the document stays neutral. No credit is consumed.
      tags:
        - Delivery
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name:
                  type: string
                tagline:
                  type: string
                description:
                  type: string
                siteUrl:
                  type: string
                logo:
                  type: string
                font:
                  type: string
                  enum: [system, apple, arial, helvetica, serif, mono]
                colors:
                  type: object
                  properties:
                    background:
                      type: string
                    foreground:
                      type: string
                    muted:
                      type: string
                    primary:
                      type: string
                social:
                  type: object
                  properties:
                    title:
                      type: string
                    description:
                      type: string
                    footer:
                      type: string
      responses:
        "200":
          description: The generated document
          content:
            application/json:
              schema:
                type: object
                properties:
                  filename:
                    type: string
                    example: DESIGN.md
                  designMd:
                    type: string
                  lint:
                    type: object
                    properties:
                      errors:
                        type: integer
                      warnings:
                        type: integer
                      infos:
                        type: integer
                  template:
                    type: string
                    example: og_brand
                  params:
                    type: object
                    additionalProperties:
                      type: string
        "400":
          description: Invalid input; the body names the offending field
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Missing or invalid API key

  /health:
    get:
      summary: Health check
      description: Check API health status
      tags:
        - System
      security: []
      responses:
        "200":
          description: API is healthy
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
                  service:
                    type: string
                    example: rendra
                  version:
                    type: string
                    example: 1.0.0
                  timestamp:
                    type: string
                    format: date-time

  /users:
    post:
      summary: Create a new user
      description: Register a new user and receive an API key
      tags:
        - Users
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                email:
                  type: string
                  format: email
                  description: User email address
                plan:
                  type: string
                  enum: [free, pro]
                  default: free
                  description: Initial subscription plan
              required:
                - email
      responses:
        "201":
          description: User created successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  id:
                    type: string
                  email:
                    type: string
                  api_key:
                    type: string
                    description: Primary API key (store this securely!)
                  plan:
                    type: string
        "409":
          description: Email already registered
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /generate:
    post:
      summary: Generate an image
      description: Create a new image generation request
      tags:
        - Generation
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/GenerationRequest"
      responses:
        "202":
          description: Generation request accepted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/GenerationResponse"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Unauthorized - invalid or missing API key
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "429":
          description: Rate limit exceeded
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /generate/{id}:
    get:
      summary: Get generation status
      description: Retrieve the status and result of a generation request
      tags:
        - Generation
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: Generation ID
          schema:
            type: string
      responses:
        "200":
          description: Generation details
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Generation"
        "404":
          description: Generation not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /dashboard:
    get:
      summary: Get dashboard stats
      description: Retrieve user statistics and usage information
      tags:
        - Dashboard
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Dashboard statistics
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DashboardStats"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /dashboard/history:
    get:
      summary: Get generation history
      description: Retrieve all generations for the authenticated user
      tags:
        - Dashboard
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: limit
          in: query
          description: Maximum number of results to return
          schema:
            type: integer
            default: 50
            minimum: 1
            maximum: 100
        - name: offset
          in: query
          description: Number of results to skip
          schema:
            type: integer
            default: 0
            minimum: 0
      responses:
        "200":
          description: Generation history
          content:
            application/json:
              schema:
                type: object
                properties:
                  generations:
                    type: array
                    items:
                      $ref: "#/components/schemas/Generation"
                  total:
                    type: integer
                    description: Total number of generations
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /dashboard/keys:
    get:
      summary: List API keys
      description: Get all API keys for the authenticated user
      tags:
        - Dashboard
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: List of API keys
          content:
            application/json:
              schema:
                type: object
                properties:
                  keys:
                    type: array
                    items:
                      $ref: "#/components/schemas/ApiKey"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

    post:
      summary: Create API key
      description: Generate a new API key
      tags:
        - Dashboard
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  description: Human-readable name for the API key
              required:
                - name
      responses:
        "201":
          description: API key created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiKey"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /dashboard/keys/{id}:
    delete:
      summary: Revoke API key
      description: Revoke an API key (cannot be undone)
      tags:
        - Dashboard
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          description: API key ID to revoke
          schema:
            type: string
      responses:
        "200":
          description: API key revoked successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  message:
                    type: string
                    example: API key revoked successfully
        "404":
          description: API key not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "401":
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
