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

# Submit a job

> Submit text to be humanized. Returns a job you poll or track by webhook.

Submit text to humanize and Wibble returns a [job](/concepts/jobs) you poll for the result or track by webhook.

## Request options

These notes add product context for the request body. The full schema, types, and defaults are generated below.

* `text` is the only required field: the text to rewrite, preserving its meaning and intent.
* `auto_detect_language` (default `true`) detects the input language for you. Supported languages today are English (US and UK). Leave it on unless you have a specific reason to fix the language yourself.
* `use_british_spellings` (default `false`) prefers British (en-GB) spellings in the output. Set it to `true` for UK English; leave it `false` for US English.

<Tip>
  Send an `Idempotency-Key` header to make a submission safe to retry. The same key with an identical body returns the original job instead of creating a new one. See [Idempotency](/concepts/idempotency).

  Set `webhook_url` to a public HTTPS endpoint to be notified when the job finishes, so you do not have to poll. See [Webhooks](/concepts/webhooks).
</Tip>


## OpenAPI

````yaml openapi.yaml POST /humanize
openapi: 3.1.0
info:
  title: Wibble API
  description: >-
    Public HTTP API for the Wibble humanizer.


    The humanizer runs asynchronously. You submit text to `POST /humanize`,
    which reserves the required API words up front and returns a job (HTTP
    `202`). You then either poll `GET /humanize/{id}` until the job reaches a
    terminal state (`succeeded` or `failed`), or register a `webhook_url` to be
    notified when the job finishes. Reserved words are refunded automatically if
    a job fails.


    ## Authentication

    All requests require a Bearer API key in the `Authorization` header. Keys
    look like `wib_live_...` and are created in the Wibble dashboard. A key must
    carry the `humanize` scope to use these endpoints.

    Treat API keys as server-side secrets. The v1 API is designed for
    server-to-server use and does not expose browser CORS access.


    ## Limits

    - Input text: up to **50,000** characters and up to **2,000** words per
    request.

    - Request body: up to **128,000** bytes.

    - Concurrency: up to **5** active jobs per account at once.

    - Rate limits: **20** submissions/minute (`POST`) and **120** lookups/minute
    (`GET`) per key.

    - Idempotency keys are retained for **24** hours.
  version: 1.0.0
  contact:
    name: Wibble Support
    url: https://www.wibbleai.com
  license:
    name: Proprietary
    url: https://www.wibbleai.com
servers:
  - url: https://www.wibbleai.com/api/v1
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Humanize
    description: Submit and retrieve humanization jobs.
