Appearance
Initiatives (GraphQL)
The Initiative finance-anchor surface is GraphQL-native (Pothos), not REST. It lives on the same authenticated GraphQL endpoint as the rest of the app and the MCP server. This page is the contract reference; for the model, read Initiatives (finance anchor) and Initiative finance.
Endpoint & auth
GraphQL is served at https://{tenant}.flowstate.inc/api/graphql. Reads require ROADMAP_INITIATIVES_VIEW; writes require the matching ROADMAP_INITIATIVES_{CREATE,UPDATE,DELETE}. Every money field (budgets, costs, variance amounts) is additionally gated by FINANCIALS_VIEW_SUMMARY — without it those fields resolve to 0 while FTE and counts stay visible.
Enums
graphql
enum FinanceMode {
CAPEX_RD_CLAIM # internal capital project — capitalise + R&D claim
CLIENT_BILLING # agency / SOW — bill + margin; never capitalisable
INTERNAL_COST # pure run-cost — neither capitalised nor billed
}
enum KeyResultDirection { INCREASE DECREASE }
enum CapexClaimSource { INITIATIVE AD_HOC }
enum CommitmentSubjectKind { EMPLOYEE TEAM CONTRACTOR }The Initiative type
The finance/governance anchor. Scalar Decimal fields serialise via toNumber.
graphql
type Initiative {
id: ID!
organizationId: ID!
name: String!
description: String
icon: String
iconColor: String!
status: String!
startDate: DateTime
targetDate: DateTime
ownerUserId: ID
sortOrder: Int!
createdAt: DateTime!
updatedAt: DateTime!
projectCount: Int!
# ── Finance anchor ──────────────────────────────────────────────
financeMode: FinanceMode!
financeModeCapabilities: FinanceModeCapabilities!
costCentreId: ID
costCentre: CostCentre
budgetedCost: Decimal # CLIENT_BILLING SOW value
budgetCurrency: String
marginPct: Decimal # CLIENT_BILLING target margin
committedFte: Decimal # planned-commitment summary
aiBudgetAmount: Decimal
aiBudgetCurrency: String
lens: PortfolioLens # finance-sliceable classification
objective: Objective # parent strategy
projects: [LiveProject!]! # adopted finance children
# ── Derived (never stored) ──────────────────────────────────────
isInert: Boolean! # no cost centre AND no child projects
capitalisable: Boolean! # not CLIENT_BILLING and not inert
# ── Rollups (each takes startDate/endDate; money gated) ─────────
effortActuals(startDate: DateTime!, endDate: DateTime!): InitiativeEffortActuals!
forecast(startDate: DateTime!, endDate: DateTime!): InitiativeForecast!
aiSpend(startDate: DateTime!, endDate: DateTime!): InitiativeAiSpend!
plannedCommitment(startDate: DateTime!, endDate: DateTime!): InitiativePlannedCommitment!
varianceSummary(startDate: DateTime!, endDate: DateTime!): InitiativeVarianceSummary!
}
type FinanceModeCapabilities {
canCapitalise: Boolean!
canRdClaim: Boolean!
canBill: Boolean!
showsMargin: Boolean!
showsBilling: Boolean!
}financeModeCapabilities is the single capability gate — see the Finance Mode matrix. costCentre resolves through the Initiative-first chain; projects are the live projects adopted into this Initiative.
Queries
| Query | Args | Returns | Notes |
|---|---|---|---|
initiatives | — | [Initiative!]! | All initiatives for the org. |
initiativesList | input: ListArgsInput | InitiativeConnection! | Paginated, server-side search / sort / group (by status or owner). |
initiative | id: ID! | Initiative | One by id. |
initiativeWithProjects | id: ID! | Initiative | One by id with child projects preloaded. |
initiativesForProject | projectId: ID! | [Initiative!]! | Initiatives a project is linked to. |
objectives | — | [Objective!]! | The strategy layer. |
objective | id: ID! | Objective | One objective with its key results. |
portfolioLenses | — | [PortfolioLens!]! | All lenses for the org. |
capexClaims | initiativeId: ID, claimPeriodKey: String | [CapexClaim!]! | Claims, optionally filtered. Requires FINANCIALS_VIEW_SUMMARY. |
capexClaim | id: ID! | CapexClaim | One claim with its evidence set. Requires FINANCIALS_VIEW_SUMMARY. |
Example: an Initiative with its variance
graphql
query InitiativeFinance($id: ID!, $start: DateTime!, $end: DateTime!) {
initiative(id: $id) {
name
financeMode
financeModeCapabilities { canCapitalise canBill showsMargin }
costCentre { id name code }
lens { name category }
objective { name keyResults { name progress } }
isInert
capitalisable
varianceSummary(startDate: $start, endDate: $end) {
forecastCost
actualCost
costVariance
costVariancePct
assignedFte
actualFte
fteVariance
currencyCode
}
}
}Rollups and variance
Each rollup field takes a (startDate, endDate) window. Money fields are gated by FINANCIALS_VIEW_SUMMARY (resolve to 0 without it); FTE, counts, and ratios are always visible.
graphql
type InitiativeEffortActuals { # actuals rolled up from effort
projectCount: Int!
actualFte: Float!
actualCost: Float! # gated
currencyCode: String!
}
type InitiativeForecast { # rolled up from allocations
projectCount: Int!
assignedFte: Float!
forecastCost: Float! # gated
currencyCode: String!
}
type InitiativeAiSpend { # from telemetry sessions stamped with the Initiative
sessionCount: Int!
totalCost: Float! # gated
currencyCode: String!
}
type InitiativePlannedCommitment { # summed across commitment tables
committedFte: Float!
aiBudgetAmount: Float! # gated
currencyCode: String!
employeeCommitmentCount: Int!
teamCommitmentCount: Int!
contractorCommitmentCount: Int!
}
type InitiativeVarianceSummary { # the headline
forecastCost: Float! # gated
actualCost: Float! # gated
costVariance: Float! # gated
costVariancePct: Float # ungated (a ratio leaks no £)
assignedFte: Float!
actualFte: Float!
fteVariance: Float!
currencyCode: String!
}Objectives + Key Results
graphql
type Objective {
id: ID!
organizationId: ID!
name: String!
description: String
status: String!
startDate: DateTime
targetDate: DateTime
ownerUserId: ID
sortOrder: Int!
keyResults: [KeyResult!]!
initiativeCount: Int!
}
type KeyResult {
id: ID!
objectiveId: ID!
name: String!
unit: String!
direction: KeyResultDirection!
targetValue: Decimal!
currentValue: Decimal
baselineValue: Decimal
sortOrder: Int!
progress: Float # normalised [0,1], null until measurable
}Portfolio lens
graphql
type PortfolioLens {
id: ID!
organizationId: ID!
name: String!
category: String! # KTLO / Revenue / Cost-Reduction / Compliance …
color: String!
icon: String
isActive: Boolean!
sortOrder: Int!
}Commitments
One GraphQL type fronts the three live commitment tables (employee / team / contractor); subjectKind discriminates.
graphql
type InitiativeCommitment {
id: ID!
liveInitiativeId: ID!
subjectKind: CommitmentSubjectKind!
subjectId: ID!
committedFte: Float!
aiBudgetAmount: Float # gated
aiBudgetCurrency: String
startDate: DateTime!
endDate: DateTime
}CapEx claim
graphql
type CapexClaim {
id: ID!
initiativeId: ID # null for ad-hoc
source: CapexClaimSource! # INITIATIVE | AD_HOC
periodStart: DateTime!
periodEnd: DateTime!
claimPeriodKey: String! # the double-count axis
jurisdictionCode: String!
frameworkId: ID
status: String!
createdAt: DateTime!
initiativeName: String
projects: [CapexClaimProject!]! # the evidence set
projectCount: Int!
}
type CapexClaimProject {
id: ID!
capexClaimId: ID!
liveProjectId: ID!
claimPeriodKey: String!
status: String!
projectName: String
projectIcon: String
projectIconColor: String
}Mutations
Initiative finance & adoption
initiativeFinanceUpdate
Patch an Initiative's finance fields. Partial — omit a field to leave it; pass null to clear a nullable field.
graphql
mutation($id: ID!, $input: InitiativeFinanceUpdateInput!) {
initiativeFinanceUpdate(id: $id, input: $input) {
id financeMode costCentreId budgetedCost marginPct committedFte
}
}graphql
input InitiativeFinanceUpdateInput {
financeMode: FinanceMode
costCentreId: ID
completionCostCentreId: ID
lensId: ID
objectiveId: ID
budgetedCost: Float
budgetCurrency: String
marginPct: Float
committedFte: Float
aiBudgetAmount: Float
aiBudgetCurrency: String
}initiativeAdoptProject
Adopt a project into an Initiative as its finance parent (≤1 parent enforced; stashes the project's own cost centre into dormantCostCentreId while adopted). Returns the updated LiveProject. Throws PROJECT_ALREADY_ADOPTED if the project already has a different parent, CROSS_ORG_ADOPTION across orgs.
graphql
mutation($initiativeId: ID!, $projectId: ID!) {
initiativeAdoptProject(initiativeId: $initiativeId, projectId: $projectId) { id initiativeId }
}initiativeDetachProject
Detach a project from its Initiative parent and restore its own cost centre from the dormant stash. Throws PROJECT_NOT_ADOPTED_HERE if the project is not adopted by that Initiative.
graphql
mutation($initiativeId: ID!, $projectId: ID!) {
initiativeDetachProject(initiativeId: $initiativeId, projectId: $projectId) { id initiativeId }
}The Initiative itself is created / edited / archived through
createInitiative,updateInitiative,archiveInitiative,deleteInitiative, and reordered/linked viaaddProjectToInitiative,removeProjectFromInitiative,reorderInitiativeProjects,reorderInitiatives.addProjectToInitiativeis the lightweight roadmap link;initiativeAdoptProjectis the finance adoption that moves the cost-centre anchor.
Objective / Key Result CRUD
| Mutation | Input | Returns |
|---|---|---|
createObjective | CreateObjectiveInput | Objective |
updateObjective | id, UpdateObjectiveInput | Objective |
deleteObjective | id | Objective |
createKeyResult | CreateKeyResultInput (objectiveId, name, unit, targetValue, …) | KeyResult |
updateKeyResult | id, UpdateKeyResultInput | KeyResult |
deleteKeyResult | id | Boolean |
Portfolio lens CRUD
| Mutation | Input | Returns |
|---|---|---|
createPortfolioLens | CreatePortfolioLensInput (name, category, …) | PortfolioLens |
updatePortfolioLens | id, UpdatePortfolioLensInput | PortfolioLens |
deletePortfolioLens | id | Boolean |
Commitment CRUD
| Mutation | Args | Returns |
|---|---|---|
createInitiativeCommitment | CreateInitiativeCommitmentInput (liveInitiativeId, subjectKind, subjectId, committedFte, startDate, …) | InitiativeCommitment |
updateInitiativeCommitment | subjectKind, id, UpdateInitiativeCommitmentInput | InitiativeCommitment |
deleteInitiativeCommitment | subjectKind, id | Boolean |
updateInitiativeCommitment / deleteInitiativeCommitment take subjectKind alongside id so the resolver targets the right backing table.
CapEx claim flows
initiativeCapexClaimCreate
Create a claim anchored at an Initiative; auto-pulls its child projects as evidence. Only valid when the Initiative is in CAPEX_RD_CLAIM mode. Throws PROJECT_ALREADY_CLAIMED on a double-count.
graphql
mutation($initiativeId: ID!, $period: CapexClaimPeriodInput!) {
initiativeCapexClaimCreate(initiativeId: $initiativeId, period: $period) {
id source claimPeriodKey jurisdictionCode projectCount
}
}graphql
input CapexClaimPeriodInput {
periodStart: DateTime!
periodEnd: DateTime!
claimPeriodKey: String! # normalised period key — the double-count axis
jurisdictionCode: String # resolves from the org default when omitted
frameworkId: ID # optional R&D framework
}adHocCapexClaimCreate
Create a claim over a hand-picked set of projects (no Initiative anchor). Throws PROJECT_ALREADY_CLAIMED on a double-count.
graphql
mutation($projectIds: [ID!]!, $period: CapexClaimPeriodInput!) {
adHocCapexClaimCreate(projectIds: $projectIds, period: $period) {
id source projectCount
}
}capexClaimConvertToInitiative
Promote an ad-hoc claim to an Initiative-anchored claim.
graphql
mutation($claimId: ID!, $initiativeId: ID!) {
capexClaimConvertToInitiative(claimId: $claimId, initiativeId: $initiativeId) {
id source initiativeId initiativeName
}
}Operating model
organizationOperatingModelUpdate
Relabel the workspace nouns + pick an operating-model preset. Gated by SETTINGS_ENTITY_CONFIG_UPDATE.
graphql
mutation($preset: String, $terminology: JSON) {
organizationOperatingModelUpdate(preset: $preset, terminology: $terminology) {
id operatingModelPreset entityTerminology
}
}Error codes
These surface as GraphQL errors with a machine-readable extensions.code (and, where useful, deep-link ids in extensions):
| Code | Raised by | extensions | Meaning |
|---|---|---|---|
PROJECT_ALREADY_ADOPTED | initiativeAdoptProject | currentInitiativeId | The project already belongs to a different Initiative (≤1 parent). |
PROJECT_NOT_ADOPTED_HERE | initiativeDetachProject | currentInitiativeId | The project is not adopted by the Initiative you tried to detach it from. |
CROSS_ORG_ADOPTION | initiativeAdoptProject | — | Project and Initiative belong to different organizations. |
PROJECT_ALREADY_CLAIMED | initiativeCapexClaimCreate, adHocCapexClaimCreate, capexClaimConvertToInitiative | conflictingClaimId, liveProjectId | A project is already in a non-rejected claim for the same claimPeriodKey (the one-claim-per-project-per-period guard). |
Adoption is idempotent: re-adopting a project into the Initiative that already owns it is a no-op, not an error.
See also
- Initiatives (finance anchor) — the model.
- Initiative finance — cost-centre inheritance, claim flows, variance.
- MCP server — the same operations as assistant tools (
search_initiatives,adopt_project_to_initiative,create_capex_claim, …). - Outbound Webhooks —
initiative,objective,portfolio_lens, andcapex_claimevents.