---
title: Endpoints
summary: Endpoint catalog for the REST API, with per-controller detail pages.
status: draft
owner: Premium Systems Team
last_reviewed: 2026-10-21
---

# Endpoints

`SAIF.Premium.Api` exposes eight routes across three controllers and one minimal
API group: premium reads and adjustments, class-code rates, an administrative
batch recalculation, and the current rate table. Seven of the eight routes are
distinct actions; `ClassCodesController` serves a single action at both `v1` and
`v2`, so it appears twice. Controller routes are attribute routes that combine a
class-level `[Route]` template with the action's `[Http<Verb>]` template, and
the version segment is either substituted from `[ApiVersion]` or written
literally as `v1`.

This page is the endpoint catalog and hub. Each controller's endpoints are documented in detail on its own page under `endpoints/`.

## Route composition

| Aspect | Finding |
| --- | --- |
| Routing mode | Attribute routes (`[ApiController]` + `[Route]`) for the three controllers; Minimal API (`MapGet`) for `RateTableEndpoints` |
| Controller route template | `[Route("v{version:apiVersion}/[controller]")]` on `PremiumsController` and `ClassCodesController`; `[Route("v1/[controller]/[action]")]` on `AdminController` |
| Versioning | URL segment `{version:apiVersion}` from `[ApiVersion("1.0")]` on `PremiumsController` and from `[ApiVersion("1.0")]` plus `[ApiVersion("2.0")]` on `ClassCodesController`; literal `v1` prefix on `AdminController` and `RateTableEndpoints` - see [Hosting pipeline](hosting-pipeline.md#api-versioning) |
| Base path | `/` (IIS application path not determined from source) |

A resolved route is the controller's `[Route]` template with its tokens
substituted, followed by the action's template. `[controller]` is replaced by the
controller name without the `Controller` suffix, keeping its casing, and
`{version:apiVersion}` by the concrete version. `PremiumsController` carries
`[Route("v{version:apiVersion}/[controller]")]` and `[ApiVersion("1.0")]`, so
`[HttpGet("{policyNumber}/history")]` on `GetPremiumHistory` resolves to
`GET /v1/Premiums/{policyNumber}/history`. `ClassCodesController` declares
versions `1.0` and `2.0`, so its one action resolves to both
`GET /v1/ClassCodes/{classCode}/rate` and `GET /v2/ClassCodes/{classCode}/rate`.

`AdminController` writes the version literally and uses the `[action]` token:
`[Route("v1/[controller]/[action]")]` with a bare `[HttpPost]` on
`RecalculateBatch` resolves to `POST /v1/Admin/RecalculateBatch`. The minimal
API group composes its `MapGroup` prefix with the `MapGet` pattern:
`MapGroup("/v1/RateTables")` plus `MapGet("/current", ...)` resolves to
`GET /v1/RateTables/current`.

`GET /v1/Premiums/estimate` and `GET /v1/Premiums/{policyNumber}` have the same
number of segments. ASP.NET Core ranks the literal segment above the parameter,
so `estimate` is never bound as a policy number.

## Controller catalog

| Controller | Route template | API version | Base | Endpoints | Documentation |
| --- | --- | --- | --- | --- | --- |
| `PremiumsController` | `v{version:apiVersion}/[controller]` | `1.0` | `ControllerBase` | 4 | Details |
| `ClassCodesController` | `v{version:apiVersion}/[controller]` | `1.0`, `2.0` | `ControllerBase` | 2 | Details |
| `AdminController` | `v1/[controller]/[action]` | - | `ControllerBase` | 1 | Details |
| `RateTableEndpoints` (minimal API group) | `MapGroup("/v1/RateTables")` | - | - | 1 | Details |

## Endpoint catalog

| Endpoint | Controller | Action | Auth | Success | Errors | Rules |
| --- | --- | --- | --- | --- | --- | --- |
| `GET /v1/Premiums/{policyNumber}` | `PremiumsController` | `GetPremium` | PremiumReadResource | `200 OK` | `400`, `404`, `500` | BR-001, BR-004 |
| `GET /v1/Premiums/{policyNumber}/history` | `PremiumsController` | `GetPremiumHistory` | PremiumReadResource | `200 OK` | `400`, `404`, `500` | BR-003 |
| `GET /v1/Premiums/estimate` | `PremiumsController` | `EstimatePremium` | PremiumReadResource | `200 OK` | `400`, `500` | BR-001, BR-004 |
| `POST /v1/Premiums/{policyNumber}/adjustments` | `PremiumsController` | `AddAdjustment` | PremiumAdjustResource | `201 Created` | `400`, `404`, `409`, `500` | BR-002 |
| `GET /v1/ClassCodes/{classCode}/rate` | `ClassCodesController` | `GetClassCodeRate` | PremiumReadResource | `200 OK` | `400`, `404`, `500` | BR-004 |
| `GET /v2/ClassCodes/{classCode}/rate` | `ClassCodesController` | `GetClassCodeRate` | PremiumReadResource | `200 OK` | `400`, `404`, `500` | BR-004 |
| `POST /v1/Admin/RecalculateBatch` | `AdminController` | `RecalculateBatch` | PremiumAdminResource | `202 Accepted` | `400`, `409`, `500` | BR-005 |
| `GET /v1/RateTables/current` | `RateTableEndpoints` | `GetCurrentRateTable` | None in code | `200 OK` | `500` | - |

The Errors column lists the statuses declared by `[ProducesResponseType]` and
thrown by the service. A caller that fails the `[SecurityApiAuthorize]` check
receives a response produced by the opaque security package; its status is not
visible in source and is not listed.

`RateTableEndpoints` carries no authorization attribute and no
`RequireAuthorization()` call, so `GET /v1/RateTables/current` is reachable by
any caller that can reach the service. See
Authentication and authorization for the
consequence.

## Request flow diagram

The following diagram visualizes how a request reaches a handling action.

```mermaid
flowchart TD
    REQ[Client request] --> PIPE["Middleware pipeline<br/>see hosting-pipeline.md"]
    PIPE --> ROUTE{Route match}
    ROUTE -->|GET /v1/Premiums/policyNumber| A["PremiumsController.GetPremium"]
    ROUTE -->|POST /v1/Premiums/policyNumber/adjustments| B["PremiumsController.AddAdjustment"]
    ROUTE -->|GET /v2/ClassCodes/classCode/rate| C["ClassCodesController.GetClassCodeRate"]
    ROUTE -->|POST /v1/Admin/RecalculateBatch| D["AdminController.RecalculateBatch"]
    ROUTE -->|GET /v1/RateTables/current| E["RateTableEndpoints.GetCurrentRateTable"]
    ROUTE -->|No match| NF[404 Not Found]
    A --> AUTH{SecurityApiAuthorize check}
    B --> AUTH
    C --> AUTH
    D --> AUTH
    AUTH -->|No| DENY[Denied by security package]
    AUTH -->|Yes| BIND{Model valid?}
    BIND -->|No| BAD[400 ValidationProblemDetails]
    BIND -->|Yes| OP[Premium service]
    OP --> OK[200, 201, or 202]
    OP --> ERR[404 or 409 from service exceptions]
    E --> OK
```

`RateTableEndpoints.GetCurrentRateTable` skips the authorization check because
the group declares none. The diagram shows one representative route per action;
the remaining `PremiumsController` routes follow the same path as `GetPremium`.

## Endpoints by operation

Endpoints that delegate to a documented processing workflow link to it here.

| Endpoint | Operation |
| --- | --- |
| `POST /v1/Admin/RecalculateBatch` | OP-001 |

## Related documentation
- API index
- Overview
- Architecture
- [Hosting pipeline](hosting-pipeline.md)
- Authentication and authorization
- Dependencies
- [Configuration](configuration.md)
- Operations and processes
- Business rules
- Data model
- Operational runbook
- Tech debt

## Sources
- `src/SAIF.Premium.Api/Controllers/PremiumsController.cs`
- `src/SAIF.Premium.Api/Controllers/ClassCodesController.cs`
- `src/SAIF.Premium.Api/Controllers/AdminController.cs`
- `src/SAIF.Premium.Api/Endpoints/RateTableEndpoints.cs`
- `src/SAIF.Premium.Api/Program.cs`
- `src/SAIF.Premium.Api/Startup.cs`
