NEXUS
APIs

MiStorage APIs

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.

Environments

EnvironmentBase URLNotes
Local developmenthttp://localhost:5179dotnet run (http profile). The https profile adds https://localhost:7206.
Interactive reference/scalar/v1Scalar UI over the OpenAPI document at /openapi/v1.json. Development only.
ProductionAzure App ServiceReached by the MiStorage frontend; auth calls go through the frontend's same-origin relay (see below).

Response envelope

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.

ApiResponse<T>
{
  "success": true,
  "message": "Success",
  "data": { ... },
  "errors": null
}

Authentication

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.

  • Send the access token as Authorization: Bearer <token>.
  • A refresh token is set as an HTTP-only cookie on login and read back by the audience's refresh endpoint. Refresh tokens are hashed at rest and rotated on every use.
  • Each audience has its own refresh-token table (RefreshTokens, TenantRefreshTokens, RenterRefreshTokens), so a cookie from one can never be presented to another.
  • Renter identity is always read from the token's sub claim, never from a route or body parameter.
Renter session policy (RenterAuth section, all keys have defaults)
KeyDefaultWhat it does
RememberMeDays30"Remember me" ticked: a persistent cookie with a rolling window that each rotation renews.
RememberMeAbsoluteDays180Ceiling on a remembered session, counted from sign-in, that no activity extends.
SessionHours12"Remember me" unticked: a session cookie, plus a server-side backstop that does not roll.
RotationGraceSeconds30How long a just-rotated token stays acceptable to the browser that rotated it — covers second-tab and reload-mid-flight races.
MaxConcurrentSessions10Live sessions per renter. At the ceiling the idlest is signed out, never the one signing in.
RevokedRetentionDays2How long a revoked row survives. While it exists a replay is recognised as theft; once swept it is merely unknown.

Rate limits

Per-IP budgets, grouped so one surface can never eat another's allowance. First matching rule wins.

