openapi: 3.0.3

info:
  title: 1FRX API
  description: |
    The 1FRX API provides programmatic access to image generation, PDF rendering,
    audio synthesis, web screenshots, document parsing, and the MCP server endpoint.

    ## Authentication
    All paid endpoints require an `Authorization` header with a Bearer token:
    ```
    Authorization: Bearer sk_live_<your-key>
    ```
    Generate keys at [dashboard.html](/dashboard.html). Keys are HMAC-signed and
    self-validating — the server verifies without a database round-trip.

    ## Base URL
    ```
    https://1frx.com
    ```

    ## Rate limits
    Limits are enforced per API key and reset every calendar month.
    | Plan     | Images   | PDFs   | Audio (min) | Screenshots | Parses |
    |----------|----------|--------|-------------|-------------|--------|
    | Basic    | 5 000    | 500    | 60          | 1 000       | 500    |
    | Pro      | 25 000   | 5 000  | 300         | 10 000      | 5 000  |
    | Business | 100 000  | 25 000 | 1 200       | 50 000      | 25 000 |
    | VIP      | Unlimited| Unlim. | Unlimited   | Unlimited   | Unlim. |

    A `429 Too Many Requests` response includes a `Retry-After` header.

    ## Errors
    All error responses share the same envelope:
    ```json
    { "error": "Human-readable message", "code": "MACHINE_READABLE_CODE" }
    ```

  version: "1.0.0"
  contact:
    name: 1FRX Support
    email: hello@1frx.com
    url: https://1frx.com
  license:
    name: Proprietary
    url: https://1frx.com/terms.html

servers:
  - url: https://1frx.com
    description: Production

