Client API

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. This page is the full reference: every client-callable endpoint, with a sample request and response.

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.

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.

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.

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. Every example below includes it.

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.

The record shape

Every data endpoint that reads or writes a row exchanges the same TableRecord envelope. properties is a map of column logical name → a typed value object whose $type discriminator identifies the column kind. permissions is a bit-flag mask (Read 1, Create 2, Write 4, Delete 8, Append 16, AppendTo 32) describing what the current user may do with the row. On writes you only need to send the columns you're changing.

The $type values mirror the Dataverse attribute kind: 0 Boolean, 2 DateTime, 3 Decimal, 4 Double, 5 Integer, 6 Lookup, 8 Money, 11 Choice, 14 String, 15 Uniqueidentifier, 40 MultiSelectChoice, 41 File, 42 Image. Lookups add name + tableName; number, money and date values carry a Dataverse-formatted formattedValue.

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.

Endpoint reference

Each endpoint below shows the method and path, what it does, a sample request (as a browser fetch call), and a sample response. Path segments in {braces} are placeholders. Unless noted otherwise, a 2xx response is the success case and failures come back as problem+json.

Records & CRUD

Single-record create, read, update and delete, plus arbitrary FetchXML queries and transactional batches. Backed by UsePowerPortalsProWebServer; every read and write applies the consumer's ITablePermissionHandler / ITableRecordPermissionHandler interceptors and any registered IFetchXmlBuilderInterceptor.

POST /api/table/{tableLogicalName}

Creates a new row in the named table. Send a TableRecord carrying the columns to set; the response returns the new record's id.

Request

Response

GET /api/table/{tableLogicalName}/{recordId}

Reads a single row by id. The optional ?columns= query parameter (comma-separated logical names) narrows the projection — omit it to retrieve the table's default column set.

Request

Response

PATCH /api/table/{tableLogicalName}/{recordId}

Updates an existing row. Only the columns present in properties are written, so send just the changed values.

Request

Response

DELETE /api/table/{tableLogicalName}/{recordId}

Deletes the row by id.

Request

Response

GET /api/retrieveMultiple?fetchXml=…

Runs an arbitrary FetchXML query and returns the matching rows plus paging info. Encode the FetchXML into the fetchXml query string — from C#, Routes.Api.GetRetrieveMultipleRoute(fetchXml) does this for you.

Request

Response

POST /api/executeMultiple?returnResponses=true|false

Executes a batch of heterogeneous requests (Create / Update / Delete / Associate / Disassociate) in a single database transaction — if any one fails, the whole batch rolls back. The body is a JSON array of OrganizationRequest objects discriminated by $type. Pass ?returnResponses=false to skip building the per-request response list.

Request

Response

Grids & charts

Server-composed query endpoints. Rather than have the client build FetchXML, you pass a view id (or your own FetchXML) plus search / sort / paging, and the server resolves columns, applies permissions and runs the query — the same single-source-of-truth path MainGrid and the chart components use.

POST /api/grids/data

Loads a page of grid data. Supply either a viewId or your own fetchXml, plus optional searchText, sorts, paging and column filters. The response carries the rows, the resolved column definitions, and the caller's table-permission mask.

Request

Response

POST /api/charts/data

Loads aggregated chart data shaped for the charting components. Accepts an aggregate config, a viewId, or raw fetchXml, plus the label / value / series column mapping; returns Chart.js-style labels and datasets.

Request

Response

Metadata & permissions

Read-only lookups for table and view metadata, the current user's permission mask, and org-wide settings. All are cached server-side, so repeat calls are cheap.

GET /api/tableMetadata/{tableLogicalName}

Returns a table's metadata — columns (with types, labels and constraints), primary id / name / image columns, and relationships.

Request

Response

GET /api/permissions/table/{tableLogicalName}

Returns the current user's combined TableSecurityPermission mask for the table as a single integer (bit flags: Read 1, Create 2, Write 4, Delete 8, Append 16, AppendTo 32).

Request

Response

GET /api/viewMetadata/{viewId}

Returns the metadata for one saved view by its GUID — its FetchXML, layout columns and view flags. The route requires a GUID, which is how it's disambiguated from the all-views route below.

Request

Response

GET /api/viewMetadata/{tableLogicalName}

Returns every saved view for a table. Shares the /api/viewMetadata/ prefix with the by-id route — a non-GUID segment (a table logical name) lands here.

Request

Response

GET /api/organizationSettings

Returns org-wide settings sourced from the Dataverse organization record: the default currency, the blocked file-extension list, and the maximum upload size in bytes.

Request

Response

Files

Read file and image column content. Binary payloads are returned base64-encoded inside JSON; the includeData flag lets you fetch metadata only (e.g. to render a download list) without transferring bytes.

