> ## 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.

# Create an image

> Creates images from a text prompt. The response carries one entry per produced
image, each holding either a link the gateway hosts or the image bytes inline.

Requires an API key.

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

Three checks run before a provider is called.

* The prompt is screened by Naga's own moderation. A flagged prompt returns
  `403 inappropriate_content`, naming the categories that fired, and no provider
  is called.
* `size` is checked for shape alone: two runs of digits with an `x` between
  them. Whether the model can produce that size is the provider's answer.
* `n` is checked against the maximum the selected model declares for itself,
  where it declares one.

`response_format` selects which field of every `data[]` entry is filled. Exactly
one of the two is present on each entry.

* `url`, the default — the gateway stores the bytes the provider returned in its
  own object store under a freshly minted random name, and responds with the
  public URL of that copy. The link belongs to Naga and points at the stored
  copy. The object carries no expiry.
* `b64_json` — the image bytes inline, as standard base64. The gateway stores
  nothing, so the response carries no link to return to later.

The account is charged after the images are delivered, at the model's published
prices. `usage` on the response is the gateway's own count and is the same count
the charge is computed from. Models that price a prompt count it in input
tokens. Produced images are counted in output tokens, using the provider's
number where it reported one and a flat 2048 per image where it did not. A
request whose images could not be delivered is not charged.

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/images/generations
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/images/generations:
    post:
      tags:
        - Images
      summary: Create an image
      description: >
        Creates images from a text prompt. The response carries one entry per
        produced

        image, each holding either a link the gateway hosts or the image bytes
        inline.


        Requires an API key.


        `model` names a model from Naga's catalog that serves
        `images.generations`, and

        the gateway selects a provider for it.


        Three checks run before a provider is called.


        * The prompt is screened by Naga's own moderation. A flagged prompt
        returns
          `403 inappropriate_content`, naming the categories that fired, and no provider
          is called.
        * `size` is checked for shape alone: two runs of digits with an `x`
        between
          them. Whether the model can produce that size is the provider's answer.
        * `n` is checked against the maximum the selected model declares for
        itself,
          where it declares one.

        `response_format` selects which field of every `data[]` entry is filled.
        Exactly

        one of the two is present on each entry.


        * `url`, the default — the gateway stores the bytes the provider
        returned in its
          own object store under a freshly minted random name, and responds with the
          public URL of that copy. The link belongs to Naga and points at the stored
          copy. The object carries no expiry.
        * `b64_json` — the image bytes inline, as standard base64. The gateway
        stores
          nothing, so the response carries no link to return to later.

        The account is charged after the images are delivered, at the model's
        published

        prices. `usage` on the response is the gateway's own count and is the
        same count

        the charge is computed from. Models that price a prompt count it in
        input

        tokens. Produced images are counted in output tokens, using the
        provider's

        number where it reported one and a flat 2048 per image where it did not.
        A

        request whose images could not be delivered is not charged.


        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_image
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ImagesGenerationsRequest'
            examples:
              basic:
                summary: One image from a prompt
                value:
                  model: gpt-image-1
                  prompt: A watercolour fox in a snowy forest
                  size: 1024x1024
                  'n': 1
        required: true
      responses:
        '200':
          description: >-
            The produced images. Each entry of `data` carries either a link the
            gateway hosts or the bytes inline, whichever `response_format` asked
            for. `usage` carries the count the charge was computed from.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ImagesResponse'
              examples:
                basic:
                  summary: One rendered image, as a link
                  value:
                    created: 1774000000
                    data:
                      - url: https://cdn.naga.ac/images/9f2c4a1b.png
                    usage:
                      input_tokens: 12
                      output_tokens: 1024
                      total_tokens: 1036
        '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/images/generations \
              -H "Authorization: Bearer $NAGA_API_KEY" \
              -H "Content-Type: application/json" \
              -d '{
              "model": "gpt-image-1",
              "prompt": "A watercolour fox in a snowy forest",
              "size": "1024x1024",
              "n": 1
            }'
        - 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.images.generate(
                model="gpt-image-1",
                prompt="A watercolour fox in a snowy forest",
                size="1024x1024",
                n=1,
            )
            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.images.generate({
              model: "gpt-image-1",
              prompt: "A watercolour fox in a snowy forest",
              size: "1024x1024",
              n: 1,
            });
            console.log(result);
