HTTP Endpoints

PowerPortalsPro publishes a JSON-over-HTTP surface under /api/* that any single-page application can call — the in-box Blazor WebAssembly client uses it to back IPowerPortalsProService and IAuthService, but the same endpoints are equally usable from a React, Vue, or vanilla-JS front end hosted alongside (or even cross-origin from) the server.

Who this is for

If you're building a Blazor app and consuming the framework via IPowerPortalsProService, you do not need to call these endpoints directly — the client implementation does it for you. This page documents the surface for teams writing a non-Blazor SPA, or wiring up a custom HTTP client, against the same server.

Enabling the endpoints

UsePowerPortalsProWebServer wires the data endpoints (table CRUD, FetchXML, metadata, files, localization, admin). MapAuthEndpoints<TUser> wires the SPA-facing auth surface — call it explicitly if your host needs cookie-auth from a SPA.

// Program.cs — server pipeline
app.UsePowerPortalsProWebServer();
app.MapAuthEndpoints<PortalUser>();

Both calls are no-ops when their feature isn't in use, so wire them once in Program.cs regardless of which interactivity mode the host runs.

The Routes type — single source of truth

PowerPortalsPro.Web.Common.Routes exposes every endpoint path as a strongly-typed property. Both the in-box client and any C#-based external SPA should reference these constants instead of hand-writing strings, so a server-side rename surfaces as a compile error rather than a runtime 404. JavaScript/TypeScript clients will of course need to inline the paths, but the C# side of Routes remains the canonical reference for what those paths are.

// Anywhere — both PowerPortalsPro.Web.Client and external C# SPAs.
var loginUrl  = Routes.Api.Auth.Login;            // "/api/auth/login"
var meUrl     = Routes.Api.Auth.Me;               // "/api/auth/me"
var createUrl = Routes.Api.Tables.GetCreateRoute("contact");
var fetchUrl  = Routes.Api.GetRetrieveMultipleRoute(fetchXml);

Routes.Api covers the data and admin endpoints; Routes.Api.Auth covers sign-in / sign-up; Routes.Api.Auth.Manage covers the signed-in user's account-management operations.

Data endpoints

Backed by UsePowerPortalsProWebServer. Every read and write applies the consumer's ITablePermissionHandler / ITableRecordPermissionHandler interceptors, plus any IFetchXmlBuilderInterceptor registered for the table.

  • POST /api/table/{table}, GET /api/table/{table}/{id}, PATCH /api/table/{table}/{id}, DELETE /api/table/{table}/{id} — single-record CRUD. The ?columns= query parameter on GET narrows the projection.
  • GET /api/retrieveMultiple?fetchXml=… — runs an arbitrary FetchXML query. Use Routes.Api.GetRetrieveMultipleRoute(fetchXml) to encode the URL correctly.
  • POST /api/executeMultiple?returnResponses=true|false — body is a JSON array of OrganizationRequest. Wrapping multiple ops in one call gives them transactional semantics; if any single request fails the whole batch rolls back.
  • GET /api/tableMetadata/{table}, GET /api/viewMetadata/{viewIdOrTable} — column / relationship / view metadata. Cached server-side, so repeat calls are cheap.
  • GET /api/files/{table}/{recordId}/{column}?includeData=true|false for single-file read; POST /api/files/{table}/{column}/batch?includeData=… with a JSON array of record IDs for batch reads (used by the FileGrid's Download Selected button).
  • GET /api/localizedStrings/{culture} for a full dump; POST /api/localizedStrings/{culture}/keys with a JSON list of keys / prefixes for narrow pre-fetches.
  • POST /api/caches/clear drops every server-side cache; POST /api/caches/{cacheName}/clear drops a single named cache; GET /api/caches lists the registered cache names. All three are gated by [Authorize(Roles = "SystemAdmin")]; only signed-in admins can call them.

Authentication endpoints

Backed by MapAuthEndpoints<TUser>. Cookie-based: a successful sign-in sets the standard ASP.NET Core Identity application cookie, and every subsequent call (data or auth) authenticates by presenting that cookie back to the server.

  • POST /api/auth/login, POST /api/auth/login/2fa, POST /api/auth/logout — credentials sign-in plus a second-factor follow-up if the account has 2FA enabled.
  • POST /api/auth/register, POST /api/auth/forgot-password, POST /api/auth/reset-password, POST /api/auth/confirm-email, POST /api/auth/resend-email-confirmation — full lifecycle for local accounts.
  • GET /api/auth/external-providers, GET /api/auth/external-login?provider=…, POST /api/auth/external-login/confirm, POST /api/auth/external-login/select — OAuth (Microsoft, Google, etc.). The external-login endpoint must be navigated to (not fetched) so the browser follows the OAuth redirect chain.
  • GET /api/auth/me — snapshot of the current principal: id, name, email, roles, backing table (contact vs. systemuser). Returns an anonymous shape (isAuthenticated: false) when no cookie is present rather than 401, so a SPA can call it on first paint without branching on status code.
  • /api/auth/manage/* — signed-in account management: profile, password, email change, 2FA enrollment / disable / recovery codes, linked external logins. Mirrors the operations the framework's classic /Account/Manage Razor pages performed via UserManager.
  • GET /api/auth/manage/personal-data, POST /api/auth/manage/personal-data/delete — GDPR-style export and account deletion.

Authentication model — cookies, not tokens

Auth is browser-cookie based. There is no JWT issuance step. A SPA calls /api/auth/login, the server sets the .AspNetCore.Identity.Application cookie on the response, and the browser attaches it on every subsequent request — including data calls under /api/table/*. From JavaScript this means fetch(..., { credentials: 'include' }) on every call (or axios.defaults.withCredentials = true); without it the cookie is dropped and the server returns 401.

// React / Vue / plain fetch — credentials: 'include' is required so the
// browser sends and stores the auth cookie issued by /api/auth/login.
const me = await fetch('/api/auth/me', { credentials: 'include' })
    .then(r => r.json());

if (me.isAuthenticated) {
    console.log(me.userName, me.roles);
}

Cross-origin SPAs

If the SPA and server are on different origins, the server must send Access-Control-Allow-Origin: <spa-origin> (not *) and Access-Control-Allow-Credentials: true, and the SPA must use credentials: 'include'. Same-origin hosting (the SPA served from the same site as the API) avoids the issue entirely.

Error responses

Failed calls return RFC 9457 application/problem+json. The in-box client implementation rehydrates the original CLR exception type from the problem details so server-side throws surface as the same exception on the client; for non-.NET SPAs, the type / title / detail / status fields are the standard handle.

See also

Related documentation:

  • IPowerPortalsProService — the C# wrapper around these endpoints — what your Blazor components inject when they don't need to issue raw HTTP themselves.
  • SystemUser sign-in — background on why /api/auth/me reports tableName: "systemuser" for some principals and tableName: "contact" for others.