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

# Encrypt payload

> Two modes, chosen by header:

- `X-Qanapi-Fields` names dot-separated paths inside a JSON body. Each
  named leaf is replaced by an envelope string; everything else is
  returned untouched.
- `X-Qanapi-Full-Body: true` treats the whole body as one payload and
  returns a single envelope as `text/plain`, whatever the body was.

Every payload gets a fresh key encryption key and data encryption key,
so a nonce is never reused under a key. Strictly AES-256-GCM: a request
asking for another algorithm is refused rather than quietly honoured.

Setting `X-Qanapi-Destination` turns the call into a proxy. See the
response description and docs/api-guide.md.




## OpenAPI

````yaml /openapi/stand-alone/v3.yaml post /api/v3/encryption/{proxy}/encrypt
openapi: 3.1.0
info:
  title: Qanapi Standalone API
  version: 3.0.0
  summary: Single-tenant encryption, key management, classification and audit service.
  description: >
    A standalone cryptographic service. Callers hand it data and it returns

    ciphertext, or hand it ciphertext and it returns data, with every operation

    authorised by policy and recorded on a tamper-evident audit trail.


    ## Authentication


    Two credentials reach the same principal:


    - **API key** in `X-Qanapi-Authorization`, for machine callers. A key
    reaches
      only the configurations it is linked to.
    - **User bearer token** in `Authorization`, from `POST /api/v3/auth/login`,
      for a person working through a console.

    An API key outranks a bearer token when a request carries both, because a

    key names a narrower credential than the user that owns it.


    ## Status codes


    The credential failures are worth stating plainly, because they are not what

    a reader would guess and clients branch on them:


    | Situation | Status |

    |---|---|

    | No credential presented at all | `400` |

    | A credential that does not verify | `401` |

    | A revoked API key | `403` |

    | Authenticated but not permitted | `403` |

    | Request body or parameter is invalid | `422` |


    All of these match the Laravel v3 API this service replaces. Every error
    body

    carries `message` and `error`; validation failures additionally carry a

    field-keyed `errors` object.


    ## Rate limiting


    Every response carries `X-RateLimit-Limit` and `X-RateLimit-Remaining`. The

    window is per node and keyed on the client address, so a cluster of n nodes

    admits n times the configured figure.
  license:
    name: Proprietary
    identifier: LicenseRef-Qanapi-Commercial
servers:
  - url: https://{host}
    description: A deployed instance, behind the reverse proxy that terminates TLS.
    variables:
      host:
        default: qanapi.example.com
  - url: http://localhost:8000
    description: The local Compose cluster, through its load balancer.
security:
  - ApiKeyAuth: []
  - BearerAuth: []
tags:
  - name: STAND
    description: |
      Every operation this service serves. Carried on all of them so the
      standalone surface can be selected as a whole, which matters where this
      description is rendered or merged alongside another Qanapi API.

      It is a marker rather than a grouping: each operation keeps the tag that
      says what it does, and that one is listed first.
  - name: Health
    description: |
      Liveness and readiness probes. Registered outside the authentication,
      audit and transport layers, so a load balancer polling them without
      credentials still gets an answer.
  - name: Authentication
    description: Logging in, and creating the first administrator.
  - name: Users
    description: User accounts and their roles.
  - name: API keys
    description: >
      Machine credentials. A key is scoped to a set of configurations and
      reaches

      nothing until it is linked to at least one.
  - name: Configurations
    description: >
      Encryption configurations, called configurations on the wire for
      compatibility

      with the v3 API this service replaces. A configuration owns a master key
      and is

      the unit of cryptographic isolation.
  - name: Classifications
    description: |
      Sensitivity labels and the clearance that reaches them.
  - name: Policies
    description: Allow and deny rules binding a principal to actions on resources.
  - name: Encryption
    description: >
      The data plane. Field-level or whole-body encryption under a
      configuration,

      optionally forwarded onward to a destination of the caller's choosing.
  - name: KMS
    description: Customer-managed key lifecycle, import and export.
  - name: Audit
    description: The tamper-evident record of everything the service did.
  - name: Transport
    description: The service's own key pair, for end-to-end encrypted request bodies.
