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

# Submit a form from the backend (internal)

> Server-to-server submission endpoint. Shares its implementation with the public
`/forms/{id}/submit` route but runs on the `internal` channel, which changes three things:
- the form is not restricted to `formType: public` — internal forms are reachable;
- CAPTCHA is not verified, even when the form has `captcha.enabled=true`;
- the stored `status` is taken from the request body's `status` instead of being forced to `pending`.
Required-field, disabled-field and type validation still run, as do field `relation` checks.

Auth: the standard `extractAuthData` chain (x-tenant-id, x-user-id or Authorization Bearer,
x-service-status) plus the `form.read` permission. There is NO additional gateway/signature
check — the `x-internal-token` gate in `src/middleware/internalGate.ts` is mounted only on
`/internal` in `src/app.ts` and does not cover this route. "Internal" describes the calling
party (a trusted backend holding user credentials), not a separate authentication scheme.
`checkLimits("formSubmissions")` applies the tenant's seat limit and can reject with 426.



## OpenAPI

````yaml /api-reference/ai-cms/openapi.json post /forms/{id}/internal-submit
openapi: 3.1.0
info:
  title: Blog API
  version: 1.0.0
servers:
  - url: https://api.octaviatech.app/cms
    description: Production
security:
  - ApiKeyAuth: []
tags:
  - name: Tags
    description: Tag management endpoints
paths:
  /forms/{id}/internal-submit:
    post:
      tags:
        - Form Submissions
      summary: Submit a form from the backend (internal)
      description: >-
        Server-to-server submission endpoint. Shares its implementation with the
        public

        `/forms/{id}/submit` route but runs on the `internal` channel, which
        changes three things:

        - the form is not restricted to `formType: public` — internal forms are
        reachable;

        - CAPTCHA is not verified, even when the form has
        `captcha.enabled=true`;

        - the stored `status` is taken from the request body's `status` instead
        of being forced to `pending`.

        Required-field, disabled-field and type validation still run, as do
        field `relation` checks.


        Auth: the standard `extractAuthData` chain (x-tenant-id, x-user-id or
        Authorization Bearer,

        x-service-status) plus the `form.read` permission. There is NO
        additional gateway/signature

        check — the `x-internal-token` gate in `src/middleware/internalGate.ts`
        is mounted only on

        `/internal` in `src/app.ts` and does not cover this route. "Internal"
        describes the calling

        party (a trusted backend holding user credentials), not a separate
        authentication scheme.

        `checkLimits("formSubmissions")` applies the tenant's seat limit and can
        reject with 426.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
            pattern: ^[a-fA-F0-9]{24}$
            example: '{{objectId}}'
          description: 24-char hex identifier of the target form.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - language
                - values
              properties:
                language:
                  type: string
                  description: >-
                    Language code this submission is stored under (e.g. "en",
                    "fa"). Defaults to "fa" when omitted.
                values:
                  type: object
                  description: >-
                    Key-value map where keys are the form's
                    `sections[].fields[].name`.

                    Field rules are enforced server-side (406 on violation):

                    - fields with `required: true` must be present and non-empty

                    - fields with `disabled: true` must be absent

                    - type is checked per field type (email, tel, number,
                    checkbox/switch boolean,
                      multi-checkbox/multi-select array, select must match an `options[].value`)
                    - a field with a `relation` must contain existing submission
                    ids of the target form;
                      `one-to-one` and `many-to-one` take a single id string, `one-to-many` and
                      `many-to-many` take a non-empty array of id strings
                  additionalProperties: {}
                status:
                  type: integer
                  description: >-
                    Initial status. Honoured on this route only (ignored on the
                    public submit route). Defaults to 0 (pending) when omitted.
                  enum:
                    - 0
                    - 1
                    - 2
                captchaToken:
                  type: string
                  description: >-
                    Accepted but not verified on this route — CAPTCHA checks are
                    skipped for the internal channel.
            examples:
              Internal submit:
                value:
                  language: en
                  status: 1
                  values:
                    fullName: John Doe
                    email: john@example.com
                    message: Hello!
      responses:
        '200':
          description: Submitted
          content:
            application/json:
              example:
                success: true
                statusCode: 200
                message: Form submitted successfully
                data:
                  submission:
                    _id: '{{objectId}}'
                    formId: '{{objectId}}'
                    language: en
                    values:
                      fullName: John Doe
                      email: john@example.com
                      message: Hello!
                    relations: []
                    status: 1
                    submittedAt: '2025-10-01T12:00:00.000Z'
                    isDeleted: false
                    createdAt: '2025-10-01T12:00:00.000Z'
                    updatedAt: '2025-10-01T12:00:00.000Z'
                    relatedEntries: []
                    reverseRelations: []
        '400':
          description: Bad request (missing or invalid x-tenant-id, or missing x-user-id)
          content:
            application/json:
              example:
                success: false
                statusCode: 400
                message: x-tenant-id is required
                data: null
        '401':
          description: Unauthorized
          content:
            application/json:
              example:
                success: false
                statusCode: 401
                message: Missing or invalid auth context
                data: null
        '403':
          description: >-
            Forbidden (tenant mismatch, missing `form.read`, or the form is not
            active)
          content:
            application/json:
              example:
                success: false
                statusCode: 403
                message: Form is not active
                data: null
        '404':
          description: >-
            Form not found, or a `relation` field referenced submissions outside
            the related form
          content:
            application/json:
              example:
                success: false
                statusCode: 404
                message: Form not found
                data: null
        '406':
          description: >-
            Validation failed (invalid form id, missing/disabled field, wrong
            type, bad relation ids)
          content:
            application/json:
              example:
                success: false
                statusCode: 406
                message: Field "Email" is required
                data: null
        '409':
          description: >-
            Conflict (a `one-to-one`/`one-to-many` relation uniqueness rule was
            violated)
          content:
            application/json:
              example:
                success: false
                statusCode: 409
                message: Field "Parent" violates one-to-one uniqueness
                data: null
        '426':
          description: Tenant form-submission limit exceeded
          content:
            application/json:
              example:
                success: false
                statusCode: 426
                message: 'formSubmissions limit exceeded (used: 500, limit: 500)'
                data:
                  used: 500
                  limit: 500
                  remaining: 0
                  overflow: 1
        '500':
          description: Server error
          content:
            application/json:
              example:
                success: false
                statusCode: 500
                message: Server Error
                data: null
      security:
        - ApiKeyAuth: []
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key

````