tags:
  - name: Images
    description: Generate social cards, OG images, and custom renders
  - name: PDFs
    description: Render invoices, certificates, reports, and custom layouts
  - name: Audio
    description: Text-to-speech synthesis via ElevenLabs
  - name: Screenshots
    description: Headless browser capture of any public URL
  - name: Parse
    description: AI-powered document and image data extraction (OCR + LLM)
  - name: Keys
    description: API key management
  - name: MCP
    description: Model Context Protocol server — AI agent tool integration
  - name: System
    description: Health and status

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: sk_live_<payload>.<hmac>
      description: API key generated from the 1FRX Dashboard

  schemas:
    Error:
      type: object
      required: [error]
      properties:
        error:
          type: string
          example: "Missing required field: template"
        code:
          type: string
          example: "MISSING_FIELD"

    ImageRequest:
      type: object
      required: [template, data]
      properties:
        template:
          type: string
          enum: [ogCard, socialSquare, twitterCard, announcementBanner, custom]
          description: |
            - `ogCard` — 1200×630 px (Open Graph / link preview)
            - `socialSquare` — 1080×1080 px (Instagram / LinkedIn)
            - `twitterCard` — 800×418 px
            - `announcementBanner` — 1200×400 px
            - `custom` — use the `html` field for a fully custom render
        data:
          type: object
          description: Template variables
          properties:
            title:       { type: string }
            subtitle:    { type: string }
            body:        { type: string }
            cta_text:    { type: string }
            brand_color: { type: string, example: "#5ee3ff" }
            logo_url:    { type: string, format: uri }
            background_color: { type: string }
            text_color:  { type: string }
        html:
          type: string
          description: Custom HTML string (only valid when `template` is `custom`)
        format:
          type: string
          enum: [png, jpeg, webp]
          default: png
        quality:
          type: integer
          minimum: 1
          maximum: 100
          default: 90
          description: JPEG/WebP compression quality
        width:
          type: integer
          description: Override template width in pixels
        height:
          type: integer
          description: Override template height in pixels

    ImageResponse:
      type: object
      properties:
        url:
          type: string
          format: uri
          description: CDN URL of the generated image (Cloudflare R2, valid 24 h)
          example: "https://cdn.1frx.com/renders/img_abc123.png"
        width:  { type: integer, example: 1200 }
        height: { type: integer, example: 630 }
        format: { type: string, example: "png" }
        size_bytes: { type: integer, example: 142580 }
        credits_used: { type: integer, example: 1 }

    PdfRequest:
      type: object
      required: [template, data]
      properties:
        template:
          type: string
          enum: [invoicePdf, certificatePdf, reportPdf, letterPdf, custom]
          description: |
            - `invoicePdf` — itemised invoice with totals
            - `certificatePdf` — landscape certificate (A4 auto-landscape)
            - `reportPdf` — multi-section report
            - `letterPdf` — formal letter on headed paper
            - `custom` — pass raw HTML in `data.html`
        data:
          type: object
          description: |
            Template-specific fields. See [PDF Generation docs](/docs/apis/pdf-gen.html) for full field lists.

            **invoicePdf:** `invoice_number`, `date`, `due_date`, `from_name`, `from_address`,
            `to_name`, `to_address`, `items[]` (`{description, quantity, unit_price}`),
            `currency`, `tax_rate`, `notes`

            **certificatePdf:** `recipient_name`, `course_name`, `issuer_name`, `date`,
            `logo_url`, `signature_url`

            **reportPdf:** `title`, `subtitle`, `sections[]` (`{heading, body}`)

            **letterPdf:** `sender_name`, `sender_address`, `recipient_name`,
            `recipient_address`, `date`, `subject`, `body`
        page_size:
          type: string
          enum: [A4, Letter, Legal, A3]
          default: A4
        landscape:
          type: boolean
          default: false
          description: Force landscape orientation (auto-true for `certificatePdf`)
        margin:
          type: string
          enum: [none, small, normal, large]
          default: normal

    PdfResponse:
      type: object
      properties:
        url:
          type: string
          format: uri
          description: CDN URL of the rendered PDF (valid 24 h)
          example: "https://cdn.1frx.com/renders/doc_abc123.pdf"
        pages:       { type: integer, example: 2 }
        size_bytes:  { type: integer, example: 87040 }
        credits_used: { type: integer, example: 1 }

    AudioRequest:
      type: object
      required: [script]
      properties:
        script:
          type: string
          description: Text to synthesise. Max 5 000 characters per request.
          example: "Welcome to 1FRX. This is a demonstration of our audio synthesis API."
        voice_id:
          type: string
          default: "21m00Tcm4TlvDq8ikWAM"
          description: ElevenLabs voice ID. Default is Rachel.
        model:
          type: string
          default: eleven_multilingual_v2
          enum: [eleven_multilingual_v2, eleven_turbo_v2, eleven_monolingual_v1]
        format:
          type: string
          enum: [mp3, wav, opus, aac]
          default: mp3
        stability:
          type: number
          minimum: 0
          maximum: 1
          default: 0.5
          description: Voice consistency (higher = more stable, lower = more expressive)
        similarity:
          type: number
          minimum: 0
          maximum: 1
          default: 0.75
          description: How closely the output matches the original voice
        speed:
          type: number
          minimum: 0.7
          maximum: 1.2
          default: 1.0

    AudioResponse:
      type: object
      properties:
        url:
          type: string
          format: uri
          description: CDN URL of the audio file (valid 24 h)
          example: "https://cdn.1frx.com/renders/audio_abc123.mp3"
        duration_seconds: { type: number, example: 4.8 }
        characters_used:  { type: integer, example: 69 }
        format:           { type: string, example: "mp3" }
        credits_used:     { type: integer, example: 1 }

    ScreenshotRequest:
      type: object
      required: [url]
      properties:
        url:
          type: string
          format: uri
          description: Publicly reachable URL to capture
          example: "https://example.com"
        device:
          type: string
          enum: [desktop, laptop, tablet, mobile]
          default: desktop
          description: |
            Viewport preset:
            - `desktop` — 1280×800
            - `laptop` — 1440×900
            - `tablet` — 768×1024
            - `mobile` — 390×844
        format:
          type: string
          enum: [png, jpeg, webp]
          default: png
        fullPage:
          type: boolean
          default: false
          description: Capture the full scrollable page, not just the viewport
        darkMode:
          type: boolean
          default: false
          description: Enable `prefers-color-scheme: dark` in the headless browser
        blockAds:
          type: boolean
          default: false
          description: Block common ad network requests before capture
        delay:
          type: integer
          default: 0
          description: Milliseconds to wait after page load before capturing (useful for animations)
        selector:
          type: string
          description: CSS selector — capture only the matched element instead of the full viewport

    ScreenshotResponse:
      type: object
      properties:
        url:
          type: string
          format: uri
          description: CDN URL of the screenshot (valid 24 h)
          example: "https://cdn.1frx.com/renders/shot_abc123.png"
        width:        { type: integer, example: 1280 }
        height:       { type: integer, example: 800 }
        format:       { type: string, example: "png" }
        size_bytes:   { type: integer, example: 342016 }
        credits_used: { type: integer, example: 1 }

    ParseRequest:
      type: object
      required: [file, mimeType]
      properties:
        file:
          type: string
          format: byte
          description: Base64-encoded file contents
        mimeType:
          type: string
          enum:
            - application/pdf
            - image/jpeg
            - image/png
            - image/webp
          description: MIME type of the encoded file
        schema:
          type: object
          description: |
            Optional JSON Schema defining the fields to extract.
            If omitted, the model auto-detects the document type and extracts
            all structured fields it can identify.
          example:
            type: object
            properties:
              invoice_number: { type: string }
              total_amount:   { type: number }
              vendor_name:    { type: string }
        hints:
          type: string
          description: Plain-English instructions to guide extraction
          example: "Extract all line items and the grand total. Ignore any handwritten annotations."

    ParseResponse:
      type: object
      properties:
        data:
          type: object
          description: Extracted structured data (shape matches `schema` if provided)
          example:
            invoice_number: "INV-2024-0042"
            total_amount: 1250.00
            vendor_name: "Acme Supplies Ltd"
        confidence:
          type: number
          minimum: 0
          maximum: 1
          example: 0.97
        document_type:
          type: string
          example: "invoice"
        credits_used: { type: integer, example: 1 }

    KeyGenerateRequest:
      type: object
      required: [email, plan]
      properties:
        email:
          type: string
          format: email
          description: Email address to associate with the key
        plan:
          type: string
          enum: [starter, pro, business, vip]

    KeyGenerateResponse:
      type: object
      properties:
        key:
          type: string
          description: The new API key. Store it now — it is not shown again.
          example: "sk_live_dXNlcjoxMjM0_a1b2c3d4e5f6"
        plan:    { type: string, example: "pro" }
        email:   { type: string, format: email }
        created: { type: string, format: date-time }

    KeyRegenerateRequest:
      type: object
      required: [key]
      properties:
        key:
          type: string
          description: The existing key to rotate

    HealthResponse:
      type: object
      properties:
        status:
          type: string
          enum: [ok, degraded]
          example: ok
        version: { type: string, example: "1.0.0" }
        uptime_seconds: { type: integer, example: 86400 }
        services:
          type: object
          properties:
            image:      { type: string, enum: [ok, degraded, down] }
            pdf:        { type: string, enum: [ok, degraded, down] }
            audio:      { type: string, enum: [ok, degraded, down] }
            screenshot: { type: string, enum: [ok, degraded, down] }
            parse:      { type: string, enum: [ok, degraded, down] }