paths:
  /humanize:
    post:
      tags:
        - Humanize
      summary: Submit a humanization job
      description: >-
        Submit text to be humanized.


        This endpoint is asynchronous. On acceptance it reserves the required
        number of API words from your account balance and returns a job with
        status `queued` (HTTP `202`). Poll `GET /humanize/{id}` until the job
        reaches a terminal state, or supply a `webhook_url` to be notified when
        the job finishes. If a job fails, the reserved words are refunded.


        **Idempotency.** Supply an `Idempotency-Key` header to safely retry
        submissions. Reusing the same key with an identical request body returns
        the original job with HTTP `200` instead of creating a new one. Reusing
        the same key with a different body within the 24-hour retention window
        returns HTTP `409 idempotency_conflict`.


        **Limits.** The text must be 1 to 50,000 characters and resolve to at
        most 2,000 words. The request body must be at most 128,000 bytes. An
        account may have at most 5 active jobs simultaneously. Submissions are
        rate limited to 20 per minute per key.
      operationId: createHumanizeJob
      parameters:
        - name: Idempotency-Key
          in: header
          required: false
          description: >-
            Optional key to make submissions idempotent. Trimmed and truncated
            to the first 256 characters. Reusing the same key with the same
            request body returns the original job (HTTP `200`); reusing it with
            a different body within the 24-hour retention window returns HTTP
            `409`.
          schema:
            type: string
            maxLength: 256
          example: 5c2f1f1a-1b2c-4d3e-9f0a-7b6c5d4e3f2a
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/HumanizeRequest'
            examples:
              minimal:
                summary: Minimal request
                value:
                  text: The mitochondria is the powerhouse of the cell.
              full:
                summary: All options
                value:
                  text: The mitochondria is the powerhouse of the cell.
                  auto_detect_language: true
                  use_british_spellings: false
                  webhook_url: https://example.com/webhooks/wibble
      responses:
        '200':
          description: >-
            Idempotent reuse. The supplied `Idempotency-Key` matched a previous
            request with an identical body, so the original job is returned.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
              example:
                id: 7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10
                status: running
                mode: humanize
                input_words: 9
                words_reserved: 9
                words_charged: 9
                status_url: >-
                  https://www.wibbleai.com/api/v1/humanize/7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10
                current_stage: refining_draft
                detected_language: en
                created_at: '2026-06-14T12:00:00.000Z'
                completed_at: null
        '202':
          description: >-
            Job created and enqueued. The job starts in status `queued`; poll
            the `status_url` or wait for a webhook.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Job'
              example:
                id: 7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10
                status: queued
                mode: humanize
                input_words: 9
                words_reserved: 9
                words_charged: 9
                status_url: >-
                  https://www.wibbleai.com/api/v1/humanize/7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10
                current_stage: detecting_language
                detected_language: null
                created_at: '2026-06-14T12:00:00.000Z'
                completed_at: null
        '400':
          description: >-
            The request was malformed. This covers invalid JSON (`bad_request`),
            schema/validation failures including invalid `webhook_url`
            destinations (`validation_error`), and text that exceeds the word
            limit (`word_limit_exceeded`).
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              examples:
                bad_request:
                  summary: Body was not valid JSON
                  value:
                    error:
                      code: bad_request
                      message: Request body must be JSON.
                validation_error_schema:
                  summary: Schema validation failed
                  value:
                    error:
                      code: validation_error
                      message: Text is required
                validation_error_webhook:
                  summary: Webhook destination rejected
                  value:
                    error:
                      code: validation_error
                      message: Webhook URL must use HTTPS.
                word_limit_exceeded:
                  summary: Too many words
                  value:
                    error:
                      code: word_limit_exceeded
                      message: Text must be 2,000 words or fewer.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          description: >-
            The account does not have enough remaining API words to reserve for
            this request.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/InsufficientWordsError'
              example:
                error:
                  code: insufficient_words
                  message: >-
                    This request needs 1,200 API words, but the account only has
                    350 remaining.
                balance: 350
                words_required: 1200
        '409':
          description: >-
            The supplied `Idempotency-Key` was already used with a different
            request body within the 24-hour retention window.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdempotencyConflictError'
              example:
                error:
                  code: idempotency_conflict
                  message: >-
                    This Idempotency-Key was already used with a different
                    request body.
                existing_job_id: 7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10
                expires_at: '2026-06-15T12:00:00.000Z'
        '413':
          description: The request body exceeded 128,000 bytes.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: payload_too_large
                  message: Request body must be 128,000 bytes or fewer.
        '429':
          description: >-
            Too many requests. Either the per-key submission rate limit of 20
            requests/minute was exceeded (`rate_limited`), or the account
            already has the maximum of 5 active jobs
            (`too_many_concurrent_jobs`). Both responses include a `Retry-After`
            header.
          headers:
            Retry-After:
              $ref: '#/components/headers/Retry-After'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/RateLimitError'
                  - $ref: '#/components/schemas/TooManyConcurrentJobsError'
              examples:
                rate_limited:
                  summary: Per-key submission rate limit exceeded
                  value:
                    error:
                      code: rate_limited
                      message: Too many API requests. Retry after the reset time.
                    retry_after_seconds: 12
                    reset_at: '2026-06-14T12:01:00.000Z'
                too_many_concurrent_jobs:
                  summary: Concurrency cap reached (Retry-After is "10")
                  value:
                    error:
                      code: too_many_concurrent_jobs
                      message: >-
                        This account already has 5 active API jobs. Wait for one
                        to finish before submitting another.
                    active_jobs: 5
                    limit: 5
        '500':
          description: >-
            The job was created and words were reserved, but enqueuing the
            humanization task failed. The reserved words are refunded
            automatically.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error:
                  code: enqueue_failed
                  message: Failed to enqueue the humanization job.
