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

# API Overview

> Base URLs, authentication, and rate limiting across the Raykoi API.

## Base URL

```
https://api.raykoi.com
```

## Two authentication models

<CardGroup cols={2}>
  <Card title="Public endpoints" icon="globe">
    No key required. Used directly by browsers — a `<form>`, `submit.js`, or the SDK's client-side adapters. Protected by CAPTCHA, honeypot, rate limiting, and origin allowlisting instead of a credential, since a browser can't keep a secret.
  </Card>

  <Card title="Server Data API" icon="key">
    Requires a secret API key (`Authorization: Bearer rk_live_...`). Server-side only — never ship a key to a browser. Used by your backend to submit on someone's behalf, or to read submissions/analytics/files.
  </Card>
</CardGroup>

<Warning>
  An API key also authenticates a *submission* the same way — pass `Authorization: Bearer rk_live_...` on `POST /api/v1/submit/p/:public_id` and it skips the CAPTCHA gate entirely, since a server-to-server caller has no browser widget to solve one. Never expose a secret key in client-side code.
</Warning>

## Public endpoints

| Method | Path                                    | Purpose                                  |
| ------ | --------------------------------------- | ---------------------------------------- |
| `GET`  | `/api/v1/forms/p/:public_id`            | Fetch a form's schema and CAPTCHA config |
| `POST` | `/api/v1/submit/p/:public_id`           | Submit form data                         |
| `POST` | `/api/v1/forms/p/:public_id/validate`   | Validate data without submitting         |
| `POST` | `/api/v1/forms/p/:public_id/events`     | Track an analytics event                 |
| `POST` | `/api/v1/forms/p/:public_id/upload-url` | Get a signed upload URL for a file field |

Notice `submit` lives at its own top-level path, not nested under `/forms/p/...` — it's the one endpoint that writes; everything else is a read or a side-effect-free helper around a form you've already fetched.

## Rate limits

Every public endpoint is rate-limited per identity (IP, or workspace for the Server Data API), backed by a shared store — not an in-memory counter that resets per server replica.

| Endpoint     | Limit       |
| ------------ | ----------- |
| Schema fetch | 60 / minute |
| Submit       | 20 / minute |
| Validate     | 30 / minute |
| Events       | 60 / minute |
| Upload URL   | 10 / minute |

A `429` includes a human-readable message; there's currently no `Retry-After` header, so back off with a fixed or exponential delay rather than parsing one.

## Errors

Every error is a JSON body with an `error` message and, where relevant, a machine-readable `code`:

```json theme={null}
{ "error": "Human-readable message", "code": "MACHINE_READABLE_CODE" }
```

| Status | Meaning                                                                                                  |
| ------ | -------------------------------------------------------------------------------------------------------- |
| `400`  | Malformed request (missing required fields, bad types).                                                  |
| `401`  | Missing or invalid API key.                                                                              |
| `403`  | CAPTCHA/honeypot/scoring block, or an origin not in `allowed_origins`.                                   |
| `404`  | Form doesn't exist (or its workspace is suspended — treated identically).                                |
| `409`  | Idempotency key reused with a different payload, or the form's schema changed since you last fetched it. |
| `422`  | Field-level validation failure — see the `errors` array.                                                 |
| `423`  | Form exists but isn't currently accepting submissions.                                                   |
| `429`  | Rate limit or quota exceeded.                                                                            |

## Response headers

Every response echoes `X-Request-Id` — include it when reporting an issue; it's the fastest way to find the exact server-side log line for your request.
