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

# Security & Error Handling

> HTTP status codes, retry strategies, and a pre-production security checklist.

## HTTP status codes

| Status | Meaning                                            | Action                                                    |
| ------ | -------------------------------------------------- | --------------------------------------------------------- |
| `400`  | Bad request — validation error, invalid parameters | Fix the request                                           |
| `401`  | Invalid, expired, or missing API key               | Check your API key                                        |
| `403`  | Missing required scope                             | Add the needed scope to your API key                      |
| `404`  | Resource not found or not in your org              | Verify the ID                                             |
| `409`  | Conflict — max attempts, duplicate webhook, etc.   | Check business logic constraints                          |
| `422`  | Grading failed — invalid answer format             | Check answer structure matches question type              |
| `423`  | Assessment is locked                               | Do not retry — a teacher has closed it to new submissions |
| `429`  | Rate limit exceeded                                | Wait for `Retry-After` seconds, then retry                |
| `500`  | Internal server error                              | Retry after a short delay                                 |

## Retry strategies

For transient errors (`429`, `500`, `502`, `503`, `504`):

```typescript theme={null}
import { EdpireError } from "@edpire/sdk/client"

async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await fn()
    } catch (err) {
      if (err instanceof EdpireError && [429, 500, 502, 503, 504].includes(err.status)) {
        if (attempt === maxRetries) throw err

        // Always honour Retry-After when present. Our 429s can carry a value far
        // larger than any backoff curve: the per-question /check limiter uses a
        // 1-hour window, so Retry-After can be up to 3600. Retrying before it
        // elapses just burns your attempts and 429s again.
        const retryAfter = err.retryAfter // seconds, when the response set the header
        const delay =
          retryAfter != null
            ? retryAfter * 1000
            : Math.min(1000 * Math.pow(2, attempt), 30000)

        await new Promise((r) => setTimeout(r, delay))
        continue
      }
      throw err // non-retryable
    }
  }
  throw new Error("unreachable")
}

const assessment = await withRetry(() => client.getAssessment("id"))
```

## Production security checklist

Before going live, verify each item:

<AccordionGroup>
  <Accordion title="API key is server-side only" icon="server">
    Never expose `edp_live_` keys to the browser or commit them to version control. Use environment variables. All Edpire API calls must go through your backend — the browser should only talk to your own proxy routes.
  </Accordion>

  <Accordion title="Webhook signatures verified" icon="shield-check">
    Reject any incoming webhook that fails HMAC-SHA256 verification. Use `timingSafeEqual` to prevent timing attacks.
  </Accordion>

  <Accordion title="Minimum scopes" icon="key">
    Each API key has only the scopes it needs. A key that only reads data should not have `write:submissions`.
  </Accordion>

  <Accordion title="Allowed Origins configured" icon="globe">
    Only your production domains are in the embed allow list. Remove localhost/staging before launch.
  </Accordion>

  <Accordion title="learner_ref is server-generated" icon="user">
    Never let the client construct or choose the `learner_ref`. Always generate it server-side from your auth session.
  </Accordion>

  <Accordion title="Idempotent webhook handlers" icon="rotate">
    Duplicate events do not cause duplicate records. Check `submission_id` uniqueness before writing.
  </Accordion>

  <Accordion title="Rate limiting handled" icon="gauge">
    Your code respects `Retry-After` headers and backs off on `429` responses.
  </Accordion>

  <Accordion title="Correct answers are opt-in only" icon="lock">
    Stored answer keys are never returned. By default `/check` returns only correctness signals. Passing `include_correct_answers: true` does return the correct answers, by design, so review and practice modes can show them. Set that flag server-side based on your own logic, and never forward it from client input.
  </Accordion>

  <Accordion title="HTTPS in production" icon="lock-keyhole">
    Webhook URLs must use HTTPS. HTTP is only permitted for `localhost` during development.
  </Accordion>
</AccordionGroup>
