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

# Classify content

> Classifies text and images against a moderation model's categories. The response
carries an `id` the gateway mints, `mdr-` followed by 28 random characters; the
`model` that judged; and `results[]` with one verdict per moderated item. This
operation returns no `usage` block.

Requires an API key.

`model` names a model from Naga's catalog that serves `moderations`, and the
gateway selects a provider for it.

`input` takes one of three shapes: a single string, an array of strings, or an
array of typed parts. A typed part is `{"type": "text", "text": …}` or
`{"type": "image_url", "image_url": {"url": …}}`, and the two may be mixed. A
part carrying any other `type` is refused, with both accepted types named. An
array holds either all strings or all typed parts.

An image `url` is an `http(s)` URL or a `data:` URI carrying the bytes inline.
Any other scheme, including `file:`, is refused while the body is read. A remote
URL is fetched before the model is called. A URL that cannot be read, or that
exceeds the deployment's inline-media limit, is refused.

`results[]` arrives in the order the moderation model returned it and is passed
through unchanged, neither padded to the number of inputs nor trimmed to them.
Each entry carries four fields.

* `flagged` — whether the item tripped any category. This is the model's own
  verdict where it sent one, and "any category true" where it sent categories
  alone.
* `categories` — the per-category booleans, in the model's own order. The key
  set belongs to that model rather than to a fixed list, so the map is read by
  key.
* `category_scores` — the scores, keyed exactly as `categories`.
* `category_applied_input_types` — which kinds of input each category was
  applied to. Present only when the model sends it, and otherwise absent rather
  than `null`.

The account is charged on input tokens alone, at the model's published prices.
There is no output side to charge. Moderation models report no usage, so the
count is the gateway's own: text is tokenized here, and every `image_url` part
costs a flat 250 input tokens regardless of the image.

Every response below `200` carries an object under `error` with a
machine-readable `type`, a human-readable `message`, and, where the failure
concerns one field, `param`. `429` and `503` carry `Retry-After` with the
seconds to wait.




## OpenAPI

````yaml /openapi/naga-api.json post /v1/moderations
openapi: 3.1.0
info:
  title: NagaAI API
  description: >-
    Naga routes inference requests to a pool of upstream providers behind one
    endpoint. Requests are written in the OpenAI or the Anthropic dialect, and
    the gateway selects a provider that serves the requested model.


    Two credentials exist and they are not interchangeable. An API key reaches
    models. A provisioning key manages the account: its keys, its balance, and
    its usage. Each operation declares which one it accepts.


    Accounts are charged for the tokens a request consumes, at the published
    price of the model that served it.
  termsOfService: https://naga.ac/legal/terms
  contact:
    name: NagaAI
    url: https://naga.ac
  version: '2026-03-31'
  summary: Unified AI API with OpenAI and Anthropic compatibility layers.
servers:
  - url: https://api.naga.ac
    description: Production
  - url: http://localhost:8500
    description: Local development
security: []
tags:
  - name: Chat Completions
    description: >-
      OpenAI-compatible chat completion requests, including streaming, tools,
      and multimodal input.
  - name: Messages
    description: >-
      Anthropic-compatible Messages API requests used by Claude-style clients
      and agents.
  - name: Responses
    description: >-
      OpenAI-compatible Responses API requests for structured input, tools, and
      streaming output items.
  - name: Embeddings
    description: >-
      Create vector embeddings for search, retrieval, clustering, and ranking
      use cases.
  - name: Images
    description: >-
      Generate or edit images with multimodal models and OpenAI-compatible
      payloads.
  - name: Audio
    description: >-
      Generate speech or convert uploaded audio into transcriptions and
      translations.
  - name: Moderations
    description: Classify text and image inputs for safety policy categories.
  - name: Models
    description: List available models, capabilities, and pricing metadata.
  - name: Startups
    description: >-
      Public metadata about AI startups and model providers surfaced by the
      gateway.
  - name: Account
    description: Provisioning-key protected account balance and usage endpoints.
  - name: API Keys
    description: Provisioning-key protected API key management endpoints.