components:
  schemas:
    ImagesGenerationsRequest:
      type: object
      description: >-
        The request body of `POST /v1/images/generations`.


        `POST /v1/images/edits` asks for the same six fields as multipart form

        parts instead, beside the image files themselves, an optional mask and
        an

        optional `background`. Its `prompt` is bounded at 32000 characters
        rather

        than at 8192.
      required:
        - model
        - prompt
      properties:
        model:
          type: string
          description: >-
            The model to generate with, 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`.
        prompt:
          type: string
          description: |-
            What to generate, in words. One to 8192 characters — characters, not
            bytes. The prompt is screened by the gateway's own moderation before
            the image model is called, and a flagged prompt is refused with 403
            `inappropriate_content` naming the categories that fired.
        quality:
          type:
            - string
            - 'null'
          description: >-
            The provider's quality knob, passed through untouched. The gateway

            neither checks the value nor holds a menu of accepted ones. What a

            value means is the serving provider's business, and a provider with
            no

            such knob drops it.
        size:
          type:
            - string
            - 'null'
          description: >-
            The requested image size, written `{width}x{height}`, as in
            `1024x1024`.

            The gateway checks the shape alone: two runs of decimal digits with
            an

            `x` between them. Any other shape is refused with 400. Whether the
            model

            can produce that particular size is the provider's answer.
        'n':
          type:
            - integer
            - 'null'
          format: int64
          description: >-
            How many images to ask for. Absent means one. Less than 1 is refused

            with 400, and so is a value above the maximum a model declares for

            itself. How many images actually come back is the provider's answer
            —

            `data` carries what it produced.
        response_format:
          $ref: '#/components/schemas/ImageResponseFormat'
          description: |-
            Whether each image comes back as a link (`url`) or as inline base64
            (`b64_json`). `url` when the key is absent, and the choice is what
            decides which field of every `data[]` entry is filled.
    ImagesResponse:
      allOf:
        - type: object
        - type: object
          required:
            - created
            - data
            - usage
          properties:
            created:
              type: integer
              format: int64
              description: >-
                When the gateway rendered this body, in whole seconds since the
                Unix

                epoch. The clock is read after the provider has answered, so the
                value

                dates the answer rather than the request.
            data:
              type: array
              items:
                $ref: '#/components/schemas/ImageDataResponse'
              description: >-
                The produced images, in the order the provider returned them —
                one

                entry per image it produced, which need not be the `n` that was
                asked

                for.
            usage:
              $ref: '#/components/schemas/ImageUsageResponse'
              description: >-
                What the request was metered on. Always present, including for a

                provider that reports no usage of its own: the gateway fills the
                block

                from its own counting.
      description: |-
        The body of a successful `POST /v1/images/generations` and of
        `POST /v1/images/edits` — one shape, answered by both routes.
    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.
    ImageResponseFormat:
      type: string
      description: >-
        How the produced images are handed back: `url` as links the gateway
        hosts,

        `b64_json` as inline base64. `url` when the key is absent.
      enum:
        - url
        - b64_json
    ImageDataResponse:
      allOf:
        - type: object
        - type: object
          properties:
            url:
              type:
                - string
                - 'null'
              description: >-
                A link to the image, sent when `response_format` was `url`. The
                gateway

                stores the bytes the provider returned in its own object store,
                under a

                freshly minted random name, and answers with the public URL of
                that

                copy. The link is Naga's own and never points at the provider.
                No

                expiry is set when the object is written. A deployment that
                configures

                no public base URL for that store answers the bare storage key
                here

                instead of an absolute URL.
            b64_json:
              type:
                - string
                - 'null'
              description: >-
                The image bytes themselves, standard base64, sent when

                `response_format` was `b64_json`. Nothing is stored on this
                branch, so

                there is no link to come back to later.
      description: >-
        One produced image. Exactly one of the two fields is filled and the
        other

        is absent from the body — which one is decided by the request's

        `response_format`, never by the provider.
    ImageUsageResponse:
      allOf:
        - type: object
        - type: object
          required:
            - output_tokens
            - total_tokens
          properties:
            input_tokens:
              type:
                - integer
                - 'null'
              format: int64
              description: >-
                Input tokens charged for. Zero whenever the model carries no
                price per

                input text token — the prompt is not counted at all in that
                case, so

                the zero says nothing about the prompt itself. Where the model
                does

                price them it is the provider's own count when the provider
                reported

                one, and the gateway's tokenization of the prompt when it did
                not. On

                `/v1/images/edits` the input images fold into this same number,
                again

                only for a model that prices them. Always present in a body this

                gateway sends.
            output_tokens:
              type: integer
              format: int64
              description: >-
                Output tokens charged for the produced images. The provider's
                own count

                when it reported one; when it did not, a flat 2048 per returned
                image,

                which is the deterministic local count this capability bills on.
            total_tokens:
              type: integer
              format: int64
              description: The sum of `input_tokens` and `output_tokens`.
      description: >-
        What the request was metered on, and therefore what it is billed on.
        These

        are the gateway's numbers rather than the provider's. Where the provider

        reported no count, the gateway's own count stands in, and where the
        model

        prices no such dimension the number is `0`.
    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.
  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.

````