
Fusion Core Services
- 758 installs
- 1 repo stars
- Updated August 4, 2026
- equinor/fusion-skills
fusion-core-services is a Claude Code skill that gives coding agents reliable access to Equinor Fusion platform services for developers who integrate enterprise Fusion APIs without rewriting connection glue each time.
About
fusion-core-services is an Equinor Fusion platform integration skill from equinor/fusion-skills, listed on Skills.sh with 425 installs and rank 18 in its catalog. It standardizes how Claude and Cursor agents call Fusion core services so developers avoid repeating bespoke API client setup. The skill targets teams building against Equinor's internal Fusion ecosystem who need consistent service discovery, authentication patterns, and endpoint usage from agent sessions. Developers reach for fusion-core-services when agent workflows must read or mutate Fusion-backed data during feature development. It assumes familiarity with Equinor Fusion—not a general-purpose REST client generator.
- Provides pre-built MCP tools for Fusion Core Services including project, context, and data access patterns
- Official skill from equinor/fusion-skills repository with 425 installs
- Enables agents to call real Fusion platform endpoints securely
- Reduces boilerplate when building internal tools or automation on top of Equinor data platforms
Fusion Core Services by the numbers
- 758 all-time installs (skills.sh)
- Ranked #1,361 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/equinor/fusion-skills --skill fusion-core-servicesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 758 |
|---|---|
| repo stars | ★ 1 |
| Last updated | August 4, 2026 |
| Repository | equinor/fusion-skills ↗ |
How do agents integrate Equinor Fusion core services?
Give their Claude or Cursor agents reliable access to Equinor Fusion platform services without writing custom API glue each time.
Who is it for?
Developers building on Equinor Fusion who want agent-assisted access to core platform services without custom glue code.
Skip if: Developers outside the Equinor Fusion ecosystem or projects needing generic OAuth REST client scaffolding.
When should I use this skill?
A developer working in Equinor Fusion asks an agent to query, update, or orchestrate Fusion core platform services.
What you get
Configured Fusion API calls, service endpoint mappings, and agent-ready integration snippets.
- Fusion API integration patterns
- Agent-callable service endpoints
By the numbers
- 425 installs on Skills.sh
- Rank 18 in equinor/fusion-skills catalog
Files
Fusion Core Services
When to use
Use when the task involves one or more Fusion Core service APIs and the agent needs to identify the right service guidance without requiring separate skill installs.
Typical triggers:
- implement a Fusion API client
- wire a Fusion backend service integration
- figure out which Fusion Core service owns a workflow
- solve a cross-service task such as context plus people, roles plus notifications, or apps plus service messages
When not to use
- Modifying code inside
fusion-core-services - Non-Fusion APIs or generic Microsoft Graph / Power BI work with no Fusion service layer
- Standalone product workflows already covered by another dedicated skill outside Fusion Core services
Required inputs
- target workflow or user goal
- target consumer shape (
react,typescript client,csharp httpclient,backend service, or other) - known service hints, if any
- versioning or authorization expectations when relevant
Instructions
1. Scope the request first.
- Identify whether the workflow touches one service or multiple.
- If service is ambiguous, use
agents/service-router.mdto map the workflow to likely services before producing implementation guidance.
2. Read only the relevant service references.
- Start with Combined API surface.
- Then open the matching per-service reference file.
- Pull in the endpoint catalog and model asset for only the services that materially affect the answer.
3. Preserve source-grounded guidance.
- Prefer controller-backed endpoint and model notes already captured in bundled references.
- Call out any route or model area that still requires direct source confirmation before shipping.
4. Handle capabilities explicitly.
- If a service exposes
OPTIONSor other access-probe routes, use them to drive capability-aware UI or mutation logic. - If a service does not expose stable probes, document conservative client behavior and treat
403 Forbiddenas the fallback capability signal.
5. Treat subscriptions as backend-only unless the reference says otherwise.
- The
/subscriptions/...routes are for application-token event registration and CloudEvent-style change handling, not normal frontend CRUD flows.
6. Return consumer-ready guidance.
- For frontend consumers, return TypeScript-friendly DTOs and a minimal client/hook pattern.
- For .NET consumers, return a typed
HttpClientplan plus DTO record suggestions. - For cross-service tasks, explain the service sequence and data handoff between services.
Service catalog
- Apps: reference, endpoint catalog, models
- Bookmarks: reference, endpoint catalog, models
- Context: reference, endpoint catalog, models
- Contract Personnel: reference, endpoint catalog, models
- Mail: reference, endpoint catalog, models
- Notification: reference, endpoint catalog, models
- People: reference, endpoint catalog, models
- Portal Config: reference, endpoint catalog, models
- Reports: reference, endpoint catalog, models
- RolesV2: reference, endpoint catalog, models
- Service Messages: reference, endpoint catalog, models
- Tasks: reference, endpoint catalog, models
Expected output
Return headings in this order: 1. Scope check 2. Service selection 3. Endpoint mapping 4. Model mapping 5. Consumer implementation plan 6. Integration code sketch 7. Validation and test notes 8. Risks and assumptions
Safety & constraints
Never:
- invent service ownership, routes, or DTO fields
- answer from generic SaaS/API assumptions when the bundled Fusion references are specific
- treat backend subscription routes as normal frontend interaction flows
Always:
- keep cross-service reasoning explicit when more than one service is involved
- call out capability-probe behavior when the service exposes
OPTIONS - prefer the narrowest set of service references needed for the user’s workflow
Service Router
Use this helper when a request mentions Fusion APIs but does not name the owning service clearly, or when the workflow obviously spans multiple services.
Goal
Identify which Fusion Core service references the parent skill should read before producing implementation guidance.
Workflow
1. Extract workflow nouns and verbs.
- Look for signals such as app metadata, context entities, people search, role activation, report embedding, notifications, bookmarks, service messages, or tasks.
2. Map the workflow to services.
- Apps: app registry, app metadata, pinned apps, governance, widgets
- Bookmarks: bookmarks, favourites, saved state payloads
- Context: contexts, relations, context types, context subscriptions
- Contract Personnel: contracts, delegates, personnel, recertifications, role assignments in contract scope
- Mail: mail send, templates, delivery status, mail logs/whitelists
- Notification: notifications and notification settings
- People: person profiles, people picker, search, presence, linked accounts, person subscriptions
- Portal Config: portals, templates, tags, categories, portal settings
- Reports: reports, embed config, report config validation, report permissions
- RolesV2: roles, claimable roles, access roles, systems, scope types
- Service Messages: service messages, active messages, app-scoped service messages
- Tasks: Fusion tasks, PIMS tasks, ProCoSys tasks, task subscriptions
3. Prefer multiple services when the workflow crosses boundaries.
- Example: resolving a context and then fetching people means
ContextplusPeople. - Example: determining whether a user can activate a role and then notifying them means
RolesV2plusNotification.
4. Return a lean routing result.
- List the selected services in priority order.
- Give one sentence for why each service applies.
- Name the exact reference and asset files the parent skill should read next.
Output contract
Return:
- selected service list
- why each service applies
- exact file paths under
references/andassets/ - any ambiguity that still requires user clarification
Constraints
- Prefer precision over breadth, but do not force a single-service answer when the workflow is clearly cross-service.
- Do not invent a service when the workflow is better answered by asking one short clarification question.
Apps API Endpoint Catalog
This catalog covers the verified public application surface in Fusion Apps. It includes application, build, tag, category, governance, and person-scoped routes. Internal support routes and widget-specific routes are still excluded here.
Application endpoints
GET /apps→ApiPagedCollection<ApiAppListItem>
OData: Expand(category, admins, owners, keywords, build, classification), Filter(type, appKey, displayName), Search, Top, Skip
OPTIONS /apps→204 NoContentwithAllow: OPTIONS,GET[,POST]OPTIONS /apps?template={appKey}→204 NoContentwith template-scoped create capability inAllowGET /apps/{appIdentifier}→ApiAppGET /apps/{appIdentifier}@{versionIdentifier}→ApiAppHEAD /apps/{appIdentifier}→ existence probe onlyOPTIONS /apps/{appIdentifier}→204 NoContentwithAllow: OPTIONS,GET[,PATCH,DELETE]POST /apps→CreateAppRequest→ApiAppPATCH /apps/{appIdentifier}→PatchAppRequest→ApiAppDELETE /apps/{appIdentifier}→204 NoContentGET /apps/{appIdentifier}/tagged-persons→ApiPagedCollection<ApiTaggedPerson>GET /apps/{appIdentifier}/changelog→ApiPagedCollection<ApiChangelog>
OData: Filter(activityId, actorUpn, actorAzureUniqueId, commandName), Top, Skip
POST /apps/{appIdentifier}/restore→ApiApp
Build and config endpoints
GET /apps/{appIdentifier}/builds→ApiPagedCollection<ApiAppVersion>GET /apps/{appIdentifier}/builds/{versionIdentifier}→ApiAppVersionDELETE /apps/{appIdentifier}/builds/{versionIdentifier}→204 NoContentPUT /apps/{appIdentifier}/builds/{versionIdentifier}/config→CreateAppBuildConfigRequest→ApiAppVersionConfigGET /apps/{appIdentifier}/builds/{versionIdentifier}/config→ApiAppVersionConfigGET /apps/{appIdentifier}/builds/{versionIdentifier}/changelog→ApiPagedCollection<ApiChangelog>
OData: Filter(activityId, actorUpn, actorAzureUniqueId, commandName), Top, Skip
Tag endpoints
GET /apps/{appIdentifier}/tags→ApiPagedCollection<ApiAppTag>GET /apps/{appIdentifier}/tags/{tagName}/history→ApiPagedCollection<ApiAppTagHistory>PUT /apps/{appIdentifier}/tags/{tagName}→CreateAppTagRequest→ApiAppTagDELETE /apps/{appIdentifier}/tags/{tagName}→204 NoContent
Category endpoints
GET /apps/categories→ApiPagedCollection<ApiAppCategory>OPTIONS /apps/categories→204 NoContentwithAllow: OPTIONS,GET[,POST]GET /apps/categories/{appCategoryIdentifier}→ApiAppCategoryPOST /apps/categories→CreateAppCategoryRequest→ApiAppCategoryPATCH /apps/categories/{appCategoryIdentifier}→PatchCategoryRequest→ApiAppCategoryGET /apps/categories/{appCategoryIdentifier}/changelog→ApiPagedCollection<ApiChangelog>
OData: Filter(activityId, actorUpn, actorAzureUniqueId, commandName), Top, Skip
Context-type capability probe
OPTIONS /context-types→204 NoContentwithAllow: OPTIONS,GET[,POST]
Governance endpoints
GET /governance-apps(v1.0-preview) →ApiPagedCollection<ApiGovernanceAppListItem>
OData: Filter(appKey), Expand(documents.content), Search, Top, Skip
GET /apps/{appIdentifier}/governance→ApiGovernanceAppOPTIONS /apps/{appIdentifier}/governance→204 NoContentwithAllow: OPTIONS,GET[,PATCH]PATCH /apps/{appIdentifier}/governance→PatchGovernanceAppRequest→ApiGovernanceAppPOST /apps/{appIdentifier}/governance/documents→AppGovernanceDocumentRequest→ApiGovernanceDocumentGET /apps/{appIdentifier}/governance/documents→ApiGovernanceDocument[]OPTIONS /apps/{appIdentifier}/governance/documents→204 NoContentwithAllow: OPTIONS,GET[,POST]GET /apps/{appIdentifier}/governance/documents/{documentType}→ApiGovernanceDocumentOPTIONS /apps/{appIdentifier}/governance/documents/{documentType}→204 NoContentwithAllow: OPTIONS,GET[,PATCH,DELETE]PATCH /apps/{appIdentifier}/governance/documents/{documentType}→PatchGovernanceDocumentRequest→ApiGovernanceDocumentDELETE /apps/{appIdentifier}/governance/documents/{documentType}→204 NoContentOPTIONS /apps/{appIdentifier}/governance/confirmation→204 NoContentwithAllow: OPTIONS[,PUT]PUT /apps/{appIdentifier}/governance/confirmation→ConfirmGovernanceRequest→ApiGovernanceConfirmationDELETE /apps/{appIdentifier}/governance/properties/{propertyName}→204 NoContent
Person-scoped endpoints
GET /persons/me/appsandGET /persons/{accountIdentifier}/apps→ApiPagedCollection<ApiPersonAppListItem>GET /persons/me/apps/{appIdentifier}andGET /persons/{accountIdentifier}/apps/{appIdentifier}→ApiPersonAppPUT /persons/me/apps/{appIdentifier}/tagandPUT /persons/{accountIdentifier}/apps/{appIdentifier}/tag→CreatePersonAppTagRequest→ApiPersonAppDELETE /persons/me/apps/{appIdentifier}/tagandDELETE /persons/{accountIdentifier}/apps/{appIdentifier}/tag→204 NoContentGET /persons/me/pinned-appsandGET /persons/{accountIdentifier}/pinned-apps→ApiPagedCollection<ApiPinnedApp>POST /persons/me/pinned-appsandPOST /persons/{accountIdentifier}/pinned-apps→CreatePinnedAppRequest→ApiPinnedAppGET /persons/me/pinned-apps/{appIdentifier}andGET /persons/{accountIdentifier}/pinned-apps/{appIdentifier}→ApiPinnedAppDELETE /persons/me/pinned-apps/{appIdentifier}andDELETE /persons/{accountIdentifier}/pinned-apps/{appIdentifier}→204 NoContent
Subscription endpoint
PUT /subscriptions/apps→ backend subscription registration/update returningApiEventSubscriptionV1
Application-token only. Use for change-event delivery, local-cache invalidation, or projection syncing rather than UI workflows.
Authorization notes
- Read routes generally require authenticated callers.
- Write routes are protected by app-scoped authorization such as app admin, business owner, trusted application, or global full-control roles.
- Governance write routes are more restrictive than app reads and often allow app business owners or governance-specific global roles.
- Person-scoped pinned-app routes additionally allow the current user on their own resources.
Typical status codes
200 OK201 Created204 NoContent400 Bad Request401 Unauthorized403 Forbidden404 Not Found409 Conflict410 Gone
Explicit exclusions
- Widget controllers and widget bundle routes
- Internal support surfaces outside the application domain
Capability probe notes
OPTIONS /appschecks app-create capability and can be scoped with?template={appKey}for template app creation.OPTIONS /apps/{appIdentifier}checks item-level mutation capability and only advertisesPATCHandDELETEwhen the caller has sufficient rights.OPTIONS /apps/categories,OPTIONS /context-types, and the governanceOPTIONSroutes expose the sameAllow-header capability pattern for adjacent administration flows.- Source also exposes
OPTIONS /widgetsandOPTIONS /widgets/{widgetIdentifier}for widget create/update/delete capability, but widget routes remain outside this catalog's current scope.
/**
* Fusion Apps API — TypeScript model definitions
*
* Source of truth: Fusion.Services.Apps/Controllers/
* No published NuGet API-model package — models are service-internal.
*
* All property names use camelCase to match JSON serialization.
*/
// ---------------------------------------------------------------------------
// Enums (serialized as strings)
// ---------------------------------------------------------------------------
export type ApiAppType = string;
export type ApiGovernanceDocumentType =
| "AccessOverview"
| "Goal"
| "DataSource"
| "PrivacyStatement"
| "LRA";
export type ApiDataClassificationLevel = string;
// ---------------------------------------------------------------------------
// Core response models
// ---------------------------------------------------------------------------
/** Returned by GET /apps and GET /apps/{appKey}. */
export interface ApiApp {
appKey: string;
id: string;
displayName: string;
description: string | null;
type: string;
category: ApiAppCategory | null;
visualization: ApiAppVisualization | null;
contexts: ApiAppContext[] | null;
keywords: string[] | null;
admins: ApiAppAdmin[] | null;
owners: ApiAppOwner[] | null;
build: ApiAppVersion | null;
classification: ApiDataClassification | null;
}
export interface ApiAppListItem extends ApiApp {}
export interface ApiPersonApp extends ApiApp {
isPinned: boolean;
}
export interface ApiPersonAppListItem extends ApiPersonApp {}
export interface ApiAppVersion {
version: string;
entryPoint: string;
tags: string[];
tag: string | null;
assetPath: string;
configUrl: string;
timestamp: string | null;
commitSha: string | null;
githubRepo: string | null;
projectPage: string | null;
annotations: Record<string, unknown>;
uploadedBy: ApiAccount | null;
uploadedDate: string;
}
export interface ApiAppCategory {
id: string;
name: string;
displayName: string;
color: string | null;
defaultIcon: string | null;
sortOrder: number | null;
}
export interface ApiAppTag {
tagName: string | null;
version: string | null;
}
export interface ApiAppTagHistory {
tagName: string;
version: string;
buildId: string;
created: string;
replaced: string | null;
isCurrent: boolean;
createdBy: ApiAccount;
deletedBy: ApiAccount | null;
}
export interface ApiAppAdmin {
id: string;
azureUniqueId: string | null;
displayName: string | null;
mail: string | null;
upn: string | null;
accountType: string | null;
accountClassification: string | null;
isExpired: boolean;
}
export interface ApiAppOwner {
id: string;
azureUniqueId: string | null;
displayName: string | null;
mail: string | null;
upn: string | null;
accountType: string | null;
accountClassification: string | null;
isExpired: boolean;
}
export interface ApiAppVisualization {
color: string | null;
icon: string | null;
sortOrder: number;
}
export interface ApiAppContext {
type: string;
isCustom: boolean;
}
export interface ApiContextType {
id: string;
name: string;
isCustom: boolean;
description: string | null;
}
export interface ApiDataClassification {
level: string;
description: string | null;
impact: string;
reason: string | null;
updatedAt: string;
updatedBy: ApiAccount;
}
export interface ApiDataClassificationDefinition {
name: string;
description: string;
impact: string;
}
// ---------------------------------------------------------------------------
// Pinned apps
// ---------------------------------------------------------------------------
export interface ApiPinnedApp {
key: string;
name: string;
description: string;
icon: string | null;
accentColor: string | null;
appCategory: ApiPinnedAppCategory | null;
}
export interface ApiPinnedAppCategory {
id: string;
displayName: string;
color: string | null;
icon: string | null;
}
// ---------------------------------------------------------------------------
// Governance models
// ---------------------------------------------------------------------------
export interface ApiGovernanceApp {
appKey: string;
displayName: string;
classification: ApiDataClassification | null;
businessOwnerOrgUnit: ApiBusinessOwnerOrgUnit | null;
confirmation: ApiGovernanceConfirmation | null;
documents: ApiGovernanceDocument[];
businessOwners: ApiBusinessOwner[];
projectCategories: ApiProjectCategory[];
projectPhases: ApiProjectPhase[];
technologyProduct: ApiTechnologyProduct | null;
supportsAllProjectPhases: boolean;
supportsAllProjectCategories: boolean;
updatedAt: string | null;
properties: Record<string, unknown> | null;
}
export interface ApiGovernanceAppListItem extends ApiGovernanceApp {}
export interface ApiGovernanceDocument {
id: string;
content: string;
type: ApiGovernanceDocumentType;
typeDisplayName: string;
createdBy: ApiAccount;
createdAt: string;
updatedBy: ApiAccount | null;
updatedAt: string | null;
}
export interface ApiGovernanceDocumentListItem extends ApiGovernanceDocument {}
export interface ApiGovernanceConfirmation {
comment: string | null;
confirmedAt: string;
confirmedBy: ApiAccount;
}
export interface ApiBusinessOwner {
role: string;
roleDisplayName: string;
createdAt: string;
azureUniqueId: string;
mail: string | null;
upn: string | null;
isExpired: boolean;
reason: string | null;
}
export interface ApiBusinessOwnerOrgUnit {
sapId: string;
fullDepartment: string | null;
}
export interface ApiProjectPhase {
id: string;
externalId: string;
createdAt: string;
name: string | null;
description: string | null;
}
export interface ApiProjectCategory {
id: string;
externalId: string;
createdAt: string;
name: string | null;
description: string | null;
}
export interface ApiTechnologyProduct {
id: string;
name: string;
description: string;
businessSolutionOwner: ApiAccount;
technologyProductManager: ApiAccount;
createdAt: string;
updatedAt: string | null;
}
// ---------------------------------------------------------------------------
// Widgets
// ---------------------------------------------------------------------------
export interface ApiWidget {
id: string;
widgetKey: string;
description: string;
admins: ApiWidgetAdmin[] | null;
build: ApiWidgetVersion | null;
}
export interface ApiWidgetVersion {
version: string;
entryPoint: string;
tag: string | null;
assetPath: string | null;
configUrl: string | null;
timestamp: string | null;
commitSha: string | null;
githubRepo: string | null;
annotations: Record<string, unknown>;
}
export interface ApiWidgetAdmin {
id: string;
azureUniqueId: string | null;
displayName: string | null;
mail: string | null;
upn: string | null;
accountType: string | null;
accountClassification: string | null;
}
export interface ApiWidgetTag {
tagName: string | null;
version: string | null;
}
// ---------------------------------------------------------------------------
// Shared models
// ---------------------------------------------------------------------------
export interface ApiAccount {
azureUniqueId: string;
displayName: string;
mail: string | null;
upn: string | null;
accountType: string;
accountClassification: string | null;
isExpired: boolean;
}
export interface ApiChangelog {
id: string;
commandName: string;
timestamp: string;
appKey: string | null;
appVersionIdentifier: string | null;
categoryName: string | null;
activityId: string | null;
actorUpn: string | null;
actorAzureUniqueId: string | null;
payload: Record<string, unknown> | null;
azureAppId: string | null;
}
export interface ApiTaggedPerson {
azureUniqueId: string;
displayName: string;
mail: string | null;
upn: string | null;
isPersonExpired: boolean;
tagName: string;
}
export interface ApiEndpointConfig {
url: string;
scopes: string[];
}
export interface ApiAppVersionConfig {
environment: Record<string, unknown>;
endpoints: Record<string, ApiEndpointConfig>;
}
// ---------------------------------------------------------------------------
// Request models
// ---------------------------------------------------------------------------
/**
* POST /apps — create a new app.
*
* Validation (FluentValidation):
* @property appKey — Required, 3–50 chars, URL-safe (BeValidAppKey)
* @property displayName — Required, 3–100 chars, no script tags
* @property description — Optional, max 500 chars, no script tags
* @property admins — Required, at least one valid account
* @property owners — Each must be a valid account
* @property keywords — Each max 50 chars
*/
export interface CreateAppRequest {
/** @minLength 3 @maxLength 50 — must be URL-safe */
appKey: string;
/** @minLength 3 @maxLength 100 */
displayName: string;
/** @maxLength 500 */
description?: string | null;
type?: string;
templateSource?: TemplateSourceRequest | null;
category?: string | null;
visualization?: ApiAppVisualization | null;
contexts?: AppContextRequest[] | null;
/** Each keyword max 50 chars */
keywords?: string[] | null;
admins: string[];
owners?: string[] | null;
}
/**
* PATCH /apps/{appKey} — update app fields.
* Only properties present in the payload are applied.
*/
export interface PatchAppRequest {
/** @maxLength 500 */
description?: string | null;
/** @minLength 3 @maxLength 100 */
displayName?: string;
type?: string;
templateSource?: TemplateSourceRequest | null;
keywords?: string[] | null;
admins?: string[];
owners?: string[] | null;
visualization?: ApiAppVisualization | null;
contexts?: AppContextRequest[] | null;
category?: string | null;
}
/**
* POST /apps/categories — create an app category.
*
* Validation:
* @property name — Required, 3–50 chars (BeValidAppCategoryName)
* @property displayName — Required, 3–100 chars
* @property color — Max 20 chars
*/
export interface CreateAppCategoryRequest {
/** @minLength 3 @maxLength 50 */
name: string;
/** @minLength 3 @maxLength 100 */
displayName: string;
/** @maxLength 20 */
color?: string | null;
defaultIcon?: string | null;
sortOrder?: number | null;
}
/**
* PATCH /apps/categories/{appCategoryIdentifier} — update an app category.
* Only properties present in the payload are applied.
*/
export interface PatchCategoryRequest {
/** @minLength 3 @maxLength 50 */
displayName?: string | null;
/** @maxLength 20 */
color?: string | null;
defaultIcon?: string | null;
sortOrder?: number | null;
}
/**
* POST /apps/{appKey}/tags/{tagName} — create an app tag.
*
* Validation:
* @property version — Valid version format, max 250
*/
export interface CreateAppTagRequest {
/** @maxLength 250 */
version: string;
}
export interface CreatePinnedAppRequest {
/** @maxLength 200 */
appKey: string;
}
/**
* POST /apps/context-types — create a context type.
*
* Validation:
* @property name — Required, 2–50 chars, URL-safe
* @property description — Required if isCustom, max 500
*/
export interface CreateContextTypeRequest {
/** @minLength 2 @maxLength 50 */
name: string;
isCustom: boolean;
/** @maxLength 500 */
description?: string | null;
}
/**
* PUT /apps/{appKey}/governance/classification — set classification.
*
* Validation:
* @property level — Required, valid classification type
* @property reason — Required if level is Confidential, max 255
*/
export interface DataClassificationRequest {
level: string;
/** @maxLength 255 */
reason?: string | null;
}
export interface TemplateSourceRequest {
appIdentifier: string;
appVersionIdentifier: string;
}
export interface AppContextRequest {
/** @maxLength 50 */
type: string;
/** @maxLength 500 */
description?: string | null;
isCustom?: boolean;
}
/**
* PUT /apps/{appKey}/governance/technology-product.
*/
export interface CreateTechnologyProductRequest {
/** @maxLength 100 */
name: string;
/** @maxLength 2000 */
description: string;
businessSolutionOwner: string;
technicalProductManager: string;
}
/**
* PUT /apps/{appKey}/governance/business-owners.
*/
export interface UpdateBusinessOwnerRequest {
accountIdentifier: string;
role: string;
/** @maxLength 1000 */
reason?: string | null;
}
/**
* POST /apps/{appKey}/governance/documents.
*/
export interface AppGovernanceDocumentRequest {
content?: string | null;
type: ApiGovernanceDocumentType;
}
export interface PatchGovernanceDocumentRequest {
content?: string | null;
}
export interface ConfirmGovernanceRequest {
/** @maxLength 255 */
comment?: string | null;
}
export interface CreatePersonAppTagRequest {
tag?: string | null;
}
export interface CreateAppBuildConfigRequest {
environment?: Record<string, unknown> | null;
endpoints?: Record<string, ApiEndpointConfigRequest> | null;
}
export interface ApiEndpointConfigRequest {
url: string;
scopes?: string[] | null;
}
export interface PatchGovernanceAppRequest {
businessOwners?: UpdateBusinessOwnerRequest[] | null;
businessOwnerOrgUnit?: UpdateBusinessOwnerOrgUnitRequest | null;
classification?: DataClassificationRequest | null;
projectPhases?: UpdateProjectPhaseRequest[] | null;
projectCategories?: UpdateProjectCategoryRequest[] | null;
technologyProduct?: UpdateTechnologyProductForAppRequest | null;
supportsAllProjectPhases?: boolean | null;
supportsAllProjectCategories?: boolean | null;
properties?: PropertiesCollection | null;
}
export interface UpdateBusinessOwnerOrgUnitRequest {
sapId?: string | null;
}
export interface UpdateProjectPhaseRequest {
externalId: string;
}
export interface UpdateProjectCategoryRequest {
externalId: string;
}
export interface UpdateTechnologyProductForAppRequest {
identifier?: string | null;
}
export interface PropertiesCollection {
[key: string]: unknown;
}
Bookmarks API Endpoint Catalog
This catalog covers the verified public bookmark and favourite surface. It includes the versioned bookmark read/list endpoints and the current-user favourite flows.
Bookmark endpoints
POST /bookmarks(v1.0) →CreateBookmarkRequest→ApiBookmarkGET /bookmarks/{id}(v1.0) →ApiBookmarkGET /bookmarks/{id}(v2.0) →ApiBookmarkV2GET /bookmarks/{id}/apply(v1.0) →ApiBookmarkPayloadPATCH /bookmarks/{id}(v1.0) →PatchBookmarkRequest→ApiBookmarkDELETE /bookmarks/{id}(v1.0) →204 NoContent
Current-user bookmark list endpoints
GET /persons/me/bookmarks(v1.0) →ApiBookmark[]
OData: Filter(appKey, contextId, sourceSystem.identifier, sourceSystem.name, sourceSystem.subSystem)
GET /persons/me/bookmarks(v2.0) →ApiBookmarkV2[]
OData: Filter(appKey, contextId, sourceSystem.identifier, sourceSystem.name, sourceSystem.subSystem, isFavourite)
Favourite endpoints
POST /persons/me/bookmarks/favourites(v1.0) →AddFavouriteRequest→200 OKDELETE /persons/me/bookmarks/favourites/{bookmarkId}(v1.0) →204 NoContentHEAD /persons/me/bookmarks/favourites/{bookmarkId}(v1.0) →200 OKwhen favourite exists, otherwise404 NotFound
Authorization notes
- Bookmark reads require bookmark creator access or shared-bookmark visibility.
- Bookmark writes require bookmark creator access.
- Favourite creation additionally requires the underlying bookmark to be shared.
- Current-user list and favourite routes rely on the authenticated user identity.
Typical status codes
200 OK201 Created204 NoContent400 Bad Request401 Unauthorized403 Forbidden404 Not Found409 Conflictwhen a favourite already exists
Explicit exclusions
- Internal event publishing behavior triggered by bookmark apply events
- Non-public operational or support surfaces outside
BookmarksControllerandPersonsController
/**
* Fusion Bookmarks API — TypeScript model definitions
*
* Source of truth: Fusion.Services.Bookmarks/Controllers/
* No published NuGet API-model package — models are service-internal.
*
* All property names use camelCase to match JSON serialization.
*/
// ---------------------------------------------------------------------------
// Enums (serialized as strings)
// ---------------------------------------------------------------------------
export type FusionAccountType =
| "Employee"
| "Consultant"
| "External"
| "Local"
| "Unknown"
| "Application";
export type AccountClassification =
| "Unclassified"
| "Internal"
| "External";
// ---------------------------------------------------------------------------
// Response models
// ---------------------------------------------------------------------------
/** Returned by GET /persons/me/bookmarks (v1) and GET /bookmarks/{id} (v1). */
export interface ApiBookmark {
id: string;
name: string;
description: string | null;
isShared: boolean;
payload: unknown | null;
appKey: string;
context: ApiFusionContext | null;
createdBy: ApiPerson;
updatedBy: ApiPerson | null;
created: string;
updated: string | null;
sourceSystem: ApiSourceSystem | null;
isFavourite: boolean | null;
}
/** Same as ApiBookmark but without payload. */
export interface ApiBookmarkV2 {
id: string;
name: string;
description: string | null;
isShared: boolean;
appKey: string;
context: ApiFusionContext | null;
createdBy: ApiPerson;
updatedBy: ApiPerson | null;
created: string;
updated: string | null;
sourceSystem: ApiSourceSystem | null;
isFavourite: boolean | null;
}
/** Returned by GET /bookmarks/{id}/apply. */
export interface ApiBookmarkPayload {
id: string;
payload: unknown;
context: ApiFusionContext | null;
}
export interface ApiSourceSystem {
/** @maxLength 50 — Required when source system is provided. */
identifier: string;
/** @maxLength 100 */
name: string | null;
/** @maxLength 100 */
subSystem: string | null;
}
export interface ApiFusionContext {
id: string;
name: string;
type: string;
}
export interface ApiPerson {
azureUniqueId: string | null;
mail: string | null;
name: string;
phoneNumber: string | null;
jobTitle: string | null;
accountType: FusionAccountType;
accountClassification: AccountClassification | null;
}
// ---------------------------------------------------------------------------
// Request models
// ---------------------------------------------------------------------------
/**
* POST /bookmarks — create a bookmark.
*
* Validation (FluentValidation):
* @property name — Required, valid name format
* @property appKey — Required, valid app key format
* @property description — Valid format when present
* @property contextId — Valid Fusion context when present
* @property payload — Required, must not be null
* @property sourceSystem.identifier — Required, max 50, no script tags
* @property sourceSystem.name — Max 100, no script tags
* @property sourceSystem.subSystem — Max 100, no script tags
*/
export interface CreateBookmarkRequest {
name: string;
appKey: string;
description?: string | null;
contextId?: string | null;
payload: unknown;
isShared?: boolean;
sourceSystem?: ApiSourceSystem | null;
}
/**
* PATCH /bookmarks/{id} — update bookmark fields.
* Only properties present in the payload are applied.
*/
export interface PatchBookmarkRequest {
name?: string;
appKey?: string;
description?: string | null;
contextId?: string | null;
payload?: unknown;
isShared?: boolean;
sourceSystem?: ApiSourceSystem | null;
}
/**
* POST /persons/me/bookmarks/favourites — add a bookmark favourite for the current user.
*/
export interface AddFavouriteRequest {
bookmarkId: string;
}
Context API Endpoint Catalog
This catalog covers the verified Fusion.Services.Context HTTP surface, including the standard context/relation CRUD routes and the shared event-subscription entry point used by application callers.
Context routes
GET /contexts- List contexts.
- Supports OData
$filteronid,title,externalId,type, andvalue.*. - Supports OData
$search. - Optional query parameter:
includeDeleted. GET /contexts/{id}- Fetch one context by id.
- Honors the force/no-cache header behavior used across Fusion services.
POST /contexts- Create a new context from
NewContextRequest. PUT /contexts/{id}- Update an existing context from
UpdateContextRequest. - Optional query parameter:
includeDeleted. DELETE /contexts/{id}- Soft-delete a context and remove its relations in the same transaction.
Relation routes
GET /relations- List relations globally.
- Supports OData
$filterontypeandsource. GET /contexts/{id}/relations- List relations for a specific context.
- Supports OData
$filterontype,externalId,id,isActive,title, andvalue.*. PUT /contexts/{parentId}/relations/{targetId}- Create or update a relation using
ContextRelationRequest. DELETE /contexts/{parentId}/relations/{targetId}- Delete the relation in both directions inside one transaction.
Type and subscription routes
GET /contexts/types- List known context types as
ApiContextTypeDetails[]. PUT /subscriptions/contexts- Renew or create a context-event subscription for application callers.
- Backend integration only; requires an application token.
- Returns
ApiEventSubscriptionV1connection details for CloudEvent-style event delivery rather than context payloads. - Uses shared subscription contracts from the
Fusion.Eventspackage rather than service-local request/view-model files.
Operational/admin routes
GET /admin/contexts/types/{sourceType}/relations/types/{targetType}/missing- Admin diagnostic route for contexts that might be missing relations.
GET /admin/cache/context/{identifier}- Cache inspection endpoint.
POST /admin/cache/context/{identifier}/reset- Cache reset endpoint.
Authorization notes
- Read endpoints run behind the standard Fusion auth pipeline even where controller methods do not declare explicit policy attributes.
- Create, update, delete, and relation-mutation routes require development-environment, elevated-user, or application-user authorization.
PUT /subscriptions/contextsonly allows application callers.
Typical status codes
200 OKfor successful reads, updates, relation mutations, and subscription renewal.204 NoContentfor successful context deletion.400 BadRequestfor invalid ids, relation payloads, or unsupported subscription types.401 Unauthorized403 Forbiddenwhen caller authorization fails.404 NotFoundfor missing contexts.409 Conflictfor duplicate contexts or duplicate relations.424 FailedDependencywhen relation creation references missing dependency data.
Notes
- The service-local
assets/models.tsfile covers the context-specific entity and request DTOs. Subscription payloads are shared cross-service contracts, so they are best treated as external package types rather than redefined locally without source ownership.
/**
* Fusion Context API — TypeScript model definitions
*
* Source of truth: Fusion.Services.Context/Controllers/ViewModels/
* No published NuGet API-model package — models are service-internal.
*
* All property names use camelCase to match JSON serialization.
*/
// ---------------------------------------------------------------------------
// Response models
// ---------------------------------------------------------------------------
/** Returned by GET /contexts and GET /contexts/{id}. */
export interface ApiContextEntity {
id: string;
externalId: string;
type: ApiContextType;
value: Record<string, unknown> | null;
title: string;
source: string | null;
isActive: boolean;
isDeleted: boolean;
created: string;
updated: string | null;
}
export interface ApiContextType {
id: string;
isChildType: boolean;
parentTypeIds: string[];
}
/** Returned by GET /contexts/types — includes JSON schema for the value shape. */
export interface ApiContextTypeDetails {
id: string;
isChildType: boolean;
parentTypeIds: string[];
/** JSON schema describing the expected context value structure. Omitted when null. */
valueSchema: Record<string, unknown> | null;
}
/** Returned by GET /contexts/{id}/relations. */
export interface ApiRelatedContextEntity extends ApiContextEntity {
relationSource: string | null;
relationType: string | null;
}
/** Returned by GET /relations. */
export interface ApiContextEntityRef {
type: string | null;
source: string | null;
targetId: string;
parentId: string;
}
export interface ApiPersonIdentifier {
uniqueId: string;
mail: string | null;
}
// ---------------------------------------------------------------------------
// Request models
// ---------------------------------------------------------------------------
/**
* POST /contexts — create a new context.
*
* Validation (FluentValidation):
* @property type.id — Required, must be an existing context type
* @property title — Required, max 200 chars, no script tags
* @property externalId — Required, max 100 chars, no script tags
* @property source — Optional, max 100 chars, no script tags
*/
export interface NewContextRequest {
type: ApiContextType;
value: Record<string, unknown>;
/** @maxLength 200 */
title: string;
/** @maxLength 100 */
source?: string | null;
isActive: boolean;
/** @maxLength 100 */
externalId: string;
}
/**
* PUT /contexts/{id} — replace a context.
*
* Validation (FluentValidation):
* Same rules as NewContextRequest, plus:
* @property id — Must match the route parameter
*/
export interface UpdateContextRequest {
id: string;
/** @maxLength 100 */
externalId: string;
type: ApiContextType;
value: Record<string, unknown>;
/** @maxLength 200 */
title: string;
/** @maxLength 100 */
source?: string | null;
isActive: boolean;
}
/**
* PUT /contexts/{parentId}/relations/{targetId} — create or update a relation.
*
* Validation (FluentValidation):
* @property source — Optional, max 100 chars, no script tags
* @property type — Optional, max 150 chars, no script tags
*/
export interface ContextRelationRequest {
/** @maxLength 100 */
source?: string | null;
/** @maxLength 150 */
type?: string | null;
}
Contract Personnel API Endpoint Catalog
This catalog covers the verified public contract-personnel surface for projects, contracts, personnel, delegates, requests, role assignments, change log, recertifications, affiliate sponsors, and valid-company/domain lookups. Subscription, cache, migration, and other operational controllers remain excluded.
Project and contract endpoints
GET /projects→ApiProjectReference[]GET /projects/{projectIdentifier}/contracts(v2.0) →ApiPagedCollection<ApiContractV2>
OData: Expand(personnel, delegates)
POST /projects/{projectIdentifier}/contracts(v2.0) →AllocateContractRequest→ApiContractV2OPTIONS /projects/{projectIdentifier}/contracts→ access probe,204 NoContentGET /projects/{projectIdentifier}/contracts/{contractIdentifier}(v2.0) →ApiContractV2
OData: Expand(personnel, delegates)
PATCH /projects/{projectIdentifier}/contracts/{contractIdentifier}(v2.0) →PatchContractRequest→ApiContractV2OPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}→ access probe,204 NoContentGET /contracts(v2.0) →ApiPagedCollection<ApiContractV2>
OData: Filter(endDate), Expand(personnel, delegates)
Personnel endpoints
GET /projects/{projectIdentifier}/contracts/{contractIdentifier}/personnel(v1.0) →ApiPagedCollection<ApiPersonnel>
OData: Filter(person.mail, person.name, person.azureUniquePersonId, person.accountStatus), Expand(roleAssignments)
GET /projects/{projectIdentifier}/contracts/{contractIdentifier}/personnel(v1.1-preview) →ApiPagedCollection<ApiPersonnel>
Adds deleted-account enrichment and duplicate-account hints.
GET /projects/{projectIdentifier}/contracts/{contractIdentifier}/personnel/{personnelId}→ApiPersonnelDELETE /projects/{projectIdentifier}/contracts/{contractIdentifier}/personnel/{personnelId}→204 NoContentPATCH /projects/{projectIdentifier}/contracts/{contractIdentifier}/personnel/{personnelId}/person→PatchPersonRequest→ApiPersonnelOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/personnel→ access probe,204 NoContentOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/personnel/{personnelId}→ access probe,204 NoContentOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/personnel/{personnelId}/person→ access probe,204 NoContent
Delegate endpoints
GET /projects/{projectIdentifier}/contracts/{contractIdentifier}/delegates→ApiPagedCollection<ApiDelegate>
OData: Filter(classification, validTo)
GET /projects/{projectIdentifier}/contracts/{contractIdentifier}/delegates/{delegateId}→ApiDelegatePOST /projects/{projectIdentifier}/contracts/{contractIdentifier}/delegates→CreateDelegateRequest→ApiDelegatePATCH /projects/{projectIdentifier}/contracts/{contractIdentifier}/delegates/{delegateId}→PatchDelegateRequest→ApiDelegateDELETE /projects/{projectIdentifier}/contracts/{contractIdentifier}/delegates/{delegateId}→204 NoContentOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/delegates→ access probe,204 NoContentOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/delegates/{delegateId}→ access probe,204 NoContent
Personnel request endpoints
GET /projects/{projectIdentifier}/contracts/{contractIdentifier}/requests→ApiPagedCollection<ApiPersonnelRequest>
OData: Expand(originalPersonnel), Filter(state, category, originalPersonnelId, updated, created, person.mail, person.name, person.azureUniquePersonId)
GET /projects/{projectIdentifier}/contracts/{contractIdentifier}/requests/{requestId}→ApiPersonnelRequestPOST /projects/{projectIdentifier}/contracts/{contractIdentifier}/requests→CreatePersonnelRequestRequest→ApiPersonnelRequestDELETE /projects/{projectIdentifier}/contracts/{contractIdentifier}/requests/{requestId}→204 NoContentPOST /projects/{projectIdentifier}/contracts/{contractIdentifier}/requests/{requestId}/approve→ApiPersonnelRequestPOST /projects/{projectIdentifier}/contracts/{contractIdentifier}/requests/{requestId}/reject→RejectPersonnelRequestRequest→ApiPersonnelRequestOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/requests→ access probe,204 NoContentOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/requests/{requestId}→ access probe,204 NoContentOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/requests/{requestId}/approve→ access probe,204 NoContentOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/requests/{requestId}/reject→ access probe,204 NoContent
Role endpoints
GET /roles→ApiRole[]GET /projects/{projectIdentifier}/contracts/{contractIdentifier}/role-assignments→ApiPersonRoleAssignment[]GET /projects/{projectIdentifier}/contracts/{contractIdentifier}/personnel/{personnelId}/role-assignments→ApiRoleAssignment[]POST /projects/{projectIdentifier}/contracts/{contractIdentifier}/personnel/{personnelId}/role-assignments→AssignRoleRequest→ApiPersonRoleAssignmentDELETE /projects/{projectIdentifier}/contracts/{contractIdentifier}/personnel/{personnelId}/role-assignments/{roleAssignmentId}→204 NoContentOPTIONS /roles→ access probe,204 NoContentOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/role-assignments→ access probe,204 NoContentOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/personnel/{personnelId}/role-assignments→ access probe,204 NoContent
Change log endpoints
GET /change-log→ApiPagedCollection<ApiChangeLog>
OData: Filter(projectMaster.id, contract.id, performedBy.azureUniqueId, performedBy.name, performedBy.mail, performed, action, source), Search, Top(100), Skip
GET /projects/{projectIdentifier}/contracts/{contractIdentifier}/change-log→ApiPagedCollection<ApiChangeLog>
OData: Filter(performedBy.azureUniqueId, performedBy.name, performedBy.mail, performed, action), Search, Top(100), Skip
OPTIONS /change-log→ access probe,204 NoContentOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/change-log→ access probe,204 NoContent
Recertification endpoints
GET /recertifications→ApiPagedCollection<ApiRecertification>
OData: Filter(contractId, projectMasterId), Top(1000), Skip
GET /recertification-errors→ApiPagedCollection<ApiRecertificationError>
OData: Filter(contractId, projectMasterId), Top(1000), Skip
Affiliate sponsor endpoints
GET /projects/{projectIdentifier}/contracts/{contractIdentifier}/affiliate-sponsors→ApiPagedCollection<ApiAffiliateSponsor>GET /affiliate-sponsors→ApiPagedCollection<ApiAffiliateSponsor>
OData: Filter(contractId)
POST /affiliate-sponsors→CreateAffiliateSponsorsRequest→ApiAffiliateSponsor[]DELETE /affiliate/{affiliateAzureUniqueId}/sponsor/{sponsorAzureUniqueId}→204 NoContentOPTIONS /affiliate-sponsors→ access probe,204 NoContent
Valid company and domain endpoints
GET /projects/{projectIdentifier}/contracts/{contractIdentifier}/valid-companies→ApiValidCompany[]GET /projects/{projectIdentifier}/contracts/{contractIdentifier}/valid-companies/{companyId}→ApiValidCompanyPOST /projects/{projectIdentifier}/contracts/{contractIdentifier}/valid-companies→CreateValidCompanyRequest→ApiValidCompanyDELETE /projects/{projectIdentifier}/contracts/{contractIdentifier}/valid-companies/{companyId}→204 NoContentOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/valid-companies→ access probe,204 NoContentOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/valid-companies/{companyId}→ access probe,204 NoContentGET /projects/{projectIdentifier}/contracts/{contractIdentifier}/valid-domains→ApiValidDomain[]GET /projects/{projectIdentifier}/contracts/{contractIdentifier}/valid-domains/{domainId}→ApiValidDomainPOST /projects/{projectIdentifier}/contracts/{contractIdentifier}/valid-domains→CreateValidDomainRequest→ApiValidDomainDELETE /projects/{projectIdentifier}/contracts/{contractIdentifier}/valid-domains/{domainId}→204 NoContentOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/valid-domains→ access probe,204 NoContentOPTIONS /projects/{projectIdentifier}/contracts/{contractIdentifier}/valid-domains/{domainId}→ access probe,204 NoContent
Authorization notes
- Most routes enforce a combination of full-control, trusted-application, employee, contract-personnel, contract-responsibility, delegate-responsibility, or contract-management rules.
- Read vs write access differs significantly on contract-scoped resources, and many routes expose an
OPTIONSprobe to reveal the effective allow set. - Delegate and request approval/rejection routes are more restricted than ordinary reads.
Typical status codes
200 OK201 Created204 NoContent400 Bad Request401 Unauthorized403 Forbidden404 Not Found409 Conflict424 FailedDependencyfor upstream integration failures
Explicit exclusions
SubscriptionControllerCacheController- migration, deleted-account, and other operational/admin-only flows not needed for normal integration work
- Source also exposes public
OPTIONSprobes for excluded route families such as contract migrations and project-masters; they remain out of this skill's normal integration catalog until the full route families are documented.
/**
* Fusion Contract Personnel API — TypeScript model definitions
*
* Source of truth: Fusion.Services.ContractPersonnel/Controllers/
* No published NuGet API-model package — models are service-internal.
*
* Version scope: V2 where available (V1 ApiContract is obsolete; use ApiContractV2).
* All property names use camelCase to match JSON serialization.
*/
// ---------------------------------------------------------------------------
// Enums (serialized as strings)
// ---------------------------------------------------------------------------
export type ApiDelegateClassification = "Unknown" | "Internal" | "External";
export type ApiAzureAccountStatus = "Available" | "Deleted";
export type ApiRequestState = "Created" | "ApprovedByCompany" | "RejectedByCompany";
export type ApiRequestCategory = "NewRequest" | "ChangeRequest";
export type ApiWorkflowState = "Running" | "Canceled" | "Error" | "Completed" | "Unknown";
export type ApiWorkflowStepState = "Pending" | "Approved" | "Rejected" | "Skipped" | "Unknown";
// ---------------------------------------------------------------------------
// Core response models
// ---------------------------------------------------------------------------
/** Contract (V2) — returned by GET /projects/{projectIdentifier}/contracts/{contractIdentifier}. */
export interface ApiContractV2 {
id: string;
name: string;
contractNumber: string;
projectMasters: ApiProjectMasterV2[];
company: ApiCompany;
startDate: string;
endDate: string;
/** Expandable — omitted when null. */
equinorCompanyRepresentative: ApiPerson | null;
/** Expandable — omitted when null. */
equinorContractResponsible: ApiPerson | null;
/** Expandable — omitted when null. */
companyRepresentative: ApiPerson | null;
/** Expandable — omitted when null. */
contractResponsible: ApiPerson | null;
/** Expandable — omitted when null. */
personnel: ApiPersonnel[] | null;
/** Expandable — omitted when null. */
delegates: ApiDelegate[] | null;
}
export interface ApiPersonnel {
id: string;
contract: ApiContractReference;
person: ApiPersonnelPerson;
/** Omitted when null. */
roleAssignments: ApiRoleAssignment[] | null;
startDate: string;
endDate: string;
created: string;
createdBy: ApiPerson;
/** Omitted when null. */
updated: string | null;
/** Omitted when null. */
updatedBy: ApiPerson | null;
}
export interface ApiPersonnelPerson extends ApiPerson {
/** Account-deletion metadata; omitted when null. */
deleteInfo: ApiPersonDeleteInfo | null;
}
export interface ApiDelegate {
id: string;
classification: ApiDelegateClassification;
created: string;
validTo: string;
/** Omitted when null. */
recertifiedDate: string | null;
person: ApiPerson;
createdBy: ApiPerson;
/** Omitted when null. */
recertifiedBy: ApiPerson | null;
contract: ApiContractReference;
}
export interface ApiPersonnelRequest {
id: string;
state: ApiRequestState;
category: ApiRequestCategory;
/** Omitted when null. */
description: string | null;
startDate: string;
endDate: string;
person: ApiPerson;
/** Omitted when null. */
originalPersonnel: ApiPersonnel | null;
created: string;
/** Omitted when null. */
updated: string | null;
createdBy: ApiPerson;
/** Omitted when null. */
updatedBy: ApiPerson | null;
contract: ApiContractReference;
workflow: ApiWorkflow;
}
// ---------------------------------------------------------------------------
// Workflow
// ---------------------------------------------------------------------------
export interface ApiWorkflow {
state: ApiWorkflowState;
steps: ApiWorkflowStep[];
}
export interface ApiWorkflowStep {
id: string;
name: string;
isCompleted: boolean;
state: ApiWorkflowStepState;
/** Omitted when null. */
started: string | null;
/** Omitted when null. */
completed: string | null;
/** Omitted when null. */
dueDate: string | null;
/** Omitted when null. */
completedBy: ApiPerson | null;
/** Omitted when null. */
description: string | null;
/** Omitted when null. */
reason: string | null;
/** Omitted when null. */
previousStep: string | null;
/** Omitted when null. */
nextStep: string | null;
}
// ---------------------------------------------------------------------------
// Supporting models
// ---------------------------------------------------------------------------
export interface ApiPerson {
azureUniquePersonId: string | null;
mail: string | null;
name: string;
phone: string | null;
upn: string | null;
accountType: string | null;
accountStatus: ApiAzureAccountStatus | null;
preferredContactMail: string | null;
}
export interface ApiPersonDeleteInfo {
deletedDate: string | null;
duplicateAzureUniqueId: string | null;
duplicateUpn: string | null;
}
export interface ApiCompany {
id: string;
name: string;
}
export interface ApiContractReference {
id: string;
name: string;
contractNumber: string;
}
export interface ApiProjectMaster {
id: string;
name: string;
isPrimary: boolean;
}
export interface ApiProjectMasterV2 {
id: string;
name: string;
isPrimary: boolean;
}
export interface ApiContractProjectMaster {
externalId: string;
name: string;
contract: ApiContractReferenceV2;
isPrimary: boolean;
}
export interface ApiContractReferenceV2 {
id: string;
name: string;
contractNumber: string;
}
export interface ApiRole {
id: string;
name: string;
}
export interface ApiAccessRole {
id: string;
name: string;
}
export interface ApiRoleAssignment {
id: string;
role: ApiRole;
}
export interface ApiPersonRoleAssignment {
id: string;
person: ApiPerson;
role: ApiRole;
}
export interface ApiAffiliateSponsor {
affiliate: ApiPerson;
sponsor: ApiPerson;
created: string;
}
export interface ApiAvailableContract {
name: string;
contractNumber: string;
companyName: string;
startDate: string;
endDate: string;
}
export interface ApiProjectReference {
id: string;
name: string;
/** Deprecated on the service side; may be omitted. */
equinorTask?: ApiEquinorTask | null;
}
export interface ApiEquinorTask {
id?: string | null;
title?: string | null;
}
// ---------------------------------------------------------------------------
// Change log
// ---------------------------------------------------------------------------
export interface ApiChangeLog {
id: string;
projectMaster: ApiProjectMaster;
contract: ApiChangeLogContract;
performedBy: ApiChangeLogPerformedBy;
performed: string;
action: string;
description: string | null;
data: unknown | null;
source: string;
}
export interface ApiChangeLogContract {
id: string;
number: string;
name: string;
contractor: string;
}
export interface ApiChangeLogPerformedBy {
azureUniqueId: string;
name: string;
mail: string | null;
upn: string | null;
}
// ---------------------------------------------------------------------------
// Recertification
// ---------------------------------------------------------------------------
export interface ApiRecertification {
rowKey: string | null;
timestamp: string | null;
contractId: string | null;
contractNumber: string | null;
contractName: string | null;
projectMasterId: string | null;
projectMasterName: string | null;
recertifiedByAzureUniqueId: string | null;
recertifiedForAzureUniqueId: string | null;
status: string;
validToDate: string | null;
}
export interface ApiRecertificationError {
rowKey: string | null;
timestamp: string | null;
contractId: string | null;
contractName: string | null;
contractNumber: string | null;
errorMessage: string | null;
projectMasterId: string | null;
projectMasterName: string | null;
status: string;
}
// ---------------------------------------------------------------------------
// Misc
// ---------------------------------------------------------------------------
export interface ApiValidCompany {
id: string;
name: string;
}
export interface ApiValidDomain {
id: string;
name: string;
}
export interface ApiDeletedAccountInfo extends ApiPerson {
deleteInfo: ApiPersonDeleteInfo | null;
}
export interface ApiPeopleMigration {
migrationId: string;
fromAzureUniqueId: string;
toAzureUniqueId: string;
}
// ---------------------------------------------------------------------------
// Request/reference models
// ---------------------------------------------------------------------------
export interface PersonReference {
azureUniquePersonId?: string | null;
mail?: string | null;
}
export interface PersonnelReference {
id: string;
}
export interface AllocateContractRequest {
/** @maxLength 50 */
contractNumber: string;
equinorCompanyRepresentative?: PersonReference | null;
equinorContractResponsible?: PersonReference | null;
companyRepresentative?: PersonReference | null;
contractResponsible?: PersonReference | null;
}
export interface PatchContractRequest {
equinorCompanyRepresentative?: PersonReference | null;
equinorContractResponsible?: PersonReference | null;
companyRepresentative?: PersonReference | null;
contractResponsible?: PersonReference | null;
}
export interface CreatePersonnelRequestRequest {
description?: string | null;
startDate: string;
endDate: string;
person: PersonReference;
originalPersonnel?: PersonnelReference | null;
}
export interface RejectPersonnelRequestRequest {
reason: string;
}
export interface CreateDelegateRequest {
person: PersonReference;
validTo: string;
classification: ApiDelegateClassification;
}
export interface PatchDelegateRequest {
validTo: string;
}
export interface AssignRoleRequest {
id: string;
}
export interface PatchPersonRequest {
preferredContactMail?: string | null;
}
export interface CreateAffiliateSponsorsRequest {
affiliateId: string;
sponsorIdentifiers: string[];
}
export interface CreateValidCompanyRequest {
/** @maxLength 250 */
name: string;
}
export interface CreateValidDomainRequest {
/** @maxLength 250 */
name: string;
}
Mail API Endpoint Catalog
This catalog covers the verified Mail service surface in Fusion.Services.Mail, including public send/template routes and the management endpoints the skill explicitly supports for whitelist, status, delivery, and audit workflows.
Core mail routes
POST /mails- Create and queue a new mail from raw subject/body content.
- Caller must resolve to an application user or have scope
Fusion.Email.Send. - Optional sender overrides can be supplied through
x-fusion-sender-app-idandx-fusion-sender-object-idheaders. GET /mails/{mailId}- Fetch a mail owned by the calling application.
- Returns
202 AcceptedwithLocationandRetry-Afterheaders while the mail is still queued. POST /templates/{templateName}/mails- Create and queue a new mail from a named template.
- Returns
404 NotFoundwhen the template does not exist and424 FailedDependencywhen template expansion or delivery fails. GET /templates- List predefined mail templates.
- Supports
$expand=templateDatathrough OData expand.
App-scoped status and delivery routes
GET /apps/{senderAppId}/mails/{mailId}- Hidden from API explorer, but implemented as an explicit sender-app lookup route.
PATCH /apps/{appId}/mails/{mailId}- Hidden from API explorer.
- Updates overall mail status using
UpdateMailStatusRequest. PATCH /apps/{appId}/mails/{mailId}/recipients/{email}- Hidden from API explorer.
- Updates delivery status for a specific recipient using
UpdateMailDeliveryRequest.
Management send routes
POST /management/send-mail(API v1)- Sends a fully specified mail payload using
SendMailRequest. - Hidden from API explorer.
POST /management/send-mail(API v2)- Sends a previously created mail by
mailIdusingSendMailRequestV2. - Hidden from API explorer.
POST /management/send-mail(API v3)- Sends a previously created mail and returns per-recipient results as
ApiMailV3[]. - Uses
SendMailRequestV3.
Whitelist and audit routes
POST /management/whitelist- Register an email recipient for non-production delivery using
WhitelistRequest. GET /management/whitelist- List all whitelist entries.
GET /management/persons/me/whitelist- List whitelist entries created by the current caller.
GET /management/undelivered-mails- List undelivered mails as
ApiPagedCollection<ApiUndeliveredMail>. - Supports OData
$filter,$top, and$skip. GET /management/undelivered-mails/{id}- Fetch one undelivered mail by identifier.
GET /management/logs- List mail log entries as
ApiPagedCollection<ApiLog>. - Supports OData
$filter,$search,$top, and$skip. GET /management/apps/{appId}/logs- App-scoped log listing with the same OData support as the global logs endpoint.
Authorization notes
POST /mails,POST /templates/{templateName}/mails, andGET /mails/{mailId}require application-user context; send routes additionally allow scopeFusion.Email.Send.- Management send and whitelist/list routes use elevated-user, trusted-application, scope-based, or sender-app authorization depending on the action.
- Undelivered-mail and log endpoints can narrow results automatically when the caller lacks broad read privileges but can still prove sender/app ownership.
Typical status codes
200 OKfor successful reads, status updates, whitelist reads, and management send v3.201 Createdfor create/send routes that create or dispatch mails.202 Acceptedwhen a mail is still queued.400 BadRequestfor invalid request payloads, missing sender identifiers, or invalid status changes.401 Unauthorized403 Forbidden404 NotFoundfor missing mails, templates, or undelivered-message identifiers.424 FailedDependencyfor SMTP, queue, template, or downstream transport failures.
Notes
- Hidden
ApiExplorerroutes are still included here because they are part of the implemented HTTP surface and are relevant for integration and status-management workflows.
/**
* Fusion Mail API — TypeScript model definitions
*
* Source of truth: Fusion.Services.Mail/Controllers/
* No published NuGet API-model package — models are service-internal.
*
* All property names use camelCase to match JSON serialization.
*/
// ---------------------------------------------------------------------------
// Enums (serialized as strings in JSON for request/response payloads)
// ---------------------------------------------------------------------------
/**
* Mail delivery status.
* Wire format uses the enum names because the request models apply StringEnumConverter.
*/
export type ApiMailStatus =
| "Processing"
| "Failed"
| "PartiallySent"
| "Sent"
| "Blocked"
| "Undelivered";
export const ApiMailStatusName = {
Processing: "Processing",
Failed: "Failed",
PartiallySent: "PartiallySent",
Sent: "Sent",
Blocked: "Blocked",
Undelivered: "Undelivered",
} as const;
// ---------------------------------------------------------------------------
// Response models
// ---------------------------------------------------------------------------
/** Returned by GET /mails/{id} and POST /mails. */
export interface ApiMail {
id: string;
subject: string;
status: ApiMailStatus;
fromDisplayName?: string | null;
recipients: string[];
body?: string | null;
}
/** Recipient delivery result (V3). */
export interface ApiMailV3 {
recipient: string;
succeeded: boolean;
errorMessage: string | null;
}
export interface ApiMailTemplate {
templateName: string;
templateData?: ApiMailTemplateData[] | null;
}
export interface ApiMailTemplateData {
fileName: string;
fileContent: string;
}
export interface ApiUndeliveredMail {
id: string;
timestamp?: string | null;
from?: string | null;
recipients?: string[] | null;
subject?: string | null;
additionalInfo: ApiAdditionalInfo | null;
body?: string | null;
bodyPreview?: string | null;
sentByApp?: ApiApplication | null;
sentByPerson: ApiPerson;
}
export interface ApiWhitelist {
expires: string;
addedBy: ApiPerson;
recipient: ApiPerson;
}
export interface ApiLog {
mailId: string;
sentByApp?: ApiApplication | null;
recipients: string[];
subject: string;
status: ApiMailStatus;
timestamp?: string | null;
}
export interface ApiApplication {
applicationId: string;
}
export interface ApiAdditionalInfo {
externalId?: string | null;
}
export interface ApiPerson {
azureUniqueId?: string | null;
email?: string | null;
}
// ---------------------------------------------------------------------------
// Request models
// ---------------------------------------------------------------------------
/**
* POST /mails — create and queue a mail.
*
* Validation (FluentValidation):
* @property recipients — Required, each must be a valid email address
* @property subject — Required, not empty
* @property body — Required, not empty
* @property attachments — Total size max 3 MB (4,194,308 bytes base64)
*/
export interface NewMailRequest {
recipients: string[];
subject: string;
body: string;
fromDisplayName?: string | null;
attachments?: MailAttachmentRequest[] | null;
}
/**
* POST /mails/{id}/send — send a previously created mail.
*
* Validation:
* @property messageId — Required, not empty GUID
* @property senderAppId — Required, not empty GUID
* @property senderAzureUniqueId — Required, not empty GUID
*/
export interface SendMailRequest {
messageId: string;
senderAppId: string;
senderAzureUniqueId: string;
recipients: string[];
fromDisplayName?: string | null;
subject: string;
body: string;
}
/**
* POST /management/send-mail (API v2).
* Sends a previously created mail item.
*/
export interface SendMailRequestV2 {
mailId: string;
senderAppId: string;
senderAzureUniqueId: string;
}
/**
* POST /management/send-mail (API v3).
* Sends a previously created mail item and returns per-recipient results.
*/
export interface SendMailRequestV3 {
mailId: string;
senderAppId: string;
senderAzureUniqueId: string;
}
/**
* POST /templates/{templateName}/mails — create and queue a mail from a named template.
*
* Validation: same recipient/subject rules as NewMailRequest.
* @property mailBody.htmlContent — Required
*/
export interface NewTemplatedMailRequest {
recipients: string[];
subject: string;
attachments?: MailAttachmentRequest[] | null;
mailBody: MailBodyRequest;
}
/**
* Email content template (positional record in C#).
* @property htmlContent — Required
*/
export interface MailBodyRequest {
htmlContent: string;
htmlFooter?: string | null;
headerTitle?: string | null;
}
/**
* Mail attachment.
*
* Validation:
* @property name — Required, max 255 chars
* @property contentBytes — Required, base64-encoded, max ~4 MB
*/
export interface MailAttachmentRequest {
/** @maxLength 255 */
name: string;
contentId?: string | null;
contentType?: string | null;
/** Base64-encoded file content. */
contentBytes: string;
isInline?: boolean | null;
lastModifiedDateTime?: string | null;
size?: number | null;
}
/**
* PATCH /apps/{appId}/mails/{mailId} — update overall mail status.
*/
export interface UpdateMailStatusRequest {
status: ApiMailStatus;
}
/**
* PATCH /apps/{appId}/mails/{mailId}/recipients/{email} — update delivery status for a specific recipient.
*/
export interface UpdateMailDeliveryRequest {
status: ApiMailStatus;
}
/**
* POST /management/whitelist — whitelist a recipient.
*
* Validation:
* @property person — Must resolve to a valid user (AzureUniqueId or Mail)
* @property expiresIn — Optional, format "15d" (days)
*/
export interface WhitelistRequest {
person: PersonReference;
expiresIn?: string | null;
}
/**
* Person lookup reference.
* Either azureUniqueId or mail should be provided.
*/
export interface PersonReference {
azureUniqueId?: string | null;
mail?: string | null;
}
Notification API Endpoint Catalog
This catalog covers the verified public notification and notification-settings surface for API version 1.0.
Notification endpoints
GET /notifications/{id}→ApiNotificationV1
Expand: fallbackHtml
GET /persons/{personIdentifier}/notifications→ApiPagedCollection<ApiNotificationV1>
Supports me alias for the current user. OData: Filter(created, seen, seenByUser, sourceSystem.identifier, sourceSystem.name, sourceSystem.subSystem), OrderBy(title, created, seen, seenByUser), Top(100), Skip
POST /persons/{personIdentifier}/notifications→NewFusionNotificationRequestV1→ApiNotificationV1PATCH /notifications/{id}→PatchFusionNotificationRequestV1→ApiNotificationV1DELETE /notifications/{id}→204 NoContent
Notification settings endpoints
GET /persons/{personIdentifier}/notifications/settings→ApiNotificationSettings
Supports me alias for the current user.
PUT /persons/{personIdentifier}/notifications/settings→NotificationSettingsRequest→ApiNotificationSettings
Supports me alias for the current user.
Authorization notes
- Notification reads allow elevated users, application users, the creating application, the notification target, and selected allowlisted app identities on person-scoped list routes.
- Notification creation additionally allows callers with the
Fusion.Notifications.Writescope. - Settings routes allow elevated users, application users, and the current user on their own settings.
mealiases depend on resolving the authenticated user identity.
Typical status codes
200 OK201 Created204 NoContent400 Bad Request401 Unauthorized403 Forbidden404 Not Found
Explicit exclusions
- Internal email delivery and SignalR dispatch infrastructure behind create operations
- Non-controller internal models and persistence details
/**
* Fusion Notification API — TypeScript model definitions
*
* Source of truth: Fusion.Services.Notification/Controllers/ViewModels/
* No published NuGet API-model package — models are service-internal.
*
* All property names use camelCase to match JSON serialization.
*/
// ---------------------------------------------------------------------------
// Enums (serialized as strings)
// ---------------------------------------------------------------------------
export type ApiEmailPriorityV1 = "Unknown" | "High" | "Default" | "Low";
// ---------------------------------------------------------------------------
// Response models
// ---------------------------------------------------------------------------
/** Returned by GET /persons/{personIdentifier}/notifications and GET /notifications/{id}. */
export interface ApiNotificationV1 {
id: string;
/** Omitted when null. */
appKey: string | null;
emailPriority: ApiEmailPriorityV1;
/** Fallback HTML content; omitted when null. Populated via $expand=fallbackHtml. */
fallbackHtml: string | null;
targetAzureUniqueId: string;
title: string;
/** Adaptive Card JSON; omitted when null. */
card: Record<string, unknown> | null;
created: string;
/** Populated via $expand=createdBy. Omitted when null. */
createdBy: ApiPerson | null;
/** Populated via $expand=createdByApplication. Omitted when null. */
createdByApplication: ApiApplication | null;
seenByUser: boolean;
seen: string | null;
sourceSystem: ApiSourceSystem | null;
}
export interface ApiNotificationSettings {
email: boolean;
delayInMinutes: number;
appConfig: ApiAppNotificationSetting[];
}
export interface ApiAppNotificationSetting {
appKey: string;
displayName: string;
description: string;
enabled: boolean;
}
export interface ApiApplication {
id: string;
title: string;
}
export interface ApiPerson {
id: string;
name: string;
jobTitle: string | null;
mail: string | null;
accountType: string;
}
export interface ApiSourceSystem {
name: string | null;
subSystem: string | null;
identifier: string | null;
}
// ---------------------------------------------------------------------------
// Request models
// ---------------------------------------------------------------------------
/**
* POST /persons/{id}/notifications — create a notification.
*
* Validation (FluentValidation):
* @property title — Required, no script tags
* @property description — Required when card is null, no script tags
* @property card — Must be valid Adaptive Card JSON when present
* @property appKey — When not empty, must resolve to a valid Fusion app
* @property sourceSystem.identifier — Required when sourceSystem provided, max 50
* @property sourceSystem.name — Max 100, no script tags
* @property sourceSystem.subSystem — Max 100, no script tags
*/
export interface NewFusionNotificationRequestV1 {
appKey?: string | null;
emailPriority?: ApiEmailPriorityV1 | null;
/** Fallback HTML for email rendering. No script tags. */
fallbackHtml?: string | null;
title: string;
/** Required when card is null. No script tags. */
description?: string | null;
/** Adaptive Card JSON payload. */
card?: Record<string, unknown> | null;
sourceSystem?: ApiSourceSystem | null;
/** Override the creator identity. */
originalCreatorUniqueId?: string | null;
}
/**
* PUT /persons/{id}/notifications/settings — update notification settings.
*/
export interface NotificationSettingsRequest {
email: boolean;
delayInMinutes: number;
appConfig: AppNotificationSettingsRequest[];
}
/**
* Per-app notification setting request.
*
* Validation:
* @property appKey — Required, max 50
*/
export interface AppNotificationSettingsRequest {
/** @maxLength 50 */
appKey: string;
enabled: boolean;
}
/**
* PATCH /notifications/{id} — mark seen.
*
* Validation:
* @property seenByUser — Required (must not be null)
*/
export interface PatchFusionNotificationRequestV1 {
seenByUser: boolean;
}
People API Endpoint Catalog
This catalog covers all public, non-admin endpoints across API versions. Admin, cache, analytics, and most migration controllers are excluded. The subscription route is documented because it is public, but it is backend-only. Obsolete endpoints (V1/V2) are noted for reference.
Subscription endpoint
PUT /subscriptions/persons→ backend subscription registration/update returningApiEventSubscriptionV1
Application-token only. Use for person-change event delivery, local projection syncing, or cache invalidation rather than UI workflows.
Person profiles — Read & manage
Current versions (recommended)
GET /persons/{personId}(API v3/v4) — fetch person profile- V3: standard person with OData expansion (roles, positions, contracts, manager)
- V4: enhanced with aggregated roles/positions and companies expansion
GET /persons/me(API v3/v4) — current authenticated user's profileGET /persons/{personId}/extended-profile(API v3) — read extended profile propertiesPATCH /persons/{personId}/extended-profile(API v3) — update isPrimaryAccount and preferredContactMail
Account linking
GET /persons/{personId}/linked-accounts(API v1) — list linked accountsPOST /persons/{personId}/linked-accounts(API v1) — link two accounts (requires both in Azure AD)- Request:
AccountLinkRequestwith identifier and optional description - Response:
ApiPersonLinkwith both profiles DELETE /persons/{personId}/linked-accounts/{targetId}(API v1) — remove account link
Legacy endpoints (use v3/v4 instead)
GET /persons/{personId}(API v1/v2) — OBSOLETE, use V3 or V4GET /serviceprincipals/{servicePrincipalId}(API v1) — fetch service principal profile as V1
Search — Query & search persons
GET /persons(API v1) — OBSOLETE: simple search query string parameter- Query parameter:
query(string) - Response:
List<ApiPersonSearchResultV1> GET /persons(API v2) — OData search with better support- Query parameter:
$search(OData search syntax) - Response:
List<ApiPersonSearchResultV2>
Batch operations — Ensure, create, validate
Ensure persons exist
POST /persons/ensure(API v2/v3) — batch-resolve/ensure persons by identifiers- Request:
ValidatePersonsRequestwith list of identifiers (mail, UPN, or Azure ID) - Response:
List<ApiPersonValidationResultV2>with per-identifier status - Note: Always returns 200, check individual statusCode for each result
POST /persons/{personId}/ensure(API v1) — OBSOLETE: single-person ensure, returnsApiPersonV1
Create local persons
POST /persons(API v1) — create local (non-Azure AD) person profiles- Request:
CreatePersonsRequestwith list of name, mail, optional fields - Response:
List<ApiCreatePersonResult>with per-person creation status (201/409/500) - Note: Runs each in separate transaction for individual error handling
Application identity
POST /application/ensure(API v1) — ensure current app/service principal exists- Requires:
AppOnlyauthorization policy - Response:
ApiPersonV1for the authenticated app identity
People Picker — Search & resolve (Graph-backed)
POST /people-picker/suggestions— search for persons and service principals- Request:
PeoplePickerQueryRequestwith queryString and optional type filter ("Person" or "SystemAccount") - Response:
ApiPagedCollection<ApiPeoplePickerAccount>(top 10 results with avatars and SAS-signed avatar URLs) POST /people-picker/resolve— batch-resolve persons/service principals by identifiers- Request:
ResolveProfilesRequestwith list of identifiers (mail, UPN, or Azure ID, max 100) - Response:
List<ApiPeoplePickerProfile>with per-identifier resolution status and details GET /people-picker/persons/{personId}/avatar— retrieve person's avatar as SVG- Requires SAS token authentication (provided in search results)
- Response: SVG image (64x64) or SVG with initials if no photo available
Presence & awareness
GET /persons/{personId}/presence(API v1) — fetch user's current presence status- Response:
ApiPresenceV1with availability and activity POST /persons/presences— batch-fetch presence for multiple users
Maintenance — Cache & state
Cache reset (elevated permissions required)
POST /persons/me/reset(API v1) — reset cache for current user- Scope: "Fusion.CacheControl.Manage" OR current user
- Response: 200 OK when complete
POST /persons/{personId}/reset(API v1) — reset cache for specific person- Scope: "Fusion.CacheControl.Manage" OR the target user
- Response: 200 OK when complete
Legacy & special operations
Group membership verification (legacy)
POST /persons/{personId}/checkMemberGroups(API v1) — check if person is member of Azure AD groups- Legacy endpoint, not recommended for new integrations
Authorization notes
- Most endpoints require elevated-user or trusted-application checks through fluent authorization
- People Picker endpoints often require valid current-user identity but some use Graph direct access
- Cache reset requires elevated permissions or the resource owner's identity
- Photo/avatar retrieval uses SAS token-based access
Typical status codes
200 OK— successful read or operation201 Created— successful creation (POST /persons)204 NoContent— delete successful400 Bad Request— invalid identifiers, missing required fields, or malformed payloads401 Unauthorized— authentication missing or invalid403 Forbidden— authorization requirement not met404 Not Found— person/resource does not exist409 Conflict— duplicate linked-account or resource already exists424 FailedDependency— upstream service (Graph, search) failure
Excluded endpoints
AdminController— admin-only operationsCacheController,AnalyticsController— internal operationsMigrationController— operational migration routes; source also exposesOPTIONS /persons/migrationsfor elevated migration capability checks- Obsolete API versions (V1/V2 for most queries, use V3/V4)
- Roles-related endpoints (managed by RolesV2 API)
/**
* Fusion People API — TypeScript model definitions
*
* Source of truth: Fusion.Services.People.ApiModels (NuGet package, multi-target net8.0/net9.0/net10.0)
* and Fusion.Services.People controller view-models.
*
* Version scope: V3/V4 are the primary target, but legacy V1/V2 DTOs are included for compatibility with still-exposed routes.
* All property names use camelCase to match the JSON serialization output.
*/
// ---------------------------------------------------------------------------
// Enums (serialized as strings)
// ---------------------------------------------------------------------------
export type ApiProfileAccountType =
| "Employee"
| "Consultant"
| "External"
| "Local"
| "Unknown"
| "Application"
| "Admin"
| "System";
export type ApiAccountClassification =
| "Unclassified"
| "Internal"
| "External";
export type ApiInvitationStatus =
| "Accepted"
| "Pending"
| "NotSent";
export type ApiGraphPresenceAvailability =
| "Available"
| "AvailableIdle"
| "Away"
| "BeRightBack"
| "Busy"
| "BusyIdle"
| "DoNotDisturb"
| "Offline"
| "PresenceUnknown";
export type ApiGraphPresenceActivity =
| "Available"
| "Away"
| "BeRightBack"
| "Busy"
| "DoNotDisturb"
| "InACall"
| "InAConferenceCall"
| "Inactive"
| "InAMeeting"
| "Offline"
| "OffWork"
| "OutOfOffice"
| "PresenceUnknown"
| "Presenting"
| "UrgentInterruptionsOnly";
export type ApiRoleType =
| "Global"
| "Scoped"
| "GlobalScoped";
export type ApiPeoplePickerAccountType =
| "Unknown"
| "Person"
| "SystemAccount";
export type ApiCompanyAccountType =
| "Unknown"
| "Employee"
| "Consultant"
| "Intern"
| "OnContract"
| "Extern"
| "AcceptingGuest"
| "ExternalHire";
export type ApiServicePrincipalType =
| "Unknown"
| "Application"
| "ManagedIdentity"
| "ServicePrincipal";
// ---------------------------------------------------------------------------
// Core person model (V3)
// ---------------------------------------------------------------------------
/** Returned by GET /persons/{id} (API v1.0). Legacy, but still used by some endpoints. */
export interface ApiPersonV1 {
fusionPersonId: string;
azureUniqueId?: string | null;
mail?: string | null;
name: string;
department?: string | null;
fullDepartment?: string | null;
mobilePhone?: string | null;
officeLocation?: string | null;
upn?: string | null;
sapId?: string | null;
employeeId?: string | null;
employeeType?: string | null;
orgUnitId?: string | null;
isResourceOwner?: boolean | null;
isExpired?: boolean | null;
expiredDate?: string | null;
isPrimaryAccount?: boolean | null;
preferredContactMail?: string | null;
accountType: ApiProfileAccountType;
invitationStatus?: ApiInvitationStatus | null;
accountClassification: ApiAccountClassification;
managerAzureUniqueId?: string | null;
linkedAccounts?: ApiProfileAccountLink[] | null;
}
/** Returned by GET /persons/{id} (API v2.0). Legacy alias of the V1-compatible shape. */
export interface ApiPersonV2 extends ApiPersonV1 {
}
/** Returned by GET /persons/{id} (API version 3.0). */
export interface ApiPersonV3 {
azureUniqueId: string | null;
name: string;
mail: string | null;
jobTitle: string | null;
department: string | null;
fullDepartment: string | null;
mobilePhone: string | null;
officeLocation: string | null;
upn: string | null;
sapId: string | null;
employeeId: string | null;
orgUnitId: string | null;
isPrimaryAccount: boolean | null;
preferredContactMail: string | null;
isResourceOwner: boolean | null;
isExpired: boolean | null;
expiredDate: string | null;
accountType: ApiProfileAccountType;
company: ApiCompanyInfo | null;
invitationStatus: ApiInvitationStatus | null;
accountClassification: ApiAccountClassification;
managerAzureUniqueId: string | null;
linkedAccounts: ApiProfileAccountLink[] | null;
/** Expandable — only populated when $expand=roles is requested. */
roles: ApiPersonRole[] | null;
/** Expandable — only populated when $expand=positions is requested. */
positions: ApiPersonPosition[] | null;
/** Expandable — only populated when $expand=contracts is requested. */
contracts: ApiPersonContract[] | null;
/** Expandable — only populated when $expand=manager is requested. */
manager: ApiManager | null;
}
/** Main person DTO returned by GET /persons/{id} (API version 4.0). */
export interface ApiPersonV4 {
azureUniqueId?: string | null;
mail?: string | null;
name: string;
jobTitle?: string | null;
department?: string | null;
fullDepartment?: string | null;
mobilePhone?: string | null;
officeLocation?: string | null;
upn?: string | null;
/** Unique identifier in SAP. */
sapId?: string | null;
/** Employee ID, aligned with AD model. */
employeeId?: string | null;
employeeType?: string | null;
/** Unique identifier for org unit in SAP. */
orgUnitId?: string | null;
isResourceOwner?: boolean | null;
isExpired?: boolean | null;
expiredDate?: string | null;
isPrimaryAccount?: boolean | null;
preferredContactMail?: string | null;
accountType: ApiProfileAccountType;
invitationStatus?: ApiInvitationStatus | null;
accountClassification: ApiAccountClassification;
managerAzureUniqueId?: string | null;
/** List of companies associated with person's contracts. Only populated when $expand=companies is requested. */
companies?: ApiCompanyInfoV4[] | null;
/** Expandable — only populated when $expand=roles is requested. */
roles?: ApiPersonRoleV4[] | null;
/** Expandable — only populated when $expand=positions is requested. */
positions?: ApiPersonPositionV4[] | null;
/** Expandable — only populated when $expand=contracts is requested. */
contracts?: ApiPersonContractV4[] | null;
manager?: ApiManager | null;
linkedAccounts?: ApiProfileAccountLink[] | null;
}
// ---------------------------------------------------------------------------
// Supporting person models
// ---------------------------------------------------------------------------
export interface ApiCompanyInfo {
id: string;
name: string;
}
export interface ApiCompanyInfoV4 {
id: string;
name?: string | null;
}
export interface ApiProfileAccountLink {
azureUniqueId: string;
mail: string | null;
isPrimaryAccount: boolean | null;
preferredContactMail: string | null;
isExpired: boolean | null;
upn: string | null;
}
export interface ApiManager {
azureUniqueId: string;
name: string;
mail: string | null;
department: string | null;
fullDepartment: string | null;
upn: string | null;
jobTitle: string | null;
accountType: ApiProfileAccountType;
accountClassification: ApiAccountClassification;
}
export interface ApiProfileRef {
azureUniqueId: string | null;
name: string;
mail: string | null;
jobTitle: string | null;
department: string | null;
fullDepartment: string | null;
mobilePhone: string | null;
officeLocation: string | null;
upn: string | null;
accountType: ApiProfileAccountType;
accountClassification: ApiAccountClassification;
}
export interface ApiPersonLink {
targetAzureUniqueId: string;
createdByAzureUniqueId: string;
description: string | null;
created: string;
target: ApiProfileRef | null;
createdBy: ApiProfileRef | null;
}
export interface ApiExtendedProfile {
preferredContactMail: string | null;
isPrimaryAccount: boolean | null;
}
// ---------------------------------------------------------------------------
// Roles
// ---------------------------------------------------------------------------
export interface ApiPersonRole {
name: string;
displayName: string | null;
sourceSystem: string | null;
/** "Global" | "Scoped" | "GlobalScoped" */
type: ApiRoleType;
isActive: boolean;
activeToUtc: string | null;
onDemandSupport: boolean;
scope: ApiPersonRoleScope | null;
}
export interface ApiPersonRoleScope {
type: string;
value: string | null;
valueType: string | null;
}
export interface ApiRoleDefinition {
name: string;
displayName: string | null;
type: ApiRoleType;
scopeType: string | null;
description: string | null;
onDemandSupport: boolean;
sourceSystem: string | null;
}
// ---------------------------------------------------------------------------
// V4-specific role and position models
// ---------------------------------------------------------------------------
/** Role model in V4 — supports aggregated scopes. */
export interface ApiPersonRoleV4 {
name: string;
displayName: string;
sourceSystem?: string | null;
type: string; // "Global" | "Scoped" | "GlobalScoped"
isActive: boolean;
activeToUtc?: string | null;
onDemandSupport: boolean;
scopes?: ApiPersonRoleScopeV4[] | null;
}
/** Role scope aggregation in V4. */
export interface ApiPersonRoleScopeV4 {
type: string;
values?: string[] | null;
valueType?: string | null;
}
// ---------------------------------------------------------------------------
// Positions & contracts
// ---------------------------------------------------------------------------
export interface ApiPersonPosition {
positionId: string;
positionExternalId: string | null;
id: string;
parentPositionId: string | null;
taskOwnerIds: string[] | null;
name: string | null;
obs: string | null;
basePosition: ApiPersonBasePosition;
project: ApiPersonProject;
isTaskOwner: boolean;
appliesFrom: string | null;
appliesTo: string | null;
workload: number | null;
}
export interface ApiPersonBasePosition {
id: string;
name: string;
type: string;
discipline: string | null;
}
export interface ApiPersonProject {
id: string;
name: string;
domainId: string | null;
type: string;
}
export interface ApiPersonContract {
id: string;
name: string;
contractNumber: string;
companyId: string | null;
companyName: string;
project: ApiPersonProject;
projectMaster: ApiProjectMaster;
positions: ApiContractPosition[];
}
export interface ApiContractPosition {
positionId: string;
id: string;
name: string;
externalPositionId: string | null;
obs: string | null;
appliesFrom: string | null;
appliesTo: string | null;
workload: number | null;
basePosition: ApiPersonBasePosition;
}
export interface ApiProjectMaster {
id: string;
name: string;
}
// ---------------------------------------------------------------------------
// V4-specific position and contract models
// ---------------------------------------------------------------------------
/** Position model in V4 (enhanced with additional metadata). */
export interface ApiPersonPositionV4 {
positionId: string;
positionExternalId?: string | null;
id: string;
parentPositionId?: string | null;
taskOwnerIds?: string[] | null;
name: string;
obs?: string | null;
type: string;
basePosition: ApiPersonBasePositionV4;
project?: ApiPersonProjectV4 | null;
isTaskOwner: boolean;
isProjectManagementTeam: boolean;
appliesFrom?: string | null;
appliesTo?: string | null;
workload?: number | null;
}
/** Base position model in V4. */
export interface ApiPersonBasePositionV4 {
id: string;
name?: string | null;
type?: string | null;
discipline?: string | null;
}
/** Project reference in V4. */
export interface ApiPersonProjectV4 {
id: string;
name: string;
domainId?: string | null;
type: string;
}
/** Contract model in V4 (with aggregated positions). */
export interface ApiPersonContractV4 {
id: string;
name: string;
contractNumber: string;
companyId?: string | null;
companyName?: string | null;
project: ApiPersonProjectV4;
projectMaster?: ApiProjectMaster | null;
positions: ApiContractPositionV4[];
}
/** Contract position model in V4. */
export interface ApiContractPositionV4 {
positionId: string;
id: string;
name: string;
externalPositionId?: string | null;
obs?: string | null;
appliesFrom?: string | null;
appliesTo?: string | null;
workload?: number | null;
basePosition: ApiPersonBasePositionV4;
}
// ---------------------------------------------------------------------------
// Presence
// ---------------------------------------------------------------------------
/** Returned by GET /persons/{id}/presence (V1). */
export interface ApiPresenceV1 {
id: string;
availability: ApiGraphPresenceAvailability;
activity: ApiGraphPresenceActivity | null;
}
// ---------------------------------------------------------------------------
// Search
// ---------------------------------------------------------------------------
/** Returned by GET /persons (search, V2). */
export interface ApiPersonSearchResultV2 {
azureUniqueId: string | null;
mail: string | null;
preferredContactMail: string | null;
name: string;
jobTitle: string | null;
department: string | null;
fullDepartment: string | null;
mobilePhone: string | null;
officeLocation: string | null;
upn: string | null;
employeeId: string | null;
orgUnitId: string | null;
isResourceOwner: boolean;
accountType: ApiProfileAccountType;
accountClassification: ApiAccountClassification;
}
/** Returned by GET /persons (search, V1). OBSOLETE - use V2. */
export interface ApiPersonSearchResultV1 {
fusionPersonId?: string | null;
azureUniquePersonId?: string | null;
name: string;
jobTitle?: string | null;
department?: string | null;
fullDepartment?: string | null;
mail?: string | null;
preferredContactMail?: string | null;
company?: string | null;
mobilePhone?: string | null;
officeLocation?: string | null;
userPrincipalName?: string | null;
employeeId?: string | null;
employeeType?: string | null;
orgUnitId?: string | null;
isResourceOwner: boolean;
accountType?: string | null;
accountClassification?: string | null;
isFusionLocal: boolean;
isAffiliateAccess: boolean;
}
// ---------------------------------------------------------------------------
// Group membership
// ---------------------------------------------------------------------------
export interface ApiGroupMembership {
azureAdGroupId: string | null;
isMember: boolean;
}
// ---------------------------------------------------------------------------
// Batch operations - Ensure/Create/Validate
// ---------------------------------------------------------------------------
/** Result from POST /persons/ensure (v2 and v3). Batch response for ensure operation. */
export interface ApiPersonValidationResultV2 {
success: boolean;
statusCode: number;
message?: string | null;
identifier: string;
person?: ApiPersonV1 | null;
}
/** Result from POST /persons (v1). Batch response for create local persons operation. */
export interface ApiCreatePersonResult {
person?: ApiPersonV1 | null;
success: boolean;
statusCode: number;
message?: string | null;
identifier: string;
index: number;
}
// ---------------------------------------------------------------------------
// Request models for person operations
// ---------------------------------------------------------------------------
/** Request for POST /persons/ensure — ensure multiple persons exist in the database. */
export interface ValidatePersonsRequest {
/**
* List of identifiers (Azure Unique ID, mail, or UPN) to resolve/ensure.
*/
personIdentifiers: string[];
}
/** Request for POST /persons — create local (non-AD) persons in the database. */
export interface CreatePersonsRequest {
persons: NewPersonRequest[];
}
export interface NewPersonRequest {
mail: string;
name: string;
jobTitle?: string | null;
mobilePhone?: string | null;
officeLocation?: string | null;
}
/** Request for POST /persons/{personId}/linked-accounts — link two person accounts. */
export interface AccountLinkRequest {
/**
* The identifier to link to (mail or Azure Unique ID).
* Must resolve to an Azure AD account.
* Maximum 100 characters.
*/
identifier: string;
/**
* Optional description for the link (max 1000 characters, no scripts).
*/
description?: string | null;
/**
* Optional — Allow overriding the creator (applications only).
*/
createdByAzureUniqueId?: string | null;
}
// ---------------------------------------------------------------------------
// People Picker — Search & resolve persons and service principals
// ---------------------------------------------------------------------------
/** Request for searching persons and service principals. */
export interface PeoplePickerQueryRequest {
/** Query string for searching (required). */
queryString?: string;
/** Optional filter by account type (e.g., "Person", "SystemAccount"). */
types?: string[];
}
/** Request for resolving persons and service principals by identifiers. */
export interface ResolveProfilesRequest {
/**
* List of Azure Unique IDs, Email addresses, or User Principal Names (UPNs) to resolve.
* Maximum 100 identifiers per request.
*/
identifiers: string[];
}
/** Account found in people picker search results. */
export interface ApiPeoplePickerAccount {
azureUniqueId: string;
name: string;
accountType: ApiPeoplePickerAccountType;
accountLabel: string;
/** Person details (when accountType is "Person"). */
person?: ApiPeoplePickerPerson | null;
/** Application details (when accountType is "SystemAccount" and it's an app). */
application?: ApiPeoplePickerApplication | null;
avatarColor: string;
avatarUrl: string;
isExpired: boolean;
}
/** Person details from people picker search results. */
export interface ApiPeoplePickerPerson {
accountType: ApiCompanyAccountType;
jobTitle?: string | null;
department?: string | null;
fullDepartment?: string | null;
employeeNumber?: string | null;
employeeType?: string | null;
managerAzureUniqueId?: string | null;
mail?: string | null;
preferredMail?: string | null;
upn?: string | null;
mobilePhone?: string | null;
}
/** Application details from people picker search results. */
export interface ApiPeoplePickerApplication {
applicationId: string;
applicationName?: string | null;
servicePrincipalType: ApiServicePrincipalType;
}
/** Resolved account result from people picker resolve endpoint. */
export interface ApiPeoplePickerProfile {
success: boolean;
statusCode: number;
errorMessage?: string | null;
identifier: string;
account?: ApiPeoplePickerResolvedAccount | null;
}
/** Resolved account details (similar to ApiPeoplePickerAccount but for resolve endpoint). */
export interface ApiPeoplePickerResolvedAccount {
azureUniqueId?: string | null;
name: string;
accountType: ApiPeoplePickerAccountType;
accountLabel: string;
/** Person details (when accountType is "Person"). */
person?: ApiPeoplePickerResolvedPerson | null;
/** Application details (when accountType is "SystemAccount" and it's an app). */
application?: ApiPeoplePickerApplication | null;
avatarColor: string;
avatarUrl: string;
isExpired: boolean;
}
/** Person details from people picker resolve endpoint (same as ApiPeoplePickerPerson). */
export interface ApiPeoplePickerResolvedPerson {
accountType: ApiCompanyAccountType;
jobTitle?: string | null;
department?: string | null;
fullDepartment?: string | null;
employeeNumber?: string | null;
employeeType?: string | null;
managerAzureUniqueId?: string | null;
mail?: string | null;
preferredMail?: string | null;
upn?: string | null;
mobilePhone?: string | null;
}
Portal Config API Endpoint Catalog
This catalog covers the verified public configuration surface for portals, templates, categories, tags, bundles, and settings. Authentication, discovery, and operational admin routes are excluded.
Portal endpoints
GET /portals→ApiPagedCollection<ApiPortalListItem>GET /portals/{portalIdentifier}→ApiPortalGET /portals/{portalIdentifier}@{versionIdentifier}→ApiPortalPOST /portals→CreatePortalRequest→ApiPortalPATCH /portals/{portalIdentifier}→PatchPortalRequest→ApiPortalDELETE /portals/{portalIdentifier}→204 NoContentPOST /portals/{portalIdentifier}/restore→ApiPortal
Portal config and template-build endpoints
PUT /portals/{portalIdentifier}@{versionIdentifier}/config→ portal config request body →ApiPortalConfigVersionGET /portals/{portalIdentifier}@{configIdentifier}/config→ApiPortalConfigVersionGET /portals/{portalIdentifier}/template-builds→ApiPortalTemplateVersionListItem[]
Portal app composition endpoints
POST /portals/{portalIdentifier}/apps→AddPortalAppRequest→ApiPortalGET /portals/{portalIdentifier}/apps→ApiPortalAppListItem[]DELETE /portals/{portalIdentifier}/apps/{appKey}→204 NoContent
Portal tag endpoints
GET /portals/{portalIdentifier}/tags→ApiPortalTag[]PUT /portals/{portalIdentifier}/tags/{tagName}→CreatePortalTagRequest→ApiPortalTagDELETE /portals/{portalIdentifier}/tags/{tagName}→204 NoContent
Template endpoints
GET /templates→ApiPagedCollection<ApiTemplateListItem>GET /templates/{templateIdentifier}→ApiTemplatePOST /templates→CreatePortalTemplateRequest→ApiTemplatePATCH /templates/{templateIdentifier}→PatchPortalTemplateRequest→ApiTemplateDELETE /templates/{templateIdentifier}→204 NoContentGET /templates/{templateIdentifier}/builds→ApiPagedCollection<ApiTemplateVersionListItem>GET /templates/{templateIdentifier}/builds/{versionIdentifier}→ApiTemplateVersion
Template tag endpoints
GET /templates/{templateIdentifier}/tags→ApiTemplateTag[]PUT /templates/{templateIdentifier}/tags/{tagName}→CreatePortalTemplateTagRequest→ApiTemplateTagDELETE /templates/{templateIdentifier}/tags/{tagName}→204 NoContent
Template bundle endpoints
POST /bundles/templates/{templateIdentifier}→ bundle upload/create responseGET /bundles/templates/{templateIdentifier}@{versionIdentifier}→ bundle metadata / redirect targetGET /bundles/templates/{templateIdentifier}/{versionIdentifier}→ bundle metadata / redirect targetGET /bundles/templates/{templateIdentifier}@{versionIdentifier}/{**resource}→ static bundle resourceGET /bundles/templates/{templateIdentifier}/{versionIdentifier}/{**resource}→ static bundle resource
Category endpoints
GET /categories→ApiPagedCollection<ApiCategory>GET /categories/{categoryIdentifier}→ApiCategoryPOST /categories→CreateCategoryRequest→ApiCategoryPATCH /categories/{categoryIdentifier}→PatchCategoryRequest→ApiCategoryDELETE /categories/{categoryIdentifier}→204 NoContent
Settings endpoint
GET /settings→ApiGlobalSettings
Authorization notes
- Read routes are generally available to authenticated callers.
- Writes typically require portal full control, trusted application access, or portal/template admin rights.
- Tag, app-composition, and restore routes are more restricted than plain reads.
- Bundle upload routes are privileged; bundle fetch routes are intended for portal runtime/resource consumption.
Typical status codes
200 OK201 Created204 NoContent400 Bad Request401 Unauthorized403 Forbidden404 Not Found409 Conflict410 Gone
Explicit exclusions
- Authentication/token routes
- Portal shell/browser controllers
- Admin/discovery/cache operations
- HTTP
OPTIONScapability probes
/**
* Fusion Portal Config API — TypeScript model definitions
*
* Source of truth: Fusion.Services.PortalConfig/Controllers/ViewModels/
* No published NuGet API-model package — models are service-internal.
*
* All property names use camelCase to match JSON serialization.
*/
// ---------------------------------------------------------------------------
// Response models — Portals
// ---------------------------------------------------------------------------
/** Returned by GET /portals/{id}. */
export interface ApiPortal {
id: string;
name: string;
displayName: string;
description: string;
isDeleted: boolean | null;
template: ApiPortalTemplate;
category: ApiCategory | null;
visualization: ApiVisualization | null;
admins: ApiAccount[];
build: ApiPortalConfigVersion | null;
}
/** Returned by GET /portals (list). */
export interface ApiPortalListItem {
id: string;
name: string;
displayName: string;
description: string;
isDeleted: boolean | null;
template: ApiPortalTemplate | null;
category: ApiCategory | null;
visualization: ApiVisualization | null;
admins: ApiAccount[] | null;
}
export interface ApiPortalConfigVersion {
version: string;
tags: string[];
templateEntry: string;
schemaEntry: string;
assetPath: string;
configUrl: string;
timestamp: string | null;
commitSha: string | null;
githubRepo: string | null;
projectPage: string | null;
annotations: Record<string, unknown>;
uploadedDate: string;
uploadedBy: ApiAccount;
schema: Record<string, unknown> | null;
config: Record<string, unknown> | null;
}
export interface ApiPortalTag {
tagName: string | null;
version: string | null;
}
export interface ApiPortalAppListItem {
appKey: string;
}
export interface ApiPortalTemplateVersionListItem {
version: string;
build: ApiTemplateVersionListItem;
isConfigured: boolean;
config: ApiPortalConfigVersionListItem;
}
export interface ApiPortalConfigVersionListItem {
tags: string[];
lastModifiedDate: string;
}
// ---------------------------------------------------------------------------
// Response models — Templates
// ---------------------------------------------------------------------------
export interface ApiPortalTemplate {
id: string;
name: string;
displayName: string;
description: string;
isDeleted: boolean | null;
}
/** Returned by GET /templates/{id}. */
export interface ApiTemplate {
id: string;
name: string;
displayName: string;
description: string;
isDeleted: boolean | null;
admins: ApiAccount[];
tags: ApiTemplateTag[];
}
export interface ApiTemplateListItem {
id: string;
name: string | null;
displayName: string | null;
description: string | null;
isDeleted: boolean | null;
admins: ApiAccount[] | null;
tags: ApiTemplateTag[] | null;
}
export interface ApiTemplateVersion {
id: string;
version: string;
tags: string[];
templateEntry: string;
schemaEntry: string;
assetPath: string;
portalTemplateId: string;
portalTemplateName: string;
timestamp: string | null;
commitSha: string | null;
githubRepo: string | null;
projectPage: string | null;
annotations: Record<string, unknown>;
uploadedDate: string;
uploadedBy: ApiAccount | null;
}
export interface ApiTemplateVersionListItem {
portalTemplateName: string;
tags: string[];
uploadedDate: string;
}
export interface ApiTemplateTag {
tagName: string | null;
version: string | null;
}
// ---------------------------------------------------------------------------
// Response models — Supporting
// ---------------------------------------------------------------------------
export interface ApiCategory {
id: string;
name: string;
displayName: string;
}
export interface ApiAccount {
azureUniqueId: string;
displayName: string;
mail: string | null;
upn: string | null;
accountType: string;
accountClassification: string | null;
isExpired: boolean;
}
export interface ApiVisualization {
color: string | null;
icon: string | null;
sortOrder: number;
}
// ---------------------------------------------------------------------------
// Response models — Settings / CSP
// ---------------------------------------------------------------------------
export interface ApiGlobalSettings {
contentSecurityPolicy: ApiContentSecurityPolicy;
}
export interface ApiContentSecurityPolicy {
reportOnly: boolean;
reportUrl: string;
directives: ApiDirective[];
}
export interface ApiDirective {
name: string;
allowedSources: ApiAllowedSource[];
}
export interface ApiAllowedSource {
source: string;
}
// ---------------------------------------------------------------------------
// Request models
// ---------------------------------------------------------------------------
/**
* POST /portals — create a portal.
*
* Validation:
* @property name — Required, 3–50 chars, URL-safe
* @property displayName — Required
* @property template — Required (valid template ID)
* @property admins — At least one valid account
*/
export interface CreatePortalRequest {
/** @minLength 3 @maxLength 50 — URL-safe */
name: string;
displayName: string;
description?: string;
template: string;
category?: string | null;
visualization?: ApiVisualization | null;
admins: string[];
}
export interface PatchVisualizationRequest {
color?: string | null;
icon?: string | null;
sortOrder?: number | null;
}
export interface PatchPortalRequest {
description?: string | null;
displayName?: string | null;
template?: string | null;
category?: string | null;
visualization?: PatchVisualizationRequest | null;
admins?: string[] | null;
}
export interface CreatePortalTemplateRequest {
/** @minLength 3 @maxLength 50 — URL-safe */
name: string;
displayName: string;
description?: string | null;
admins: string[];
}
export interface PatchPortalTemplateRequest {
description?: string | null;
displayName?: string | null;
admins?: string[] | null;
}
export interface AddPortalAppRequest {
/** @minLength 3 @maxLength 50 — must be a valid app key */
appKey: string;
}
export interface CreatePortalTagRequest {
/** @maxLength 250 */
version: string;
}
export interface CreatePortalTemplateTagRequest {
/** @maxLength 250 */
version: string;
}
export interface CreateCategoryRequest {
/** @minLength 3 @maxLength 50 */
name: string;
/** @minLength 3 @maxLength 100 */
displayName: string;
}
export interface PatchCategoryRequest {
/** @minLength 3 @maxLength 100 */
displayName?: string | null;
}
Reports API Endpoint Catalog
This catalog covers the verified public report, config, token, markdown-content, group-query, and schema surface. Explicit admin helper routes remain excluded.
Report query endpoints
GET /reports→ApiReport[]
OData: Filter(title, ownedBy, publishedBy, isEmbedOnly, globalIdentifier), Search, Top
GET /reports/{id}→ApiReportOPTIONS /reports/{id}→ access probe,204 NoContentOPTIONS /reports/{id}/contexts/{contextExternalId}/contexttypes/{contextType}/checkaccess→ advanced context-membership access probe,204 NoContentGET /reports/{id}/rlsrequirements→ markdown/string responseGET /reports/{id}/auditlog→ApiAuditLog
OData: Expand(requests)
Report config and token endpoints
GET /reports/{id}/config→ApiConfigGET /reports/{id}/config/embedinfo→ApiEmbedConfigGET /reports/{id}/token→ApiAccessTokenPOST /reports/config/validate→ValidateConfigRequest→ApiConfigValidationPUT /reports/{id}/config→UpdateConfigRequest→ApiConfigValidationPUT /reports/{id}/config/generic→ generic JSON body →GenericConfig
Report markdown content endpoints
GET /reports/{id}/description/content→ markdown/string responseGET /reports/{id}/accessdescription/content→ markdown/string responseGET /reports/{id}/technicaldocument/content→ markdown/string responseGET /reports/{id}/securitypermission/content→ markdown/string responsePUT /reports/{id}/description/content→UpdateMarkdownRequest→ markdown/string responsePUT /reports/{id}/accessdescription/content→UpdateMarkdownRequest→ markdown/string responsePUT /reports/{id}/technicaldocument/content→UpdateMarkdownRequest→ markdown/string responsePUT /reports/{id}/securitypermission/content→UpdateMarkdownRequest→ markdown/string response
Report lifecycle endpoints
POST /reports→CreateReportRequest→ApiReportPUT /reports/{id}→UpdateReportRequest→ApiReportPUT /reports/{id}/publish→truePUT /reports/{id}/unpublish→trueDELETE /reports/{id}→200 OK
Group and schema endpoints
GET /groups/query→ApiGroup[]
OData: Search
GET /public/schemas/{type}→ JSON schema document
Verified schema key: rls-configuration.json
Authorization notes
- Main report routes require the reports API policy and often an additional ownership, admin-role, or embed-authorization check.
- Read access to a report is not identical to CRUD access;
OPTIONSroutes expose the effective capabilities. - Token issuance performs embed validation and may fail on Power BI or RLS dependency issues.
groups/queryallows employees, consultants, elevated users, application users, and callers withFusion.Reports.Groups.Read./public/**schema routes are anonymous.
Typical status codes
200 OK201 Created204 NoContentfor capability checks or empty markdown content400 Bad Request401 Unauthorized403 Forbidden404 Not Found409 Conflict424 FailedDependencyfor Power BI and related downstream dependency failures500 InternalServerError
Explicit exclusions
POST /admin/reports/{id}/config/hashAdminController-only operational/dataflow endpoints
/**
* Fusion Reports API — TypeScript model definitions
*
* Source of truth: Fusion.Services.Reports/Controllers/ViewModels/
* No published NuGet API-model package — models are service-internal.
*
* All property names use camelCase to match JSON serialization.
*/
// ---------------------------------------------------------------------------
// Enums (serialized as strings)
// ---------------------------------------------------------------------------
export type ApiReportType = string;
export type ApiConfigType = string;
export type ApiEmbedType = string;
export type ApiPowerBITokenType = string;
export type ApiSecurityRequirementCheckType = string;
export type ApiRequirementType = string;
export type ApiGroupType = string;
export type ApiItemValidationType = "Error" | "Warning" | "Info";
// ---------------------------------------------------------------------------
// Core response models
// ---------------------------------------------------------------------------
/** Returned by GET /reports and GET /reports/{id}. */
export interface ApiReport {
id: string;
globalIdentifier: string | null;
title: string;
dateCreatedUtc: string;
dateModifiedUtc: string | null;
datePublishedUtc: string | null;
ownedBy: ApiPerson | null;
userTargetGroup: string | null;
dataRefreshRate: string | null;
dataSources: string | null;
access: string | null;
isPublished: boolean;
isEmbedOnly: boolean;
allowExternalUsers: boolean;
allowOnlyEmployees: boolean;
denyExtHire: boolean;
securityRequirementCheck: ApiSecurityRequirementCheckType;
securityRequirements: ApiSecurityRequirement[] | null;
publishedBy: ApiPerson | null;
apiEmbedConfig: ApiEmbedConfig | null;
reportType: ApiReportType;
}
export interface ApiEmbedConfig {
id: string;
type: ApiConfigType;
embedConfig: ApiDynamicEmbedConfig | null;
}
export interface ApiConfig {
type: ApiConfigType;
}
export interface ApiDynamicEmbedConfig {
name: string | null;
generic: Record<string, unknown> | null;
}
export interface ApiDynamicPowerBiEmbedConfig {
name: string | null;
embedType: ApiEmbedType;
embedUrl: string | null;
tokenType: ApiPowerBITokenType;
datasetId: string | null;
dashboardId: string | null;
groupId: string | null;
reportId: string | null;
tileId: string | null;
rlsConfiguration: ApiRlsConfiguration | null;
}
export interface ApiPowerBIEmbedConfig {
name: string | null;
embedType: ApiEmbedType;
embedUrl: string | null;
tokenType: ApiPowerBITokenType;
groupId: string | null;
datasetId: string | null;
dashboardId: string | null;
reportId: string | null;
tileId: string | null;
rlsConfiguration: ApiRlsConfiguration | null;
}
export interface ApiRlsConfiguration {
version: number;
/** Computed from the config shape. */
typeName: string | null;
globalAccessRequirement: unknown | null;
identity: unknown | null;
roles: unknown[] | null;
}
export interface ApiSecurityRequirement {
id: string | null;
requirementType: ApiRequirementType;
value: string | null;
}
// ---------------------------------------------------------------------------
// Person / Group
// ---------------------------------------------------------------------------
export interface ApiPerson {
id: string;
azureUniqueId: string | null;
name: string | null;
department: string | null;
jobTitle: string | null;
officeLocation: string | null;
mail: string | null;
mobilePhone: string | null;
isAffiliateAccess: boolean;
accountType: string | null;
}
export interface ApiGroup {
id: string;
displayName: string;
description: string;
type: ApiGroupType;
}
// ---------------------------------------------------------------------------
// Status / Health
// ---------------------------------------------------------------------------
export interface ApiReportStatus {
reportId: string;
title: string | null;
hasRlsConfig: boolean;
hasSecurityRequirements: boolean;
hasPbiDependencyErrors: boolean;
reportStatus: ApiFusionReportStatus | null;
pbiStatus: ApiPbiStatus | null;
pbiDataSet: unknown | null;
pbiDataSetLastRefresh: unknown | null;
pbiDataSetRefreshSchedule: unknown | null;
}
export interface ApiFusionReportStatus {
health: string;
message: string;
timer: string;
}
export interface ApiPbiStatus {
health: string;
message: string | null;
}
// ---------------------------------------------------------------------------
// Access token
// ---------------------------------------------------------------------------
export interface ApiAccessToken {
expirationUtc: string;
token: string;
}
// ---------------------------------------------------------------------------
// Audit
// ---------------------------------------------------------------------------
export interface ApiAuditLog {
reportTitle: string | null;
/** Computed from users list. */
distinctUsersCount: number | null;
users: ApiAuditLogEntry[];
}
export interface ApiAuditLogEntry {
requestCount: number | null;
userEmail: string | null;
requests: ApiAuditRequest[] | null;
}
export interface ApiAuditRequest {
requestedDate: string;
statusCode: string | null;
message: string | null;
}
// ---------------------------------------------------------------------------
// Config validation
// ---------------------------------------------------------------------------
export interface ApiConfigValidation {
items: ApiReportConfigItemValidation[];
/** Computed — true when no Error items. */
isValid: boolean;
}
export interface ApiReportConfigItemValidation {
type: ApiItemValidationType;
code: string;
message: string;
}
// ---------------------------------------------------------------------------
// DataFlow (admin)
// ---------------------------------------------------------------------------
export interface ApiDataFlow {
id: string;
name: string;
description: string;
modelUrl: string;
configuredBy: string;
pbiStatus: ApiPbiStatus | null;
clientId: string;
clientSecretHash: string;
transactions: ApiPbiDataFlowTransaction[] | null;
}
export interface ApiPbiDataFlowTransaction {
id: string;
refreshType: string;
startTime: string | null;
endTime: string | null;
status: string;
}
// ---------------------------------------------------------------------------
// Request models
// ---------------------------------------------------------------------------
/**
* POST /reports — create a report.
*
* Validation (FluentValidation):
* @property title — Required, no script tags
* @property ownedBy — Required
* @property globalIdentifier — Optional, 3–50 chars, URL-safe
* @property dataRefreshRate — No script tags when present
* @property dataSources — No script tags when present
* @property access — No script tags when present
* @property userTargetGroup — No script tags when present
*/
export interface CreateReportRequest {
title: string;
ownedBy: string;
/** @minLength 3 @maxLength 50 — URL-safe */
globalIdentifier?: string | null;
dataRefreshRate?: string | null;
dataSources?: string | null;
access?: string | null;
userTargetGroup?: string | null;
reportType?: ApiReportType;
}
/**
* PUT /reports/{id}/config — update report embed configuration.
*
* Validation:
* @property type — Required
* @property tokenType — Required when type is Power BI
* @property groupId — Required when type is Power BI
* @property embedUrl — Must be an absolute URL, no script tags
* @property name — No script tags when present
*/
export interface UpdateConfigRequest {
name?: string | null;
type: ApiConfigType;
embedType?: ApiEmbedType;
embedUrl?: string | null;
tokenType?: ApiPowerBITokenType;
groupId?: string | null;
datasetId?: string | null;
dashboardId?: string | null;
reportId?: string | null;
tileId?: string | null;
rlsConfiguration?: ApiRlsConfiguration | null;
}
/**
* PUT /reports/{id} — update report metadata and ACL-related settings.
*/
export interface UpdateReportRequest {
title: string;
ownedBy: ApiPerson;
securityRequirementCheck?: ApiSecurityRequirementCheckType | null;
securityRequirements?: ApiSecurityRequirement[] | null;
userTargetGroup?: string | null;
dataRefreshRate?: string | null;
dataSources?: string | null;
access?: string | null;
allowExternalUsers: boolean;
allowOnlyEmployees: boolean;
denyExtHire: boolean;
isEmbedOnly: boolean;
globalIdentifier?: string | null;
}
/**
* POST /reports/config/validate — validate report embed configuration before save.
*/
export interface ValidateConfigRequest {
embedType?: ApiEmbedType | null;
embedUrl?: string | null;
tokenType?: ApiPowerBITokenType | null;
groupId: string;
reportId?: string | null;
datasetId?: string | null;
dashboardId?: string | null;
tileId?: string | null;
rlsConfiguration?: ApiRlsConfiguration | null;
}
/**
* PUT /reports/{id}/config/generic — generic JSON config parsing result.
*/
export interface GenericConfig {
isValid: boolean;
config?: unknown;
errorMessage?: string | null;
}
/**
* PUT markdown-content endpoints under `/reports/{id}/.../content`.
*/
export interface UpdateMarkdownRequest {
content?: string | null;
}
Service Messages API Endpoint Catalog
This catalog covers the verified public service-message surface for collection, item, active-message, and app-scoped routes. Admin-only routes remain excluded.
Collection endpoints
GET /service-messages→ApiServiceMessage[]
OData: Filter(relevantPortals), OrderBy(visibility.appliesFrom, visibility.appliesTo, created)
POST /service-messages→CreateServiceMessageRequest→ApiServiceMessageOPTIONS /service-messages→ access probe,204 NoContent
Item endpoints
GET /service-messages/{messageId}→ApiServiceMessagePATCH /service-messages/{messageId}→UpdateServiceMessageRequest→ApiServiceMessageDELETE /service-messages/{messageId}→204 NoContentOPTIONS /service-messages/{messageId}→ access probe,204 NoContent
Active-message endpoints
GET /service-messages/active→ApiActiveServiceMessage[]POST /service-messages/active/publish→200 OK
App-scoped endpoints
GET /apps/{appKey}/service-messages→ApiServiceMessage[]POST /apps/{appKey}/service-messages→CreateServiceMessageForAppRequest→ApiServiceMessagePATCH /apps/{appKey}/service-messages/{messageId}→UpdateServiceMessageForAppRequest→ApiServiceMessage
Authorization notes
- Collection and item routes allow trusted applications, callers with
ServiceMessagesScopes.Manage, and in some cases app admins/owners for the affected apps. - Collection reads can run in limited mode for app admins/owners and are filtered down to messages for apps the caller actually administers.
- App-scoped routes require either manage scope or admin/owner access for the specific app.
- Publish-active routes are more restrictive than reads and require manage scope or trusted application access.
Typical status codes
200 OK204 NoContent400 Bad Request401 Unauthorized403 Forbidden404 Not Found424 FailedDependencywhen dependent app/profile resolution fails
Explicit exclusions
AdminControllerroutes
/**
* Fusion Core Services model asset index.
*
* Read the service-specific model files in this directory for concrete TypeScript shapes.
* This file exists to give the consolidated skill a single stable model entry point.
*/
export const fusionCoreServicesModelAssets = {
apps: "assets/fusion-apps-models.ts",
bookmarks: "assets/fusion-bookmarks-models.ts",
context: "assets/fusion-context-models.ts",
contractPersonnel: "assets/fusion-contract-personnel-models.ts",
mail: "assets/fusion-mail-models.ts",
notification: "assets/fusion-notification-models.ts",
people: "assets/fusion-people-models.ts",
portalConfig: "assets/fusion-portal-config-models.ts",
reports: "assets/fusion-reports-models.ts",
rolesV2: "assets/fusion-roles-v2-models.ts",
serviceMessages: "assets/fusion-service-messages-models.ts",
tasks: "assets/fusion-tasks-models.ts",
} as const;
export type FusionCoreServiceName = keyof typeof fusionCoreServicesModelAssets;Related skills
FAQ
What platform does fusion-core-services integrate?
fusion-core-services connects coding agents to Equinor Fusion core platform services, standardizing API access so developers skip rewriting connection glue for each agent session.
How popular is fusion-core-services on Skills.sh?
fusion-core-services shows 425 installs and rank 18 on Skills.sh under equinor/fusion-skills, indicating active use among Fusion platform developers.