Skip to content

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

QueryArgsReturnsNotes
initiatives[Initiative!]!All initiatives for the org.
initiativesListinput: ListArgsInputInitiativeConnection!Paginated, server-side search / sort / group (by status or owner).
initiativeid: ID!InitiativeOne by id.
initiativeWithProjectsid: ID!InitiativeOne by id with child projects preloaded.
initiativesForProjectprojectId: ID![Initiative!]!Initiatives a project is linked to.
objectives[Objective!]!The strategy layer.
objectiveid: ID!ObjectiveOne objective with its key results.
portfolioLenses[PortfolioLens!]!All lenses for the org.
capexClaimsinitiativeId: ID, claimPeriodKey: String[CapexClaim!]!Claims, optionally filtered. Requires FINANCIALS_VIEW_SUMMARY.
capexClaimid: ID!CapexClaimOne 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 via addProjectToInitiative, removeProjectFromInitiative, reorderInitiativeProjects, reorderInitiatives. addProjectToInitiative is the lightweight roadmap link; initiativeAdoptProject is the finance adoption that moves the cost-centre anchor.

Objective / Key Result CRUD

MutationInputReturns
createObjectiveCreateObjectiveInputObjective
updateObjectiveid, UpdateObjectiveInputObjective
deleteObjectiveidObjective
createKeyResultCreateKeyResultInput (objectiveId, name, unit, targetValue, …)KeyResult
updateKeyResultid, UpdateKeyResultInputKeyResult
deleteKeyResultidBoolean

Portfolio lens CRUD

MutationInputReturns
createPortfolioLensCreatePortfolioLensInput (name, category, …)PortfolioLens
updatePortfolioLensid, UpdatePortfolioLensInputPortfolioLens
deletePortfolioLensidBoolean

Commitment CRUD

MutationArgsReturns
createInitiativeCommitmentCreateInitiativeCommitmentInput (liveInitiativeId, subjectKind, subjectId, committedFte, startDate, …)InitiativeCommitment
updateInitiativeCommitmentsubjectKind, id, UpdateInitiativeCommitmentInputInitiativeCommitment
deleteInitiativeCommitmentsubjectKind, idBoolean

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
  }
}

See Operating-model settings.

Error codes

These surface as GraphQL errors with a machine-readable extensions.code (and, where useful, deep-link ids in extensions):

CodeRaised byextensionsMeaning
PROJECT_ALREADY_ADOPTEDinitiativeAdoptProjectcurrentInitiativeIdThe project already belongs to a different Initiative (≤1 parent).
PROJECT_NOT_ADOPTED_HEREinitiativeDetachProjectcurrentInitiativeIdThe project is not adopted by the Initiative you tried to detach it from.
CROSS_ORG_ADOPTIONinitiativeAdoptProjectProject and Initiative belong to different organizations.
PROJECT_ALREADY_CLAIMEDinitiativeCapexClaimCreate, adHocCapexClaimCreate, capexClaimConvertToInitiativeconflictingClaimId, liveProjectIdA 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

Flowstate Documentation