## Overview

RiskOS™ APIs return errors in a consistent JSON structure with an appropriate HTTP status code.

> 📘  
> **Note**:  
> Some endpoints may omit the `code` field (legacy behavior), but the overall structure and error categories remain consistent.

## Error model summary

- All errors typically include `error` (customer-facing message)
- Most errors include `code` (machine-readable identifier)
- Optional fields:
  - `message` → internal/debug detail
  - `details` → structured metadata
  - `invalid_args` → field-level validation issues
- Use `code` + HTTP status for programmatic handling
- Do not rely on parsing `error` strings

## Standard error response format

JSON

```json
{
  "error": "Customer-facing error message",
  "code": "MACHINE_READABLE_ERROR_CODE",
  "message": "Internal error details (optional)",
  "details": {
    "key": "Additional context (optional)"
  },
  "invalid_args": [
    {
      "field": "field_name",
      "value": "invalid_value",
      "tag": "Validation error details"
    }
  ]
}
```

## Error fields

| Field               | Type                    | Required               | Description                                   |
|---------------------|-------------------------|-----------------------|-----------------------------------------------|
| `error`             | String                  | Required               | Customer-facing message.                      |
| `code`              | String                  | Conditionally required | Machine-readable error code.                 |
| `message`           | String                  | Optional              | Internal/debug detail.                       |
| `details`           | Object                  | Optional              | Structured metadata.                          |
| `invalid_args`      | Array of Objects        | Optional              | Field-level validation errors (includes `field`, `value`, and `tag`). |

### Message types

Message types help interpret how error strings may vary and whether they are safe to parse.

- **Exact** → always returned as written
- **Template** → contains variables like `{contentType}`
- **Dynamic** → varies at runtime
- **Upstream** → returned from an external system

## HTTP status codes

| Status | Meaning                          | When it occurs                                    |
|--------|-----------------------------------|--------------------------------------------------|
| `400`  | Bad Request                       | Invalid syntax, missing fields, invalid values   |
| `401`  | Unauthorized                      | Missing or invalid authentication                 |
| `403`  | Forbidden                         | Authenticated but blocked by policy              |
| `404`  | Not Found                        | Invalid endpoint or resource ID                   |
| `415`  | Unsupported Media Type           | Invalid `Content-Type`                           |
| `422`  | Unprocessable Entity             | Semantically invalid data                         |
| `429`  | Too Many Requests                | Rate limit exceeded                               |
| `500`  | Internal Server Error            | Unexpected server error                           |
| `502`  | Bad Gateway                      | Upstream dependency failure                        |
| `503`  | Service Unavailable              | Temporary service disruption                      |
| `504`  | Gateway Timeout                  | Upstream timeout                                  |

> Retry transient errors such as `408`, `409`, `423`, `429`, `502`, `503`, and `504` using exponential backoff.

> 📘  
> **Note**:  
> - `400` → structural or schema issues (invalid JSON, missing required fields)
> - `422` → valid structure, but invalid data semantics (e.g., unsupported values)

## Error categories

Each error below follows a consistent pattern: HTTP status → category → meaning → troubleshooting → example. This structure is intentional to support both developer readability and machine parsing.

### Validation errors

#### `INVALID_REQUEST`

**HTTP**: `400 Bad Request`

**Category**: Validation

Occurs when the request contains invalid syntax, missing required fields, or unsupported values.

**How to troubleshoot**

- Check the `invalid_args` array for field-level issues
- Validate payload against API specification

**Examples**

JSON

```json
{
  "error": "invalid request payload. please refer invalid_args for details",
  "code": "INVALID_REQUEST",
  "invalid_args": [
    {
      "field": "Workflow",
      "value": "",
      "tag": "required"
    }
  ]
}
```

JSON

```json
{
  "error": "attachment extension tws not supported",
  "code": "INVALID_REQUEST"
}
```

#### `INVALID_PAYLOAD`

**HTTP**: `400 Bad Request`

**Category**: Validation

Occurs when the request body is not valid JSON.

**How to troubleshoot**

- Ensure valid JSON format
- Confirm `Content-Type: application/json`

JSON

```json
{
  "error": "The evaluation request body contains invalid JSON.",
  "code": "INVALID_PAYLOAD"
}
```

#### `UNSUPPORTED_MEDIA_TYPE`

**HTTP**: `415 Unsupported Media Type`

**Category**: Validation

**Message Type**: Template

Occurs when the request uses an unsupported `Content-Type`.

JSON

```json
{
  "error": "unsupported media type: {contentType}",
  "code": "UNSUPPORTED_MEDIA_TYPE"
}
```

#### `INVALID_ID`

**HTTP**: `400 Bad Request`

**Category**: Validation

Occurs when an identifier is malformed or invalid.

JSON

```json
{
  "error": "specified id is invalid",
  "code": "INVALID_ID"
}
```

### Workflow errors

#### `WORKFLOW_NOT_FOUND`

**HTTP**: `400 Bad Request`

**Category**: Configuration

JSON

```json
{
  "error": "workflow not found with the given version",
  "code": "WORKFLOW_NOT_FOUND"
}
```

#### `WORKFLOW_NOT_PUBLISHED`

**HTTP**: `400 Bad Request`

**Category**: Configuration

JSON