BucketPathsBudget
auth/api/auth/* and /api/tenant-auth/* login, register, forgot / reset password10 / min
renter-auth/api/renter-auth/* login, register, verify, resend, forgot / reset password10 / min
rental/api/rental/start, /api/rental/quote20 / min
public/api/public/* — the anonymous storefront120 / min
security-reports/api/security/* — CSP and integrity reports60 / 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.

The SaaS feature flag

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).

Endpoint reference

Grouped by audience. Paths are relative to the API base URL; {id}-style segments are GUIDs unless noted.

Public storefront

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.

EndpointAuthNotes
GETapi/public/{subdomain}AnonymousLocation profile plus the size guide, annotated with live availability. The one call a storefront landing page needs.
GETapi/public/{subdomain}/unitsAnonymousA page of available units. Query: category, climateControlled, sort (rate, rate_desc, size, size_desc, name), page, pageSize (default 24, max 100).
GETapi/public/{subdomain}/units/{id}AnonymousOne unit, for the detail view and the checkout's confirm step.

Renter authentication

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.

EndpointAuthNotes
POSTapi/renter-auth/registerAnonymousCreate a renter account at a location.
POSTapi/renter-auth/verify-emailAnonymousConfirm the address with the emailed token.
POSTapi/renter-auth/resend-verificationAnonymousSend a fresh verification link.
POSTapi/renter-auth/forgot-passwordAnonymousStart password recovery. Uniform response regardless of whether the address exists.
POSTapi/renter-auth/reset-passwordAnonymousFinish password recovery with the emailed token.
POSTapi/renter-auth/loginAnonymousReturns an access token and sets the refresh cookie. rememberMe selects the persistent policy.
POSTapi/renter-auth/refreshAnonymousCookie-authenticated. Rotates the refresh token and returns a new access token.
POSTapi/renter-auth/revokeAnonymousCookie-authenticated sign-out for this session only.
POSTapi/renter-auth/revoke-allRenter tokenSign out every session for this renter.
GETapi/renter-auth/profileRenter tokenThe renter's own profile.
PUTapi/renter-auth/profileRenter tokenUpdate the renter's own profile.
GETapi/renter-auth/meRenter tokenWho am I, from the token.

Rental flow

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.

EndpointAuthNotes
GETapi/rental/quote/{subdomain}/{unitId}AnonymousWhat this unit will cost before a card is entered. Optional startDate query. Includes which application fields the location asks for.
GETapi/rental/payment-config/{subdomain}AnonymousWhat the browser needs to tokenize a card at this location.
POSTapi/rental/startRenter tokenCharge 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/rental/my-leasesRenter tokenThe signed-in renter's leases.
GETapi/rental/my-leases/{leaseId}Renter tokenOne lease in full — the account's unit page.
PUTapi/rental/my-leases/{leaseId}/move-out-noticeRenter tokenGive notice to move out. Not a cancellation: the lease stays active and rent keeps accruing until staff end it.
DELETEapi/rental/my-leases/{leaseId}/move-out-noticeRenter tokenWithdraw a notice that has not been acted on yet.
GETapi/rental/my-invoicesRenter tokenThe signed-in renter's rent invoices.

Renter account

What a signed-in renter does with their own account. Every action reads the renter id from the token.

EndpointAuthNotes
GETapi/renter-account/addressesRenter tokenThe address book.
POSTapi/renter-account/addressesRenter tokenAdd an address.
PUTapi/renter-account/addresses/{addressId}Renter tokenEdit an address.
PUTapi/renter-account/addresses/{addressId}/defaultRenter tokenMake this the default address (demotes the previous one).
DELETEapi/renter-account/addresses/{addressId}Renter tokenRemove an address.

Location staff authentication

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.

EndpointAuthNotes
POSTapi/tenant-auth/registerStaff tokenCreate 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/tenant-auth/loginAnonymousReturns an access token and sets the refresh cookie.
POSTapi/tenant-auth/enterHQ 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/tenant-auth/refreshAnonymousCookie-authenticated token rotation.
POSTapi/tenant-auth/revokeAnonymousCookie-authenticated sign-out.
GETapi/tenant-auth/meStaff tokenThe signed-in staff member and their location.
POSTapi/tenant-auth/forgot-passwordAnonymousStart password recovery.
POSTapi/tenant-auth/reset-passwordAnonymousFinish password recovery.
GETapi/tenant-auth/check-subdomainAnonymousIs this subdomain taken?
POSTapi/tenant-auth/self-registerAnonymousSaaS onlyCompany self-signup with a card.
GETapi/tenant-auth/payment-configAnonymousSaaS onlyPlatform merchant config for subscription checkout.
GETapi/tenant-auth/plan/{customerId}Staff tokenSaaS onlyThe company's current subscription plan.
POSTapi/tenant-auth/plan/{customerId}/upgradeStaff tokenSaaS onlyChange subscription plan.

Location console

Everything a location's staff do day to day. {customerId} is the location, and must match the one in the caller's token.

EndpointAuthNotes
GETapi/tenant/{customerId}/dashboardStaff tokenThe location's dashboard figures.
GETapi/tenant/{customerId}/unitsStaff tokenAll units at the location.
GETapi/tenant/{customerId}/units/size-categoriesStaff tokenThe size guide: every category with label, blurb, icon and typical dimensions.
GETapi/tenant/{customerId}/units/{id}Staff tokenOne unit.
POSTapi/tenant/{customerId}/unitsStaff tokenCreate a unit. Size category derives from the dimensions; status starts as Available. Names are unique within a location.
PUTapi/tenant/{customerId}/units/{id}Staff tokenEdit a unit: name, rate, dimensions, climate control, out-of-service, and its own application-field overrides.
PATCHapi/tenant/{customerId}/units/{id}/assignStaff tokenAssign a renter to a unit (staff-side rental).
PATCHapi/tenant/{customerId}/units/{id}/vacateStaff tokenReturn a unit to Available.
DELETEapi/tenant/{customerId}/units/{id}Staff tokenDelete a unit.
GETapi/tenant/{customerId}/rentersStaff tokenRenters at this location.
GETapi/tenant/{customerId}/renters/{id}Staff tokenOne renter.
POSTapi/tenant/{customerId}/rentersStaff tokenCreate a renter record on the renter's behalf.
PUTapi/tenant/{customerId}/renters/{id}Staff tokenEdit a renter.
DELETEapi/tenant/{customerId}/renters/{id}Staff tokenDelete a renter.
GETapi/tenant/{customerId}/leasesStaff tokenAll leases at the location, optionally filtered by status.
POSTapi/tenant/{customerId}/leases/{leaseId}/endStaff tokenMove-out: ends the lease, returns the unit to Available, stops further rent, and only if explicitly asked writes off what is owed.
GETapi/tenant/{customerId}/settingsStaff tokenThe location's own settings: the phone number renters see, and which application fields it asks for.
PUTapi/tenant/{customerId}/settingsStaff tokenUpdate those settings. Nothing that decides money, renames or re-subdomains the location.
GETapi/tenant/{customerId}/notifications/templatesStaff tokenNotification templates.
POSTapi/tenant/{customerId}/notifications/templatesStaff tokenCreate a template.
PUTapi/tenant/{customerId}/notifications/templates/{id}Staff tokenEdit a template.
PATCHapi/tenant/{customerId}/notifications/templates/{id}/toggleStaff tokenEnable / disable a template.
DELETEapi/tenant/{customerId}/notifications/templates/{id}Staff tokenDelete a template.
POSTapi/tenant/{customerId}/notifications/recipientsStaff tokenResolve a recipient selection to renters.
POSTapi/tenant/{customerId}/notifications/previewStaff tokenRender a template against a recipient.
POSTapi/tenant/{customerId}/notifications/sendStaff tokenSend a batch.
GETapi/tenant/{customerId}/notifications/historyStaff tokenSent notifications.
GETapi/tenant/{customerId}/notifications/{id}Staff tokenOne notification.
GETapi/tenant/{customerId}/notifications/unread-countStaff tokenBadge count.

HQ authentication

Sign-in for system users behind /super-admin.

EndpointAuthNotes
POSTapi/auth/registerAnonymousCreate a system user. Open registration here is a known gap under the single-operator model and is tracked in the configuration checklist.
POSTapi/auth/loginAnonymousReturns an access token and sets the refresh cookie.
POSTapi/auth/refreshAnonymousCookie-authenticated token rotation.
POSTapi/auth/revokeAnonymousCookie-authenticated sign-out.
GETapi/auth/meHQ tokenThe signed-in user with role and permissions.
POSTapi/auth/forgot-passwordAnonymousStart password recovery.
POSTapi/auth/reset-passwordAnonymousFinish password recovery.

HQ — locations and their staff

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.

EndpointAuthNotes
GETapi/customerHQ tokenList locations. Query: search, status, page, pageSize, sortBy, sortOrder.
GETapi/customer/{id}HQ tokenOne location.
POSTapi/customerHQ tokenCreate a location. One contact email may name several locations.
PUTapi/customer/{id}HQ tokenEdit a location.
PATCHapi/customer/{id}/statusHQ tokenChange status. Body: { status, reason }.
POSTapi/customer/{id}/banHQ tokenBan a location. Body: { reason, banUntil }.
POSTapi/customer/{id}/unbanHQ tokenLift a ban.
DELETEapi/customer/{id}HQ tokenDelete a location.
GETapi/customer/{id}/staffHQ tokenThe location's staff logins.
POSTapi/customer/{id}/staffHQ tokenAdd a staff login. Body: { firstName, lastName, email, password, confirmPassword, role? }.
PATCHapi/customer/{id}/staff/{staffId}/activeHQ tokenActivate / deactivate a login. Body: { isActive }.
PUTapi/customer/{id}/staff/{staffId}/passwordHQ tokenSet a new password. Body: { newPassword, confirmPassword }.

HQ — users, roles and permissions

System users and the RBAC behind them.

EndpointAuthNotes
GETapi/userHQ tokenList system users.
GETapi/user/{id}HQ tokenOne user.
POSTapi/userHQ tokenCreate a user.
PUTapi/user/{id}HQ tokenEdit a user.
PATCHapi/user/{id}/toggle-activeHQ tokenActivate / deactivate.
DELETEapi/user/{id}HQ tokenDelete a user.
GETapi/user/rolesHQ tokenRoles (Super Admin / Admin / Manager) and their permissions.
GETapi/user/permissionsHQ tokenAll permissions.
PUTapi/user/roles/{roleId}/permissionsHQ tokenReplace a role's permission set.

HQ — dashboard, protection plans, tasks, settings

The rest of the owner-level console.

EndpointAuthNotes
GETapi/dashboard/statsHQ tokenOperator figures: occupancy, rent roll, arrears. (Under SaaS: MRR, subscriptions, churn.)
GETapi/protectionPlansHQ tokenRenter protection plans.
GETapi/protectionPlans/{id}HQ tokenOne plan.
POSTapi/protectionPlansHQ tokenCreate a plan.
PUTapi/protectionPlans/{id}HQ tokenEdit a plan.
PATCHapi/protectionPlans/{id}/toggleHQ tokenEnable / disable.
DELETEapi/protectionPlans/{id}HQ tokenDelete a plan.
GETapi/taskHQ tokenThe HQ task board.
GETapi/task/{id}HQ tokenOne task.
POSTapi/taskHQ tokenCreate a task.
PUTapi/task/{id}HQ tokenEdit a task.
PATCHapi/task/{id}/moveHQ tokenMove a task between columns.
DELETEapi/task/{id}HQ tokenDelete a task.
GETapi/settingsHQ tokenSystem settings, including the installation-wide application-field defaults.
PUTapi/settingsHQ tokenUpdate system settings.

HQ — notifications

Templates, batches, history and replies at owner level.

EndpointAuthNotes
GETapi/notifications/templatesHQ tokenTemplates.
POSTapi/notifications/templatesHQ tokenCreate a template.
PUTapi/notifications/templates/{id}HQ tokenEdit a template.
PATCHapi/notifications/templates/{id}/toggleHQ tokenEnable / disable a template.
DELETEapi/notifications/templates/{id}HQ tokenDelete a template.
GETapi/notifications/companiesHQ tokenLocations available as recipients.
POSTapi/notifications/recipientsHQ tokenResolve a recipient selection.
POSTapi/notifications/previewHQ tokenRender a template.
POSTapi/notifications/sendHQ tokenSend a batch.
GETapi/notifications/historyHQ tokenSent notifications.
GETapi/notifications/{id}HQ tokenOne notification with its thread.
POSTapi/notifications/{id}/replyHQ tokenReply on a thread.
GETapi/notifications/unread-countHQ tokenBadge count.
PATCHapi/notifications/{id}/readHQ tokenMark as read.

HQ — payment gateway operations

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.

EndpointAuthNotes
GETapi/payment-admin/processing-profilesHQ tokenReadiness: confirm the merchant reads transaction-ready on the gateway.
POSTapi/payment-admin/custom-fields/invoice-numberHQ tokenCreate the InvoiceNumber custom field on the merchant so every charge carries the invoice number. Safe to repeat.
POSTapi/payment-admin/boardHQ tokenSaaS onlyOne-time reseller boarding of a location's own merchant.
GETapi/payment/subscription/{subscriptionId}/autopayHQ tokenAutopay enrolment state for a subscription.
POSTapi/payment/subscription/{subscriptionId}/autopayHQ tokenEnrol / change autopay.
POSTapi/payment/reconcileHQ tokenPoll-based reconciliation against the gateway.

Archived — SaaS billing

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.

EndpointAuthNotes
GETapi/subscriptionplanAnonymousSaaS onlyPublic plan list.
GETapi/subscriptionplan/{id}HQ tokenSaaS onlyOne plan.
POSTapi/subscriptionplanHQ tokenSaaS onlyCreate a plan.
PUTapi/subscriptionplan/{id}HQ tokenSaaS onlyEdit a plan.
PATCHapi/subscriptionplan/{id}/toggleHQ tokenSaaS onlyEnable / disable.
DELETEapi/subscriptionplan/{id}HQ tokenSaaS onlyDelete a plan.
GETapi/invoiceHQ tokenSaaS onlyPlatform invoices.
GETapi/invoice/summaryHQ tokenSaaS onlyBilling summary (SQL-side aggregation).
GETapi/invoice/customer/{customerId}HQ tokenSaaS onlyInvoices for one company.
GETapi/invoice/{id}HQ tokenSaaS onlyOne invoice.
POSTapi/invoiceHQ tokenSaaS onlyCreate an invoice.
PUTapi/invoice/{id}HQ tokenSaaS onlyEdit an invoice.
POSTapi/invoice/{id}/paymentHQ tokenSaaS onlyRecord a payment.
PATCHapi/invoice/{id}/mark-overdueHQ tokenSaaS onlyMark overdue.
PATCHapi/invoice/{id}/cancelHQ tokenSaaS onlyCancel.
POSTapi/invoice/generateHQ tokenSaaS onlyGenerate the period's invoices.
POSTapi/invoice/{id}/chargeHQ tokenSaaS onlyCharge the card on file.
POSTapi/invoice/{id}/refundHQ tokenSaaS onlyRefund.
GETapi/payment-method/customer/{customerId}HQ tokenSaaS onlyA company's cards on file.
POSTapi/payment-method/customer/{customerId}HQ tokenSaaS onlyAdd a tokenized card.
PATCHapi/payment-method/customer/{customerId}/{paymentMethodId}/defaultHQ tokenSaaS onlyMake default.
DELETEapi/payment-method/customer/{customerId}/{paymentMethodId}HQ tokenSaaS onlyRemove a card.

Security reporting

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.

EndpointAuthNotes
POSTapi/security/csp-reportAnonymousCSP violation reports — accepts the legacy csp-report envelope and the Reporting API array.
POSTapi/security/page-integrityAnonymousPage-integrity findings from the payment pages. Logged at Error.