# Permission Matrix Audit

Scanned 182 route files under `src/app/api/v1/`.

**No violations.** Every internal v1 handler is permission-gated.

**Allow-listed:** 46 handler(s) intentionally skip `withPermission`.

| File                                                             | Method | Reason                                                                            |
| ---------------------------------------------------------------- | ------ | --------------------------------------------------------------------------------- |
| `src/app/api/v1/admin/alerts/run-engine/route.ts`                | POST   | Admin-only — gated by isSuperAdmin inside handler.                                |
| `src/app/api/v1/admin/connections/route.ts`                      | GET    | Admin-only — gated by isSuperAdmin inside handler.                                |
| `src/app/api/v1/admin/errors/route.ts`                           | GET    | Admin-only — gated by isSuperAdmin inside handler.                                |
| `src/app/api/v1/admin/health/route.ts`                           | GET    | Admin-only — gated by isSuperAdmin inside handler.                                |
| `src/app/api/v1/admin/ocr-settings/route.ts`                     | GET    | Admin-only — gated by isSuperAdmin inside handler.                                |
| `src/app/api/v1/admin/ocr-settings/route.ts`                     | PUT    | Admin-only — gated by isSuperAdmin inside handler.                                |
| `src/app/api/v1/admin/ocr-settings/test/route.ts`                | POST   | Admin-only — gated by isSuperAdmin inside handler.                                |
| `src/app/api/v1/admin/queues/[queueName]/[jobId]/retry/route.ts` | POST   | Admin-only — gated by isSuperAdmin inside handler.                                |
| `src/app/api/v1/admin/queues/[queueName]/[jobId]/route.ts`       | DELETE | Admin-only — gated by isSuperAdmin inside handler.                                |
| `src/app/api/v1/admin/queues/[queueName]/route.ts`               | GET    | Admin-only — gated by isSuperAdmin inside handler.                                |
| `src/app/api/v1/admin/queues/route.ts`                           | GET    | Admin-only — gated by isSuperAdmin inside handler.                                |
| `src/app/api/v1/admin/users/options/route.ts`                    | GET    | Admin-only — gated by isSuperAdmin inside handler.                                |
| `src/app/api/v1/ai/email-draft/[jobId]/route.ts`                 | GET    | TODO: needs ai:\* permission catalog entry. Currently allow-listed.               |
| `src/app/api/v1/ai/email-draft/route.ts`                         | POST   | TODO: needs ai:\* permission catalog entry. Currently allow-listed.               |
| `src/app/api/v1/ai/interest-score/bulk/route.ts`                 | GET    | TODO: needs ai:\* permission catalog entry. Currently allow-listed.               |
| `src/app/api/v1/ai/interest-score/route.ts`                      | GET    | TODO: needs ai:\* permission catalog entry. Currently allow-listed.               |
| `src/app/api/v1/alerts/[id]/acknowledge/route.ts`                | POST   | Alerts are user-scoped; port-filtered via auth context.                           |
| `src/app/api/v1/alerts/[id]/dismiss/route.ts`                    | POST   | Alerts are user-scoped; port-filtered via auth context.                           |
| `src/app/api/v1/alerts/count/route.ts`                           | GET    | Alerts are user-scoped; port-filtered via auth context.                           |
| `src/app/api/v1/alerts/route.ts`                                 | GET    | Alerts are user-scoped; port-filtered via auth context.                           |
| `src/app/api/v1/berth-reservations/[id]/route.ts`                | PATCH  | TODO: PATCH should map to reservations:edit (not currently in catalog).           |
| `src/app/api/v1/currency/convert/route.ts`                       | POST   | Currency reference data; port-scoped, no PII.                                     |
| `src/app/api/v1/currency/rates/refresh/route.ts`                 | POST   | TODO: gate with admin:manage_settings — currently allow-listed.                   |
| `src/app/api/v1/currency/rates/route.ts`                         | GET    | Currency reference data; port-scoped, no PII.                                     |
| `src/app/api/v1/custom-fields/[entityId]/route.ts`               | GET    | TODO: needs custom_fields:\* permission. PUT path internally validated.           |
| `src/app/api/v1/custom-fields/[entityId]/route.ts`               | PUT    | TODO: needs custom_fields:\* permission. PUT path internally validated.           |
| `src/app/api/v1/expenses/export/parent-company/route.ts`         | POST   | Internally gated by isSuperAdmin inside the handler.                              |
| `src/app/api/v1/me/route.ts`                                     | GET    | Self-endpoint — auth is sufficient.                                               |
| `src/app/api/v1/me/route.ts`                                     | PATCH  | Self-endpoint — auth is sufficient.                                               |
| `src/app/api/v1/notifications/[notificationId]/route.ts`         | PATCH  | User-scoped notifications — caller is the resource owner.                         |
| `src/app/api/v1/notifications/preferences/route.ts`              | GET    | User-scoped notifications — caller is the resource owner.                         |
| `src/app/api/v1/notifications/preferences/route.ts`              | PUT    | User-scoped notifications — caller is the resource owner.                         |
| `src/app/api/v1/notifications/read-all/route.ts`                 | POST   | User-scoped notifications — caller is the resource owner.                         |
| `src/app/api/v1/notifications/route.ts`                          | GET    | User-scoped notifications — caller is the resource owner.                         |
| `src/app/api/v1/notifications/unread-count/route.ts`             | GET    | User-scoped notifications — caller is the resource owner.                         |
| `src/app/api/v1/saved-views/[id]/route.ts`                       | PATCH  | User-self saved views — caller is the resource owner.                             |
| `src/app/api/v1/saved-views/[id]/route.ts`                       | DELETE | User-self saved views — caller is the resource owner.                             |
| `src/app/api/v1/saved-views/route.ts`                            | GET    | User-self saved views — caller is the resource owner.                             |
| `src/app/api/v1/saved-views/route.ts`                            | POST   | User-self saved views — caller is the resource owner.                             |
| `src/app/api/v1/search/recent/route.ts`                          | GET    | Port-scoped search — results filtered by auth context (resources have own perms). |
| `src/app/api/v1/search/route.ts`                                 | GET    | Port-scoped search — results filtered by auth context (resources have own perms). |
| `src/app/api/v1/settings/feature-flag/route.ts`                  | GET    | Public read of feature-flag bool — no PII; auth is sufficient.                    |
| `src/app/api/v1/tags/options/route.ts`                           | GET    | Tags are cross-cutting reference data; port-scoped via auth.                      |
| `src/app/api/v1/tags/route.ts`                                   | GET    | Tags are cross-cutting reference data; port-scoped via auth.                      |
| `src/app/api/v1/users/me/preferences/route.ts`                   | GET    | User-self preferences — caller is the resource owner.                             |
| `src/app/api/v1/users/me/preferences/route.ts`                   | PATCH  | User-self preferences — caller is the resource owner.                             |
