Headless Forms

Error Responses

Form-specific errors use the RFC 9457 (Problem Details) format with Content-Type: application/problem+json. These include a machine-readable type URI, a short title, the HTTP status code, and an optional human-readable detail message.

All other errors (validation, authentication, rate limiting, etc.) use Laravel's standard JSON error format.

Problem Details Fields

Field Type Description
type string A URI that identifies the error type. Points to this documentation page.
title string A short, human-readable summary of the error type.
status integer The HTTP status code.
detail string A human-readable explanation specific to this occurrence (optional).

Form-Specific Errors (Problem Details)

Form Inactive {#form-inactive}

Returned when the form is not accepting submissions.

{
  "type": "https://headless-form.robertboes.nl/docs/api-errors#form-inactive",
  "title": "Form Inactive",
  "status": 403,
  "detail": "This form is currently inactive."
}

Origin Not Allowed {#origin-not-allowed}

Returned when the request's Origin header doesn't match any of the form's allowed origins.

{
  "type": "https://headless-form.robertboes.nl/docs/api-errors#origin-not-allowed",
  "title": "Origin Not Allowed",
  "status": 403,
  "detail": "The request origin is not in this form's allowed origins list."
}

Origin Required {#origin-required}

Returned when strict origin enforcement is enabled and no Origin header is present.

{
  "type": "https://headless-form.robertboes.nl/docs/api-errors#origin-required",
  "title": "Origin Required",
  "status": 403,
  "detail": "The Origin header is required for this form."
}

Spam Detected {#honeypot-detected}

Returned when the honeypot field contains a value (indicating a bot).

{
  "type": "https://headless-form.robertboes.nl/docs/api-errors#honeypot-detected",
  "title": "Spam Detected",
  "status": 422,
  "detail": "Bot protection verification failed."
}

Bot Protection Failed {#bot-protection-failed}

Returned when the bot protection challenge token is invalid or missing.

{
  "type": "https://headless-form.robertboes.nl/docs/api-errors#bot-protection-failed",
  "title": "Bot Protection Failed",
  "status": 422,
  "detail": "Bot protection verification failed."
}

Standard Errors

These errors use Laravel's default JSON format with a message field.

Validation Error

Returned when submitted data fails validation. Includes field-level error details.

{
  "message": "The email field must be a valid email address.",
  "errors": {
    "email": [
      "The email field must be a valid email address."
    ],
    "name": [
      "The name field is required."
    ]
  }
}

Too Many Requests

Returned when the rate limit is exceeded. Includes a Retry-After header with the number of seconds to wait.

HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
  "message": "Too Many Attempts."
}

Unauthorized

Returned when authentication is required but missing or invalid.

{
  "message": "Unauthenticated."
}

Forbidden

Returned when the authenticated user does not have permission to perform the action.

{
  "message": "Token does not have the required ability."
}

Not Found

Returned when the requested resource does not exist.

{
  "message": "Not Found."
}

Error Handling Example

const response = await fetch('https://headless-form.robertboes.nl/api/v1/form/01JCK...', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ name: '', email: 'invalid' }),
});

if (!response.ok) {
  const error = await response.json();

  if (error.type) {
    // Problem Details error (form-specific)
    console.error(`${error.title}: ${error.detail ?? ''}`);
  } else if (response.status === 422 && error.errors) {
    // Validation error
    for (const [field, messages] of Object.entries(error.errors)) {
      console.error(`${field}: ${messages.join(', ')}`);
    }
  } else if (response.status === 429) {
    const retryAfter = response.headers.get('Retry-After');
    console.error(`Rate limited. Try again in ${retryAfter} seconds.`);
  } else {
    console.error(error.message);
  }
}