# Submit endpoint

`POST /api/v1/submit/{slug}`

Send any JSON object. If the endpoint has a validation schema, fields are checked against it. If the honeypot field is non-empty, the request returns a fake 200 — bots learn nothing. The default honeypot field name is `website`.

## Examples

```bash
curl -X POST https://forms.devxgroup.io/api/v1/submit/YOUR_SLUG \
  -H "x-api-key: fr_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"email":"you@example.com","name":"You"}'
```

```javascript
const res = await fetch(
  "https://forms.devxgroup.io/api/v1/submit/YOUR_SLUG",
  {
    method: "POST",
    headers: {
      "x-api-key": "fr_live_YOUR_KEY",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ email: "you@example.com", name: "You" }),
  }
);

const data = await res.json();
// { ok: true, lead_id: "..." }
```

```html
<form
  action="https://forms.devxgroup.io/api/v1/submit/YOUR_SLUG"
  method="POST"
>
  <!-- Hidden API key field (use server-side proxy for sensitive keys) -->
  <input type="hidden" name="_api_key" value="fr_live_YOUR_KEY" />

  <input type="email" name="email" placeholder="you@example.com" required />
  <input type="text" name="name" placeholder="Your name" />

  <!-- Honeypot — leave empty -->
  <input type="text" name="website" style="display:none" tabindex="-1" />

  <button type="submit">Join waitlist</button>
</form>
```

## Success response

```json
{ "ok": true, "lead_id": "abc123" }
```

If the endpoint uses double opt-in, the response also includes `"pending_confirmation": true`. The lead is saved; it is confirmed once the person clicks the link in their email.

## Error codes

| Code | HTTP | Meaning |
|------|------|---------|
| MISSING_KEY | 401 | No API key provided |
| INVALID_KEY | 401 | Key not found, revoked, or wrong |
| UNKNOWN_ENDPOINT | 404 | Slug not found for this tenant |
| ENDPOINT_DISABLED | 403 | Endpoint exists but is turned off |
| VALIDATION_FAILED | 422 | Body fails the endpoint schema |
| PLAN_LIMIT_REACHED | 402 | Tenant's monthly submission quota exceeded |
| PAYLOAD_TOO_LARGE | 413 | Request body exceeds 64 KB |
| ORIGIN_NOT_ALLOWED | 403 | Origin is not in the endpoint allowlist |
| CAPTCHA_FAILED | 403 | Turnstile verification failed |
| RATE_LIMITED | 429 | 60 req/min per (tenant + IP) exceeded |
| INVALID_TOKEN | 400 | Confirm/unsubscribe token unknown |
| INTERNAL_ERROR | 500 | Unhandled server error |

```json
{ "ok": false, "error": { "code": "MISSING_KEY", "message": "Provide x-api-key header" } }
```

```json
{ "ok": false, "error": { "code": "INVALID_KEY", "message": "Key not found or revoked" } }
```

```json
{ "ok": false, "error": { "code": "UNKNOWN_ENDPOINT", "message": "No endpoint found for this slug" } }
```

```json
{
  "ok": false,
  "error": {
    "code": "VALIDATION_FAILED",
    "message": "email: invalid email",
    "fields": { "email": "invalid email" }
  }
}
```

```http
HTTP/1.1 429 Too Many Requests
Retry-After: 42

{ "ok": false, "error": { "code": "RATE_LIMITED", "message": "Too many submissions. Try again in 60s." } }
```