paths:
  /api/v3/encryption/{proxy}/encrypt:
    parameters:
      - $ref: '#/components/parameters/ProxySlug'
    post:
      tags:
        - Encryption
        - STAND
      summary: Encrypt payload
      description: |
        Two modes, chosen by header:

        - `X-Qanapi-Fields` names dot-separated paths inside a JSON body. Each
          named leaf is replaced by an envelope string; everything else is
          returned untouched.
        - `X-Qanapi-Full-Body: true` treats the whole body as one payload and
          returns a single envelope as `text/plain`, whatever the body was.

        Every payload gets a fresh key encryption key and data encryption key,
        so a nonce is never reused under a key. Strictly AES-256-GCM: a request
        asking for another algorithm is refused rather than quietly honoured.

        Setting `X-Qanapi-Destination` turns the call into a proxy. See the
        response description and docs/api-guide.md.
      operationId: encrypt
      parameters:
        - $ref: '#/components/parameters/Fields'
        - $ref: '#/components/parameters/FullBody'
        - $ref: '#/components/parameters/Mode'
        - $ref: '#/components/parameters/ClassificationHeader'
        - $ref: '#/components/parameters/RequireFields'
        - $ref: '#/components/parameters/Destination'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              description: |
                Any JSON document in field mode. In whole-body mode the body may
                be any bytes at all, including non-JSON.
            example:
              name: Jane Doe
              ssn: 123-45-6789
              dob: '1980-01-01'
          text/plain:
            schema:
              type: string
      responses:
        '200':
          description: |
            In field mode, the document with the named fields replaced by
            envelopes. In whole-body mode, one envelope as `text/plain`.

            When `X-Qanapi-Destination` was set this is instead whatever the
            destination answered, relayed verbatim with its own status, body and
            headers, and stamped with `X-Qanapi-Forwarded-To`.
          headers:
            X-Qanapi-Action-Count:
              $ref: '#/components/headers/ActionCount'
            X-Qanapi-Forwarded-To:
              $ref: '#/components/headers/ForwardedTo'
          content:
            application/json:
              example:
                name: Jane Doe
                ssn: QANAPI-RS:v1:MDFhMDg2ZGQ:Vpp6gZK4ZRUwgvpcnA:$
                dob: QANAPI-RS:v1:MDFhMDg2ZGU:T3RoZXJjaXBoZXJ0ZXh0:$
            text/plain:
              schema:
                type: string
                examples:
                  - QANAPI-RS:v1:MDFhMDg2ZGQ:Vpp6gZK4ZRUwgvpcnA:$
        '400':
          $ref: '#/components/responses/NoCredential'
        '401':
          $ref: '#/components/responses/BadCredential'
        '403':
          description: |
            The policy engine refused the action, or the caller does not hold
            the clearance named in `X-Qanapi-Classification`. Applying a
            classification the caller lacks is refused rather than allowed,
            because it would let them write data they immediately cannot read.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          $ref: '#/components/responses/ProxyNotReachable'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '422':
          $ref: '#/components/responses/ValidationFailed'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '500':
          description: |
            A forwarding destination was named and could not be reached. The
            cryptography succeeded; the forward did not.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  parameters:
    ProxySlug:
      name: proxy
      in: path
      required: true
      description: >
        The configuration's proxy slug. Sixteen random lowercase characters
        unless

        one was pinned at creation.
      schema:
        type: string
        examples:
          - 952zpx6vyig3sdyp
    Fields:
      name: X-Qanapi-Fields
      in: header
      description: |
        Comma-separated dot paths to encrypt or decrypt, for example
        `ssn,user.address.postcode`. Absent in whole-body mode.
      schema:
        type: string
        examples:
          - ssn,user.dob
    FullBody:
      name: X-Qanapi-Full-Body
      in: header
      description: Treat the whole body as one payload. `true`, `1`, `yes` or `on`.
      schema:
        type: string
        examples:
          - 'true'
    Mode:
      name: X-Qanapi-Mode
      in: header
      description: Alternative spelling of whole-body mode; `full_body`.
      schema:
        type: string
        enum:
          - full_body
          - full-body
    ClassificationHeader:
      name: X-Qanapi-Classification
      in: header
      description: |
        Apply a classification to what is being encrypted, by slug or id.
        Refused unless the caller holds that clearance.
      schema:
        type: string
    RequireFields:
      name: X-Qanapi-Require-Fields
      in: header
      description: |
        Fail the request when a named field path matched nothing, rather than
        silently returning the document unchanged. Off by default; worth setting
        in any caller that would rather hear about a typo than store plaintext.
      schema:
        type: string
        examples:
          - 'true'
    Destination:
      name: X-Qanapi-Destination
      in: header
      description: |
        Forward the processed payload to this absolute URL and relay the answer
        back. Every request header travels except `x-qanapi-*`, `authorization`
        and the hop-by-hop set. Restricted by `FORWARD_ALLOWED_HOSTS` where the
        installation sets it.
      schema:
        type: string
        format: uri
  headers:
    ActionCount:
      description: How many cryptographic operations the request performed.
      schema:
        type: integer
    ForwardedTo:
      description: >
        Present only when the response was relayed from a destination, naming

        it, so a caller can tell a proxied answer from one this service
        produced.
      schema:
        type: string
        format: uri
    RateLimitLimit:
      description: Requests allowed per minute, per node.
      schema:
        type: integer
    RateLimitRemaining:
      description: Requests left in the current window.
      schema:
        type: integer
  responses:
    NoCredential:
      description: |
        No credential was presented. `400` rather than `401`, matching the v3
        API this service replaces; `401` means a credential was presented and
        did not verify.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    BadCredential:
      description: A credential was presented and did not verify.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ProxyNotReachable:
      description: >
        Reported as a missing API key. The answer is deliberately the same

        whether no configuration has that slug or the presented key is not
        linked to

        the one that does, so an unrelated key cannot enumerate configurations.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            message: API key not found
            error: ApiKeyNotFoundException
    PayloadTooLarge:
      description: The body exceeded `MAX_BODY_BYTES`, 10 MB by default.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    ValidationFailed:
      description: |
        The request body or a parameter is invalid. `message` is always
        `Validation failed`; the specifics are in `errors`, keyed by field.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ValidationError'
    TooManyRequests:
      description: The rate limit for this client address was exhausted.
      headers:
        X-RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  schemas:
    Error:
      type: object
      required:
        - message
        - error
      properties:
        message:
          type: string
          description: >
            Human readable. A 5xx is described by its category and nothing more:

            operational detail such as a path or a setting name stays in the
            log.
          examples:
            - Unauthorized.
        error:
          type: string
          description: |
            A stable machine readable code. This service sends it and the v3 API
            it replaces does not, which is what lets the two share the same
            `message` strings without losing specificity: `Resource not found.`
            still arrives alongside `KmsKeyNotFoundException`.
          examples:
            - InvalidApiKeyException
    ValidationError:
      allOf:
        - $ref: '#/components/schemas/Error'
        - type: object
          required:
            - errors
          properties:
            errors:
              type: object
              description: >
                Messages keyed by the field they concern. A failure with no
                single

                field to blame is filed under `request`.
              additionalProperties:
                type: array
                items:
                  type: string
              examples:
                - type:
                    - The type field is required.
                  configuration_ids:
                    - No configuration has the id `...`.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-Qanapi-Authorization
      description: >
        A machine credential, `qk_` followed by its secret. The first twelve

        characters are an indexed prefix; the rest is compared in constant time

        against a stored SHA-256 hash. A key reaches only the configurations it
        is

        linked to.


        It may also be sent as `Authorization: Bearer qk_...`, which is

        recognised by the prefix.
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        A user token from `POST /api/v3/auth/login`. The token asserts a role,
        but the database decides it, so a role changed or a user deleted after
        the token was issued takes effect immediately. A token minted before a
        password change is refused.

````