GET /api/files/{tableLogicalName}/{recordId}/{columnName}?includeData=…

Returns the file metadata for a file / image column on one record. With ?includeData=true the base64 content is embedded; with false only the name and size come back.

Request

Response

POST /api/files/{tableLogicalName}/{columnName}/batch?includeData=…

Fetches file metadata (and optionally content) for many records of the same table / column in one round-trip — the body is a JSON array of record GUIDs. Used by the FileGrid's Download Selected so the client doesn't fire N separate calls.

Request

Response

POST /api/files/createFileArchive

Zips the chosen file-column values for a set of records server-side. Returns the raw application/zip stream by default, or — with responseFormat: 1 — a JSON envelope carrying the base64 archive. POST (not GET) so large id lists don't hit URL-length limits.

Request

Response

Localization bundles

Localized strings for non-Blazor front ends. The /api/localizedStrings route returns the whole tree for a culture; the /localizations/* routes serve thumbprinted, immutable-cached bundles (default, per-table, per-view) for efficient incremental loading — these are public and don't require the auth cookie.

GET /api/localizedStrings/{culture}

Returns the full localized-string tree for a culture as a nested object — framework strings, app overrides, table and choice labels.

Request

Response

GET /localizations/version

Returns the localization manifest — just the list of supported locales. Served no-cache so a new release is detected on the next page load. Public.

Request

Response

GET /localizations/{locale}/thumbprints

Returns the content thumbprints for one locale: the default bundle plus every loaded table and view. Clients fetch these, then request only the bundles whose thumbprint changed. Public.

Request

Response

GET /localizations/default/{filename} · /tables/{tableName}/{filename} · /views/{viewId}/{filename}

The three bundle families — default (cross-cutting strings), per-table (a table's strings plus the global choices its columns reference), and per-view. The filename is {locale}.{thumbprint}.json and each is served public, immutable, max-age=31536000, so a stable thumbprint is a guaranteed cache hit. Public.

Request

Response

Culture

Switches the browser's active culture by writing the culture cookie.

GET /Culture/{culture}?redirectUri=…

Sets the culture cookie and 302-redirects to redirectUri. Navigate to it (full page load) rather than fetching it, so the Set-Cookie and redirect take effect. Public.

Request

Response

Admin — cache management

Server-cache inspection and invalidation. All three endpoints are gated by [Authorize(Roles = "SystemAdmin")] — only a signed-in administrator can call them.

GET /api/caches

Lists the names of every registered server-side cache. Requires the SystemAdmin role.

Request

Response

POST /api/caches/clear

Clears every server-side cache and returns a per-cache result (whether it succeeded and how long it took). Requires the SystemAdmin role.

Request

Response

POST /api/caches/{cacheName}/clear

Clears a single named cache (the name comes from the list endpoint). Returns 404 if no cache by that name is registered. Requires the SystemAdmin role.

Request

Response

Authentication

Sign-in, sign-up and the account lifecycle, backed by MapAuthEndpoints<TUser>. Cookie-based: a successful sign-in sets the ASP.NET Core Identity application cookie, and every later call authenticates by presenting it back. Most return a result enum at HTTP 200 rather than signalling outcome through the status code.

POST /api/auth/login

Signs in with email + password. The result enum distinguishes success, a required second factor, bad credentials, an unconfirmed email, and lockout. On success the auth cookie is set on the response.

Request

Response

POST /api/auth/login/2fa

Completes a sign-in that returned RequiresTwoFactor by submitting the authenticator (or recovery) code. rememberMachine sets the trusted-browser cookie so future sign-ins on this browser skip the second factor.

Request

Response

POST /api/auth/logout

Clears the auth cookie, ending the session.

Request

Response

POST /api/auth/register

Creates a new local account. Depending on configuration the result is either a confirmation email sent, an immediate sign-in, or a clash with an existing email. Weak-password and other validation failures come back as 400 problem+json.

Request

Response

POST /api/auth/forgot-password

Starts a password reset by emailing a reset link. Always returns 200 with no body whether or not the address exists, so it can't be used to probe for registered emails.

Request

Response

POST /api/auth/reset-password

Completes a reset using the token from the email plus the new password. result distinguishes success, an invalid / expired token, and a rejected password (with the validation messages in errors).

Request

Response

POST /api/auth/confirm-email

Confirms a newly-registered email using the user id and token from the confirmation link.

Request

Response

POST /api/auth/resend-email-confirmation

Re-sends the email-confirmation link. Like forgot-password, it always returns 200 with no body to avoid leaking which addresses are registered.

Request

Response

GET /api/auth/options

Returns the sign-in configuration in one anonymous call: localAccountsEnabled (whether the portal accepts local username/password accounts) plus the configured external (OAuth) providers — scheme name and display name — for the sign-in buttons.

Request

Response

GET /api/auth/external-login?provider=…&returnUrl=…

Kicks off the OAuth flow for a provider. It returns a 302 challenge to the provider, so navigate the browser to it (don't fetch) — a SPA should set window.location.href.

Request

Response

GET /api/auth/external-login/pending

After the OAuth callback, snapshots the in-flight external login: the provider, the identity's claims, and — when the email matches more than one portal identity — the candidate list to choose from. Returns 204 when there's no pending login.

Request

Response

POST /api/auth/external-login/confirm

Completes a first-time external sign-in for a new account by confirming the email to associate. Resolves to a sign-in, a confirmation email, no-pending, or failure.

Request

Response

POST /api/auth/external-login/select

Completes an external sign-in when the identity matched multiple portal identities, by choosing which one (Contact or SystemUser) to sign in as.

Request

Response

GET /api/auth/me

Snapshot of the current principal — id, name, email, roles, and the backing table (contact vs. systemuser), plus any alternate sibling identity. Returns an anonymous shape (isAuthenticated: false) rather than 401 when no cookie is present, so a SPA can call it on first paint without branching on status code.

Request

Response

POST /api/auth/switch-identity

Swaps the current cookie for the user's alternate sibling identity (the Contact↔SystemUser pairing surfaced on /api/auth/me). The JSON body is an empty object.

Request

Response

Account management

The signed-in user's self-service operations under /api/auth/manage/* — profile, password, email, two-factor, linked external logins, and personal data. All require an authenticated session and mirror the framework's classic /Account/Manage Razor pages.

GET /api/auth/manage/profile

Returns the user's profile (name, mobile, email) read from the linked Dataverse contact, plus Identity status flags (email confirmed, has a password, 2FA enabled, read-only).

Request

Response

POST /api/auth/manage/profile

Updates the first / last name and mobile phone on the linked contact. Returns 200 on success; SystemUser-backed identities are read-only and get 403.

Request

Response

POST /api/auth/manage/password/set

Adds a local password to an account that doesn't have one (e.g. an external-login-only account). Validation messages, if any, come back in errors.

Request

Response

POST /api/auth/manage/password/change

Changes the local password; requires the current password. result distinguishes success, a wrong current password, and a rejected new password.

Request

Response

POST /api/auth/manage/email/change

Starts an email change by sending a confirmation link to the new address. The change only takes effect once that link is followed.

Request

Response

POST /api/auth/manage/email/send-confirmation

Re-sends the confirmation link for the user's current email. sent is false when the email is already confirmed.

Request

Response

GET /api/auth/manage/2fa

Returns the 2FA state — whether an authenticator is enrolled, whether 2FA is on, whether this browser is remembered, and how many recovery codes remain.

Request

Response

GET /api/auth/manage/authenticator/setup

Returns the shared key and otpauth:// URI for the QR-code enrollment screen. Pair it with the verify endpoint to finish enabling 2FA.

Request

Response

POST /api/auth/manage/authenticator/verify

Verifies a code from the authenticator app and enables 2FA. On the first enrollment the response also returns the initial set of recovery codes.

Request

Response

POST /api/auth/manage/authenticator/reset

Rotates the authenticator key. This also disables 2FA, so the user must re-enroll.

Request

Response

POST /api/auth/manage/2fa/disable

Turns 2FA off for the account.

Request

Response

POST /api/auth/manage/2fa/recovery-codes/generate

Regenerates the recovery codes, replacing any existing set, and returns the new codes.

Request

Response

POST /api/auth/manage/2fa/forget-browser

Clears this browser's trusted-device cookie, so 2FA will be required again on the next sign-in here.

Request

Response

GET /api/auth/manage/external-logins

Lists the external logins currently linked to the account.

Request

Response

GET /api/auth/manage/login-info

A combined view of the user's sign-in paths — linked external logins plus whether a local password is set — used to decide whether removing a login would lock the user out.

Request

Response

GET /api/auth/manage/external-logins/link?provider=…

Starts the OAuth flow that links an additional provider to the signed-in account. Returns a 302 challenge, so navigate to it rather than fetching it.

Request

Response

GET /api/auth/manage/external-logins/link/callback

The OAuth callback for the link flow. The provider redirects the browser here; the server attaches the login and 302-redirects to the returnUrl originally supplied. You don't call this directly.

Request

Response

POST /api/auth/manage/external-logins/remove

Unlinks one external login by provider + provider key.

Request

Response

GET /api/auth/manage/personal-data

Exports the user's personal data — every [PersonalData] property plus their linked external logins — for GDPR-style download.

Request

Response

POST /api/auth/manage/personal-data/delete

Permanently deletes the user's account and signs them out. Requires the current password when the account has one; pass null for external-only accounts. result distinguishes success, a wrong password, and the case where a password is required but wasn't supplied.

Request

Response

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.