components:
  schemas:
    HumanizeRequest:
      type: object
      required:
        - text
      properties:
        text:
          type: string
          minLength: 1
          maxLength: 50000
          description: >-
            The text to humanize. Must be 1 to 50,000 characters and resolve to
            at most 2,000 words.
          example: The mitochondria is the powerhouse of the cell.
        auto_detect_language:
          type: boolean
          default: true
          description: >-
            Whether to auto-detect the input language. Affects the initial
            processing stage.
        use_british_spellings:
          type: boolean
          default: false
          description: Whether to prefer British spellings in the output.
        webhook_url:
          type: string
          format: uri
          maxLength: 2048
          description: >-
            Optional HTTPS URL to receive a webhook when the job finishes. Must
            be HTTPS, must not include credentials, and must not resolve to a
            private, loopback, or otherwise blocked address.
          example: https://example.com/webhooks/wibble
    Job:
      type: object
      description: A humanization job.
      required:
        - id
        - status
        - mode
        - input_words
        - words_reserved
        - words_charged
        - status_url
        - current_stage
        - detected_language
        - created_at
        - completed_at
      properties:
        id:
          type: string
          format: uuid
          description: Unique job identifier.
          example: 7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10
        status:
          type: string
          enum:
            - queued
            - running
            - succeeded
            - failed
          description: Current job status. Terminal states are `succeeded` and `failed`.
        mode:
          type: string
          description: The processing mode. Always `humanize` for this endpoint.
          example: humanize
        input_words:
          type: integer
          description: Number of words counted in the input text.
          example: 9
        words_reserved:
          type: integer
          description: Number of API words reserved from the account balance for this job.
          example: 9
        words_charged:
          type: integer
          description: >-
            Net words charged (`words_reserved` minus refunds, floored at 0).
            For a failed job this is `0` because reserved words are refunded.
          example: 9
        status_url:
          type: string
          format: uri
          description: Absolute URL for polling this job.
          example: >-
            https://www.wibbleai.com/api/v1/humanize/7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10
        current_stage:
          type:
            - string
            - 'null'
          description: The current pipeline stage, or null if unavailable.
          example: refining_draft
        detected_language:
          type:
            - string
            - 'null'
          description: The detected input language, or null if not yet detected.
          example: en
        created_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp when the job was created.
          example: '2026-06-14T12:00:00.000Z'
        completed_at:
          type:
            - string
            - 'null'
          format: date-time
          description: ISO 8601 timestamp when the job completed, or null if not finished.
          example: null
        output:
          type: string
          description: >-
            The humanized text. Present only when `status` is `succeeded`;
            omitted otherwise.
          example: Mitochondria are the cell's powerhouses.
        error:
          $ref: '#/components/schemas/JobError'
      examples:
        - id: 7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10
          status: queued
          mode: humanize
          input_words: 9
          words_reserved: 9
          words_charged: 9
          status_url: >-
            https://www.wibbleai.com/api/v1/humanize/7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10
          current_stage: detecting_language
          detected_language: null
          created_at: '2026-06-14T12:00:00.000Z'
          completed_at: null
    Error:
      type: object
      description: Standard error envelope.
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Machine-readable error code.
              example: validation_error
            message:
              type: string
              description: Human-readable error message.
              example: Text is required
    InsufficientWordsError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          required:
            - balance
            - words_required
          properties:
            balance:
              type: integer
              description: Remaining API word balance on the account.
              example: 350
            words_required:
              type: integer
              description: API words required to process this request.
              example: 1200
      example:
        error:
          code: insufficient_words
          message: >-
            This request needs 1,200 API words, but the account only has 350
            remaining.
        balance: 350
        words_required: 1200
    IdempotencyConflictError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          required:
            - existing_job_id
            - expires_at
          properties:
            existing_job_id:
              type: string
              description: ID of the job originally created with this idempotency key.
              example: 7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10
            expires_at:
              type: string
              format: date-time
              description: >-
                ISO 8601 timestamp when the idempotency key's retention window
                ends (24 hours after first use).
              example: '2026-06-15T12:00:00.000Z'
      example:
        error:
          code: idempotency_conflict
          message: This Idempotency-Key was already used with a different request body.
        existing_job_id: 7b42f1d6-0a8c-4e8f-9a21-c6d9f47c2b10
        expires_at: '2026-06-15T12:00:00.000Z'
    RateLimitError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          required:
            - retry_after_seconds
            - reset_at
          properties:
            retry_after_seconds:
              type: integer
              description: Seconds to wait before retrying.
              example: 12
            reset_at:
              type: string
              format: date-time
              description: ISO 8601 timestamp when the rate-limit window resets.
              example: '2026-06-14T12:01:00.000Z'
      example:
        error:
          code: rate_limited
          message: Too many API requests. Retry after the reset time.
        retry_after_seconds: 12
        reset_at: '2026-06-14T12:01:00.000Z'
    TooManyConcurrentJobsError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          required:
            - active_jobs
            - limit
          properties:
            active_jobs:
              type: integer
              description: Number of currently active jobs on the account.
              example: 5
            limit:
              type: integer
              description: Maximum concurrent jobs allowed (5).
              example: 5
      example:
        error:
          code: too_many_concurrent_jobs
          message: >-
            This account already has 5 active API jobs. Wait for one to finish
            before submitting another.
        active_jobs: 5
        limit: 5
    JobError:
      type: object
      description: Present only when the job `status` is `failed`; omitted otherwise.
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: Machine-readable error code. Defaults to `processing_failed`.
          example: processing_failed
        message:
          type: string
          description: Human-readable error message.
          example: Humanization failed. Reserved words have been refunded.
  headers:
    X-RateLimit-Limit:
      description: The maximum number of requests allowed in the current window.
      schema:
        type: integer
      example: 20
    X-RateLimit-Remaining:
      description: The number of requests remaining in the current window.
      schema:
        type: integer
      example: 19
    X-RateLimit-Reset:
      description: ISO 8601 timestamp at which the current rate-limit window resets.
      schema:
        type: string
        format: date-time
      example: '2026-06-14T12:01:00.000Z'
    Retry-After:
      description: >-
        Number of seconds to wait before retrying. For
        `too_many_concurrent_jobs` this is the literal value `10`.
      schema:
        type: string
      example: '10'
  responses:
    Unauthorized:
      description: >-
        Authentication failed. The `Authorization` header was missing (`Missing
        API key.`) or the key was invalid, expired, revoked, or lacking the
        `humanize` scope (`Invalid API key.`). This response does not include
        rate-limit headers.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            missing:
              summary: No API key provided
              value:
                error:
                  code: unauthorized
                  message: Missing API key.
            invalid:
              summary: Key invalid, expired, revoked, or wrong scope
              value:
                error:
                  code: unauthorized
                  message: Invalid API key.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        API key authentication. Pass your key as `Authorization: Bearer <key>`.
        Keys look like `wib_live_...` and are created in the Wibble dashboard. A
        key must have the `humanize` scope to use these endpoints.

````