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

# Error Handling

> Error codes, validation rules, and how to handle failures gracefully.

## Error Response Format

Every error response follows the same structure:

```json theme={null}
{
  "status": "error",
  "code": "ERROR_CODE",
  "message": "Human-readable description of what went wrong",
  "field": "fieldName",
  "requestId": "req_a1b2c3d4"
}
```

| Field       | Always Present | Description                                                      |
| ----------- | -------------- | ---------------------------------------------------------------- |
| `status`    | Yes            | Always `"error"`                                                 |
| `code`      | Yes            | Machine-readable error code (use this for programmatic handling) |
| `message`   | Yes            | Human-readable description (may change — don't parse this)       |
| `field`     | No             | Which field caused the error (only on validation errors)         |
| `requestId` | Yes            | Unique request ID for support inquiries                          |

<Tip>
  Always use the `code` field for error handling in your code, not `message`. The message text may be updated to be more helpful — the code will remain stable.
</Tip>

## Error Codes

| HTTP Status | Code                   | Description                                                                                  |
| ----------- | ---------------------- | -------------------------------------------------------------------------------------------- |
| 400         | `INVALID_JSON`         | Request body is not valid JSON                                                               |
| 400         | `FIELD_REQUIRED`       | A required field is missing. The `field` property tells you which one.                       |
| 400         | `FIELD_INVALID`        | Field value is invalid (wrong type, out of range, too long, etc.)                            |
| 401         | `UNAUTHORIZED`         | Missing or invalid API key. See [Authentication](/api/pms/authentication).                   |
| 401         | `KEY_REVOKED`          | API key has been revoked by the hotel admin                                                  |
| 404         | `GUEST_NOT_FOUND`      | No active guest with the given `pmsGuestId`                                                  |
| 409         | `ROOM_OCCUPIED`        | Room already has an active guest. Response includes `currentGuest` info.                     |
| 409         | `DUPLICATE_GUEST`      | An active guest with the same `pmsGuestId` already exists                                    |
| 422         | `INVALID_DATE_FORMAT`  | Date is not in ISO 8601 format (`YYYY-MM-DD`)                                                |
| 422         | `INVALID_PHONE_FORMAT` | Phone number is not in E.164 format                                                          |
| 422         | `ROOM_NOT_FOUND`       | Room number doesn't exist in the hotel's configured room list                                |
| 429         | `RATE_LIMITED`         | Too many requests. See `Retry-After` header and [Rate Limits](/api/pms/testing#rate-limits). |
| 500         | `INTERNAL_ERROR`       | Server error. Contact support with the `requestId`.                                          |

## Validation Rules

Every field is validated on the server. Here are the rules for each field:

| Field          | Type    | Constraints                                                                     |
| -------------- | ------- | ------------------------------------------------------------------------------- |
| `pmsGuestId`   | string  | Required. 1–255 characters. Must be unique among active guests.                 |
| `lastName`     | string  | Required. 1–100 characters.                                                     |
| `firstName`    | string  | Optional. 1–100 characters.                                                     |
| `roomNumber`   | string  | Required for check-in. Must match a room in the hotel's configured room list.   |
| `phone`        | string  | Optional. Must be E.164 format (see below).                                     |
| `checkInDate`  | string  | Optional (defaults to today). Must be `YYYY-MM-DD`.                             |
| `checkOutDate` | string  | Required for check-in. Must be `YYYY-MM-DD`. Must be on or after `checkInDate`. |
| `language`     | string  | Optional. ISO 639-1 code: `en`, `tr`, `de`, `fr`, `es`, `ru`, etc.              |
| `nationality`  | string  | Optional. ISO 3166-1 alpha-2 code: `US`, `TR`, `DE`, etc.                       |
| `email`        | string  | Optional. Must be a valid email format.                                         |
| `notes`        | string  | Optional. Maximum 2,000 characters.                                             |
| `adults`       | integer | Optional. Range: 1–20. Defaults to 1.                                           |
| `children`     | integer | Optional. Range: 0–20. Defaults to 0.                                           |

## Date Format

**Strict ISO 8601. No exceptions.**

| Format         | Example       | Status         |
| -------------- | ------------- | -------------- |
| `YYYY-MM-DD`   | `2026-02-18`  | Accepted       |
| `DD.MM.YYYY`   | `18.02.2026`  | Rejected (422) |
| `MM/DD/YYYY`   | `02/18/2026`  | Rejected (422) |
| `DD-Mon-YYYY`  | `18-Feb-2026` | Rejected (422) |
| Unix timestamp | `1708214400`  | Rejected (422) |

When a date fails validation, the error message includes what you sent and what's expected:

```json theme={null}
{
  "status": "error",
  "code": "INVALID_DATE_FORMAT",
  "message": "Date must be in ISO 8601 format: YYYY-MM-DD. Received: '18.02.2026'",
  "field": "checkInDate",
  "requestId": "req_y5z6a7b8"
}
```

<Warning>
  **Turkish PMS systems (Protel, Byte, Hicell) commonly use `DD.MM.YYYY` format.** Make sure your integration converts dates to `YYYY-MM-DD` before sending to RecepAI.
</Warning>

## Phone Number Format

**E.164 format required.** Starts with `+`, followed by country code and number. No spaces, dashes, or parentheses.

| Example             | Status   | Why                               |
| ------------------- | -------- | --------------------------------- |
| `+905551234567`     | Accepted | Correct E.164                     |
| `+491234567890`     | Accepted | Correct E.164                     |
| `+442071234567`     | Accepted | Correct E.164                     |
| `05551234567`       | Rejected | Missing country code (`+90`)      |
| `+90 555 123 4567`  | Rejected | Contains spaces                   |
| `0090-555-123-4567` | Rejected | Contains dashes and leading zeros |
| `(555) 123-4567`    | Rejected | US local format — not E.164       |

```json theme={null}
{
  "status": "error",
  "code": "INVALID_PHONE_FORMAT",
  "message": "Phone must be in E.164 format (+905551234567). Received: '05551234567'",
  "field": "phone",
  "requestId": "req_c3d4e5f6"
}
```

## Common Mistakes

<AccordionGroup>
  <Accordion title="Sending dates in DD.MM.YYYY format">
    This is the most common error for Turkish PMS integrations. Protel, Byte, and Hicell all default to `DD.MM.YYYY` internally.

    **Fix:** Convert all dates to `YYYY-MM-DD` before sending. For example, `18.02.2026` → `2026-02-18`.
  </Accordion>

  <Accordion title="Missing country code on phone numbers">
    Sending `05551234567` instead of `+905551234567`. The phone field requires the full international number with country code.

    **Fix:** Prepend the country code with `+`. For Turkey, that's `+90`. For Germany, `+49`. For the UK, `+44`.
  </Accordion>

  <Accordion title="Sending roomNumber as an integer">
    Sending `{"roomNumber": 301}` instead of `{"roomNumber": "301"}`. Room numbers are strings because they can contain letters (e.g., `"301A"`, `"P2-105"`).

    **Fix:** Always send room numbers as strings: `"301"`, not `301`.
  </Accordion>

  <Accordion title="Forgetting the Idempotency-Key header">
    All POST endpoints require an `Idempotency-Key` header. Without it, you'll receive a `400` error.

    **Fix:** Include `Idempotency-Key: your_unique_key` in every POST request. See [Idempotency](/api/pms/testing#idempotency) for key format recommendations.
  </Accordion>

  <Accordion title="Reusing the same Idempotency-Key for different operations">
    Each unique operation needs its own key. If you use the same key for two different guests, the second request will return the first guest's cached response.

    **Fix:** Use a key format like `{operation}_{pmsGuestId}_{date}`. Example: `checkin_RES2026001_20260218`.
  </Accordion>

  <Accordion title="Checking in a guest without checking out the previous one">
    If Room 301 still has an active guest and you try to check in a new guest to Room 301, you'll get a `409 ROOM_OCCUPIED` error.

    **Fix:** Always send a [checkout](/api/pms/checkout) for the departing guest before checking in the new guest. Or use the [Full Sync](/api/pms/sync) endpoint which handles this automatically.
  </Accordion>
</AccordionGroup>

## Handling Errors in Your Code

Here's a recommended error handling pattern:

```javascript theme={null}
async function checkinGuest(slug, apiKey, guestData, idempotencyKey) {
  const response = await fetch(
    `https://app.recepai.ai/api/pms/v1/${slug}/guests/checkin`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': idempotencyKey
      },
      body: JSON.stringify(guestData)
    }
  );

  const data = await response.json();

  if (response.ok) {
    // Success — guest created (or idempotent replay)
    return data;
  }

  // Handle specific error codes
  switch (data.code) {
    case 'ROOM_OCCUPIED':
      // Room has another guest — check them out first
      console.log(`Room occupied by ${data.currentGuest.lastName}`);
      break;
    case 'DUPLICATE_GUEST':
      // Guest already checked in — maybe use update instead
      console.log(`Guest already exists: ${data.existingGuest.guestId}`);
      break;
    case 'RATE_LIMITED':
      // Wait and retry
      const retryAfter = data.retryAfter || 60;
      await sleep(retryAfter * 1000);
      return checkinGuest(slug, apiKey, guestData, idempotencyKey);
    case 'FIELD_REQUIRED':
    case 'FIELD_INVALID':
    case 'INVALID_DATE_FORMAT':
    case 'INVALID_PHONE_FORMAT':
      // Fix your payload — don't retry with the same data
      console.error(`Validation error on ${data.field}: ${data.message}`);
      break;
    default:
      console.error(`Unexpected error: ${data.code} — ${data.message}`);
  }

  throw new Error(`PMS API error: ${data.code}`);
}
```

<Info>
  **Retry guidance:**

  * **4xx errors** (except 429): Fix your request before retrying. Retrying the same payload will return the same error.
  * **429 errors**: Wait for the `retryAfter` period, then retry.
  * **5xx errors**: Retry with exponential backoff (1s, 2s, 4s, 8s). Include the `requestId` if contacting support.
</Info>
