Skip to main content

API Reference

The Wayd API is a RESTful ASP.NET Core Web API.

Base URL​

  • Local Development: https://localhost:5001 (or dynamic port via Aspire)
  • API reference (Scalar): https://localhost:5001/scalar

Authentication​

All API requests require a Wayd JWT in the Authorization: Bearer header, regardless of which login provider the user authenticated with. The JWT carries the user's identity and permission claims and is minted by one of two login flows:

Username / password​

Authenticate with the local login endpoint:

POST /api/auth/login
{
"userName": "your-username",
"password": "your-password"
}

The response is the same shape for every login flow:

{
"token": "<wayd-jwt>",
"refreshToken": "<wayd-refresh-token>",
"tokenExpiresAt": "2026-04-21T19:00:00Z",
"mustChangePassword": false
}

Include the Wayd JWT in subsequent requests:

Authorization: Bearer {token}

OIDC providers (token exchange)​

For users who sign in through an OIDC identity provider (Microsoft Entra ID or another OIDC provider registered under Settings → Access → Identity Providers), the frontend obtains an access token from the provider (via oidc-client-ts) for the API scope and exchanges it for a Wayd JWT:

POST /api/auth/exchange
{
"provider": "<provider-name>",
"subjectToken": "<access-token-from-the-provider>"
}

provider is the provider's name as GET /api/auth/providers lists it. The response shape is identical to /login. The provider token is only used during the exchange — all subsequent API calls use the Wayd JWT.

An unknown or disabled provider, or a token that fails validation, returns HTTP 401. A Microsoft Entra ID sign-in creates the Wayd account on first use; any other provider signs in only a user who already has an account linked to it, and returns 401 otherwise.

GET /api/auth/providers (anonymous) lists what a client can sign in with: local (always true) and, under oidc, each enabled provider's name, displayName, providerType, authority, clientId and scopes.

Refreshing tokens​

When the Wayd JWT expires, exchange the refresh token for a new JWT:

POST /api/auth/refresh-token
{
"token": "<expired-wayd-jwt>",
"refreshToken": "<wayd-refresh-token>"
}

Same response shape. Refresh works the same way for every login provider — the refresh flow doesn't need to know how the user originally authenticated.

Refresh tokens rotate: each refresh returns a new one and invalidates the one you presented. Store the new value and discard the old.

Presenting a refresh token that was already rotated away is treated as reuse — evidence that two parties hold tokens from the same chain — and revokes that session. Other sessions are unaffected. A client that keeps a single in-flight refresh will never trigger this; one that races several concurrent refreshes can.

Sessions​

Each sign-in creates a session. Signing in on a second device does not invalidate the first.

Signing out ends the calling device's session:

POST /api/auth/logout
{
"refreshToken": "<wayd-refresh-token>"
}

Returns 204. The refresh token identifies which session to end — without it, or with one that matches no live session, nothing is revoked.

To end every session:

POST /api/auth/logout-all

To list live sessions:

POST /api/auth/sessions
{
"refreshToken": "<wayd-refresh-token>"
}

Returns device label, IP address, timestamps, and isCurrent marking the caller's own session. The refresh token is optional and only used to set isCurrent; the response never contains a token or hash. This is a POST rather than a GET so the token stays out of URLs, access logs, and browser history.

To end one session by id:

DELETE /api/auth/sessions/{sessionId}

Returns 204, or 404 when the id is not one of the caller's live sessions.

All four endpoints require authentication and act only on the calling user — no user id is read from the request.

note

Revoking a session stops it refreshing, but that device's existing JWT stays valid until it expires (TokenExpirationInMinutes, 60 by default). Revocation is not instant.

Personal access tokens are not sessions and are unaffected by sign-out. Revoke them separately.

Personal Access Tokens (PATs)​

Personal Access Tokens provide a way to authenticate API requests without interactive login. They are ideal for scripts, CI/CD pipelines, and integrations.

To use a PAT, include it in the x-api-key header:

x-api-key: your-personal-access-token

Managing PATs:

  • Navigate to your Profile page
  • Open the PATs tab
  • Create a new token with a name and an expiration date (at most two years away); each user can hold up to 10 active tokens
  • Copy the token immediately — it cannot be retrieved after creation
  • Revoke tokens that are no longer needed

PATs inherit the permissions of the user who created them. See Your Profile for details on creating and managing tokens.

API Areas​

AreaBase RouteDescription
Work Management/api/work/Workspaces and their work items, sprints, work processes, work types, statuses
Organization/api/organization/Teams, teams of teams, employees, holiday calendars
Planning/api/planning/Planning intervals, risks, roadmaps, story maps, poker sessions, estimation scales
Project Portfolio Management/api/ppm/Portfolios, programs, projects, tasks, ranking, lifecycles, strategic initiatives
Strategic Management/api/strategic-management/Strategic themes, visions, strategies
Product Management/api/product-management/Products, product types, tag categories, versions, releases, release packages, deployments, environments, delivery metrics
App Integrations/api/app-integrations/Connections (Azure DevOps, Azure OpenAI)
Imports/api/imports/CSV import runs and their rows
Scoring Models/api/scoring-models/Scoring models
Status Workflows/api/status-workflows/, /api/workflow-assignments/Status workflows and where they apply
Search/api/search/Global search
Auth/api/auth/Login, refresh, exchange, providers, sessions, first-run setup
User Management/api/user-management/Users, profiles, roles, permissions, PATs, identity providers
Admin/api/admin/Background jobs, messaging, feature flags, system settings

