openapi: 3.1.0
info:
  title: Omniyond API
  version: "1.0.0"
  summary: Pay-per-use utility API for AI agents.
  description: |
    Omniyond is a pay-per-use utility API for AI agents. The keystone is a
    **scheduler**: schedule any HTTP call to run once or on a recurring interval,
    with automatic retries (30s / 5m / 30m, then a dead-letter queue) and delivery
    to storage or a callback URL.

    ## Authentication

    Two ways to pay, no plan or subscription:

    - **API key (prepaid credits):** send `Authorization: Bearer omni_...`. Buy a
      credit pack ($9 / $29 / $99, credits never expire); each scheduled run costs
      $0.01. Balances are in microUSD ($1 = 1,000,000).
    - **x402 (pay per call):** send no key. The API replies `402 Payment Required`
      with payment requirements in the `payment-required` response header (USDC on
      Base); pay and resend with the `X-PAYMENT` header. No account needed. During
      private beta the network is Base Sepolia (testnet).

    Every `402` response body links this spec and `/llms.txt`.
  contact:
    name: Omniyond
    url: https://omniyond.com
servers:
  - url: https://omniyond.com
    description: Production
security:
  - ApiKeyAuth: []
  - x402: []
tags:
  - name: scheduler
    description: Schedule and manage HTTP jobs.
  - name: utility
    description: Standalone paid utility endpoints.
  - name: mcp
    description: Model Context Protocol surface.
