One REST API serving three audiences. Every response uses the same envelope, every protected endpoint takes a short-lived bearer token, and refresh happens through an HTTP-only cookie.
| Environment | Base URL | Notes |
|---|---|---|
| Local development | http://localhost:5179 | dotnet run (http profile). The https profile adds https://localhost:7206. |
| Interactive reference | /scalar/v1 | Scalar UI over the OpenAPI document at /openapi/v1.json. Development only. |
| Production | Azure App Service | Reached by the MiStorage frontend; auth calls go through the frontend's same-origin relay (see below). |
The repository's mistorage-backend.http file carries ready-to-run requests for the Visual Studio / VS Code REST clients.
Every endpoint returns ApiResponse<T>: success, a human-readable message, the payload in data, and an optional errors list. Errors use the same shape with success: false.
{
"success": true,
"message": "Success",
"data": { ... },
"errors": null
}Access tokens are JWTs that expire after 15 minutes and are held in browser memory only. Each audience mints its own token with a userType claim (system, tenant or renter) and is validated by a matching policy; the tables below label each endpoint with the policy it requires.
Authorization: Bearer <token>.refresh endpoint. Refresh tokens are hashed at rest and rotated on every use.RefreshTokens, TenantRefreshTokens, RenterRefreshTokens), so a cookie from one can never be presented to another.sub claim, never from a route or body parameter.From the browser the API is cross-site, so a refresh cookie it sets is one the frontend's own middleware can never read. The frontend therefore relays auth calls server-side (/api/*-auth/* routes, POST only) and re-issues the cookie first-party. Bearer calls go direct. The relay may forward the real client address in X-Client-Ip, which the API honours only when X-Proxy-Secret matches Security:TrustedProxySecret.
| Key | Default | What it does |
|---|---|---|
RememberMeDays | 30 | "Remember me" ticked: a persistent cookie with a rolling window that each rotation renews. |
RememberMeAbsoluteDays | 180 | Ceiling on a remembered session, counted from sign-in, that no activity extends. |
SessionHours | 12 | "Remember me" unticked: a session cookie, plus a server-side backstop that does not roll. |
RotationGraceSeconds | 30 | How long a just-rotated token stays acceptable to the browser that rotated it — covers second-tab and reload-mid-flight races. |
MaxConcurrentSessions | 10 | Live sessions per renter. At the ceiling the idlest is signed out, never the one signing in. |
RevokedRetentionDays | 2 | How long a revoked row survives. While it exists a replay is recognised as theft; once swept it is merely unknown. |
Per-IP budgets, grouped so one surface can never eat another's allowance. First matching rule wins.
| Bucket | Paths | Budget |
|---|---|---|
auth | /api/auth/* and /api/tenant-auth/* login, register, forgot / reset password | 10 / min |
renter-auth | /api/renter-auth/* login, register, verify, resend, forgot / reset password | 10 / min |
rental | /api/rental/start, /api/rental/quote | 20 / min |
public | /api/public/* — the anonymous storefront | 120 / min |
security-reports | /api/security/* — CSP and integrity reports | 60 / min |
Renter auth is deliberately separate from staff auth: renters arrive in far greater numbers, and a shared allowance would let them exhaust the budget staff need to sign in. Refresh is unmetered on purpose.
Endpoints marked SaaS only below belong to the archived multi-tenant model. With Saas:Enabled off (the default) they answer 404 Not found to everyone, signed in or not. Three others change shape rather than disappear: POST api/tenant-auth/register becomes an administrator action, rent settles to the configured merchant regardless of per-location boarding, and GET api/dashboard/stats returns operator figures (occupancy, rent roll, arrears) instead of SaaS ones (MRR, subscriptions, churn).
Grouped by audience. Paths are relative to the API base URL; {id}-style segments are GUIDs unless noted.
Anonymous, read-only, GET only. Only live locations and Available units are visible. Responses are cacheable for 60 seconds. {subdomain} resolves a location by subdomain first, custom domain second.
| Endpoint | Auth | Notes |
|---|---|---|
GETapi | Anonymous | Location profile plus the size guide, annotated with live availability. The one call a storefront landing page needs. |
GETapi | Anonymous | A page of available units. Query: category, climateControlled, sort (rate, rate_desc, size, size_desc, name), page, pageSize (default 24, max 100). |
GETapi | Anonymous | One unit, for the detail view and the checkout's confirm step. |
Identity for storage customers. Accounts are scoped to one location. With RenterAuth:RequireEmailVerification on, registration returns no session and login is refused until the address is confirmed; with it off, registration signs the renter straight in.
| Endpoint | Auth | Notes |
|---|---|---|
POSTapi | Anonymous | Create a renter account at a location. |
POSTapi | Anonymous | Confirm the address with the emailed token. |
POSTapi | Anonymous | Send a fresh verification link. |
POSTapi | Anonymous | Start password recovery. Uniform response regardless of whether the address exists. |
POSTapi | Anonymous | Finish password recovery with the emailed token. |
POSTapi | Anonymous | Returns an access token and sets the refresh cookie. rememberMe selects the persistent policy. |
POSTapi | Anonymous | Cookie-authenticated. Rotates the refresh token and returns a new access token. |
POSTapi | Anonymous | Cookie-authenticated sign-out for this session only. |
POSTapi | Renter token | Sign out every session for this renter. |
GETapi | Renter token | The renter's own profile. |
PUTapi | Renter token | Update the renter's own profile. |
GETapi | Renter token | Who am I, from the token. |
The renter-facing half of renting a unit. Quotes are anonymous; anything touching money needs the Renter policy, and the location comes from the token.
| Endpoint | Auth | Notes |
|---|---|---|
GETapi | Anonymous | What this unit will cost before a card is entered. Optional startDate query. Includes which application fields the location asks for. |
GETapi | Anonymous | What the browser needs to tokenize a card at this location. |
POSTapi | Renter token | Charge the card (when the gateway is enabled), create the lease, take the unit off the market. First invoice is left outstanding when the gateway is off. |
GETapi | Renter token | The signed-in renter's leases. |
GETapi | Renter token | One lease in full — the account's unit page. |
PUTapi | Renter token | Give notice to move out. Not a cancellation: the lease stays active and rent keeps accruing until staff end it. |
DELETEapi | Renter token | Withdraw a notice that has not been acted on yet. |
GETapi | Renter token | The signed-in renter's rent invoices. |
What a signed-in renter does with their own account. Every action reads the renter id from the token.
| Endpoint | Auth | Notes |
|---|---|---|
GETapi | Renter token | The address book. |
POSTapi | Renter token | Add an address. |
PUTapi | Renter token | Edit an address. |
PUTapi | Renter token | Make this the default address (demotes the previous one). |
DELETEapi | Renter token | Remove an address. |
Sign-in for a location's staff. Under the single-operator model there is no self-service registration: accounts are created by HQ or by an existing colleague.
| Endpoint | Auth | Notes |
|---|---|---|
POSTapi | Staff token | Create a staff account. With SaaS off this requires an owner or colleague's token, takes the location from the token, and returns no session. Under SaaS it is anonymous self-service. |
POSTapi | Anonymous | Returns an access token and sets the refresh cookie. |
POSTapi | HQ token | "Manage Facility" from HQ: an administrator's bearer token yields a staff session as the location's Owner, without a second login. Audited. |
POSTapi | Anonymous | Cookie-authenticated token rotation. |
POSTapi | Anonymous | Cookie-authenticated sign-out. |
GETapi | Staff token | The signed-in staff member and their location. |
POSTapi | Anonymous | Start password recovery. |
POSTapi | Anonymous | Finish password recovery. |
GETapi | Anonymous | Is this subdomain taken? |
POSTapi | AnonymousSaaS only | Company self-signup with a card. |
GETapi | AnonymousSaaS only | Platform merchant config for subscription checkout. |
GETapi | Staff tokenSaaS only | The company's current subscription plan. |
POSTapi | Staff tokenSaaS only | Change subscription plan. |
Everything a location's staff do day to day. {customerId} is the location, and must match the one in the caller's token.
| Endpoint | Auth | Notes |
|---|---|---|
GETapi | Staff token | The location's dashboard figures. |
GETapi | Staff token | All units at the location. |
GETapi | Staff token | The size guide: every category with label, blurb, icon and typical dimensions. |
GETapi | Staff token | One unit. |
POSTapi | Staff token | Create a unit. Size category derives from the dimensions; status starts as Available. Names are unique within a location. |
PUTapi | Staff token | Edit a unit: name, rate, dimensions, climate control, out-of-service, and its own application-field overrides. |
PATCHapi | Staff token | Assign a renter to a unit (staff-side rental). |
PATCHapi | Staff token | Return a unit to Available. |
DELETEapi | Staff token | Delete a unit. |
GETapi | Staff token | Renters at this location. |
GETapi | Staff token | One renter. |
POSTapi | Staff token | Create a renter record on the renter's behalf. |
PUTapi | Staff token | Edit a renter. |
DELETEapi | Staff token | Delete a renter. |
GETapi | Staff token | All leases at the location, optionally filtered by status. |
POSTapi | Staff token | Move-out: ends the lease, returns the unit to Available, stops further rent, and only if explicitly asked writes off what is owed. |
GETapi | Staff token | The location's own settings: the phone number renters see, and which application fields it asks for. |
PUTapi | Staff token | Update those settings. Nothing that decides money, renames or re-subdomains the location. |
GETapi | Staff token | Notification templates. |
POSTapi | Staff token | Create a template. |
PUTapi | Staff token | Edit a template. |
PATCHapi | Staff token | Enable / disable a template. |
DELETEapi | Staff token | Delete a template. |
POSTapi | Staff token | Resolve a recipient selection to renters. |
POSTapi | Staff token | Render a template against a recipient. |
POSTapi | Staff token | Send a batch. |
GETapi | Staff token | Sent notifications. |
GETapi | Staff token | One notification. |
GETapi | Staff token | Badge count. |
Sign-in for system users behind /super-admin.
| Endpoint | Auth | Notes |
|---|---|---|
POSTapi | Anonymous | Create a system user. Open registration here is a known gap under the single-operator model and is tracked in the configuration checklist. |
POSTapi | Anonymous | Returns an access token and sets the refresh cookie. |
POSTapi | Anonymous | Cookie-authenticated token rotation. |
POSTapi | Anonymous | Cookie-authenticated sign-out. |
GETapi | HQ token | The signed-in user with role and permissions. |
POSTapi | Anonymous | Start password recovery. |
POSTapi | Anonymous | Finish password recovery. |
The Customer entity, which under the single-operator model means a location. The staff endpoints were added 2026-09-17 behind the location page's Staff tab.
| Endpoint | Auth | Notes |
|---|---|---|
GETapi | HQ token | List locations. Query: search, status, page, pageSize, sortBy, sortOrder. |
GETapi | HQ token | One location. |
POSTapi | HQ token | Create a location. One contact email may name several locations. |
PUTapi | HQ token | Edit a location. |
PATCHapi | HQ token | Change status. Body: { status, reason }. |
POSTapi | HQ token | Ban a location. Body: { reason, banUntil }. |
POSTapi | HQ token | Lift a ban. |
DELETEapi | HQ token | Delete a location. |
GETapi | HQ token | The location's staff logins. |
POSTapi | HQ token | Add a staff login. Body: { firstName, lastName, email, password, confirmPassword, role? }. |
PATCHapi | HQ token | Activate / deactivate a login. Body: { isActive }. |
PUTapi | HQ token | Set a new password. Body: { newPassword, confirmPassword }. |
System users and the RBAC behind them.
| Endpoint | Auth | Notes |
|---|---|---|
GETapi | HQ token | List system users. |
GETapi | HQ token | One user. |
POSTapi | HQ token | Create a user. |
PUTapi | HQ token | Edit a user. |
PATCHapi | HQ token | Activate / deactivate. |
DELETEapi | HQ token | Delete a user. |
GETapi | HQ token | Roles (Super Admin / Admin / Manager) and their permissions. |
GETapi | HQ token | All permissions. |
PUTapi | HQ token | Replace a role's permission set. |
The rest of the owner-level console.
| Endpoint | Auth | Notes |
|---|---|---|
GETapi | HQ token | Operator figures: occupancy, rent roll, arrears. (Under SaaS: MRR, subscriptions, churn.) |
GETapi | HQ token | Renter protection plans. |
GETapi | HQ token | One plan. |
POSTapi | HQ token | Create a plan. |
PUTapi | HQ token | Edit a plan. |
PATCHapi | HQ token | Enable / disable. |
DELETEapi | HQ token | Delete a plan. |
GETapi | HQ token | The HQ task board. |
GETapi | HQ token | One task. |
POSTapi | HQ token | Create a task. |
PUTapi | HQ token | Edit a task. |
PATCHapi | HQ token | Move a task between columns. |
DELETEapi | HQ token | Delete a task. |
GETapi | HQ token | System settings, including the installation-wide application-field defaults. |
PUTapi | HQ token | Update system settings. |
Templates, batches, history and replies at owner level.
| Endpoint | Auth | Notes |
|---|---|---|
GETapi | HQ token | Templates. |
POSTapi | HQ token | Create a template. |
PUTapi | HQ token | Edit a template. |
PATCHapi | HQ token | Enable / disable a template. |
DELETEapi | HQ token | Delete a template. |
GETapi | HQ token | Locations available as recipients. |
POSTapi | HQ token | Resolve a recipient selection. |
POSTapi | HQ token | Render a template. |
POSTapi | HQ token | Send a batch. |
GETapi | HQ token | Sent notifications. |
GETapi | HQ token | One notification with its thread. |
POSTapi | HQ token | Reply on a thread. |
GETapi | HQ token | Badge count. |
PATCHapi | HQ token | Mark as read. |
Infrequent PhoenixGate admin operations, not a runtime pipeline. Readiness stays available under the single-operator model; boarding does not, because rent settles to the client's own merchant and there is nobody left to board.
| Endpoint | Auth | Notes |
|---|---|---|
GETapi | HQ token | Readiness: confirm the merchant reads transaction-ready on the gateway. |
POSTapi | HQ token | Create the InvoiceNumber custom field on the merchant so every charge carries the invoice number. Safe to repeat. |
POSTapi | HQ tokenSaaS only | One-time reseller boarding of a location's own merchant. |
GETapi | HQ token | Autopay enrolment state for a subscription. |
POSTapi | HQ token | Enrol / change autopay. |
POSTapi | HQ token | Poll-based reconciliation against the gateway. |
Subscription plans, platform invoices and operator cards on file. All of these answer 404 while Saas:Enabled is false. Listed so the flag's effect is visible.
| Endpoint | Auth | Notes |
|---|---|---|
GETapi | AnonymousSaaS only | Public plan list. |
GETapi | HQ tokenSaaS only | One plan. |
POSTapi | HQ tokenSaaS only | Create a plan. |
PUTapi | HQ tokenSaaS only | Edit a plan. |
PATCHapi | HQ tokenSaaS only | Enable / disable. |
DELETEapi | HQ tokenSaaS only | Delete a plan. |
GETapi | HQ tokenSaaS only | Platform invoices. |
GETapi | HQ tokenSaaS only | Billing summary (SQL-side aggregation). |
GETapi | HQ tokenSaaS only | Invoices for one company. |
GETapi | HQ tokenSaaS only | One invoice. |
POSTapi | HQ tokenSaaS only | Create an invoice. |
PUTapi | HQ tokenSaaS only | Edit an invoice. |
POSTapi | HQ tokenSaaS only | Record a payment. |
PATCHapi | HQ tokenSaaS only | Mark overdue. |
PATCHapi | HQ tokenSaaS only | Cancel. |
POSTapi | HQ tokenSaaS only | Generate the period's invoices. |
POSTapi | HQ tokenSaaS only | Charge the card on file. |
POSTapi | HQ tokenSaaS only | Refund. |
GETapi | HQ tokenSaaS only | A company's cards on file. |
POSTapi | HQ tokenSaaS only | Add a tokenized card. |
PATCHapi | HQ tokenSaaS only | Make default. |
DELETEapi | HQ tokenSaaS only | Remove a card. |
Collection point for browser-side integrity signals on the payment pages. Anonymous by necessity; contents are a lead, never evidence. Bodies are size-capped and every field truncated before logging.
| Endpoint | Auth | Notes |
|---|---|---|
POSTapi | Anonymous | CSP violation reports — accepts the legacy csp-report envelope and the Reporting API array. |
POSTapi | Anonymous | Page-integrity findings from the payment pages. Logged at Error. |