POST /api/auth/setup creates the first administrator with the one-time bootstrap token, and answers 409 once any user exists; see First-Run Setup.

OpenAPI Specification​

The full OpenAPI document is generated by Microsoft.AspNetCore.OpenApi and available at /openapi/v1.json (or /openapi.json) when the API is running. /.well-known/api-catalog lists the document and the API reference in the RFC 9727 API catalog format, for tools that discover an API's description without knowing where it is served.

The TypeScript client is auto-generated from this spec and located in:

wayd.web.reactclient/src/services/wayd-api.ts

Activity History Endpoints​

Every record that raises domain events exposes its history at the same sub-route:

GET {record route}/{idOrKey}/activities?page=1&pageSize=50

page is 1-based and pageSize is clamped to 1–100. The response is a PagedResponse<ActivityLogDto>, newest first. Reading a record's activity requires the same View permission as reading the record itself, and the handler resolves the record before reading its history — an unknown id or key returns 404 rather than an empty page.

It is available on teams, teams-of-teams, holiday-calendars, planning-intervals, sprints, risks, portfolios, programs, projects, strategic-initiatives, strategic-themes, products, versions, releases, release-packages, deployments, scoring-models, status-workflows and work-processes. See Activity Log for what the corresponding UI does.

A planning interval objective is reached through the interval that holds it, so its history hangs off that route rather than a top-level one:

GET planning-intervals/{idOrKey}/objectives/{objectiveIdOrKey}/activities?page=1&pageSize=50

An objective belonging to a different interval returns 404, as an unknown one does.

Three administration records take an id rather than an id or key, and live under their own areas:

GET user-management/users/{id}/activities?page=1&pageSize=50
GET user-management/oidc-providers/{id}/activities?page=1&pageSize=50
GET app-integrations/connections/{id}/activities?page=1&pageSize=50

Two system settings sections keep a history of their own, with the same paging:

GET admin/settings/scheduling/activities?page=1&pageSize=50
GET admin/settings/sprint-metrics/activities?page=1&pageSize=50

A user's history includes the history of the personal access tokens they own; those entries come back with isRelated set and raisedOn naming the token, or null once the token has been deleted.

PPM Ranking Endpoints​

Portfolio project ranking is exposed under /api/ppm/portfolios:

  • PUT /api/ppm/portfolios/{id}/project-ranks — Reposition an ordered batch of projects within a portfolio's ranking.
  • PUT /api/ppm/portfolios/{id}/project-ranks/rebalance — Re-space a portfolio's ranks to clean whole-number gaps.
  • GET /api/ppm/portfolios/{id}/ranking-scoreboard — Get the current scoring-model breakdown used by the portfolio Ranking tab.

Background Job Endpoints​

Background jobs are exposed under /api/admin/background-jobs. See Background Jobs for what the corresponding UI does.

Reading — all require Permissions.BackgroundJobs.View:

  • GET /api/admin/background-jobs/jobs?state=&pageNumber=&pageSize= — A page of jobs in one lifecycle state. pageNumber is 0-based; pageSize is clamped to 1–500.
  • GET /api/admin/background-jobs/jobs/{jobId} — Full detail for one job: arguments, state history, and the exception with its stack trace when the job failed.
  • GET /api/admin/background-jobs/statistics — Counts split into current and all-time groups.
  • GET /api/admin/background-jobs/servers — Worker processes and their heartbeats.
  • GET /api/admin/background-jobs/recurring — Registered recurring schedules.
  • GET /api/admin/background-jobs/job-types — The job types available to run.
  • GET /api/admin/background-jobs/running — Currently processing jobs.

Acting — Run triggers work, Delete removes it:

  • POST /api/admin/background-jobs/run?jobTypeId= — Queue a job to run now. Requires Permissions.BackgroundJobs.Run.
  • POST /api/admin/background-jobs — Register a recurring job. Requires Permissions.BackgroundJobs.Run. Job types that cannot be scheduled are rejected with a 400.
  • POST /api/admin/background-jobs/jobs/{jobId}/requeue — Queue a job for another attempt. Requires Permissions.BackgroundJobs.Run.
  • DELETE /api/admin/background-jobs/jobs/{jobId} — Delete a job. Requires Permissions.BackgroundJobs.Delete.
  • DELETE /api/admin/background-jobs/recurring/{recurringJobId} — Remove a recurring schedule. Requires Permissions.BackgroundJobs.Delete.