paths:
  /v1/schedule:
    post:
      tags: [scheduler]
      operationId: scheduleJob
      summary: Schedule an HTTP job
      description: |
        Schedule an HTTP request to run once (omit `schedule`) or on a recurring
        interval (`5m`, `1h`, `1d`; minimum 5 minutes, 90-day horizon). Each run
        costs $0.01, prepaid from your credit balance. With x402, the price is
        $0.01 × `maxRuns` — pass `?maxRuns=N` in the query for recurring jobs.
      parameters:
        - name: maxRuns
          in: query
          required: false
          description: x402 only — number of recurring runs to prepay. Sets the x402 price ($0.01 × maxRuns).
          schema:
            type: integer
            minimum: 1
            maximum: 1000
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ScheduleInput'
      responses:
        '201':
          description: Job scheduled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ScheduledJob'
        '400':
          $ref: '#/components/responses/ValidationError'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/jobs/{id}:
    get:
      tags: [scheduler]
      operationId: getJob
      summary: Get a job
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/JobId'
      responses:
        '200':
          description: Job detail with recent runs.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobDetail'
        '404':
          $ref: '#/components/responses/NotFound'
    delete:
      tags: [scheduler]
      operationId: cancelJob
      summary: Cancel a job
      description: Cancels the job and refunds its unused prepaid balance back to your credits.
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/JobId'
      responses:
        '200':
          description: Job cancelled.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CancelResult'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/jobs/{id}/results:
    get:
      tags: [scheduler]
      operationId: getJobResults
      summary: Get a job's run results
      security:
        - ApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/JobId'
        - name: body
          in: query
          required: false
          description: Set to `1` or `true` to include each run's stored response body.
          schema:
            type: string
            enum: ['1', 'true']
      responses:
        '200':
          description: Run results.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/JobResults'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/hello:
    get:
      tags: [utility]
      operationId: hello
      summary: Paid hello-world
      description: Minimal paid endpoint for testing the payment flow. $0.001 per call.
      responses:
        '200':
          description: Greeting.
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string, example: 'hello, agent' }
                  payer: { type: string }
                  paidWith: { type: string, enum: [credits, x402] }
                  timestamp: { type: string, format: date-time }
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/qr:
    post:
      tags: [utility]
      operationId: qr
      summary: Generate a QR code (SVG)
      description: Returns an SVG QR code (image/svg+xml). $0.01 per call.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [data]
              properties:
                data: { type: string, maxLength: 2048, description: Text or URL to encode. }
                ecc: { type: string, enum: [L, M, Q, H], default: M, description: Error-correction level. }
                border: { type: integer, minimum: 0, maximum: 20, default: 2, description: Quiet-zone border in modules. }
      responses:
        '200':
          description: SVG QR code.
          content:
            image/svg+xml:
              schema: { type: string }
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/og-image:
    post:
      tags: [utility]
      operationId: ogImage
      summary: Render a social (OG) image
      description: Renders a PNG (default 1200x630) from a title, description and theme; returns a hosted URL. $0.01 per call.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [title]
              properties:
                title: { type: string, maxLength: 200 }
                description: { type: string, maxLength: 300 }
                theme: { type: string, enum: [dark, light], default: dark }
                width: { type: integer, minimum: 400, maximum: 2000, default: 1200 }
                height: { type: integer, minimum: 200, maximum: 2000, default: 630 }
      responses:
        '200':
          description: Hosted image URL.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/StoredFile' }
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/pdf:
    post:
      tags: [utility]
      operationId: pdf
      summary: Render a PDF
      description: Renders a PDF from raw HTML or a URL (SSRF-protected); returns a hosted URL. $0.02 per call.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                html: { type: string, maxLength: 500000, description: Raw HTML. Provide exactly one of html or url. }
                url: { type: string, format: uri, description: URL to render. Provide exactly one of html or url. }
                format: { type: string, enum: [A4, Letter, Legal], default: A4 }
                landscape: { type: boolean, default: false }
      responses:
        '200':
          description: Hosted PDF URL.
          content:
            application/json:
              schema: { $ref: '#/components/schemas/StoredFile' }
        '400':
          $ref: '#/components/responses/ValidationError'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/extract:
    post:
      tags: [utility]
      operationId: extract
      summary: Extract a web page
      description: Renders a URL in a real headless browser (handles JS-heavy pages) and extracts it as clean markdown, rendered HTML, or a list of links. SSRF-protected. $0.01 per call.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url: { type: string, format: uri, maxLength: 2048 }
                format: { type: string, enum: [markdown, content, links], default: markdown, description: 'markdown = clean LLM-ready markdown; content = rendered HTML; links = list of links.' }
                visibleLinksOnly: { type: boolean, default: false, description: 'For format=links: only links visible on the page.' }
                excludeExternalLinks: { type: boolean, default: false, description: 'For format=links: exclude links to external domains.' }
      responses:
        '200':
          description: Extracted page. Fields depend on format.
          content:
            application/json:
              schema:
                type: object
                properties:
                  format: { type: string, enum: [markdown, content, links] }
                  url: { type: string, format: uri }
                  markdown: { type: string, description: Present when format=markdown. }
                  html: { type: string, description: Present when format=content. }
                  title: { type: string, description: Present when format=content. }
                  status: { type: integer, description: Rendered page HTTP status; present when format=content. }
                  links: { type: array, items: { type: string }, description: Present when format=links. }
        '400':
          $ref: '#/components/responses/ValidationError'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/link:
    post:
      tags: [utility]
      operationId: createLink
      summary: Create a short link
      description: Creates a short link that 302-redirects to the target (SSRF-protected) and counts clicks. Redirect served at /l/{slug}. $0.01 per call.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url: { type: string, format: uri, maxLength: 2048 }
                slug: { type: string, pattern: '^[a-zA-Z0-9_-]{3,40}$', description: Optional custom slug; random if omitted. }
                expiresInDays: { type: integer, minimum: 1, maximum: 365 }
      responses:
        '200':
          description: Short link created.
          content:
            application/json:
              schema:
                type: object
                properties:
                  slug: { type: string }
                  url: { type: string, format: uri }
                  target: { type: string, format: uri }
                  expiresAt: { type: string, format: date-time, nullable: true }
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '409':
          description: Slug already taken.
        '429':
          $ref: '#/components/responses/RateLimited'
  /v1/link/{slug}:
    get:
      tags: [utility]
      operationId: getLink
      summary: Get a short link's stats
      description: Returns the target, total click count and expiry for a short link you created. API key only — the link must belong to your project.
      security:
        - ApiKeyAuth: []
      parameters:
        - name: slug
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Link details.
          content:
            application/json:
              schema:
                type: object
                properties:
                  slug: { type: string }
                  url: { type: string, format: uri }
                  target: { type: string, format: uri }
                  clicks: { type: integer }
                  createdAt: { type: string, format: date-time }
                  expiresAt: { type: string, format: date-time, nullable: true }
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/files/{token}:
    get:
      tags: [utility]
      operationId: getFile
      summary: Fetch a generated file
      description: Fetches a file produced by og-image or pdf. The token is the capability — no auth required. Cached 24h.
      security: []
      parameters:
        - name: token
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: The stored file (image/png or application/pdf).
        '404':
          description: File not found or expired.
  /mcp:
    post:
      tags: [mcp]
      operationId: mcp
      summary: MCP server (Streamable HTTP)
      description: |
        Model Context Protocol endpoint — stateless Streamable HTTP, POST only,
        requires an API key. Exposes the scheduler as tools: `schedule_job`,
        `get_job`, `cancel_job`, `get_job_results`.
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              description: A JSON-RPC 2.0 MCP request (e.g. tools/list, tools/call).
      responses:
        '200':
          description: JSON-RPC 2.0 MCP response.
          content:
            application/json:
              schema:
                type: object
        '401':
          $ref: '#/components/responses/Unauthorized'