paths:
  /v1/moderations:
    post:
      tags:
        - Moderations
      summary: Classify content
      description: >
        Classifies text and images against a moderation model's categories. The
        response

        carries an `id` the gateway mints, `mdr-` followed by 28 random
        characters; the

        `model` that judged; and `results[]` with one verdict per moderated
        item. This

        operation returns no `usage` block.


        Requires an API key.


        `model` names a model from Naga's catalog that serves `moderations`, and
        the

        gateway selects a provider for it.


        `input` takes one of three shapes: a single string, an array of strings,
        or an

        array of typed parts. A typed part is `{"type": "text", "text": …}` or

        `{"type": "image_url", "image_url": {"url": …}}`, and the two may be
        mixed. A

        part carrying any other `type` is refused, with both accepted types
        named. An

        array holds either all strings or all typed parts.


        An image `url` is an `http(s)` URL or a `data:` URI carrying the bytes
        inline.

        Any other scheme, including `file:`, is refused while the body is read.
        A remote

        URL is fetched before the model is called. A URL that cannot be read, or
        that

        exceeds the deployment's inline-media limit, is refused.


        `results[]` arrives in the order the moderation model returned it and is
        passed

        through unchanged, neither padded to the number of inputs nor trimmed to
        them.

        Each entry carries four fields.


        * `flagged` — whether the item tripped any category. This is the model's
        own
          verdict where it sent one, and "any category true" where it sent categories
          alone.
        * `categories` — the per-category booleans, in the model's own order.
        The key
          set belongs to that model rather than to a fixed list, so the map is read by
          key.
        * `category_scores` — the scores, keyed exactly as `categories`.

        * `category_applied_input_types` — which kinds of input each category
        was
          applied to. Present only when the model sends it, and otherwise absent rather
          than `null`.

        The account is charged on input tokens alone, at the model's published
        prices.

        There is no output side to charge. Moderation models report no usage, so
        the

        count is the gateway's own: text is tokenized here, and every
        `image_url` part

        costs a flat 250 input tokens regardless of the image.


        Every response below `200` carries an object under `error` with a

        machine-readable `type`, a human-readable `message`, and, where the
        failure

        concerns one field, `param`. `429` and `503` carry `Retry-After` with
        the

        seconds to wait.
      operationId: create_moderation
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Moderation'
            examples:
              basic:
                summary: Classify one string
                value:
                  model: omni-moderation-latest
                  input: I want to hurt them.
        required: true
      responses:
        '200':
          description: >-
            The verdict. `results` carries one entry per input, each with the
            flag, the per-category verdicts and the scores behind them.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ModerationsResponse'
              examples:
                basic:
                  summary: One result, flagged
                  value:
                    id: mdr-4tKq7Zc1Nb9Wx2Ry8Vd3Ms6Ph0Q
                    model: omni-moderation-latest
                    results:
                      - flagged: true
                        categories:
                          violence: true
                          harassment: false
                        category_scores:
                          violence: 0.9124
                          harassment: 0.0217
        '400':
          description: |-
            The request is malformed: a field failed its constraint, the body is
            not valid JSON, or the model name is unknown.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          description: >-
            The API key is missing, malformed, disabled, or does not belong to
            an

            account that may call this route.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              examples:
                invalid-api-key:
                  summary: 401 — the key is not one this gateway serves
                  value:
                    error:
                      type: invalid_api_key
                      message: >-
                        The provided API key is invalid. Please ensure your API
                        key is correct and active. You can find more information
                        about obtaining a key on our website: https://naga.ac If
                        you just created this key, wait a few minutes and try
                        again.
        '402':
          description: The account has run out of credits. Top up the balance and retry.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              examples:
                insufficient-credits:
                  summary: 402 — the account has no credits left
                  value:
                    error:
                      type: insufficient_quota
                      message: >-
                        More credits required to process this request. Visit
                        https://naga.ac/dashboard/credits to add credits.
        '403':
          description: >-
            The account may not use this model, or the content was blocked by
            the

            moderation policy.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '408':
          description: |-
            The request body was not received within the time the gateway waits
            for it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '410':
          description: The model is deprecated and no longer served. Pick a current one.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '413':
          description: The request body exceeds the configured size limit.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '422':
          description: |-
            The request was understood but its usage could not be metered, so it
            was not served.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '429':
          description: >-
            The account exceeded its rate limit. `Retry-After` carries the wait
            in

            seconds.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
              examples:
                rate-limited:
                  summary: 429 — too many requests; `Retry-After` carries the wait
                  value:
                    error:
                      type: rate_limit_exceeded
                      message: Rate limit reached. Please try again in 3s.
        '500':
          description: >-
            The gateway failed for a reason of its own. Report it — this status
            is

            reserved for Naga's own bugs and is never an upstream's verdict.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '503':
          description: |-
            No upstream is available right now, or the gateway is shedding load.
            `Retry-After` carries the suggested wait.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
      security:
        - bearerAuth: []
        - xApiKeyAuth: []
      x-codeSamples:
        - lang: bash
          label: curl
          source: |-
            curl https://api.naga.ac/v1/moderations \
              -H "Authorization: Bearer $NAGA_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{
              "model": "omni-moderation-latest",
              "input": "I want to hurt them."
            }'
        - lang: python
          label: openai-python
          source: |
            import os
            from openai import OpenAI

            client = OpenAI(
                api_key=os.environ["NAGA_API_KEY"],
                base_url="https://api.naga.ac/v1",
            )

            result = client.moderations.create(
                model="omni-moderation-latest",
                input="I want to hurt them.",
            )
            print(result)
        - lang: javascript
          label: openai-node
          source: |
            import OpenAI from "openai";

            const client = new OpenAI({
              apiKey: process.env.NAGA_API_KEY,
              baseURL: "https://api.naga.ac/v1",
            });

            const result = await client.moderations.create({
              model: "omni-moderation-latest",
              input: "I want to hurt them.",
            });
            console.log(result);
