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

# Submissions

> Schema validation, idempotent retries, and how errors are shaped.

## Schema validation

Every submission is validated server-side against the form's schema — this is the source of truth; any client-side validation (the SDK does a local pre-check before it even calls the network) is a UX nicety, never the actual gate.

A validation failure returns `422` with a per-field breakdown:

```json theme={null}
{
  "error": "Validation failed",
  "errors": [
    { "field": "email", "fieldId": "email", "message": "Enter a valid email address", "type": "pattern" }
  ]
}
```

`type` is one of a fixed, stable vocabulary you can key UI logic off of without parsing message text:

| Type                      | Meaning                                                             |
| ------------------------- | ------------------------------------------------------------------- |
| `required`                | A required field was empty or missing.                              |
| `minLength` / `maxLength` | Text length outside the configured bounds.                          |
| `min` / `max`             | A numeric value outside the configured bounds.                      |
| `pattern`                 | Failed a format check — email, URL, phone, date, or a custom regex. |

An unrecognized field in your submission isn't rejected — it's tracked and surfaced in the dashboard as "unknown field detected," which is how you notice your markup and your schema have drifted apart.

## Idempotency

Every submission gets a dedup key whether you ask for one or not — the SDK auto-generates a fresh UUID per attempt if you don't supply one via `Idempotency-Key`. If the same key arrives twice with the **same payload**, the second request returns the original result without creating a duplicate submission or consuming a second quota unit.

If the same key arrives with a **different** payload, that's rejected outright — a reused key is never silently treated as "close enough":

```json theme={null}
{ "error": "Idempotency-Key was already used with a different payload.", "code": "IDEMPOTENCY_KEY_REUSED" }
```

This matters most for anything that might retry on its own — a flaky network, a backend job queue, a user double-clicking submit.

## Success response

```json theme={null}
{
  "success": true,
  "submission_id": "sub_pub_1a2b3c",
  "submission_number": 42
}
```

## Quotas

Workspaces on a plan with a submission limit get a `429` once it's reached:

```json theme={null}
{ "error": "Submission limit reached for this workspace. Please contact the form owner." }
```

Quota is checked atomically at insert time — a burst of concurrent submissions right at the limit can't overshoot it via a race condition.

## A closed form

A form with submissions turned off in its settings returns `423`, not `404` — the form exists, it's just not currently accepting data:

```json theme={null}
{ "error": "This form is not currently accepting submissions." }
```