components:
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: omni_*
      description: Prepaid-credit API key. Create one in the Omniyond dashboard.
    x402:
      type: apiKey
      in: header
      name: X-PAYMENT
      description: |
        x402 pay-per-call. Omit the API key to receive a `402` with payment
        requirements in the `payment-required` response header, then pay and
        resend with the `X-PAYMENT` header. USDC on Base.
  parameters:
    JobId:
      name: id
      in: path
      required: true
      description: Job id returned by POST /v1/schedule.
      schema:
        type: string
  schemas:
    ScheduleInput:
      type: object
      required: [url]
      properties:
        url:
          type: string
          format: uri
          description: URL to call. http/https only, public hosts only (SSRF-protected).
        method:
          type: string
          enum: [GET, POST, PUT, PATCH, DELETE, HEAD]
          default: POST
        headers:
          type: object
          additionalProperties:
            type: string
          description: Optional request headers.
        body:
          type: string
          maxLength: 100000
          description: Optional request body.
        schedule:
          type: string
          description: 'Recurrence interval: 5m, 1h, 1d. Omit for a one-shot job. Minimum 5m, 90-day horizon.'
          examples: ['1h']
        maxRuns:
          type: integer
          minimum: 1
          maximum: 1000
          description: For recurring jobs — how many runs to prepay (each costs $0.01).
        runAt:
          type: string
          format: date-time
          description: ISO timestamp for the first run. Defaults to now.
        delivery:
          type: string
          enum: [store, callback, both]
          default: store
          description: Where results go — stored for retrieval, POSTed to callbackUrl, or both.
        callbackUrl:
          type: string
          format: uri
          description: URL to POST each run result to. Required when delivery is callback or both.
        pay:
          type: boolean
          default: false
          description: >-
            When true, the scheduler pays the target endpoint via x402 (USDC on Base) on each run
            from Omniyond's operator wallet, up to maxPriceUsd. Lets an agent schedule a job that
            itself buys a paid API. Requires an API key with prepaid credits (rejected on x402 jobs).
        maxPriceUsd:
          type: number
          minimum: 0
          maximum: 1
          description: >-
            Max USD to pay the downstream API per run when pay=true. Defaults to $0.10, hard-capped
            at $1.00. Reserved per run from your credits; unspent reservation is refunded on cancel.
    ScheduledJob:
      type: object
      properties:
        id: { type: string }
        status: { type: string, example: scheduled }
        kind: { type: string, enum: [http, x402] }
        nextRunAt: { type: string, format: date-time, nullable: true }
        schedule: { type: string, nullable: true }
        maxRuns: { type: integer }
        balanceMicrousd:
          type: integer
          description: Prepaid balance for this job, in microUSD.
        feePerRunMicrousd: { type: integer, example: 10000 }
        maxDownstreamMicrousd:
          type: integer
          description: Per-run downstream payment cap in microUSD (0 for http jobs).
        payment:
          $ref: '#/components/schemas/PaymentReceipt'
    JobDetail:
      type: object
      properties:
        id: { type: string }
        status:
          type: string
          enum: [scheduled, queued, running, paused, completed, cancelled, failed]
        kind: { type: string, enum: [http, x402] }
        targetUrl: { type: string }
        method: { type: string }
        schedule: { type: string, nullable: true }
        nextRunAt: { type: string, format: date-time, nullable: true }
        deliveryMode: { type: string, enum: [store, callback, both] }
        callbackUrl: { type: string, nullable: true }
        balanceMicrousd: { type: integer }
        feeMicrousd: { type: integer }
        maxDownstreamMicrousd: { type: integer, description: 'Per-run downstream payment cap in microUSD (0 for http jobs).' }
        createdAt: { type: string, format: date-time }
        updatedAt: { type: string, format: date-time }
        recentRuns:
          type: array
          items:
            $ref: '#/components/schemas/JobRun'
    JobRun:
      type: object
      properties:
        id: { type: string }
        attempt: { type: integer }
        status: { type: string, enum: [running, success, error] }
        responseCode: { type: integer, nullable: true }
        paidMicrousd: { type: integer, nullable: true, description: 'Amount paid to the downstream API on this run, in microUSD (x402 jobs only).' }
        txHash: { type: string, nullable: true, description: 'Settlement transaction hash for the downstream payment (x402 jobs only).' }
        startedAt: { type: string, format: date-time }
        finishedAt: { type: string, format: date-time, nullable: true }
        error: { type: string, nullable: true }
        resultR2Key:
          type: string
          nullable: true
          description: Storage key of the run's response body (results endpoint only).
        body:
          type: string
          description: The run's stored response body (only when ?body=1).
    CancelResult:
      type: object
      properties:
        id: { type: string }
        status: { type: string, example: cancelled }
        refundedMicrousd: { type: integer }
    JobResults:
      type: object
      properties:
        jobId: { type: string }
        runs:
          type: array
          items:
            $ref: '#/components/schemas/JobRun'
    PaymentReceipt:
      type: object
      description: Present on x402-paid responses.
      properties:
        payer: { type: string }
        transaction: { type: string }
        network: { type: string }
    StoredFile:
      type: object
      description: A generated file hosted at /v1/files/{token}.
      properties:
        url: { type: string, format: uri }
        width: { type: integer }
        height: { type: integer }
        payment: { $ref: '#/components/schemas/PaymentReceipt' }
    DiscoveryLinks:
      type: object
      properties:
        docs: { type: string, format: uri, example: 'https://omniyond.com/llms.txt' }
        openapi: { type: string, format: uri, example: 'https://omniyond.com/openapi.yaml' }
    InsufficientCredits:
      allOf:
        - $ref: '#/components/schemas/DiscoveryLinks'
        - type: object
          properties:
            error: { type: string, example: insufficient_credits }
            message: { type: string }
            requiredMicrousd: { type: integer }
            balanceMicrousd: { type: integer }
            buyCreditsUrl: { type: string, format: uri }
  responses:
    ValidationError:
      description: Invalid request (bad schedule, SSRF-blocked URL, missing callbackUrl, etc.).
      content:
        application/json:
          schema:
            type: object
            properties:
              statusMessage: { type: string }
    PaymentRequired:
      description: |
        Payment required. For x402, the payment requirements are in the
        `payment-required` response header and the body links the docs. For an
        API key with insufficient credits, the body is an InsufficientCredits
        object. Either way the body links this spec and /llms.txt.
      headers:
        payment-required:
          description: Base64-encoded x402 PaymentRequirements (x402 path only).
          schema:
            type: string
      content:
        application/json:
          schema:
            oneOf:
              - $ref: '#/components/schemas/DiscoveryLinks'
              - $ref: '#/components/schemas/InsufficientCredits'
    RateLimited:
      description: Too many requests. Retry after the `Retry-After` header.
      headers:
        Retry-After:
          schema:
            type: integer
      content:
        application/json:
          schema:
            type: object
            properties:
              statusMessage: { type: string }
              data:
                type: object
                properties:
                  retryAfterSeconds: { type: integer }
    NotFound:
      description: Job not found, or not owned by this API key.
      content:
        application/json:
          schema:
            type: object
            properties:
              statusMessage: { type: string, example: Job not found }
    Unauthorized:
      description: Missing or invalid API key.
      content:
        application/json:
          schema:
            type: object
            properties:
              statusMessage: { type: string, example: Invalid API key }