components:
  schemas:
    Moderation:
      type: object
      description: The request body of `POST /v1/moderations`.
      required:
        - model
        - input
      properties:
        model:
          type: string
          description: >-
            The moderation model, named by its id or by one of its aliases. A
            name

            this gateway does not serve on this route is refused with 400

            `model_not_found`; a name it served and has since retired, with 410

            `model_deprecated`.
        input:
          $ref: '#/components/schemas/ModerationInput'
          description: >-
            What to moderate. It is billed on tokens counted here rather than

            reported by the model: text is tokenized locally, and each
            `image_url`

            part costs a flat 250 input tokens whatever the image is.
    ModerationsResponse:
      allOf:
        - type: object
        - type: object
          required:
            - id
            - model
            - results
          properties:
            id:
              type: string
              description: >-
                The identifier of this moderation, minted by the gateway: `mdr-`

                followed by 28 random letters and digits. It is Naga's own and
                matches

                no identifier the upstream may have used.
            model:
              type: string
              description: >-
                The model that moderated, as the canonical id the requested name

                resolved to — an alias sent in the request comes back resolved
                here.

                The value is this gateway's catalog name, not anything the
                provider

                reported about itself.
            results:
              type: array
              items:
                $ref: '#/components/schemas/ModerationResultResponse'
              description: >-
                The verdicts, in the order the moderation model returned them,
                normally

                one per input item. The array is passed through as it came:
                nothing

                here pads it out to the number of inputs or trims it to them.
      description: >-
        The body of a successful `POST /v1/moderations`. It carries no `usage`

        block, unlike the gateway's other non-LLM routes; the request is billed
        all

        the same, on tokens counted here rather than reported by the model.
    ErrorEnvelope:
      type: object
      description: |-
        `{"error": {…}}` — the body of every non-2xx answer outside
        `/v1/messages`.
      required:
        - error
      properties:
        error:
          $ref: '#/components/schemas/ErrorBody'
          description: >-
            The failure itself, and the envelope's only member. Every non-2xx
            HTTP

            status arrives with it, and a 2xx answer never does.
    ModerationInput:
      oneOf:
        - type: string
          title: Plain text
        - type: array
          title: List of texts
          items:
            type: string
        - type: array
          title: List of typed parts
          items:
            $ref: '#/components/schemas/ModerationPart'
      description: >-
        What to moderate: one string, an array of strings, or an array of typed

        parts — `text` and `image_url` objects, which may be mixed in one array.


        An `input` of `[]` is accepted by the route, though the union published

        here refuses it: both array arms match the empty array, and `oneOf`
        demands

        exactly one match.
    ModerationResultResponse:
      allOf:
        - type: object
        - type: object
          required:
            - flagged
            - categories
            - category_scores
          properties:
            flagged:
              type: boolean
              description: >-
                Whether this input tripped any category. The moderation model's
                own

                verdict when it sent one; when it sent `categories` without a
                verdict,

                the gateway derives this as "any category true".
            categories:
              type: object
              description: >-
                The per-category booleans the moderation model reported, in its
                own

                order. The key set is that model's. There is no fixed list of

                categories here, and two models need not agree on one, so a
                client

                reads the map rather than named keys. A value the model sends as

                something other than a boolean is read as `false`.
              additionalProperties:
                type: boolean
              propertyNames:
                type: string
            category_scores:
              type: object
              description: >-
                The per-category scores the moderation model reported, keyed
                exactly

                like `categories`. A score the model sends as something other
                than a

                number fails the whole request rather than arriving as a zero
                the

                caller cannot tell from a real one.
              additionalProperties:
                type: number
                format: double
              propertyNames:
                type: string
            category_applied_input_types:
              type:
                - object
                - 'null'
              description: >-
                Which kinds of input each category was applied to, as the
                moderation

                model labeled them. Sent only when the model sends it: the key
                is

                absent from the body otherwise, never `null`.
              additionalProperties:
                type: array
                items:
                  type: string
              propertyNames:
                type: string
      description: One verdict — the moderation of a single input item.
    ErrorBody:
      type: object
      description: |-
        The `error` member: what went wrong, in the caller's terms.

        `code` and `param` are optional AND nullable, and both halves are load
        bearing: the boundary omits them, the LLM dialect writes them as `null`.
        A schema that only allowed one of the two would contradict a body the
        gateway actually sends.
      required:
        - type
        - message
      properties:
        type:
          type: string
          description: >-
            The machine-readable class of the failure, for example
            `invalid_request_error`

            or `server_error`.
        message:
          type: string
          description: The human-readable sentence shown to the caller.
        code:
          type:
            - string
            - 'null'
          description: The finer-grained code, when the failure has one.
        param:
          type:
            - string
            - 'null'
          description: The request field the failure is about, when it is about one.
    ModerationPart:
      oneOf:
        - oneOf:
            - $ref: '#/components/schemas/TextInput'
          title: Text part
        - oneOf:
            - $ref: '#/components/schemas/ImageUrlInput'
          title: Image URL part
      description: >-
        One element of the typed-parts arm: a `text` part or an `image_url`
        part,

        told apart by the `type` the element carries itself.
    TextInput:
      type: object
      description: A text part of a typed-parts `input` array.
      required:
        - type
        - text
      properties:
        type:
          $ref: '#/components/schemas/TextTag'
          description: >-
            Always the literal `text`, and required: it is what selects this
            part

            shape. A part whose `type` is outside `text` and `image_url` is
            refused

            with a message naming both.
        text:
          type: string
          description: The text to moderate.
    ImageUrlInput:
      type: object
      description: An image part of a typed-parts `input` array.
      required:
        - type
        - image_url
      properties:
        type:
          $ref: '#/components/schemas/ImageUrlTag'
          description: >-
            Always the literal `image_url`, and required: it is what selects
            this

            part shape. A part whose `type` is outside `text` and `image_url` is

            refused with a message naming both.
        image_url:
          $ref: '#/components/schemas/ModerationImageUrl'
          description: The image to moderate.
    TextTag:
      type: string
      description: The tag of a text part — the literal `text`, its only value.
      enum:
        - text
    ImageUrlTag:
      type: string
      description: The tag of an image part — the literal `image_url`, its only value.
      enum:
        - image_url
    ModerationImageUrl:
      type: object
      description: Where an `image_url` part's image comes from.
      required:
        - url
      properties:
        url:
          type: string
          description: >-
            The image itself: an `http(s)` URL, or a `data:` URI carrying the
            bytes

            inline. Any other scheme, `file:` among them, is refused while the
            body

            is being read. A remote URL is fetched before the moderation model
            is

            called. One whose body exceeds the deployment's inline-media limit,
            or

            that cannot be read at all, is refused.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: 'An account API key. Send it as `Authorization: Bearer <key>`.'
    xApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        An account API key. Send it as `x-api-key: <key>`, the header
        Anthropic-style clients use.

````