API documentation

Integrate image-prompt moderation through Kapel’s versioned inference endpoint. The current contract is pre-launch; this UI rollout does not activate live payments or GPU capacity.

Endpoint and authentication

POST https://api.kapel.ai/v1/models/image-prompt-moderation:infer
Authorization: Bearer <your-server-side-key>
Content-Type: application/json
Idempotency-Key: <unique-operation-key-at-least-16-characters>

Create an API key in the authenticated console and copy its secret once. Never expose the key in browser code or mobile application bundles. The request body and successful response are model-specific JSON objects; consult the endpoint’s qualified model schema before sending production traffic.

Retries and availability

Reuse the same idempotency key and request body when retrying the same operation. An unknown or pending execution is not permission to submit a second billable operation with a different key. Handle insufficient credits (402), rate limits (429), unavailable capacity (503), and inference timeouts (504). See the machine-readable contract for error codes and retryability fields.

Performance and classification

Millisecond inference describes model execution measured in controlled tests, not an end-to-end latency guarantee. The 99% NSFW recall operating point depends on classification threshold and evaluation conditions; it is not a guarantee for every prompt or workload. Your application decides how to handle the returned classification.

Machine-readable API contract

Download OpenAPI

Read the contract
openapi: 3.1.0
info:
  title: Kapel Inference API
  version: 1.0.0-draft
servers:
  - url: https://api.kapel.ai
paths:
  /v1/models/{model_name}:infer:
    post:
      operationId: inferModel
      security:
        - bearerApiKey: []
      parameters:
        - name: model_name
          in: path
          required: true
          schema: {type: string, maxLength: 128}
        - name: Idempotency-Key
          in: header
          required: true
          schema: {type: string, minLength: 16, maxLength: 255}
      requestBody:
        required: true
        content:
          application/json:
            schema: {type: object}
      responses:
        "200":
          description: Model-specific synchronous response
          headers:
            X-Request-Id: {schema: {type: string}}
            Cache-Control: {schema: {type: string, const: "private, no-store"}}
          content:
            application/json:
              schema: {type: object}
        "400": {$ref: "#/components/responses/Error"}
        "401": {$ref: "#/components/responses/Error"}
        "402": {$ref: "#/components/responses/Error"}
        "403": {$ref: "#/components/responses/Error"}
        "409": {$ref: "#/components/responses/Error"}
        "413": {$ref: "#/components/responses/Error"}
        "415": {$ref: "#/components/responses/Error"}
        "429": {$ref: "#/components/responses/Error"}
        "503": {$ref: "#/components/responses/Error"}
        "504": {$ref: "#/components/responses/Error"}
components:
  securitySchemes:
    bearerApiKey:
      type: http
      scheme: bearer
  schemas:
    Error:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error:
          type: object
          additionalProperties: false
          required: [code, message, request_id, retryable]
          properties:
            code:
              type: string
              enum: [invalid_request, invalid_model_input, invalid_api_key, forbidden_scope, account_blocked, insufficient_credits, duplicate_pending, already_processed, request_too_large, unsupported_media_type, rate_limited, concurrency_limited, model_unavailable, authorization_unavailable, inference_timeout, execution_unknown]
            message: {type: string}
            request_id: {type: string}
            retryable: {type: boolean}
  responses:
    Error:
      description: Stable error envelope
      content:
        application/json:
          schema: {$ref: "#/components/schemas/Error"}