security:
  - bearerAuth: []

paths:
  /api/image:
    post:
      tags: [Images]
      summary: Generate an image
      description: |
        Renders a branded image using a named template or custom HTML.
        Returns a CDN URL valid for 24 hours. Supported output formats: PNG, JPEG, WebP.
      operationId: generateImage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ImageRequest'
            examples:
              ogCard:
                summary: OG card for a blog post
                value:
                  template: ogCard
                  data:
                    title: "How 1FRX connects banks to crypto"
                    subtitle: "Platform deep-dive"
                    brand_color: "#5ee3ff"
                    logo_url: "https://1frx.com/logo.png"
                  format: png
      responses:
        '200':
          description: Image generated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImageResponse'
        '400':
          description: Invalid request parameters
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '401':
          description: Missing or invalid API key
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429':
          description: Monthly credit limit reached
          headers:
            Retry-After:
              schema: { type: string }
              description: ISO 8601 timestamp when credits reset
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '500':
          description: Render service error
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /api/pdf-gen:
    post:
      tags: [PDFs]
      summary: Render a PDF document
      description: |
        Generates a PDF from a named template with structured data, or from
        custom HTML. Returns a CDN URL valid for 24 hours.
      operationId: generatePdf
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PdfRequest'
            examples:
              invoice:
                summary: Simple invoice PDF
                value:
                  template: invoicePdf
                  data:
                    invoice_number: "INV-001"
                    date: "2026-04-17"
                    due_date: "2026-05-17"
                    from_name: "1FRX Ltd"
                    to_name: "Acme Corp"
                    items:
                      - description: "Pro plan subscription"
                        quantity: 1
                        unit_price: 39.00
                    currency: GBP
                    tax_rate: 20
                  page_size: A4
      responses:
        '200':
          description: PDF rendered successfully
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PdfResponse' }
        '400':
          description: Invalid parameters
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '401':
          description: Unauthorised
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429':
          description: Credit limit reached
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /api/audio:
    post:
      tags: [Audio]
      summary: Synthesise speech from text
      description: |
        Converts a text script to audio using ElevenLabs voices.
        Returns a CDN URL. Supported formats: MP3, WAV, Opus, AAC.
        Maximum input: 5 000 characters per request.
      operationId: generateAudio
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AudioRequest'
            examples:
              simple:
                summary: Basic TTS
                value:
                  script: "Welcome to 1FRX. Your AI-native platform."
                  format: mp3
      responses:
        '200':
          description: Audio synthesised successfully
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AudioResponse' }
        '400':
          description: Invalid parameters or script too long
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '401':
          description: Unauthorised
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429':
          description: Credit limit reached
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /api/screenshot:
    post:
      tags: [Screenshots]
      summary: Capture a web page screenshot
      description: |
        Uses a headless Chromium instance to capture any publicly reachable URL.
        Supports full-page captures, viewport presets, dark mode, and element-level
        selector targeting.
      operationId: takeScreenshot
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScreenshotRequest'
            examples:
              desktop:
                summary: Desktop viewport capture
                value:
                  url: "https://1frx.com"
                  device: desktop
                  fullPage: false
                  format: png
              mobileFullPage:
                summary: Full-page mobile capture
                value:
                  url: "https://1frx.com"
                  device: mobile
                  fullPage: true
                  delay: 500
      responses:
        '200':
          description: Screenshot captured successfully
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ScreenshotResponse' }
        '400':
          description: Invalid URL or parameters
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '401':
          description: Unauthorised
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429':
          description: Credit limit reached
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /api/parse:
    post:
      tags: [Parse]
      summary: Extract structured data from a document or image
      description: |
        Combines OCR and LLM extraction to pull structured fields from PDFs,
        invoices, receipts, ID documents, and any image containing text.

        Pass a base64-encoded file and an optional JSON Schema. If no schema is
        provided, the model auto-identifies the document type and returns all
        detectable structured fields.
      operationId: parseDocument
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ParseRequest'
      responses:
        '200':
          description: Extraction successful
          content:
            application/json:
              schema: { $ref: '#/components/schemas/ParseResponse' }
        '400':
          description: Invalid or unreadable file
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '401':
          description: Unauthorised
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
        '429':
          description: Credit limit reached
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /api/keys/generate:
    post:
      tags: [Keys]
      summary: Generate a new API key
      description: |
        Creates a new HMAC-signed API key for the specified plan. The raw key is
        returned once and not stored — save it immediately.
      operationId: generateKey
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/KeyGenerateRequest' }
      responses:
        '200':
          description: Key created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/KeyGenerateResponse' }
        '400':
          description: Invalid email or plan
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /api/keys/regenerate:
    post:
      tags: [Keys]
      summary: Rotate an existing API key
      description: |
        Invalidates the current key and issues a new one for the same plan and
        email. The old key stops working immediately.
      operationId: regenerateKey
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/KeyRegenerateRequest' }
      responses:
        '200':
          description: New key issued
          content:
            application/json:
              schema: { $ref: '#/components/schemas/KeyGenerateResponse' }
        '401':
          description: Invalid or expired key
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /api/health:
    get:
      tags: [System]
      summary: Health check
      description: Returns the current operational status of each service subsystem.
      operationId: healthCheck
      security: []
      responses:
        '200':
          description: Status report
          content:
            application/json:
              schema: { $ref: '#/components/schemas/HealthResponse' }

  /api/mcp:
    post:
      tags: [MCP]
      summary: MCP Streamable HTTP endpoint
      description: |
        Implements the [Model Context Protocol](https://modelcontextprotocol.io)
        Streamable HTTP transport (spec 2024-11-05). Accepts JSON-RPC 2.0 messages
        and exposes all five 1FRX tools to any MCP-compatible AI agent.

        **Supported JSON-RPC methods:**
        - `initialize` — MCP handshake; returns server info and tool capabilities
        - `tools/list` — Returns all tool definitions with JSON input schemas
        - `tools/call` — Execute a named tool (`generate_image`, `generate_pdf`,
          `generate_audio`, `take_screenshot`, `parse_document`)
        - `ping` — Keepalive; returns empty result

        Configure in Claude Desktop (`claude_desktop_config.json`):
        ```json
        {
          "mcpServers": {
            "1frx": {
              "type": "http",
              "url": "https://1frx.com/api/mcp"
            }
          }
        }
        ```
      operationId: mcpEndpoint
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [jsonrpc, method]
              properties:
                jsonrpc:
                  type: string
                  enum: ["2.0"]
                id:
                  oneOf:
                    - type: string
                    - type: integer
                  description: Omit for notifications
                method:
                  type: string
                  enum: [initialize, "tools/list", "tools/call", ping]
                params:
                  type: object
            examples:
              initialize:
                summary: MCP handshake
                value:
                  jsonrpc: "2.0"
                  id: 1
                  method: initialize
                  params:
                    protocolVersion: "2024-11-05"
                    clientInfo: { name: "my-agent", version: "1.0" }
              toolsList:
                summary: List available tools
                value:
                  jsonrpc: "2.0"
                  id: 2
                  method: "tools/list"
              toolsCall:
                summary: Call generate_image
                value:
                  jsonrpc: "2.0"
                  id: 3
                  method: "tools/call"
                  params:
                    name: generate_image
                    arguments:
                      template: ogCard
                      data:
                        title: "Hello from Claude"
      responses:
        '200':
          description: JSON-RPC 2.0 response
          content:
            application/json:
              schema:
                type: object
                properties:
                  jsonrpc: { type: string, example: "2.0" }
                  id:      { oneOf: [{ type: string }, { type: integer }] }
                  result:  { type: object }
                  error:
                    type: object
                    properties:
                      code:    { type: integer }
                      message: { type: string }