```json
{
  "error": "workflow is not published",
  "code": "WORKFLOW_NOT_PUBLISHED"
}
```

### Authentication and authorization

#### `AUTHENTICATION_FAILED`

**HTTP**: `401 Unauthorized`

**Category**: Authentication

**How to troubleshoot**

- Ensure `Authorization: Bearer` header is present
- Verify API key is valid

JSON

```json
{
  "error": "authentication failed",
  "code": "AUTHENTICATION_FAILED"
}
```

#### `PERMISSION_DENIED`

**HTTP**: `403 Forbidden`

**Category**: Authorization

Occurs when the API key is valid but lacks required permissions.

JSON

```json
{
  "error": "you don't have permission to access this workflow",
  "code": "PERMISSION_DENIED"
}
```

### Security errors

#### `IP_NOT_ALLOWED`

**HTTP**: `403 Forbidden`

**Category**: Security

JSON

```json
{
  "error": "ip address rejected",
  "code": "IP_NOT_ALLOWED"
}
```

#### `NO_IP_FILTER_CONFIGURED`

**HTTP**: `403 Forbidden`

**Category**: Security

JSON

```json
{
  "error": "ip address rejected, no filter configured",
  "code": "NO_IP_FILTER_CONFIGURED"
}
```

### Resource errors

#### `NOT_FOUND`

**HTTP**: `404 Not Found`

**Category**: Resource

Note: May not include `code` in legacy responses.

JSON

```json
{
  "error": "evaluation record not found"
}
```

### Request format errors

#### `UNPROCESSABLE_ENTITY`

**HTTP**: `422 Unprocessable Entity`

**Category**: Validation

JSON

```json
{
  "error": "unsupported_schema",
  "message": "Address country not supported"
}
```

### Rate limiting and server errors

#### `RATE_LIMIT`

**HTTP**: `429 Too Many Requests`

**Category**: Rate Limiting

**How to troubleshoot**

- Use the `Retry-After` header when provided
- Otherwise apply exponential backoff

JSON

```json
{
  "error": "rate_limit",
  "message": "Too many requests, slow down"
}
```

#### `INTERNAL_ERROR`

**HTTP**: `500 Internal Server Error`

**Category**: Server

JSON

```json
{
  "error": "workflow test execution failed",
  "code": "INTERNAL_ERROR"
}
```

#### `BAD_GATEWAY`

**HTTP**: `502 Bad Gateway`

**Category**: Server

Occurs when an upstream service fails.

JSON

```json
{
  "error": "upstream service error"
}
```

## Error code index

| Code                     | HTTP | Category       |
|--------------------------|------|----------------|
| INVALID_REQUEST          | 400  | Validation      |
| INVALID_PAYLOAD          | 400  | Validation      |
| INVALID_ID               | 400  | Validation      |
| WORKFLOW_NOT_FOUND       | 400  | Configuration   |
| WORKFLOW_NOT_PUBLISHED    | 400  | Configuration   |
| AUTHENTICATION_FAILED     | 401  | Authentication  |
| PERMISSION_DENIED        | 403  | Authorization   |
| IP_NOT_ALLOWED           | 403  | Security        |
| UNSUPPORTED_MEDIA_TYPE   | 415  | Validation      |
| INTERNAL_ERROR           | 500  | Server          |
| RATE_LIMIT               | 429  | Rate Limiting   |

## Best practices for handling errors

### 1. Retry behavior

**2xx – Success**

- 200 / 201 – Request accepted and processed successfully.
- Evaluation is created and executed; response includes `decision`, `eval_status`, and enrichment results (each with its own `status_code`, typically 200).

**4xx – Client Errors (Do Not Retry As-Is)**

- 400 – Invalid input, schema, or payload. **Always permanent**. Fix the request before retrying.
- 401 / 403 – Authentication or permission issues. Verify API keys, headers, and environment.

**Retry-safe (Transient) Errors**

- 408, 409, 423, 429, 502, 503, 504
- These indicate temporary conditions such as timeouts, rate limits, or service availability issues

**Recommended Retry Strategy**

- Start with a 1–2 second delay
- Double delay after each attempt (2s, 4s, 8s, 16s, …)
- Add small random jitter (±1s)
- Cap at ~5 attempts or ~1 minute total

### 2. Idempotency

**POST `/api/evaluation`**

- Each request is treated as a new evaluation, even if the same `id` is reused.
- If a client timeout occurs, RiskOS™ aborts processing and a retry will create a **new evaluation**.

### 3. Webhooks

***RiskOS™ → Your Endpoint***

- Your endpoint must return a **2xx (typically 200)** within the timeout window
- Any 2xx response stops retries permanently

### 4. Troubleshooting

**400 – Bad Request**

Check:

- JSON schema alignment with API reference (field names, types, required fields)
- Required top-level fields (`id`, `timestamp`, `workflow`)
- Required enrichment-specific data (PII, address, phone, bank data)

**401 / 403 – Authentication & Authorization**

Verify:

- Correct API key for environment (sandbox vs production)

### 5. FAQs

Which HTTP errors are safe to retry?

Retry transient errors such as 408, 409, 423, 429, 502, 503, 504 using exponential backoff.

Are 429 responses possible?

Yes. RiskOS™ may return 429 for rate limiting. Handle using exponential backoff.
