openapi: 3.0.3 info: title: 'Orgonaut API Documentation' description: '' version: 1.0.0 servers: - url: 'https://app.orgonaut.co' tags: - name: 'AI Tooling Usage' description: '' - name: Actors description: '' - name: Aggregates description: '' - name: Astro description: '' - name: Auth/Tokens description: '' - name: Costs description: '' - name: 'Department Types' description: '' - name: Departments description: '' - name: Initiatives description: '' - name: Integrations description: '' - name: Lineage description: '' - name: 'Org Placements' description: '' - name: 'Org Units' description: '' - name: 'Org Units — Metrics' description: '' - name: 'Org Units — Structure' description: '' - name: 'Org Units — Velocity' description: '' - name: 'Organisation Accounts' description: "\nAPI descriptions intentionally use “Organisation Account” wording for user-facing clarity." - name: Positions description: 'Manage effective-dated team positions, linking actors, teams, and compensation within each scenario.' - name: 'Scenario Tasks' description: '' - name: Scenarios description: '' - name: Snapshots description: '' - name: Teams description: '' - name: 'Teams — Velocity' description: '' - name: 'Tenant Invitations' description: '' - name: 'Tenant Members' description: '' components: securitySchemes: default: type: http scheme: bearer description: 'You can retrieve your token by visiting your dashboard and clicking Generate API token.' bearerAuth: type: http scheme: bearer parameters: X-Tenant: name: X-Tenant in: header required: true schema: type: string description: 'Tenant slug; required on the root domain and optional when using a tenant subdomain.' security: - default: [] bearerAuth: [] paths: /api/v1/ai-tooling/usage: get: summary: 'AI Tooling — List daily usage records' operationId: aIToolingListDailyUsageRecords description: "Returns a paginated list of daily usage records across all matched and unmatched actors.\nRecords are ordered by `usage_date` descending (most recent first). Each record represents\none actor's usage of one AI tooling provider on a single day.\n\nSupports filtering by actor, provider, and date range. Unmatched records (where the\nsource email could not be resolved to an Orgonaut actor) have `actor_id: null`.\n\nRequires `ai-tooling.manage` ability." parameters: - in: query name: actor_id description: 'Filter by actor ID. Only returns records matched to this actor.' example: 42 required: false schema: type: integer description: 'Filter by actor ID. Only returns records matched to this actor.' example: 42 - in: query name: provider description: 'Filter by provider slug (e.g. "claude").' example: claude required: false schema: type: string description: 'Filter by provider slug (e.g. "claude").' example: claude - in: query name: from description: 'date Start of date range (inclusive, YYYY-MM-DD).' example: '2026-03-01' required: false schema: type: string description: 'date Start of date range (inclusive, YYYY-MM-DD).' example: '2026-03-01' - in: query name: to description: 'date End of date range (inclusive, YYYY-MM-DD). Must be on or after `from`.' example: '2026-03-25' required: false schema: type: string description: 'date End of date range (inclusive, YYYY-MM-DD). Must be on or after `from`.' example: '2026-03-25' - in: query name: per_page description: 'Results per page. Min 1, max 100. Defaults to 25.' example: 25 required: false schema: type: integer description: 'Results per page. Min 1, max 100. Defaults to 25.' example: 25 - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 128 actor_id: 42 provider: claude source_email: aoife.ryan@example.com usage_date: '2026-03-24' sessions_count: 5 lines_added: 320 lines_removed: 85 commits_count: 4 pull_requests_count: 1 total_input_tokens: 125000 total_output_tokens: 48000 total_cache_read_tokens: 32000 total_cache_creation_tokens: 8000 total_cost_cents: 245 currency: USD tool_actions: edit_tool: accepted: 18 rejected: 2 bash_tool: accepted: 12 rejected: 0 model_breakdown: - model: claude-opus-4-6 input_tokens: 80000 output_tokens: 30000 - model: claude-sonnet-4-6 input_tokens: 45000 output_tokens: 18000 matched_at: '2026-03-24T03:15:42+00:00' actor: id: 42 name: 'Aoife Ryan' email: aoife.ryan@example.com links: first: '/?page=1' last: '/?page=3' prev: null next: '/?page=2' meta: current_page: 1 last_page: 3 per_page: 25 total: 72 properties: data: type: array example: - id: 128 actor_id: 42 provider: claude source_email: aoife.ryan@example.com usage_date: '2026-03-24' sessions_count: 5 lines_added: 320 lines_removed: 85 commits_count: 4 pull_requests_count: 1 total_input_tokens: 125000 total_output_tokens: 48000 total_cache_read_tokens: 32000 total_cache_creation_tokens: 8000 total_cost_cents: 245 currency: USD tool_actions: edit_tool: accepted: 18 rejected: 2 bash_tool: accepted: 12 rejected: 0 model_breakdown: - model: claude-opus-4-6 input_tokens: 80000 output_tokens: 30000 - model: claude-sonnet-4-6 input_tokens: 45000 output_tokens: 18000 matched_at: '2026-03-24T03:15:42+00:00' actor: id: 42 name: 'Aoife Ryan' email: aoife.ryan@example.com items: type: object properties: id: type: integer example: 128 actor_id: type: integer example: 42 provider: type: string example: claude source_email: type: string example: aoife.ryan@example.com usage_date: type: string example: '2026-03-24' sessions_count: type: integer example: 5 lines_added: type: integer example: 320 lines_removed: type: integer example: 85 commits_count: type: integer example: 4 pull_requests_count: type: integer example: 1 total_input_tokens: type: integer example: 125000 total_output_tokens: type: integer example: 48000 total_cache_read_tokens: type: integer example: 32000 total_cache_creation_tokens: type: integer example: 8000 total_cost_cents: type: integer example: 245 currency: type: string example: USD tool_actions: type: object properties: edit_tool: type: object properties: accepted: type: integer example: 18 rejected: type: integer example: 2 bash_tool: type: object properties: accepted: type: integer example: 12 rejected: type: integer example: 0 model_breakdown: type: array example: - model: claude-opus-4-6 input_tokens: 80000 output_tokens: 30000 - model: claude-sonnet-4-6 input_tokens: 45000 output_tokens: 18000 items: type: object properties: model: type: string example: claude-opus-4-6 input_tokens: type: integer example: 80000 output_tokens: type: integer example: 30000 matched_at: type: string example: '2026-03-24T03:15:42+00:00' actor: type: object properties: id: type: integer example: 42 name: type: string example: 'Aoife Ryan' email: type: string example: aoife.ryan@example.com links: type: object properties: first: type: string example: '/?page=1' last: type: string example: '/?page=3' prev: type: string example: null nullable: true next: type: string example: '/?page=2' meta: type: object properties: current_page: type: integer example: 1 last_page: type: integer example: 3 per_page: type: integer example: 25 total: type: integer example: 72 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: to: - 'The to field must be a date after or equal to from.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: to: type: array example: - 'The to field must be a date after or equal to from.' items: type: string tags: - 'AI Tooling Usage' requestBody: required: false content: application/json: schema: type: object properties: actor_id: type: integer description: 'The id of an existing record in the actors table.' example: 11 provider: type: string description: 'Must not be greater than 50 characters.' example: pzdszigdqrrkthqp from: type: string description: 'Must be a valid date.' example: '2026-04-13T21:34:33' to: type: string description: 'Must be a valid date. Must be a date after or equal to from.' example: '2069-05-04' per_page: type: integer description: 'Must be at least 1. Must not be greater than 100.' example: 4 '/api/v1/ai-tooling/actors/{actor_id}/summary': get: summary: 'AI Tooling — Actor summary' operationId: aIToolingActorSummary description: "Returns aggregated AI tooling cost and usage totals for a single actor. Includes the\nlast full calendar month total, month-to-date total, and a per-provider breakdown.\n\nUseful for actor profile cards and dashboards.\n\nRequires `ai-tooling.manage` ability." parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: actor_id: 42 has_data: true monthly_cost_cents: 850 mtd_cost_cents: 320 sessions: 15 commits: 8 prs: 2 lines_added: 450 lines_removed: 120 by_provider: claude: cost_cents: 850 mtd_cost_cents: 320 sessions: 15 properties: data: type: object properties: actor_id: type: integer example: 42 has_data: type: boolean example: true monthly_cost_cents: type: integer example: 850 mtd_cost_cents: type: integer example: 320 sessions: type: integer example: 15 commits: type: integer example: 8 prs: type: integer example: 2 lines_added: type: integer example: 450 lines_removed: type: integer example: 120 by_provider: type: object properties: claude: type: object properties: cost_cents: type: integer example: 850 mtd_cost_cents: type: integer example: 320 sessions: type: integer example: 15 tags: - 'AI Tooling Usage' parameters: - in: path name: actor_id description: 'The ID of the actor.' example: 11 required: true schema: type: integer - in: path name: actor description: 'The actor ID.' example: 42 required: true schema: type: integer '/api/v1/ai-tooling/actors/{actor_id}/series': get: summary: 'AI Tooling — Actor daily series' operationId: aIToolingActorDailySeries description: "Returns a daily time series of AI tooling usage for a single actor. Each data point\ncontains cost, token consumption, session count, and code-change metrics for one day.\nDesigned for rendering cost trend and activity charts on actor profile pages.\n\nDefaults to the last 6 months when `from`/`to` are omitted.\n\nRequires `ai-tooling.manage` ability." parameters: - in: query name: from description: 'date Start of date range (YYYY-MM-DD). Defaults to 6 months ago (start of month).' example: '2025-10-01' required: false schema: type: string description: 'date Start of date range (YYYY-MM-DD). Defaults to 6 months ago (start of month).' example: '2025-10-01' - in: query name: to description: 'date End of date range (YYYY-MM-DD). Must be on or after `from`. Defaults to today.' example: '2026-03-25' required: false schema: type: string description: 'date End of date range (YYYY-MM-DD). Must be on or after `from`. Defaults to today.' example: '2026-03-25' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - date: '2026-03-23' cost_cents: 120 sessions: 3 commits: 2 prs: 0 lines_added: 180 lines_removed: 45 input_tokens: 52000 output_tokens: 19000 - date: '2026-03-24' cost_cents: 245 sessions: 5 commits: 4 prs: 1 lines_added: 320 lines_removed: 85 input_tokens: 125000 output_tokens: 48000 properties: data: type: array example: - date: '2026-03-23' cost_cents: 120 sessions: 3 commits: 2 prs: 0 lines_added: 180 lines_removed: 45 input_tokens: 52000 output_tokens: 19000 - date: '2026-03-24' cost_cents: 245 sessions: 5 commits: 4 prs: 1 lines_added: 320 lines_removed: 85 input_tokens: 125000 output_tokens: 48000 description: 'Array of daily data points, ordered by date ascending.' items: type: object properties: date: type: string example: '2026-03-23' cost_cents: type: integer example: 120 sessions: type: integer example: 3 commits: type: integer example: 2 prs: type: integer example: 0 lines_added: type: integer example: 180 lines_removed: type: integer example: 45 input_tokens: type: integer example: 52000 output_tokens: type: integer example: 19000 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: to: - 'The to field must be a date after or equal to from.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: to: type: array example: - 'The to field must be a date after or equal to from.' items: type: string tags: - 'AI Tooling Usage' requestBody: required: false content: application/json: schema: type: object properties: from: type: string description: 'Must be a valid date.' example: '2026-04-13T21:34:33' to: type: string description: 'Must be a valid date. Must be a date after or equal to from.' example: '2069-05-04' parameters: - in: path name: actor_id description: 'The ID of the actor.' example: 11 required: true schema: type: integer - in: path name: actor description: 'The actor ID.' example: 42 required: true schema: type: integer '/api/v1/ai-tooling/teams/{team_id}/summary': get: summary: 'AI Tooling — Team summary' operationId: aIToolingTeamSummary description: "Returns aggregated AI tooling cost and usage totals for all actors placed in the team's\norg unit. Covers the last full calendar month and month-to-date period.\n\nIf the team has no associated org unit, zeroed metrics are returned.\n\nRequires `ai-tooling.manage` ability." parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: team_id: 7 monthly_cost_cents: 2500 mtd_cost_cents: 1200 sessions: 45 commits: 22 prs: 6 lines_added: 1800 lines_removed: 400 properties: data: type: object properties: team_id: type: integer example: 7 monthly_cost_cents: type: integer example: 2500 mtd_cost_cents: type: integer example: 1200 sessions: type: integer example: 45 commits: type: integer example: 22 prs: type: integer example: 6 lines_added: type: integer example: 1800 lines_removed: type: integer example: 400 tags: - 'AI Tooling Usage' parameters: - in: path name: team_id description: 'The ID of the team.' example: 11 required: true schema: type: integer - in: path name: team description: 'The team ID.' example: 7 required: true schema: type: integer '/api/v1/ai-tooling/departments/{department_id}/summary': get: summary: 'AI Tooling — Department summary' operationId: aIToolingDepartmentSummary description: "Returns aggregated AI tooling cost and usage totals for a department. By default,\naggregates across the entire department subtree (all descendant org units). Pass\n`scope=direct` to limit to actors placed directly in the department's own org unit.\n\nIf the department has no associated org unit, zeroed metrics are returned.\n\nRequires `ai-tooling.manage` ability." parameters: - in: query name: scope description: 'Aggregation scope: "direct" (department org unit only) or "subtree" (all descendants). Defaults to "subtree".' example: subtree required: false schema: type: string description: 'Aggregation scope: "direct" (department org unit only) or "subtree" (all descendants). Defaults to "subtree".' example: subtree - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: department_id: 3 scope: subtree monthly_cost_cents: 8500 mtd_cost_cents: 3200 sessions: 120 commits: 55 prs: 12 lines_added: 5000 lines_removed: 1200 properties: data: type: object properties: department_id: type: integer example: 3 scope: type: string example: subtree monthly_cost_cents: type: integer example: 8500 mtd_cost_cents: type: integer example: 3200 sessions: type: integer example: 120 commits: type: integer example: 55 prs: type: integer example: 12 lines_added: type: integer example: 5000 lines_removed: type: integer example: 1200 tags: - 'AI Tooling Usage' parameters: - in: path name: department_id description: 'The ID of the department.' example: 11 required: true schema: type: integer - in: path name: department description: 'The department ID.' example: 3 required: true schema: type: integer /api/v1/actors: get: summary: 'Actors — List' operationId: actorsList description: "Returns actors with their primary team resolved from primary org-unit placements (primary=true) inside\nthe active scenario context.\n\nRequires `actors.read` ability." parameters: - in: query name: page description: 'The page number.' example: 1 required: false schema: type: integer description: 'The page number.' example: 1 - in: query name: per_page description: 'Max 100.' example: 25 required: false schema: type: integer description: 'Max 100.' example: 25 - in: query name: sort description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,name,created_at.' example: '-created_at,name' required: false schema: type: string description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,name,created_at.' example: '-created_at,name' - in: query name: 'filter[status]' description: 'Filter by status.' example: active required: false schema: type: string description: 'Filter by status.' example: active - in: query name: 'filter[department_id]' description: 'Filter by department id. Uses the department org unit mapping derived from `org_unit_actor`.' example: 5 required: false schema: type: integer description: 'Filter by department id. Uses the department org unit mapping derived from `org_unit_actor`.' example: 5 - in: query name: 'filter[org_unit_id]' description: 'Filter by org unit id (department or team subtree).' example: 10 required: false schema: type: integer description: 'Filter by org unit id (department or team subtree).' example: 10 - in: query name: 'fields[actors]' description: 'Sparse fields for actors.' example: 'id,first_name,last_name' required: false schema: type: string description: 'Sparse fields for actors.' example: 'id,first_name,last_name' - in: query name: include description: 'Comma-separated expansions. Allowed: manager,org_units,team,department. The `team`/`department` flags surface computed placement data.' example: 'manager,team' required: false schema: type: string description: 'Comma-separated expansions. Allowed: manager,org_units,team,department. The `team`/`department` flags surface computed placement data.' example: 'manager,team' - in: query name: as_of description: 'The date/datetime used for time-window checks. Defaults to now.' example: '2025-09-01' required: false schema: type: string description: 'The date/datetime used for time-window checks. Defaults to now.' example: '2025-09-01' - in: query name: scenario_id description: 'The scenario ID to scope results. Omit or pass `scenario_id=null` for baseline data.' example: 4 required: false schema: type: integer description: 'The scenario ID to scope results. Omit or pass `scenario_id=null` for baseline data.' example: 4 - in: query name: with_counts description: 'Comma-separated relationship counts. Allowed: reports.' example: reports required: false schema: type: string description: 'Comma-separated relationship counts. Allowed: reports.' example: reports - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 44 first_name: Aoife last_name: Ryan name: 'Aoife Ryan' email: aoife.ryan@example.com manager_id: 2 team: id: 15 name: 'Team CúChulainn' team_type: id: 3 name: 'Product Squad' slug: product-squad department: id: 3 name: Engineering properties: data: type: array example: - id: 44 first_name: Aoife last_name: Ryan name: 'Aoife Ryan' email: aoife.ryan@example.com manager_id: 2 team: id: 15 name: 'Team CúChulainn' team_type: id: 3 name: 'Product Squad' slug: product-squad department: id: 3 name: Engineering items: type: object properties: id: type: integer example: 44 first_name: type: string example: Aoife last_name: type: string example: Ryan name: type: string example: 'Aoife Ryan' email: type: string example: aoife.ryan@example.com manager_id: type: integer example: 2 team: type: object properties: id: type: integer example: 15 name: type: string example: 'Team CúChulainn' team_type: type: object properties: id: type: integer example: 3 name: type: string example: 'Product Squad' slug: type: string example: product-squad department: type: object properties: id: type: integer example: 3 name: type: string example: Engineering 400: description: '' content: application/json: schema: type: object example: code: query.invalid_parameter message: 'Unsupported sort: salary' details: allowed: - id - name - created_at properties: code: type: string example: query.invalid_parameter message: type: string example: 'Unsupported sort: salary' details: type: object properties: allowed: type: array example: - id - name - created_at items: type: string 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: per_page: - 'The per page must be at least 1.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: per_page: type: array example: - 'The per page must be at least 1.' items: type: string tags: - Actors post: summary: 'Store a newly created actor.' operationId: storeANewlyCreatedActor description: "Requires `actors.create` ability.\nManager must belong to the same tenant and cannot reference the actor themselves." parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 201: description: '' content: application/json: schema: type: object example: data: id: 44 first_name: Aoife last_name: Ryan name: 'Aoife Ryan' email: aoife.ryan@example.com kind: human status: active contract_type: perm manager_id: 2 start_date: '2025-09-01' end_date: '2025-09-30' created_at: '2025-01-10T09:00:00Z' properties: data: type: object properties: id: type: integer example: 44 first_name: type: string example: Aoife last_name: type: string example: Ryan name: type: string example: 'Aoife Ryan' email: type: string example: aoife.ryan@example.com kind: type: string example: human status: type: string example: active contract_type: type: string example: perm manager_id: type: integer example: 2 start_date: type: string example: '2025-09-01' end_date: type: string example: '2025-09-30' created_at: type: string example: '2025-01-10T09:00:00Z' 429: description: '' content: application/json: schema: type: object example: code: limit.actors message: 'Actor plan limit reached (250/250). Upgrade your plan to add more people.' limit: 250 current: 250 properties: code: type: string example: limit.actors message: type: string example: 'Actor plan limit reached (250/250). Upgrade your plan to add more people.' limit: type: integer example: 250 current: type: integer example: 250 tags: - Actors requestBody: required: true content: application/json: schema: type: object properties: first_name: type: string description: "The actor's first name." example: Aoife last_name: type: string description: "The actor's last name." example: Ryan email: type: string description: 'Optional contact email for the actor. Human actors can be created without email.' example: aoife.ryan@example.com nullable: true external_uid: type: string description: 'Durable Orgonaut actor identifier. If omitted, API generates a ULID.' example: 01J9ZQ4J6W2Q0WJ9V6YV5NPQ4W nullable: true source_hris: type: string description: '' example: null source_hris_id: type: string description: '' example: null kind: type: string description: 'The actor kind (human, agent, robot). Defaults to human when omitted.' example: human status: type: string description: 'Employment status.' example: active enum: - active - inactive nullable: true fte: type: number description: 'Contractual FTE (0.01–1.00). 1.00 = full-time, 0.50 = half-time.' example: 2306379.0 position_id: type: integer description: 'ID of an existing position to assign. Must exist in positions table for this tenant.' example: 42 nullable: true create_placement: type: boolean description: 'Whether to automatically create an org unit placement.' example: true nullable: true location: type: string description: "The actor's location." example: 'Dublin, IE' nullable: true start_date: type: string description: 'The start date. Format: YYYY-MM-DD.' example: '2025-09-01' nullable: true end_date: type: string description: 'The end date. Format: YYYY-MM-DD.' example: '2025-09-30' nullable: true manager_id: type: integer description: 'nullable The ID of the manager within this tenant.' example: 2 nullable: true agent_profile: type: object description: 'Agent profile payload (required when kind=agent).' example: provider: openai properties: provider: type: string description: 'Agent provider (openai, anthropic, azure_openai, custom).' example: openai nullable: true orchestrator: type: string description: 'Optional orchestrator identifier.' example: langgraph nullable: true meta: type: object description: 'Optional structured metadata payload.' example: capabilities: - search properties: { } nullable: true endpoint_base_url: type: string description: 'Optional base URL for provider API.' example: 'https://api.openai.com' model_default: type: string description: 'Default model identifier.' example: gpt-4.1 nullable: true required: - first_name - last_name - fte '/api/v1/actors/{id}': get: summary: 'Actors — Show' operationId: actorsShow description: "Returns an actor with their primary team resolved from primary org-unit placement (primary=true) within\nthe active scenario context.\n\nRequires a Sanctum token with the `actors.read` ability and an `X-Tenant` header." parameters: - in: query name: include description: 'Comma-separated expansions. Allowed: manager,org_units,team,department. The `team`/`department` flags surface computed placement data.' example: 'manager,team' required: false schema: type: string description: 'Comma-separated expansions. Allowed: manager,org_units,team,department. The `team`/`department` flags surface computed placement data.' example: 'manager,team' - in: query name: as_of description: 'The date/datetime used for time-window checks. Defaults to now.' example: '2025-09-01' required: false schema: type: string description: 'The date/datetime used for time-window checks. Defaults to now.' example: '2025-09-01' - in: query name: scenario_id description: 'The scenario context to resolve against. Defaults to current (baseline if none).' example: null required: false schema: type: string description: 'The scenario context to resolve against. Defaults to current (baseline if none).' example: null - in: query name: with_counts description: 'Comma-separated relationship counts. Allowed: reports.' example: reports required: false schema: type: string description: 'Comma-separated relationship counts. Allowed: reports.' example: reports - in: header name: X-Tenant description: '' example: 'string required Tenant slug' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 44 first_name: Aoife last_name: Ryan name: 'Aoife Ryan' email: aoife.ryan@example.com kind: human status: active contract_type: perm manager_id: 2 manager: id: 2 first_name: Brian last_name: "O'Neil" name: "Brian O'Neil" reports_count: 3 team: id: 15 name: 'Team CúChulainn' team_type: id: 3 name: 'Product Squad' slug: product-squad department: id: 3 name: Engineering created_at: '2025-01-10T09:00:00Z' properties: data: type: object properties: id: type: integer example: 44 first_name: type: string example: Aoife last_name: type: string example: Ryan name: type: string example: 'Aoife Ryan' email: type: string example: aoife.ryan@example.com kind: type: string example: human status: type: string example: active contract_type: type: string example: perm manager_id: type: integer example: 2 manager: type: object properties: id: type: integer example: 2 first_name: type: string example: Brian last_name: type: string example: "O'Neil" name: type: string example: "Brian O'Neil" reports_count: type: integer example: 3 team: type: object properties: id: type: integer example: 15 name: type: string example: 'Team CúChulainn' team_type: type: object properties: id: type: integer example: 3 name: type: string example: 'Product Squad' slug: type: string example: product-squad department: type: object properties: id: type: integer example: 3 name: type: string example: Engineering created_at: type: string example: '2025-01-10T09:00:00Z' tags: - Actors put: summary: 'Update the specified actor.' operationId: updateTheSpecifiedActor description: "Requires `actors.update` ability and an `X-Tenant` header.\nManager must belong to the same tenant and cannot reference the actor themselves." parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 44 first_name: Aoife last_name: Ryan name: 'Aoife Ryan' email: aoife.ryan@example.com kind: human status: active manager_id: 2 team: id: 15 name: 'Team CúChulainn' department: id: 3 name: Engineering created_at: '2025-01-10T09:00:00Z' properties: data: type: object properties: id: type: integer example: 44 first_name: type: string example: Aoife last_name: type: string example: Ryan name: type: string example: 'Aoife Ryan' email: type: string example: aoife.ryan@example.com kind: type: string example: human status: type: string example: active manager_id: type: integer example: 2 team: type: object properties: id: type: integer example: 15 name: type: string example: 'Team CúChulainn' department: type: object properties: id: type: integer example: 3 name: type: string example: Engineering created_at: type: string example: '2025-01-10T09:00:00Z' tags: - Actors requestBody: required: true content: application/json: schema: type: object properties: first_name: type: string description: "The actor's first name." example: Aoife last_name: type: string description: "The actor's last name." example: Ryan email: type: string description: 'Optional contact email for the actor. Human actors can remain blank.' example: aoife.ryan@example.com nullable: true external_uid: type: string description: 'Durable Orgonaut actor identifier.' example: 01J9ZQ4J6W2Q0WJ9V6YV5NPQ4W nullable: true source_hris: type: string description: '' example: null source_hris_id: type: string description: '' example: null kind: type: string description: 'The actor kind (immutable once set; updates rejected).' example: human status: type: string description: 'Employment status.' example: active enum: - active - inactive nullable: true fte: type: number description: 'Contractual FTE (0.01–1.00). 1.00 = full-time, 0.50 = half-time.' example: 2306379.0 position_id: type: integer description: 'ID of an existing position to assign. Must exist in positions table for this tenant.' example: 42 nullable: true create_placement: type: boolean description: 'Whether to automatically create an org unit placement.' example: true nullable: true location: type: string description: "The actor's location." example: 'Dublin, IE' nullable: true start_date: type: string description: 'The start date. Format: YYYY-MM-DD.' example: '2025-09-01' nullable: true end_date: type: string description: 'The end date. Format: YYYY-MM-DD.' example: '2025-09-30' nullable: true manager_id: type: integer description: 'nullable The ID of the manager within this tenant.' example: 2 nullable: true agent_profile: type: object description: 'Agent profile payload.' example: provider: openai properties: provider: type: string description: 'Agent provider (openai, anthropic, azure_openai, custom).' example: openai enum: - openai - anthropic - azure_openai - custom nullable: true orchestrator: type: string description: 'Optional orchestrator identifier. Must not be greater than 255 characters.' example: langgraph nullable: true meta: type: object description: 'Optional structured metadata payload.' example: capabilities: - search properties: { } nullable: true nullable: true required: - first_name - last_name - fte delete: summary: 'Remove the specified actor.' operationId: removeTheSpecifiedActor description: 'Requires `actors.delete` ability and an `X-Tenant` header.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug' schema: type: string responses: 204: description: '' content: application/json: schema: type: object example: { } properties: { } tags: - Actors parameters: - in: path name: id description: 'The ID of the actor.' example: 44 required: true schema: type: integer '/api/v1/actors/{actor_id}/compensations': get: summary: 'Actor compensations — List' operationId: actorCompensationsList description: "Lists compensation slices for the given actor. When a scenario context is supplied,\nscenario slices are returned alongside baseline fallbacks, ordered with scenario data first.\n\nRequires `actors.read` ability." parameters: - in: query name: scenario_id description: 'Scenario id to scope results; omit or pass null for baseline only.' example: 4 required: false schema: type: integer description: 'Scenario id to scope results; omit or pass null for baseline only.' example: 4 - in: query name: as_of description: 'Date used to flag current slices.' example: '2025-03-01' required: false schema: type: string description: 'Date used to flag current slices.' example: '2025-03-01' - in: query name: include description: 'Comma-separated relationships. Allowed: scenario,actor.' example: scenario required: false schema: type: string description: 'Comma-separated relationships. Allowed: scenario,actor.' example: scenario - in: query name: 'fields[actor_compensations]' description: 'Sparse fieldset.' example: 'id,currency,base_salary_cents' required: false schema: type: string description: 'Sparse fieldset.' example: 'id,currency,base_salary_cents' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 currency: USD base_salary_cents: 12000000 properties: data: type: array example: - id: 1 currency: USD base_salary_cents: 12000000 items: type: object properties: id: type: integer example: 1 currency: type: string example: USD base_salary_cents: type: integer example: 12000000 tags: - Actors post: summary: 'Actor compensations — Create' operationId: actorCompensationsCreate description: "Stores a new compensation slice for the actor.\n\nRequires `actors.update` ability." parameters: - in: query name: include description: 'Comma-separated relationships. Allowed: scenario,actor.' example: scenario required: false schema: type: string description: 'Comma-separated relationships. Allowed: scenario,actor.' example: scenario - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 201: description: '' content: application/json: schema: type: object example: data: id: 9 currency: EUR daily_rate_cents: 55000 properties: data: type: object properties: id: type: integer example: 9 currency: type: string example: EUR daily_rate_cents: type: integer example: 55000 tags: - Actors requestBody: required: true content: application/json: schema: type: object properties: scenario_id: type: integer description: 'Scenario context; omit or null for baseline actuals. The id of an existing record in the scenarios table.' example: 3 nullable: true employment_type: type: string description: 'Employment type matching actor records.' example: Permanent enum: - Permanent - Contractor currency: type: string description: 'Three-letter currency code defined in Settings → Currencies. Must contain only letters. Must be 3 characters.' example: USD base_salary_cents: type: integer description: 'Annual base salary in cents (for FTE). This field is required when daily_rate_cents is not present. Must be at least 0.' example: 12000000 nullable: true daily_rate_cents: type: integer description: 'Daily contractor rate in cents (for contractors). This field is required when base_salary_cents is not present. Must be at least 0.' example: 45000 nullable: true valid_from: type: string description: 'Start date for the compensation window (YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d.' example: '2025-01-01' valid_to: type: string description: 'Optional end date for the compensation window (YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d. Must be a date after or equal to valid_from.' example: '2025-12-31' nullable: true meta: type: object description: 'Arbitrary metadata to persist alongside the record.' example: note: 'Includes sign-on bonus' properties: { } nullable: true required: - employment_type - currency - valid_from parameters: - in: path name: actor_id description: 'The ID of the actor.' example: 11 required: true schema: type: integer - in: path name: actor description: 'The actor identifier.' example: 11 required: true schema: type: integer '/api/v1/actors/{actor_id}/compensations/{id}': patch: summary: 'Actor compensations — Update' operationId: actorCompensationsUpdate description: "Updates an existing compensation slice for the actor.\n\nRequires `actors.update` ability." parameters: - in: query name: include description: 'Comma-separated relationships. Allowed: scenario,actor.' example: scenario required: false schema: type: string description: 'Comma-separated relationships. Allowed: scenario,actor.' example: scenario - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 9 currency: USD base_salary_cents: 9900000 properties: data: type: object properties: id: type: integer example: 9 currency: type: string example: USD base_salary_cents: type: integer example: 9900000 tags: - Actors requestBody: required: true content: application/json: schema: type: object properties: scenario_id: type: integer description: 'Scenario context; omit or null for baseline actuals. The id of an existing record in the scenarios table.' example: 3 nullable: true employment_type: type: string description: 'Employment type matching actor records.' example: Contractor enum: - Permanent - Contractor currency: type: string description: 'Three-letter currency code defined in Settings → Currencies. Must contain only letters. Must be 3 characters.' example: GBP base_salary_cents: type: integer description: 'Annual base salary in cents (for FTE). This field is required when daily_rate_cents is not present. Must be at least 0.' example: 9800000 nullable: true daily_rate_cents: type: integer description: 'Daily contractor rate in cents (for contractors). This field is required when base_salary_cents is not present. Must be at least 0.' example: 60000 nullable: true valid_from: type: string description: 'Start date for the compensation window (YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d.' example: '2025-02-01' valid_to: type: string description: 'Optional end date for the compensation window (YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d. Must be a date after or equal to valid_from.' example: '2025-09-30' nullable: true meta: type: object description: 'Arbitrary metadata to persist alongside the record.' example: note: 'Updated after promotion' properties: { } nullable: true required: - employment_type - currency - valid_from delete: summary: 'Actor compensations — Delete' operationId: actorCompensationsDelete description: "Soft deletes a compensation slice.\n\nRequires `actors.delete` ability." parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 204: description: '' content: application/json: schema: type: object example: { } properties: { } tags: - Actors parameters: - in: path name: actor_id description: 'The ID of the actor.' example: 11 required: true schema: type: integer - in: path name: id description: 'The ID of the compensation.' example: 11 required: true schema: type: integer - in: path name: actor description: 'The actor identifier.' example: 11 required: true schema: type: integer - in: path name: compensation description: 'The compensation identifier.' example: 11 required: true schema: type: integer /api/v1/aggregates/recalculate: post: summary: 'Queue recalculation of cached aggregates.' operationId: queueRecalculationOfCachedAggregates description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: status: queued properties: status: type: string example: queued tags: - Aggregates /api/v1/astro/capabilities: get: summary: 'List Astro capabilities for the current tenant, user, and context.' operationId: listAstroCapabilitiesForTheCurrentTenantUserAndContext description: '' parameters: - in: query name: category description: 'Filter by category key.' example: work_with_scenarios required: false schema: type: string description: 'Filter by category key.' example: work_with_scenarios - in: query name: kind description: 'Filter by capability kind.' example: read required: false schema: type: string description: 'Filter by capability kind.' example: read - in: query name: availability description: 'Filter by computed availability.' example: available required: false schema: type: string description: 'Filter by computed availability.' example: available - in: query name: include_unavailable description: 'Include capabilities that are unavailable in the current context.' example: false required: false schema: type: boolean description: 'Include capabilities that are unavailable in the current context.' example: false - in: query name: view_mode description: 'Override the current Astro view mode (live, scenario, snapshot).' example: scenario required: false schema: type: string description: 'Override the current Astro view mode (live, scenario, snapshot).' example: scenario - in: query name: scenario_id description: 'Optional scenario id used for scenario- or snapshot-scoped discovery.' example: 7 required: false schema: type: integer description: 'Optional scenario id used for scenario- or snapshot-scoped discovery.' example: 7 - in: query name: focused_entity_type description: 'Optional focused entity type (actor, department, org_unit, scenario, team).' example: team required: false schema: type: string description: 'Optional focused entity type (actor, department, org_unit, scenario, team).' example: team - in: query name: focused_entity_id description: 'Optional focused entity id.' example: 12 required: false schema: type: integer description: 'Optional focused entity id.' example: 12 - in: query name: as_of description: 'date Optional as-of date for scoped discovery.' example: '2026-03-01' required: false schema: type: string description: 'date Optional as-of date for scoped discovery.' example: '2026-03-01' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - key: explore_organisation title: 'Explore organisation' description: 'Browse the current organisation structure, departments, teams, and root summaries.' capabilities: - key: explore.organisation title: 'Explore organisation structure' kind: read availability_status: available meta: context: view_mode: live scope_key: baseline counts: available: 5 limited: 2 unavailable: 0 coming_soon: 3 properties: data: type: array example: - key: explore_organisation title: 'Explore organisation' description: 'Browse the current organisation structure, departments, teams, and root summaries.' capabilities: - key: explore.organisation title: 'Explore organisation structure' kind: read availability_status: available items: type: object properties: key: type: string example: explore_organisation title: type: string example: 'Explore organisation' description: type: string example: 'Browse the current organisation structure, departments, teams, and root summaries.' capabilities: type: array example: - key: explore.organisation title: 'Explore organisation structure' kind: read availability_status: available items: type: object properties: key: type: string example: explore.organisation title: type: string example: 'Explore organisation structure' kind: type: string example: read availability_status: type: string example: available meta: type: object properties: context: type: object properties: view_mode: type: string example: live scope_key: type: string example: baseline counts: type: object properties: available: type: integer example: 5 limited: type: integer example: 2 unavailable: type: integer example: 0 coming_soon: type: integer example: 3 tags: - Astro '/api/v1/astro/capabilities/{key}': get: summary: 'Show one Astro capability for the current tenant, user, and context.' operationId: showOneAstroCapabilityForTheCurrentTenantUserAndContext description: '' parameters: - in: query name: view_mode description: 'Override the current Astro view mode (live, scenario, snapshot).' example: scenario required: false schema: type: string description: 'Override the current Astro view mode (live, scenario, snapshot).' example: scenario - in: query name: scenario_id description: 'Optional scenario id used for scenario- or snapshot-scoped discovery.' example: 7 required: false schema: type: integer description: 'Optional scenario id used for scenario- or snapshot-scoped discovery.' example: 7 - in: query name: focused_entity_type description: 'Optional focused entity type (actor, department, org_unit, scenario, team).' example: team required: false schema: type: string description: 'Optional focused entity type (actor, department, org_unit, scenario, team).' example: team - in: query name: focused_entity_id description: 'Optional focused entity id.' example: 12 required: false schema: type: integer description: 'Optional focused entity id.' example: 12 - in: query name: as_of description: 'date Optional as-of date for scoped discovery.' example: '2026-03-01' required: false schema: type: string description: 'date Optional as-of date for scoped discovery.' example: '2026-03-01' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: key: explore.organisation title: 'Explore organisation structure' kind: read availability_status: available examples: - prompt: 'List the departments in the current scope.' properties: data: type: object properties: key: type: string example: explore.organisation title: type: string example: 'Explore organisation structure' kind: type: string example: read availability_status: type: string example: available examples: type: array example: - prompt: 'List the departments in the current scope.' items: type: object properties: prompt: type: string example: 'List the departments in the current scope.' 404: description: '' content: application/json: schema: type: object example: message: 'Capability not found.' properties: message: type: string example: 'Capability not found.' tags: - Astro parameters: - in: path name: key description: 'Capability key.' example: explore.organisation required: true schema: type: string /api/v1/tokens: get: summary: "List the authenticated user's tokens." operationId: listTheAuthenticatedUsersTokens description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: array items: type: object properties: id: type: integer example: 12 name: type: string example: 'CI/CD Bot' abilities: type: array example: - actors.read - scenarios.update items: type: string last_used_at: type: string example: '2025-08-01T09:30:12Z' created_at: type: string example: '2025-07-15T12:00:00Z' updated_at: type: string example: '2025-08-01T09:30:12Z' example: - id: 12 name: 'CI/CD Bot' abilities: - actors.read - scenarios.update last_used_at: '2025-08-01T09:30:12Z' created_at: '2025-07-15T12:00:00Z' updated_at: '2025-08-01T09:30:12Z' tags: - Auth/Tokens post: summary: 'Issue a new actoral access token.' operationId: issueANewActoralAccessToken description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug for token scope. Example: "demo"' schema: type: string responses: 201: description: '' content: application/json: schema: type: object example: token: 1|abcdef123456 properties: token: type: string example: 1|abcdef123456 403: description: '' content: application/json: schema: type: object example: code: auth.forbidden message: Forbidden. properties: code: type: string example: auth.forbidden message: type: string example: Forbidden. 429: description: '' content: application/json: schema: type: object example: code: limit.api_tokens message: 'API token plan limit reached (1/1). Revoke an existing token or upgrade your plan.' limit: 1 current: 1 properties: code: type: string example: limit.api_tokens message: type: string example: 'API token plan limit reached (1/1). Revoke an existing token or upgrade your plan.' limit: type: integer example: 1 current: type: integer example: 1 tags: - Auth/Tokens requestBody: required: false content: application/json: schema: type: object properties: email: type: string description: 'The email address of the user to authenticate.' example: loma53@example.com password: type: string description: 'The password of the user to authenticate.' example: secret-password-123 name: type: string description: 'The human-readable label for this token (shown in your account).' example: 'CI/CD Bot' abilities: type: array description: 'Optional abilities for the token.' example: - actors.read - scenarios.update items: type: string security: [] '/api/v1/tokens/{id}': delete: summary: 'Revoke a token.' operationId: revokeAToken description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 204: description: '' content: application/json: schema: type: object example: { } properties: { } tags: - Auth/Tokens patch: summary: 'Rename a token.' operationId: renameAToken description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 204: description: '' content: application/json: schema: type: object example: { } properties: { } tags: - Auth/Tokens requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'New human‑friendly name for the token. Must not be greater than 255 characters.' example: 'CI bot token' required: - name parameters: - in: path name: id description: 'The ID of the token.' example: ab required: true schema: type: string - in: path name: token description: 'The token ID.' example: 11 required: true schema: type: integer '/api/v1/tokens/{token}/rotate': post: summary: 'Rotate a token.' operationId: rotateAToken description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: token: 1|abcdef123456 properties: token: type: string example: 1|abcdef123456 tags: - Auth/Tokens parameters: - in: path name: token description: 'The token ID.' example: 11 required: true schema: type: integer /api/v1/actor-cost-events: post: summary: 'Agent Cost Events — Create' operationId: agentCostEventsCreate description: "Ingest a machine cost event with idempotency protection. Duplicate idempotency keys\nreturn the existing event instead of creating a new row." parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 1 actor_id: 44 actor_agent_profile_id: 9 org_unit_id: 12 scenario_id: null occurred_at: '2025-09-01T12:00:00Z' input_tokens: 1200000 cached_input_tokens: 250000 output_tokens: 640000 amount: 18.75 currency: USD provider: openai model: gpt-4.1 meta: refund: false created_at: '2025-09-02T10:00:00Z' properties: data: type: object properties: id: type: integer example: 1 actor_id: type: integer example: 44 actor_agent_profile_id: type: integer example: 9 org_unit_id: type: integer example: 12 scenario_id: type: string example: null nullable: true occurred_at: type: string example: '2025-09-01T12:00:00Z' input_tokens: type: integer example: 1200000 cached_input_tokens: type: integer example: 250000 output_tokens: type: integer example: 640000 amount: type: number example: 18.75 currency: type: string example: USD provider: type: string example: openai model: type: string example: gpt-4.1 meta: type: object properties: refund: type: boolean example: false created_at: type: string example: '2025-09-02T10:00:00Z' 201: description: '' content: application/json: schema: type: object example: data: id: 1 actor_id: 44 actor_agent_profile_id: 9 org_unit_id: 12 scenario_id: null occurred_at: '2025-09-01T12:00:00Z' input_tokens: 1200000 cached_input_tokens: 250000 output_tokens: 640000 amount: 18.75 currency: USD provider: openai model: gpt-4.1 meta: refund: false created_at: '2025-09-02T10:00:00Z' properties: data: type: object properties: id: type: integer example: 1 actor_id: type: integer example: 44 actor_agent_profile_id: type: integer example: 9 org_unit_id: type: integer example: 12 scenario_id: type: string example: null nullable: true occurred_at: type: string example: '2025-09-01T12:00:00Z' input_tokens: type: integer example: 1200000 cached_input_tokens: type: integer example: 250000 output_tokens: type: integer example: 640000 amount: type: number example: 18.75 currency: type: string example: USD provider: type: string example: openai model: type: string example: gpt-4.1 meta: type: object properties: refund: type: boolean example: false created_at: type: string example: '2025-09-02T10:00:00Z' tags: - Costs requestBody: required: true content: application/json: schema: type: object properties: occurred_at: type: string description: 'Event timestamp (ISO-8601). Must be a valid date.' example: '2025-09-01T12:00:00Z' amount: type: number description: 'Cost amount in the event currency. Optional when token metrics are provided and central model pricing is configured.' example: 42.55 nullable: true currency: type: string description: 'Currency code (ISO-4217). Required when amount is provided. Must be 3 characters.' example: USD nullable: true idempotency_key: type: string description: 'Unique idempotency key for this event. Must not be greater than 255 characters.' example: evt_01HX... actor_id: type: integer description: 'Agent actor ID (required). Provider is resolved from the actor profile. The id of an existing record in the actors table.' example: 44 org_unit_id: type: integer description: 'Org unit snapshot attribution (optional). The id of an existing record in the org_units table.' example: 12 nullable: true scenario_id: type: integer description: 'Optional scenario check; must match the actor profile scenario when provided. The id of an existing record in the scenarios table.' example: 3 nullable: true model: type: string description: 'Model identifier. Optional when the actor profile has a default model. Must not be greater than 255 characters.' example: gpt-4.1 nullable: true input_tokens: type: integer description: 'Input token count for the event. Must be at least 0.' example: 1200000 nullable: true cached_input_tokens: type: integer description: 'Cached input token count for the event. Must be at least 0.' example: 250000 nullable: true output_tokens: type: integer description: 'Output token count for the event. Must be at least 0.' example: 640000 nullable: true meta: type: object description: 'Arbitrary structured metadata for the event.' example: refund: false properties: { } nullable: true required: - occurred_at - idempotency_key - actor_id /api/v1/settings/ai/costs: get: summary: 'AI Model Costs — List' operationId: aIModelCostsList description: 'List tenant-global model pricing rows.' parameters: - in: query name: sort description: 'Comma-separated sort list. Allowed: id,provider,model,effective_from,effective_to,created_at. Prefix with `-` for descending order.' example: '-effective_from,provider' required: false schema: type: string description: 'Comma-separated sort list. Allowed: id,provider,model,effective_from,effective_to,created_at. Prefix with `-` for descending order.' example: '-effective_from,provider' - in: query name: 'filter[provider]' description: 'Filter by provider slug.' example: openai required: false schema: type: string description: 'Filter by provider slug.' example: openai - in: query name: 'filter[model]' description: 'Filter by model identifier.' example: gpt-4.1 required: false schema: type: string description: 'Filter by model identifier.' example: gpt-4.1 - in: query name: 'filter[currency]' description: 'Filter by currency code.' example: USD required: false schema: type: string description: 'Filter by currency code.' example: USD - in: query name: 'fields[ai_model_costs]' description: 'Sparse fieldset for AI model costs.' example: 'id,provider,model,currency,is_active' required: false schema: type: string description: 'Sparse fieldset for AI model costs.' example: 'id,provider,model,currency,is_active' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 11 provider: openai model: gpt-4.1 currency: USD input_mtok_price: 5 cached_input_mtok_price: 0.5 output_mtok_price: 15 effective_from: '2026-01-01' effective_to: null is_active: true created_at: '2026-03-07T09:00:00Z' updated_at: '2026-03-07T09:00:00Z' links: first: null last: null prev: null next: null meta: current_page: 1 last_page: 1 per_page: 1 total: 1 properties: data: type: array example: - id: 11 provider: openai model: gpt-4.1 currency: USD input_mtok_price: 5 cached_input_mtok_price: 0.5 output_mtok_price: 15 effective_from: '2026-01-01' effective_to: null is_active: true created_at: '2026-03-07T09:00:00Z' updated_at: '2026-03-07T09:00:00Z' items: type: object properties: id: type: integer example: 11 provider: type: string example: openai model: type: string example: gpt-4.1 currency: type: string example: USD input_mtok_price: type: integer example: 5 cached_input_mtok_price: type: number example: 0.5 output_mtok_price: type: integer example: 15 effective_from: type: string example: '2026-01-01' effective_to: type: string example: null nullable: true is_active: type: boolean example: true created_at: type: string example: '2026-03-07T09:00:00Z' updated_at: type: string example: '2026-03-07T09:00:00Z' links: type: object properties: first: type: string example: null nullable: true last: type: string example: null nullable: true prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: current_page: type: integer example: 1 last_page: type: integer example: 1 per_page: type: integer example: 1 total: type: integer example: 1 tags: - Costs post: summary: 'AI Model Costs — Create' operationId: aIModelCostsCreate description: 'Create a tenant-global model pricing row.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 201: description: '' content: application/json: schema: type: object example: data: id: 11 provider: openai model: gpt-4.1 currency: USD input_mtok_price: 5 cached_input_mtok_price: 0.5 output_mtok_price: 15 effective_from: '2026-01-01' effective_to: null is_active: true created_at: '2026-03-07T09:00:00Z' updated_at: '2026-03-07T09:00:00Z' properties: data: type: object properties: id: type: integer example: 11 provider: type: string example: openai model: type: string example: gpt-4.1 currency: type: string example: USD input_mtok_price: type: integer example: 5 cached_input_mtok_price: type: number example: 0.5 output_mtok_price: type: integer example: 15 effective_from: type: string example: '2026-01-01' effective_to: type: string example: null nullable: true is_active: type: boolean example: true created_at: type: string example: '2026-03-07T09:00:00Z' updated_at: type: string example: '2026-03-07T09:00:00Z' tags: - Costs requestBody: required: true content: application/json: schema: type: object properties: provider: type: string description: 'Model provider identifier. Must not be greater than 64 characters.' example: openai model: type: string description: 'Provider model identifier. Must not be greater than 255 characters.' example: gpt-4.1 currency: type: string description: 'Tenant currency code. Must contain only letters. Must be 3 characters.' example: USD input_mtok_price: type: number description: 'Price per 1M input tokens. Must be at least 0.' example: 5.0 cached_input_mtok_price: type: number description: 'Price per 1M cached input tokens. Must be at least 0.' example: 0.5 nullable: true output_mtok_price: type: number description: 'Price per 1M output tokens. Must be at least 0.' example: 15.0 effective_from: type: string description: 'Effective start date (YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d.' example: '2026-01-01' effective_to: type: string description: 'Optional effective end date (YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d. Must be a date after or equal to effective_from.' example: '2026-06-30' nullable: true required: - provider - model - currency - input_mtok_price - output_mtok_price - effective_from '/api/v1/settings/ai/costs/{ai_model_cost}': get: summary: 'AI Model Costs — Show' operationId: aIModelCostsShow description: 'Return a single tenant-global model pricing row.' parameters: - in: query name: 'fields[ai_model_costs]' description: 'Sparse fieldset for AI model costs.' example: 'id,provider,model,currency,is_active' required: false schema: type: string description: 'Sparse fieldset for AI model costs.' example: 'id,provider,model,currency,is_active' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 11 provider: openai model: gpt-4.1 currency: USD input_mtok_price: 5 cached_input_mtok_price: 0.5 output_mtok_price: 15 effective_from: '2026-01-01' effective_to: null is_active: true created_at: '2026-03-07T09:00:00Z' updated_at: '2026-03-07T09:00:00Z' properties: data: type: object properties: id: type: integer example: 11 provider: type: string example: openai model: type: string example: gpt-4.1 currency: type: string example: USD input_mtok_price: type: integer example: 5 cached_input_mtok_price: type: number example: 0.5 output_mtok_price: type: integer example: 15 effective_from: type: string example: '2026-01-01' effective_to: type: string example: null nullable: true is_active: type: boolean example: true created_at: type: string example: '2026-03-07T09:00:00Z' updated_at: type: string example: '2026-03-07T09:00:00Z' tags: - Costs patch: summary: 'AI Model Costs — Update' operationId: aIModelCostsUpdate description: 'Update a tenant-global model pricing row.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 11 provider: openai model: gpt-4.1-mini currency: USD input_mtok_price: 0.8 cached_input_mtok_price: 0.2 output_mtok_price: 3.2 effective_from: '2026-03-01' effective_to: null is_active: true created_at: '2026-03-07T09:00:00Z' updated_at: '2026-03-07T10:15:00Z' properties: data: type: object properties: id: type: integer example: 11 provider: type: string example: openai model: type: string example: gpt-4.1-mini currency: type: string example: USD input_mtok_price: type: number example: 0.8 cached_input_mtok_price: type: number example: 0.2 output_mtok_price: type: number example: 3.2 effective_from: type: string example: '2026-03-01' effective_to: type: string example: null nullable: true is_active: type: boolean example: true created_at: type: string example: '2026-03-07T09:00:00Z' updated_at: type: string example: '2026-03-07T10:15:00Z' tags: - Costs requestBody: required: true content: application/json: schema: type: object properties: provider: type: string description: 'Model provider identifier. Must not be greater than 64 characters.' example: openai model: type: string description: 'Provider model identifier. Must not be greater than 255 characters.' example: gpt-4.1 currency: type: string description: 'Tenant currency code. Must contain only letters. Must be 3 characters.' example: USD input_mtok_price: type: number description: 'Price per 1M input tokens. Must be at least 0.' example: 5.0 cached_input_mtok_price: type: number description: 'Price per 1M cached input tokens. Must be at least 0.' example: 0.5 nullable: true output_mtok_price: type: number description: 'Price per 1M output tokens. Must be at least 0.' example: 15.0 effective_from: type: string description: 'Effective start date (YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d.' example: '2026-01-01' effective_to: type: string description: 'Optional effective end date (YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d. Must be a date after or equal to effective_from.' example: '2026-06-30' nullable: true required: - provider - model - currency - input_mtok_price - output_mtok_price - effective_from delete: summary: 'AI Model Costs — Delete' operationId: aIModelCostsDelete description: 'Delete a tenant-global model pricing row.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 204: description: '' content: application/json: schema: type: object example: { } properties: { } tags: - Costs parameters: - in: path name: ai_model_cost description: 'The AI model cost row id.' example: 11 required: true schema: type: integer /api/v1/department-types/search: get: summary: 'Return the top matching Department Types for the active tenant.' operationId: returnTheTopMatchingDepartmentTypesForTheActiveTenant description: 'Requires `departments.read` ability.' parameters: - in: query name: q description: 'Search term applied to name and slug.' example: ops required: false schema: type: string description: 'Search term applied to name and slug.' example: ops - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "acme"' schema: type: string responses: 200: description: '' content: application/json: schema: type: array items: type: object properties: id: type: integer example: 1 name: type: string example: Operations slug: type: string example: operations color: type: string example: '#008080' example: - id: 1 name: Operations slug: operations color: '#008080' tags: - 'Department Types' /api/v1/department-types: post: summary: 'Create a Department Type for the active tenant.' operationId: createADepartmentTypeForTheActiveTenant description: 'Requires `departments.create` ability.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "acme"' schema: type: string responses: 201: description: '' content: application/json: schema: type: object example: id: 3 name: Operations slug: operations color: '#008080' properties: id: type: integer example: 3 name: type: string example: Operations slug: type: string example: operations color: type: string example: '#008080' tags: - 'Department Types' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Display name.' example: Operations slug: type: string description: 'Optional custom slug; defaults to a slugified name.' example: ops nullable: true color: type: string description: 'Optional hex colour (with or without #).' example: '#008080' nullable: true description: type: string description: 'Optional description of the classification.' example: 'Supports company-wide services' nullable: true required: - name /api/v1/departments: get: summary: 'Display a paginated listing of departments.' operationId: displayAPaginatedListingOfDepartments description: 'Requires `departments.read` ability.' parameters: - in: query name: page description: 'The page number.' example: 1 required: false schema: type: integer description: 'The page number.' example: 1 - in: query name: per_page description: 'Max 100.' example: 25 required: false schema: type: integer description: 'Max 100.' example: 25 - in: query name: sort description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,name,created_at.' example: '-created_at,name' required: false schema: type: string description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,name,created_at.' example: '-created_at,name' - in: query name: 'filter[lead_id]' description: 'Filter by lead.' example: 44 required: false schema: type: integer description: 'Filter by lead.' example: 44 - in: query name: 'fields[departments]' description: 'Sparse fields for departments.' example: 'id,name,department_type_id' required: false schema: type: string description: 'Sparse fields for departments.' example: 'id,name,department_type_id' - in: query name: include description: 'Comma-separated relationships. Allowed: lead,departmentType,parentDepartment,aggregate.' example: 'lead,departmentType' required: false schema: type: string description: 'Comma-separated relationships. Allowed: lead,departmentType,parentDepartment,aggregate.' example: 'lead,departmentType' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 3 name: Engineering lead_id: 44 department_type_id: 7 department_type: id: 7 name: Division slug: division color: '#0052cc' parent_department_id: 2 parent_department: id: 2 name: Product is_active: true description: 'Platform engineering for core services.' links: first: null last: null prev: null next: null meta: total: 1 current_page: 1 per_page: 15 last_page: 1 properties: data: type: array example: - id: 3 name: Engineering lead_id: 44 department_type_id: 7 department_type: id: 7 name: Division slug: division color: '#0052cc' parent_department_id: 2 parent_department: id: 2 name: Product is_active: true description: 'Platform engineering for core services.' items: type: object properties: id: type: integer example: 3 name: type: string example: Engineering lead_id: type: integer example: 44 department_type_id: type: integer example: 7 department_type: type: object properties: id: type: integer example: 7 name: type: string example: Division slug: type: string example: division color: type: string example: '#0052cc' parent_department_id: type: integer example: 2 parent_department: type: object properties: id: type: integer example: 2 name: type: string example: Product is_active: type: boolean example: true description: type: string example: 'Platform engineering for core services.' links: type: object properties: first: type: string example: null nullable: true last: type: string example: null nullable: true prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: total: type: integer example: 1 current_page: type: integer example: 1 per_page: type: integer example: 15 last_page: type: integer example: 1 400: description: '' content: application/json: schema: type: object example: code: query.invalid_parameter message: 'Unsupported sort: salary' details: allowed: - id - name - created_at properties: code: type: string example: query.invalid_parameter message: type: string example: 'Unsupported sort: salary' details: type: object properties: allowed: type: array example: - id - name - created_at items: type: string 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: per_page: - 'The per page must be at least 1.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: per_page: type: array example: - 'The per page must be at least 1.' items: type: string tags: - Departments post: summary: 'Store a newly created department.' operationId: storeANewlyCreatedDepartment description: 'Requires `departments.create` ability.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 201: description: '' content: application/json: schema: type: object example: data: id: 3 name: Engineering lead_id: 44 cost_center: ENG-001 department_type_id: 7 department_type: id: 7 name: Division slug: division color: '#0052cc' parent_department_id: null parent_department: null is_active: true description: 'Owns platform reliability.' properties: data: type: object properties: id: type: integer example: 3 name: type: string example: Engineering lead_id: type: integer example: 44 cost_center: type: string example: ENG-001 department_type_id: type: integer example: 7 department_type: type: object properties: id: type: integer example: 7 name: type: string example: Division slug: type: string example: division color: type: string example: '#0052cc' parent_department_id: type: string example: null nullable: true parent_department: type: string example: null nullable: true is_active: type: boolean example: true description: type: string example: 'Owns platform reliability.' tags: - Departments requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Department name (unique per tenant and scenario). Must not be greater than 255 characters.' example: Engineering department_type_id: type: integer description: 'Department type identifier. The id of an existing record in the department_types table.' example: 5 parent_org_unit_id: type: integer description: 'Optional parent org unit id.' example: 42 nullable: true cost_center: type: string description: 'Optional cost center code. Must not be greater than 50 characters.' example: ENG-1001 nullable: true department_lead_id: type: integer description: 'Actor id of department lead. The id of an existing record in the actors table.' example: 44 nullable: true is_active: type: boolean description: 'Whether the department is active.' example: true nullable: true description: type: string description: 'Optional department description.' example: 'Supports the full product org.' nullable: true required: - name - department_type_id '/api/v1/departments/{id}': get: summary: 'Display a specific department.' operationId: displayASpecificDepartment description: 'Requires `departments.read` ability.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 3 name: Engineering lead_id: 44 cost_center: ENG-001 department_type_id: 7 department_type: id: 7 name: Division slug: division color: '#0052cc' parent_department_id: 2 parent_department: id: 2 name: Product is_active: true description: 'Platform engineering for core services.' properties: data: type: object properties: id: type: integer example: 3 name: type: string example: Engineering lead_id: type: integer example: 44 cost_center: type: string example: ENG-001 department_type_id: type: integer example: 7 department_type: type: object properties: id: type: integer example: 7 name: type: string example: Division slug: type: string example: division color: type: string example: '#0052cc' parent_department_id: type: integer example: 2 parent_department: type: object properties: id: type: integer example: 2 name: type: string example: Product is_active: type: boolean example: true description: type: string example: 'Platform engineering for core services.' tags: - Departments put: summary: 'Update a department.' operationId: updateADepartment description: 'Requires `departments.update` ability.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 3 name: 'Engineering & Infrastructure' lead_id: 44 cost_center: ENG-001 department_type_id: 7 department_type: id: 7 name: Division slug: division color: '#0052cc' parent_department_id: 2 parent_department: id: 2 name: Product is_active: true description: 'Platform engineering for core services.' properties: data: type: object properties: id: type: integer example: 3 name: type: string example: 'Engineering & Infrastructure' lead_id: type: integer example: 44 cost_center: type: string example: ENG-001 department_type_id: type: integer example: 7 department_type: type: object properties: id: type: integer example: 7 name: type: string example: Division slug: type: string example: division color: type: string example: '#0052cc' parent_department_id: type: integer example: 2 parent_department: type: object properties: id: type: integer example: 2 name: type: string example: Product is_active: type: boolean example: true description: type: string example: 'Platform engineering for core services.' tags: - Departments requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Department name (unique per tenant and scenario). Must not be greater than 255 characters.' example: Engineering department_type_id: type: integer description: 'Department type identifier. The id of an existing record in the department_types table.' example: 5 parent_org_unit_id: type: integer description: 'Optional parent org unit id.' example: 42 nullable: true department_lead_id: type: integer description: 'Actor id of department lead. The id of an existing record in the actors table.' example: 44 nullable: true cost_center: type: string description: 'Optional cost center code. Must not be greater than 50 characters.' example: ENG-1001 nullable: true is_active: type: boolean description: 'Whether the department is active.' example: true nullable: true description: type: string description: 'Optional department description.' example: 'Supports the full product org.' nullable: true required: - name - department_type_id delete: summary: 'Delete a department.' operationId: deleteADepartment description: 'Requires `departments.delete` ability.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 204: description: '' content: application/json: schema: type: object example: { } properties: { } tags: - Departments parameters: - in: path name: id description: 'The ID of the department.' example: 11 required: true schema: type: integer /api/v1/initiatives: get: summary: 'Display a paginated listing of initiatives.' operationId: displayAPaginatedListingOfInitiatives description: 'Requires `initiatives.read` ability.' parameters: - in: query name: page description: 'The page number.' example: 1 required: false schema: type: integer description: 'The page number.' example: 1 - in: query name: per_page description: 'Max 100.' example: 25 required: false schema: type: integer description: 'Max 100.' example: 25 - in: query name: sort description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,title,created_at.' example: '-created_at,title' required: false schema: type: string description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,title,created_at.' example: '-created_at,title' - in: query name: search description: 'Search by title or description.' example: cost required: false schema: type: string description: 'Search by title or description.' example: cost - in: query name: 'filter[status]' description: 'Filter by status.' example: active required: false schema: type: string description: 'Filter by status.' example: active - in: query name: 'filter[baseline_scenario_id]' description: 'Filter by baseline scenario.' example: 1 required: false schema: type: integer description: 'Filter by baseline scenario.' example: 1 - in: query name: 'filter[target_scenario_id]' description: 'Filter by target scenario.' example: 2 required: false schema: type: integer description: 'Filter by target scenario.' example: 2 - in: query name: 'fields[initiatives]' description: 'Sparse fields for initiatives.' example: 'id,title' required: false schema: type: string description: 'Sparse fields for initiatives.' example: 'id,title' - in: query name: include description: 'Comma-separated relationships. Allowed: baseline_scenario,target_scenario.' example: 'baseline_scenario,target_scenario' required: false schema: type: string description: 'Comma-separated relationships. Allowed: baseline_scenario,target_scenario.' example: 'baseline_scenario,target_scenario' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 title: 'Cost Reduction' status: active - id: 2 title: 'Mobile Workforce Rollout' status: draft links: first: null last: null prev: null next: null meta: total: 4 current_page: 1 per_page: 15 last_page: 1 properties: data: type: array example: - id: 1 title: 'Cost Reduction' status: active - id: 2 title: 'Mobile Workforce Rollout' status: draft items: type: object properties: id: type: integer example: 1 title: type: string example: 'Cost Reduction' status: type: string example: active links: type: object properties: first: type: string example: null nullable: true last: type: string example: null nullable: true prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: total: type: integer example: 4 current_page: type: integer example: 1 per_page: type: integer example: 15 last_page: type: integer example: 1 400: description: '' content: application/json: schema: type: object example: code: query.invalid_parameter message: 'Unsupported sort: salary' details: allowed: - id - title - created_at properties: code: type: string example: query.invalid_parameter message: type: string example: 'Unsupported sort: salary' details: type: object properties: allowed: type: array example: - id - title - created_at items: type: string 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: per_page: - 'The per page must be at least 1.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: per_page: type: array example: - 'The per page must be at least 1.' items: type: string tags: - Initiatives post: summary: 'Store a newly created initiative.' operationId: storeANewlyCreatedInitiative description: 'Requires `initiatives.create` ability.' parameters: - in: query name: 'fields[initiatives]' description: 'Sparse fields for initiatives.' example: 'id,title' required: false schema: type: string description: 'Sparse fields for initiatives.' example: 'id,title' - in: query name: include description: 'Comma-separated relationships. Allowed: baseline_scenario,target_scenario.' example: 'baseline_scenario,target_scenario' required: false schema: type: string description: 'Comma-separated relationships. Allowed: baseline_scenario,target_scenario.' example: 'baseline_scenario,target_scenario' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 201: description: '' content: application/json: schema: type: object example: data: id: 1 title: 'Cost Reduction' description: 'Reduce infrastructure spend by 15%' status: active baseline_scenario_id: 1 target_scenario_id: 2 properties: data: type: object properties: id: type: integer example: 1 title: type: string example: 'Cost Reduction' description: type: string example: 'Reduce infrastructure spend by 15%' status: type: string example: active baseline_scenario_id: type: integer example: 1 target_scenario_id: type: integer example: 2 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: title: - 'The title field is required.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: title: type: array example: - 'The title field is required.' items: type: string tags: - Initiatives requestBody: required: true content: application/json: schema: type: object properties: title: type: string description: 'Initiative title. Must not be greater than 255 characters.' example: 'Reduce Cloud Spend' description: type: string description: 'Optional summary/description.' example: 'Target a 15% reduction by Q4.' nullable: true status: type: string description: 'Lifecycle status.' example: active enum: - draft - active - paused - completed baseline_scenario_id: type: integer description: 'Baseline scenario id. The id of an existing record in the scenarios table.' example: 1 nullable: true target_scenario_id: type: integer description: 'Target scenario id. The id of an existing record in the scenarios table.' example: 2 nullable: true required: - title - status '/api/v1/initiatives/{id}': get: summary: 'Display a specific initiative.' operationId: displayASpecificInitiative description: 'Requires `initiatives.read` ability.' parameters: - in: query name: 'fields[initiatives]' description: 'Sparse fields for initiatives.' example: 'id,title' required: false schema: type: string description: 'Sparse fields for initiatives.' example: 'id,title' - in: query name: include description: 'Comma-separated relationships. Allowed: baseline_scenario,target_scenario.' example: 'baseline_scenario,target_scenario' required: false schema: type: string description: 'Comma-separated relationships. Allowed: baseline_scenario,target_scenario.' example: 'baseline_scenario,target_scenario' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 1 title: 'Cost Reduction' description: 'Reduce infrastructure spend by 15%' status: active baseline_scenario: id: 1 name: 'Current State' status: published target_scenario: id: 2 name: 'Optimized State' status: draft properties: data: type: object properties: id: type: integer example: 1 title: type: string example: 'Cost Reduction' description: type: string example: 'Reduce infrastructure spend by 15%' status: type: string example: active baseline_scenario: type: object properties: id: type: integer example: 1 name: type: string example: 'Current State' status: type: string example: published target_scenario: type: object properties: id: type: integer example: 2 name: type: string example: 'Optimized State' status: type: string example: draft tags: - Initiatives put: summary: 'Update a specific initiative.' operationId: updateASpecificInitiative description: 'Requires `initiatives.update` ability.' parameters: - in: query name: 'fields[initiatives]' description: 'Sparse fields for initiatives.' example: 'id,title' required: false schema: type: string description: 'Sparse fields for initiatives.' example: 'id,title' - in: query name: include description: 'Comma-separated relationships. Allowed: baseline_scenario,target_scenario.' example: 'baseline_scenario,target_scenario' required: false schema: type: string description: 'Comma-separated relationships. Allowed: baseline_scenario,target_scenario.' example: 'baseline_scenario,target_scenario' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 1 title: 'Cost Optimization' description: 'Reduce infrastructure spend by 20%' status: active baseline_scenario_id: 1 target_scenario_id: 2 properties: data: type: object properties: id: type: integer example: 1 title: type: string example: 'Cost Optimization' description: type: string example: 'Reduce infrastructure spend by 20%' status: type: string example: active baseline_scenario_id: type: integer example: 1 target_scenario_id: type: integer example: 2 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: status: - 'The selected status is invalid.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: status: type: array example: - 'The selected status is invalid.' items: type: string tags: - Initiatives requestBody: required: true content: application/json: schema: type: object properties: title: type: string description: 'Initiative title. Must not be greater than 255 characters.' example: 'Reduce Cloud Spend' description: type: string description: 'Optional summary/description.' example: 'Target a 15% reduction by Q4.' nullable: true status: type: string description: 'Lifecycle status.' example: paused enum: - draft - active - paused - completed baseline_scenario_id: type: integer description: 'Baseline scenario id. The id of an existing record in the scenarios table.' example: 1 nullable: true target_scenario_id: type: integer description: 'Target scenario id. The id of an existing record in the scenarios table.' example: 2 nullable: true required: - title - status delete: summary: 'Delete a specific initiative.' operationId: deleteASpecificInitiative description: 'Requires `initiatives.delete` ability.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 204: description: '' content: application/json: schema: type: object example: { } properties: { } tags: - Initiatives parameters: - in: path name: id description: 'The ID of the initiative.' example: 11 required: true schema: type: integer '/api/v1/integrations/bamboohr/webhook/{tenant}': post: summary: 'Receive BambooHR webhook payload.' operationId: receiveBambooHRWebhookPayload description: "This endpoint always responds quickly. When the tenant is missing, the plan\ndoes not include HRIS integrations, or webhook auth fails, it still returns\n`202 Accepted` without queueing sync work." parameters: [] responses: 202: description: '' content: application/json: schema: type: object example: status: accepted properties: status: type: string example: accepted tags: - Integrations security: [] parameters: - in: path name: tenant description: 'Tenant slug.' example: demo required: true schema: type: string /api/v1/lineage: get: summary: 'List baseline lineage events (promotions + snapshot restores).' operationId: listBaselineLineageEventspromotions+SnapshotRestores description: '' parameters: - in: query name: page description: 'The page number.' example: 1 required: false schema: type: integer description: 'The page number.' example: 1 - in: query name: per_page description: 'Max 100.' example: 25 required: false schema: type: integer description: 'Max 100.' example: 25 - in: query name: 'filter[type]' description: 'Filter by lineage type (scenario_promotion, snapshot_restore).' example: scenario_promotion required: false schema: type: string description: 'Filter by lineage type (scenario_promotion, snapshot_restore).' example: scenario_promotion - in: query name: 'filter[scenario_id]' description: 'Filter by related scenario id (promoted/applied/archived).' example: 42 required: false schema: type: integer description: 'Filter by related scenario id (promoted/applied/archived).' example: 42 - in: query name: 'filter[applied_from]' description: 'date Filter by applied_at date (inclusive, YYYY-MM-DD).' example: '2025-01-01' required: false schema: type: string description: 'date Filter by applied_at date (inclusive, YYYY-MM-DD).' example: '2025-01-01' - in: query name: 'filter[applied_to]' description: 'date Filter by applied_at date (inclusive, YYYY-MM-DD).' example: '2025-12-31' required: false schema: type: string description: 'date Filter by applied_at date (inclusive, YYYY-MM-DD).' example: '2025-12-31' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 type: scenario_promotion applied_at: '2026-01-30T12:00:00Z' links: first: null last: null prev: null next: null meta: total: 1 current_page: 1 per_page: 15 last_page: 1 properties: data: type: array example: - id: 1 type: scenario_promotion applied_at: '2026-01-30T12:00:00Z' items: type: object properties: id: type: integer example: 1 type: type: string example: scenario_promotion applied_at: type: string example: '2026-01-30T12:00:00Z' links: type: object properties: first: type: string example: null nullable: true last: type: string example: null nullable: true prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: total: type: integer example: 1 current_page: type: integer example: 1 per_page: type: integer example: 15 last_page: type: integer example: 1 tags: - Lineage requestBody: required: false content: application/json: schema: type: object properties: page: type: integer description: 'Must be at least 1.' example: 61 per_page: type: integer description: 'Must be at least 1. Must not be greater than 100.' example: 7 filter: type: object description: '' example: [] properties: type: type: string description: '' example: snapshot_restore enum: - scenario_promotion - snapshot_restore nullable: true scenario_id: type: integer description: '' example: 11 nullable: true applied_from: type: string description: 'Must be a valid date.' example: '2026-04-13T21:34:33' nullable: true applied_to: type: string description: 'Must be a valid date.' example: '2026-04-13T21:34:33' nullable: true '/api/v1/actors/{actor_id}/placements': get: summary: 'Actor placements — List' operationId: actorPlacementsList description: "Return placements for an actor as of a date. By default only active placements\nare returned; pass `include_history=1` to include historical rows as well.\n\nRequires `placements.read` ability." parameters: - in: query name: as_of description: 'Filter placements active on this ISO 8601 date.' example: '2025-09-01' required: false schema: type: string description: 'Filter placements active on this ISO 8601 date.' example: '2025-09-01' - in: query name: include_history description: 'Include historical placements in addition to the active set.' example: true required: false schema: type: boolean description: 'Include historical placements in addition to the active set.' example: true - in: query name: scenario_id description: 'Scenario slug or id to scope placements; omit or pass `null` for baseline.' example: future-plan required: false schema: type: string description: 'Scenario slug or id to scope placements; omit or pass `null` for baseline.' example: future-plan - in: query name: include description: 'Comma-separated relationships. Allowed: actor,org_unit.' example: 'actor,org_unit' required: false schema: type: string description: 'Comma-separated relationships. Allowed: actor,org_unit.' example: 'actor,org_unit' - in: query name: 'fields[placements]' description: 'Sparse fieldset.' example: 'id,org_unit_id,valid_from,valid_to' required: false schema: type: string description: 'Sparse fieldset.' example: 'id,org_unit_id,valid_from,valid_to' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 org_unit_id: 10 primary: true allocation_pct: 100 fte: 1 valid_from: '2025-01-01' valid_to: null is_active: true properties: data: type: array example: - id: 1 org_unit_id: 10 primary: true allocation_pct: 100 fte: 1 valid_from: '2025-01-01' valid_to: null is_active: true items: type: object properties: id: type: integer example: 1 org_unit_id: type: integer example: 10 primary: type: boolean example: true allocation_pct: type: integer example: 100 fte: type: integer example: 1 valid_from: type: string example: '2025-01-01' valid_to: type: string example: null nullable: true is_active: type: boolean example: true tags: - 'Org Placements' post: summary: 'Actor placements — Create' operationId: actorPlacementsCreate description: "Creates a new placement for the given actor.\n\nRequires `placements.write` ability." parameters: - in: query name: include description: 'Comma-separated relationships. Allowed: actor,org_unit.' example: org_unit required: false schema: type: string description: 'Comma-separated relationships. Allowed: actor,org_unit.' example: org_unit - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 201: description: '' content: application/json: schema: type: object example: data: id: 9 org_unit_id: 10 primary: false allocation_pct: 60 fte: 0.6 valid_from: '2025-09-01' valid_to: null is_active: true properties: data: type: object properties: id: type: integer example: 9 org_unit_id: type: integer example: 10 primary: type: boolean example: false allocation_pct: type: integer example: 60 fte: type: number example: 0.6 valid_from: type: string example: '2025-09-01' valid_to: type: string example: null nullable: true is_active: type: boolean example: true tags: - 'Org Placements' requestBody: required: true content: application/json: schema: type: object properties: org_unit_id: type: integer description: 'Org unit receiving the placement.' example: 10 scenario_id: type: integer description: 'Scenario context for the placement; defaults to the org unit scenario.' example: 4 nullable: true primary: type: boolean description: 'Mark as the primary placement.' example: true allocation_pct: type: integer description: "Percentage of the actor's time for this team (0–300). Totals across placements may exceed 100% (over-allocation is allowed)." example: 60 valid_from: type: string description: 'Start date (YYYY-MM-DD).' example: '2025-09-01' nullable: true valid_to: type: string description: 'End date (YYYY-MM-DD).' example: '2025-12-31' nullable: true source: type: string description: 'Source identifier for auditing.' example: manual nullable: true meta: type: object description: 'Additional metadata payload.' example: [] properties: { } nullable: true fte: type: number description: "nullable Effective FTE override for this placement window (0.01–1.00). If omitted, the actor's contractual FTE is used." example: 0.6 required: - org_unit_id - allocation_pct parameters: - in: path name: actor_id description: 'The ID of the actor.' example: 11 required: true schema: type: integer - in: path name: actor description: 'The actor identifier.' example: 11 required: true schema: type: integer '/api/v1/placements/{id}': patch: summary: 'Actor placements — Update' operationId: actorPlacementsUpdate description: "Updates an existing placement.\n\nRequires `placements.write` ability." parameters: - in: query name: include description: 'Comma-separated relationships. Allowed: actor,org_unit.' example: actor required: false schema: type: string description: 'Comma-separated relationships. Allowed: actor,org_unit.' example: actor - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 9 org_unit_id: 12 allocation_pct: 50 fte: 0.5 valid_from: '2025-06-01' valid_to: '2025-09-30' is_active: false properties: data: type: object properties: id: type: integer example: 9 org_unit_id: type: integer example: 12 allocation_pct: type: integer example: 50 fte: type: number example: 0.5 valid_from: type: string example: '2025-06-01' valid_to: type: string example: '2025-09-30' is_active: type: boolean example: false tags: - 'Org Placements' requestBody: required: false content: application/json: schema: type: object properties: org_unit_id: type: integer description: 'Org unit receiving the placement.' example: 12 scenario_id: type: integer description: 'Scenario context for the placement; defaults to the current value.' example: 4 nullable: true primary: type: boolean description: 'Mark as the primary placement.' example: false allocation_pct: type: integer description: "Percentage of the actor's time for this team (0–300). Totals across placements may exceed 100% (over-allocation is allowed)." example: 50 valid_from: type: string description: 'Start date (YYYY-MM-DD).' example: '2025-06-01' nullable: true valid_to: type: string description: 'End date (YYYY-MM-DD).' example: '2025-09-30' nullable: true source: type: string description: 'Source identifier for auditing.' example: manual nullable: true meta: type: object description: 'Additional metadata payload.' example: [] properties: { } nullable: true fte: type: number description: "nullable Effective FTE override for this placement window (0.01–1.00). If omitted, the actor's contractual FTE is used." example: 0.5 delete: summary: 'Actor placements — End' operationId: actorPlacementsEnd description: "Ends a placement by stamping the `valid_to` date. Omitting the\n`valid_to` query parameter defaults to today.\n\nRequires `placements.delete` ability." parameters: - in: query name: valid_to description: 'Date to end the placement on (YYYY-MM-DD). Defaults to today.' example: '2025-10-01' required: false schema: type: string description: 'Date to end the placement on (YYYY-MM-DD). Defaults to today.' example: '2025-10-01' - in: query name: include description: 'Comma-separated relationships. Allowed: actor,org_unit.' example: org_unit required: false schema: type: string description: 'Comma-separated relationships. Allowed: actor,org_unit.' example: org_unit - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 9 valid_to: '2025-10-01' is_active: false properties: data: type: object properties: id: type: integer example: 9 valid_to: type: string example: '2025-10-01' is_active: type: boolean example: false tags: - 'Org Placements' parameters: - in: path name: id description: 'The ID of the placement.' example: 11 required: true schema: type: integer - in: path name: placement description: 'Placement identifier.' example: 11 required: true schema: type: integer '/api/v1/org-units/{org_unit_id}/actors': get: summary: 'Org unit roster — List' operationId: orgUnitRosterList description: "Lists active placements for the specified org unit. Results default to placements\nactive today but may be scoped to a different date using `as_of`.\n\nRequires `placements.read` ability." parameters: - in: query name: as_of description: 'Date used to evaluate active placements.' example: '2025-09-01' required: false schema: type: string description: 'Date used to evaluate active placements.' example: '2025-09-01' - in: query name: scenario_id description: 'Scenario slug or id to scope placements; omit or pass `null` for baseline.' example: future-plan required: false schema: type: string description: 'Scenario slug or id to scope placements; omit or pass `null` for baseline.' example: future-plan - in: query name: include description: 'Comma-separated relationships. Allowed: actor,org_unit.' example: actor required: false schema: type: string description: 'Comma-separated relationships. Allowed: actor,org_unit.' example: actor - in: query name: 'fields[placements]' description: 'Sparse fieldset.' example: 'id,actor_id,allocation_pct' required: false schema: type: string description: 'Sparse fieldset.' example: 'id,actor_id,allocation_pct' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 7 actor: id: 14 name: "Aisling O'Neill" allocation_pct: 100 fte: 1 valid_from: '2025-07-01' valid_to: null is_active: true properties: data: type: array example: - id: 7 actor: id: 14 name: "Aisling O'Neill" allocation_pct: 100 fte: 1 valid_from: '2025-07-01' valid_to: null is_active: true items: type: object properties: id: type: integer example: 7 actor: type: object properties: id: type: integer example: 14 name: type: string example: "Aisling O'Neill" allocation_pct: type: integer example: 100 fte: type: integer example: 1 valid_from: type: string example: '2025-07-01' valid_to: type: string example: null nullable: true is_active: type: boolean example: true tags: - 'Org Placements' parameters: - in: path name: org_unit_id description: 'The ID of the org unit.' example: 11 required: true schema: type: integer - in: path name: org_unit description: 'The org unit identifier.' example: 11 required: true schema: type: integer /api/v1/org-units: get: summary: 'Display a paginated listing of org units.' operationId: displayAPaginatedListingOfOrgUnits description: '' parameters: - in: query name: page description: 'The page number.' example: 1 required: false schema: type: integer description: 'The page number.' example: 1 - in: query name: per_page description: 'Max 100.' example: 25 required: false schema: type: integer description: 'Max 100.' example: 25 - in: query name: sort description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,label,created_at.' example: '-created_at,label' required: false schema: type: string description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,label,created_at.' example: '-created_at,label' - in: query name: 'filter[type]' description: 'Filter by the org unit type (string). Typical values include node, team, and department. Other values may appear if inserted in data.' example: node required: false schema: type: string description: 'Filter by the org unit type (string). Typical values include node, team, and department. Other values may appear if inserted in data.' example: node - in: query name: 'filter[scenario_id]' description: 'Filter by scenario ID.' example: 1 required: false schema: type: integer description: 'Filter by scenario ID.' example: 1 - in: query name: 'filter[parent_id]' description: 'Filter by parent org unit ID.' example: 42 required: false schema: type: integer description: 'Filter by parent org unit ID.' example: 42 - in: query name: 'fields[org_units]' description: 'Sparse fields for org units.' example: 'id,label' required: false schema: type: string description: 'Sparse fields for org units.' example: 'id,label' - in: query name: include description: 'Comma-separated relationships. Allowed: scenario,actors,metrics.' example: 'scenario,actors,metrics' required: false schema: type: string description: 'Comma-separated relationships. Allowed: scenario,actors,metrics.' example: 'scenario,actors,metrics' - in: query name: as_of description: 'date Filter actors memberships or metrics by date. Format: YYYY-MM-DD.' example: '2025-09-01' required: false schema: type: string description: 'date Filter actors memberships or metrics by date. Format: YYYY-MM-DD.' example: '2025-09-01' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 202 type: node entity_id: 4102 label: 'Retail – Ireland' scenario_id: 1 parent_id: 101 links: first: null last: null prev: null next: null meta: total: 1 current_page: 1 per_page: 15 last_page: 1 properties: data: type: array example: - id: 202 type: node entity_id: 4102 label: 'Retail – Ireland' scenario_id: 1 parent_id: 101 items: type: object properties: id: type: integer example: 202 type: type: string example: node entity_id: type: integer example: 4102 label: type: string example: 'Retail – Ireland' scenario_id: type: integer example: 1 parent_id: type: integer example: 101 links: type: object properties: first: type: string example: null nullable: true last: type: string example: null nullable: true prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: total: type: integer example: 1 current_page: type: integer example: 1 per_page: type: integer example: 15 last_page: type: integer example: 1 400: description: '' content: application/json: schema: type: object example: code: query.invalid_parameter message: 'Unsupported sort: salary' details: allowed: - id - label - created_at properties: code: type: string example: query.invalid_parameter message: type: string example: 'Unsupported sort: salary' details: type: object properties: allowed: type: array example: - id - label - created_at items: type: string 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: per_page: - 'The per page must be at least 1.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: per_page: type: array example: - 'The per page must be at least 1.' items: type: string tags: - 'Org Units' post: summary: 'Create a new org unit.' operationId: createANewOrgUnit description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 201: description: '' content: application/json: schema: type: object example: data: id: 210 type: team entity_id: 15 label: 'Team CúChulainn' scenario_id: 1 parent_id: 202 valid_from: '2025-01-01' valid_to: '2025-12-31' properties: data: type: object properties: id: type: integer example: 210 type: type: string example: team entity_id: type: integer example: 15 label: type: string example: 'Team CúChulainn' scenario_id: type: integer example: 1 parent_id: type: integer example: 202 valid_from: type: string example: '2025-01-01' valid_to: type: string example: '2025-12-31' tags: - 'Org Units' requestBody: required: true content: application/json: schema: type: object properties: type: type: string description: 'Org unit type.' example: team entity_id: type: integer description: 'Linked entity ID.' example: 15 label: type: string description: 'Optional display label.' example: 'Team CúChulainn' nullable: true scenario_id: type: integer description: 'Scenario identifier.' example: 1 nullable: true parent_id: type: integer description: 'Parent org unit ID.' example: 202 nullable: true valid_from: type: string description: 'The start date. Format: YYYY-MM-DD.' example: '2025-01-01' valid_to: type: string description: 'The end date. Format: YYYY-MM-DD.' example: '2025-12-31' nullable: true required: - type - entity_id - scenario_id - valid_from /api/v1/org-units/summary: get: summary: 'Get a summarized charts view.' operationId: getASummarizedChartsView description: '' parameters: - in: query name: root_id description: 'The ID of the root org unit.' example: 202 required: true schema: type: integer description: 'The ID of the root org unit.' example: 202 - in: query name: as_of description: 'date Format YYYY-MM-DD.' example: '2025-09-01' required: false schema: type: string description: 'date Format YYYY-MM-DD.' example: '2025-09-01' nullable: true - in: query name: sprint_id description: 'The sprint for velocity metrics.' example: 42 required: false schema: type: integer description: 'The sprint for velocity metrics.' example: 42 nullable: true - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 202 label: 'Retail – Ireland' type: node entity_id: 4102 rollup: direct_headcount: 12 subtree_headcount: 38 direct_monthly_cost_cents: 528500 subtree_monthly_cost_cents: 1285000 velocity_sp: 35 velocity_team_count: 3 children: [] properties: data: type: object properties: id: type: integer example: 202 label: type: string example: 'Retail – Ireland' type: type: string example: node entity_id: type: integer example: 4102 rollup: type: object properties: direct_headcount: type: integer example: 12 subtree_headcount: type: integer example: 38 direct_monthly_cost_cents: type: integer example: 528500 subtree_monthly_cost_cents: type: integer example: 1285000 velocity_sp: type: integer example: 35 velocity_team_count: type: integer example: 3 children: type: array example: [] tags: - 'Org Units' /api/v1/org-units/tree: get: summary: 'Get an org subtree (nested) for the current tenant.' operationId: getAnOrgSubtreenestedForTheCurrentTenant description: "Returns the subtree for the given root_id (or the tenant root if omitted),\nup to a maximum depth. Supports includes (members,parent,ancestors,path,metrics),\nsearch by name, and filtering by type." parameters: - in: query name: root_id description: 'Optional root node id.' example: 1 required: false schema: type: integer description: 'Optional root node id.' example: 1 - in: query name: depth description: 'The max depth to traverse from the root. Default: 2.' example: 3 required: false schema: type: integer description: 'The max depth to traverse from the root. Default: 2.' example: 3 - in: query name: include description: 'Comma-separated includes: actors,members,parent,ancestors,path,metrics.' example: 'actors,parent' required: false schema: type: string description: 'Comma-separated includes: actors,members,parent,ancestors,path,metrics.' example: 'actors,parent' - in: query name: filter description: '' example: [] required: false schema: type: object description: '' example: [] properties: { } - in: query name: filter.type description: 'Filter by org unit type.' example: team required: false schema: type: string description: 'Filter by org unit type.' example: team - in: query name: search description: 'Case-insensitive search on label.' example: Apollo required: false schema: type: string description: 'Case-insensitive search on label.' example: Apollo - in: query name: sort description: 'Sort by label asc/desc. Allowed: label, -label. Default: label.' example: '-label' required: false schema: type: string description: 'Sort by label asc/desc. Allowed: label, -label. Default: label.' example: '-label' - in: query name: 'filter[type]' description: 'Filter by org unit type.' example: team required: false schema: type: string description: 'Filter by org unit type.' example: team - in: header name: X-Tenant description: '' example: 'string required The tenant slug or id.' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 101 label: Engineering code: null type: node entity_id: 4102 parent_id: null scenario_id: 1 valid_from: null valid_to: null created_at: '2025-01-10T09:00:00Z' parent: null ancestors: [] path: - Engineering metrics: as_of: '2025-01-10' direct_headcount: 0 subtree_headcount: 0 direct_monthly_cost: value: 0 formatted: €0.00 currency: EUR subtree_monthly_cost: value: 0 formatted: €0.00 currency: EUR children: - id: 202 label: 'Team CúChulainn' code: null type: team entity_id: 15 parent_id: 101 scenario_id: 1 valid_from: null valid_to: null created_at: '2025-01-10T09:00:00Z' parent: id: 101 label: Engineering ancestors: - id: 101 label: Engineering path: - Engineering - 'Team CúChulainn' metrics: as_of: '2025-01-10' direct_headcount: 0 subtree_headcount: 0 direct_monthly_cost: value: 0 formatted: €0.00 currency: EUR subtree_monthly_cost: value: 0 formatted: €0.00 currency: EUR children: [] properties: data: type: object properties: id: type: integer example: 101 label: type: string example: Engineering code: type: string example: null nullable: true type: type: string example: node entity_id: type: integer example: 4102 parent_id: type: string example: null nullable: true scenario_id: type: integer example: 1 valid_from: type: string example: null nullable: true valid_to: type: string example: null nullable: true created_at: type: string example: '2025-01-10T09:00:00Z' parent: type: string example: null nullable: true ancestors: type: array example: [] path: type: array example: - Engineering items: type: string metrics: type: object properties: as_of: type: string example: '2025-01-10' direct_headcount: type: integer example: 0 subtree_headcount: type: integer example: 0 direct_monthly_cost: type: object properties: value: type: integer example: 0 formatted: type: string example: €0.00 currency: type: string example: EUR subtree_monthly_cost: type: object properties: value: type: integer example: 0 formatted: type: string example: €0.00 currency: type: string example: EUR children: type: array example: - id: 202 label: 'Team CúChulainn' code: null type: team entity_id: 15 parent_id: 101 scenario_id: 1 valid_from: null valid_to: null created_at: '2025-01-10T09:00:00Z' parent: id: 101 label: Engineering ancestors: - id: 101 label: Engineering path: - Engineering - 'Team CúChulainn' metrics: as_of: '2025-01-10' direct_headcount: 0 subtree_headcount: 0 direct_monthly_cost: value: 0 formatted: €0.00 currency: EUR subtree_monthly_cost: value: 0 formatted: €0.00 currency: EUR children: [] items: type: object properties: id: type: integer example: 202 label: type: string example: 'Team CúChulainn' code: type: string example: null nullable: true type: type: string example: team entity_id: type: integer example: 15 parent_id: type: integer example: 101 scenario_id: type: integer example: 1 valid_from: type: string example: null nullable: true valid_to: type: string example: null nullable: true created_at: type: string example: '2025-01-10T09:00:00Z' parent: type: object properties: id: type: integer example: 101 label: type: string example: Engineering ancestors: type: array example: - id: 101 label: Engineering items: type: object properties: id: type: integer example: 101 label: type: string example: Engineering path: type: array example: - Engineering - 'Team CúChulainn' items: type: string metrics: type: object properties: as_of: type: string example: '2025-01-10' direct_headcount: type: integer example: 0 subtree_headcount: type: integer example: 0 direct_monthly_cost: type: object properties: value: type: integer example: 0 formatted: type: string example: €0.00 currency: type: string example: EUR subtree_monthly_cost: type: object properties: value: type: integer example: 0 formatted: type: string example: €0.00 currency: type: string example: EUR children: type: array example: [] tags: - 'Org Units' '/api/v1/org-units/{id}/descendants': get: summary: 'Get flattened descendants with cursor pagination.' operationId: getFlattenedDescendantsWithCursorPagination description: '' parameters: - in: query name: depth description: 'The max depth to traverse from the root. Default: 3.' example: 2 required: false schema: type: integer description: 'The max depth to traverse from the root. Default: 3.' example: 2 - in: query name: include description: 'Comma-separated includes: actors,members,parent,ancestors,path,metrics.' example: parent required: false schema: type: string description: 'Comma-separated includes: actors,members,parent,ancestors,path,metrics.' example: parent - in: query name: filter description: '' example: [] required: false schema: type: object description: '' example: [] properties: { } - in: query name: filter.type description: 'Filter by org unit type.' example: team required: false schema: type: string description: 'Filter by org unit type.' example: team - in: query name: search description: 'Case-insensitive search on label.' example: Apollo required: false schema: type: string description: 'Case-insensitive search on label.' example: Apollo - in: query name: per_page description: 'Items per page. Max 200.' example: 50 required: false schema: type: integer description: 'Items per page. Max 200.' example: 50 - in: query name: cursor description: 'Pagination cursor' example: ab required: false schema: type: string description: 'Pagination cursor' example: ab - in: query name: 'filter[type]' description: 'Filter by org unit type.' example: team required: false schema: type: string description: 'Filter by org unit type.' example: team - in: header name: X-Tenant description: '' example: 'string required The tenant slug or id.' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 202 label: 'Team CúChulainn' code: null type: team entity_id: 15 parent_id: 101 scenario_id: 1 valid_from: null valid_to: null created_at: '2025-01-10T09:00:00Z' metrics: as_of: '2025-01-10' direct_headcount: 0 subtree_headcount: 0 direct_monthly_cost: value: 0 formatted: €0.00 currency: EUR subtree_monthly_cost: value: 0 formatted: €0.00 currency: EUR - id: 203 label: 'Team Fianna' code: null type: team entity_id: 16 parent_id: 101 scenario_id: 1 valid_from: null valid_to: null created_at: '2025-01-10T09:00:00Z' links: next: null prev: null meta: per_page: 50 properties: data: type: array example: - id: 202 label: 'Team CúChulainn' code: null type: team entity_id: 15 parent_id: 101 scenario_id: 1 valid_from: null valid_to: null created_at: '2025-01-10T09:00:00Z' metrics: as_of: '2025-01-10' direct_headcount: 0 subtree_headcount: 0 direct_monthly_cost: value: 0 formatted: €0.00 currency: EUR subtree_monthly_cost: value: 0 formatted: €0.00 currency: EUR - id: 203 label: 'Team Fianna' code: null type: team entity_id: 16 parent_id: 101 scenario_id: 1 valid_from: null valid_to: null created_at: '2025-01-10T09:00:00Z' items: type: object properties: id: type: integer example: 202 label: type: string example: 'Team CúChulainn' code: type: string example: null nullable: true type: type: string example: team entity_id: type: integer example: 15 parent_id: type: integer example: 101 scenario_id: type: integer example: 1 valid_from: type: string example: null nullable: true valid_to: type: string example: null nullable: true created_at: type: string example: '2025-01-10T09:00:00Z' metrics: type: object properties: as_of: type: string example: '2025-01-10' direct_headcount: type: integer example: 0 subtree_headcount: type: integer example: 0 direct_monthly_cost: type: object properties: value: type: integer example: 0 formatted: type: string example: €0.00 currency: type: string example: EUR subtree_monthly_cost: type: object properties: value: type: integer example: 0 formatted: type: string example: €0.00 currency: type: string example: EUR links: type: object properties: next: type: string example: null nullable: true prev: type: string example: null nullable: true meta: type: object properties: per_page: type: integer example: 50 tags: - 'Org Units' parameters: - in: path name: id description: 'The root id.' example: 1 required: true schema: type: integer '/api/v1/org-units/{org_unit}': get: summary: 'Show a single org unit summary.' operationId: showASingleOrgUnitSummary description: '' parameters: - in: query name: as_of description: 'date Format YYYY-MM-DD.' example: '2025-09-01' required: false schema: type: string description: 'date Format YYYY-MM-DD.' example: '2025-09-01' nullable: true - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 202 label: 'Retail – Ireland' type: node entity_id: 4102 rollup: direct_headcount: 12 subtree_headcount: 38 direct_monthly_cost_cents: 528500 subtree_monthly_cost_cents: 1285000 velocity_sp: 35 velocity_team_count: 3 children: [] properties: data: type: object properties: id: type: integer example: 202 label: type: string example: 'Retail – Ireland' type: type: string example: node entity_id: type: integer example: 4102 rollup: type: object properties: direct_headcount: type: integer example: 12 subtree_headcount: type: integer example: 38 direct_monthly_cost_cents: type: integer example: 528500 subtree_monthly_cost_cents: type: integer example: 1285000 velocity_sp: type: integer example: 35 velocity_team_count: type: integer example: 3 children: type: array example: [] tags: - 'Org Units' patch: summary: 'Update an org unit.' operationId: updateAnOrgUnit description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 210 type: team entity_id: 15 label: 'Team CúChulainn' scenario_id: 1 parent_id: 202 valid_from: '2025-01-01' valid_to: '2025-12-31' properties: data: type: object properties: id: type: integer example: 210 type: type: string example: team entity_id: type: integer example: 15 label: type: string example: 'Team CúChulainn' scenario_id: type: integer example: 1 parent_id: type: integer example: 202 valid_from: type: string example: '2025-01-01' valid_to: type: string example: '2025-12-31' tags: - 'Org Units' requestBody: required: true content: application/json: schema: type: object properties: type: type: string description: 'Org unit type.' example: team entity_id: type: integer description: 'Linked entity ID.' example: 15 label: type: string description: 'Optional display label.' example: 'Team CúChulainn' nullable: true scenario_id: type: integer description: 'Scenario identifier.' example: 1 nullable: true parent_id: type: integer description: 'Parent org unit ID.' example: 202 nullable: true valid_from: type: string description: 'The start date. Format: YYYY-MM-DD.' example: '2025-01-01' valid_to: type: string description: 'The end date. Format: YYYY-MM-DD.' example: '2025-12-31' nullable: true required: - type - entity_id - scenario_id - valid_from parameters: - in: path name: org_unit description: 'The ID of the org unit.' example: 202 required: true schema: type: integer '/api/v1/org-units/{org_unit}/children': get: summary: "List an org unit's immediate children." operationId: listAnOrgUnitsImmediateChildren description: '' parameters: - in: query name: as_of description: 'date Format YYYY-MM-DD.' example: '2025-09-01' required: false schema: type: string description: 'date Format YYYY-MM-DD.' example: '2025-09-01' nullable: true - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 210 label: 'Team CúChulainn' type: team entity_id: 15 rollup: direct_headcount: 8 subtree_headcount: 8 direct_monthly_cost_cents: 428500 subtree_monthly_cost_cents: 428500 velocity_sp: 18 velocity_team_count: 1 children: [] properties: data: type: array example: - id: 210 label: 'Team CúChulainn' type: team entity_id: 15 rollup: direct_headcount: 8 subtree_headcount: 8 direct_monthly_cost_cents: 428500 subtree_monthly_cost_cents: 428500 velocity_sp: 18 velocity_team_count: 1 children: [] items: type: object properties: id: type: integer example: 210 label: type: string example: 'Team CúChulainn' type: type: string example: team entity_id: type: integer example: 15 rollup: type: object properties: direct_headcount: type: integer example: 8 subtree_headcount: type: integer example: 8 direct_monthly_cost_cents: type: integer example: 428500 subtree_monthly_cost_cents: type: integer example: 428500 velocity_sp: type: integer example: 18 velocity_team_count: type: integer example: 1 children: type: array example: [] tags: - 'Org Units' parameters: - in: path name: org_unit description: 'The ID of the org unit.' example: 202 required: true schema: type: integer '/api/v1/org-units/{org_unit}/metrics': get: summary: 'Show the cached aggregate metrics for a single org unit.' operationId: showTheCachedAggregateMetricsForASingleOrgUnit description: '' parameters: - in: query name: as_of description: 'date The date to compute metrics as of.' example: '2025-09-01' required: false schema: type: string description: 'date The date to compute metrics as of.' example: '2025-09-01' nullable: true - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: as_of: '2025-09-01' direct_headcount: 8 subtree_headcount: 37 direct_monthly_cost_cents: 1250000 subtree_monthly_cost_cents: 4185000 metrics: span_of_control: 2 properties: data: type: object properties: as_of: type: string example: '2025-09-01' direct_headcount: type: integer example: 8 subtree_headcount: type: integer example: 37 direct_monthly_cost_cents: type: integer example: 1250000 subtree_monthly_cost_cents: type: integer example: 4185000 metrics: type: object properties: span_of_control: type: integer example: 2 tags: - 'Org Units — Metrics' parameters: - in: path name: org_unit description: 'The org unit id.' example: 202 required: true schema: type: integer '/api/v1/org-units/{org_unit_id}/move': post: summary: 'Move an org unit to a new parent.' operationId: moveAnOrgUnitToANewParent description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required The tenant slug.' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 202 label: 'Retail – Ireland' code: null type: node entity_id: 4102 parent_id: 88 scenario_id: 1 valid_from: '2025-01-01' valid_to: null created_at: '2025-08-20T10:00:00Z' properties: data: type: object properties: id: type: integer example: 202 label: type: string example: 'Retail – Ireland' code: type: string example: null nullable: true type: type: string example: node entity_id: type: integer example: 4102 parent_id: type: integer example: 88 scenario_id: type: integer example: 1 valid_from: type: string example: '2025-01-01' valid_to: type: string example: null nullable: true created_at: type: string example: '2025-08-20T10:00:00Z' tags: - 'Org Units — Structure' requestBody: required: true content: application/json: schema: type: object properties: target_parent_id: type: integer description: 'nullable New parent org unit id.' example: 11 nullable: true position: type: integer description: 'The desired index among siblings.' example: 11 nullable: true expected_updated_at: type: string description: 'ISO8601 timestamp for optimistic concurrency.' example: ab required: - expected_updated_at parameters: - in: path name: org_unit_id description: 'The ID of the org unit.' example: 11 required: true schema: type: integer - in: path name: org_unit description: 'The org unit id being moved.' example: 202 required: true schema: type: integer /api/v1/org-units/merge: post: summary: 'Merge two org units.' operationId: mergeTwoOrgUnits description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required The tenant slug.' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 202 label: 'Retail – Ireland' code: null type: node entity_id: 4102 parent_id: 101 scenario_id: 1 valid_from: '2025-01-01' valid_to: null created_at: '2025-08-20T10:00:00Z' properties: data: type: object properties: id: type: integer example: 202 label: type: string example: 'Retail – Ireland' code: type: string example: null nullable: true type: type: string example: node entity_id: type: integer example: 4102 parent_id: type: integer example: 101 scenario_id: type: integer example: 1 valid_from: type: string example: '2025-01-01' valid_to: type: string example: null nullable: true created_at: type: string example: '2025-08-20T10:00:00Z' tags: - 'Org Units — Structure' requestBody: required: true content: application/json: schema: type: object properties: source_id: type: integer description: 'The source unit to merge from.' example: 11 target_id: type: integer description: 'The target unit to merge into.' example: 11 strategy: type: string description: 'The merge strategy.' example: ab nullable: true expected_updated_at_source: type: string description: 'Optimistic concurrency timestamp for the source.' example: '2025-08-29T10:00:00Z' nullable: true expected_updated_at_target: type: string description: 'Optimistic concurrency timestamp for the target.' example: '2025-08-29T10:00:00Z' nullable: true required: - source_id - target_id '/api/v1/org-units/{org_unit_id}/split': post: summary: 'Split an org unit.' operationId: splitAnOrgUnit description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required The tenant slug.' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: original: id: 202 label: 'Retail – Ireland' code: null type: node entity_id: 4102 parent_id: 101 scenario_id: 1 valid_from: '2025-01-01' valid_to: null created_at: '2025-08-20T10:00:00Z' created: id: 244 label: 'Payments – Ops' code: null type: node entity_id: 4102 parent_id: 101 scenario_id: 1 valid_from: '2025-01-01' valid_to: null created_at: '2026-03-07T10:30:00Z' properties: data: type: object properties: original: type: object properties: id: type: integer example: 202 label: type: string example: 'Retail – Ireland' code: type: string example: null nullable: true type: type: string example: node entity_id: type: integer example: 4102 parent_id: type: integer example: 101 scenario_id: type: integer example: 1 valid_from: type: string example: '2025-01-01' valid_to: type: string example: null nullable: true created_at: type: string example: '2025-08-20T10:00:00Z' created: type: object properties: id: type: integer example: 244 label: type: string example: 'Payments – Ops' code: type: string example: null nullable: true type: type: string example: node entity_id: type: integer example: 4102 parent_id: type: integer example: 101 scenario_id: type: integer example: 1 valid_from: type: string example: '2025-01-01' valid_to: type: string example: null nullable: true created_at: type: string example: '2026-03-07T10:30:00Z' tags: - 'Org Units — Structure' requestBody: required: true content: application/json: schema: type: object properties: mode: type: string description: 'new_sibling or new_child.' example: ab label: type: string description: 'Label for the new unit.' example: ab nullable: true children_to_move: type: array description: 'IDs of children to move.' example: - ab items: type: string expected_updated_at: type: string description: 'ISO8601 timestamp.' example: ab required: - mode - children_to_move - expected_updated_at parameters: - in: path name: org_unit_id description: 'The ID of the org unit.' example: 11 required: true schema: type: integer - in: path name: org_unit description: 'The org unit id being split.' example: 202 required: true schema: type: integer '/api/v1/org-units/{org_unit_id}': delete: summary: 'Soft delete an org unit.' operationId: softDeleteAnOrgUnit description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required The tenant slug.' schema: type: string responses: 204: description: '' content: application/json: schema: type: object example: { } properties: { } tags: - 'Org Units — Structure' requestBody: required: true content: application/json: schema: type: object properties: target_parent_id: type: integer description: 'New parent org unit id (nullable for root). The id of an existing record in the org_units table.' example: 101 nullable: true position: type: integer description: 'Desired index among siblings (0-based). Must be at least 0.' example: 2 nullable: true expected_updated_at: type: string description: 'ISO8601 timestamp.' example: ab required: - expected_updated_at parameters: - in: path name: org_unit_id description: 'The ID of the org unit.' example: 11 required: true schema: type: integer - in: path name: org_unit description: 'The org unit id being deleted.' example: 202 required: true schema: type: integer '/api/v1/org-units/{org_unit_id}/restore': post: summary: 'Restore an org unit.' operationId: restoreAnOrgUnit description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required The tenant slug.' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 202 label: 'Retail – Ireland' code: null type: node entity_id: 4102 parent_id: 101 scenario_id: 1 valid_from: '2025-01-01' valid_to: null created_at: '2025-08-20T10:00:00Z' properties: data: type: object properties: id: type: integer example: 202 label: type: string example: 'Retail – Ireland' code: type: string example: null nullable: true type: type: string example: node entity_id: type: integer example: 4102 parent_id: type: integer example: 101 scenario_id: type: integer example: 1 valid_from: type: string example: '2025-01-01' valid_to: type: string example: null nullable: true created_at: type: string example: '2025-08-20T10:00:00Z' tags: - 'Org Units — Structure' parameters: - in: path name: org_unit_id description: 'The ID of the org unit.' example: 11 required: true schema: type: integer - in: path name: org_unit description: 'The soft-deleted org unit id.' example: 202 required: true schema: type: integer '/api/v1/org-units/{org_unit}/velocity': get: summary: 'Get velocity for an org unit.' operationId: getVelocityForAnOrgUnit description: '' parameters: - in: query name: sprint_id description: 'The sprint to query.' example: 11 required: false schema: type: integer description: 'The sprint to query.' example: 11 nullable: true - in: query name: as_of description: 'date Mutually exclusive with sprint_id.' example: ab required: false schema: type: string description: 'date Mutually exclusive with sprint_id.' example: ab nullable: true - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: org_unit_id: 202 sprint_id: 42 period: label: 'Sprint 18' starts_at: '2025-09-01' ends_at: '2025-09-14' velocity_sp: 88 teams_count: 4 properties: data: type: object properties: org_unit_id: type: integer example: 202 sprint_id: type: integer example: 42 period: type: object properties: label: type: string example: 'Sprint 18' starts_at: type: string example: '2025-09-01' ends_at: type: string example: '2025-09-14' velocity_sp: type: integer example: 88 teams_count: type: integer example: 4 tags: - 'Org Units — Velocity' parameters: - in: path name: org_unit description: 'The org unit ID.' example: 202 required: true schema: type: integer '/api/v1/org-units/{org_unit}/velocity/series': get: summary: 'Get velocity series for an org unit.' operationId: getVelocitySeriesForAnOrgUnit description: '' parameters: - in: query name: from_sprint_id description: 'Starting sprint ID. The id of an existing record in the sprint_periods table.' example: 1 required: false schema: type: integer description: 'Starting sprint ID. The id of an existing record in the sprint_periods table.' example: 1 nullable: true - in: query name: to_sprint_id description: 'Ending sprint ID. The id of an existing record in the sprint_periods table.' example: 10 required: false schema: type: integer description: 'Ending sprint ID. The id of an existing record in the sprint_periods table.' example: 10 nullable: true - in: query name: from description: 'Start date (YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d.' example: '2025-01-01' required: false schema: type: string description: 'Start date (YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d.' example: '2025-01-01' nullable: true - in: query name: to description: 'End date (YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d.' example: '2025-06-01' required: false schema: type: string description: 'End date (YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d.' example: '2025-06-01' nullable: true - in: query name: limit description: 'Maximum number of sprints to return. Must be at least 1. Must not be greater than 50.' example: 12 required: false schema: type: integer description: 'Maximum number of sprints to return. Must be at least 1. Must not be greater than 50.' example: 12 nullable: true - in: query name: order description: 'Sort order by sprint start.' example: asc required: false schema: type: string description: 'Sort order by sprint start.' example: asc enum: - asc - desc nullable: true - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - org_unit_id: 202 sprint_id: 41 period: label: 'Sprint 17' starts_at: '2025-08-18' ends_at: '2025-08-31' velocity_sp: 81 teams_count: 4 - org_unit_id: 202 sprint_id: 42 period: label: 'Sprint 18' starts_at: '2025-09-01' ends_at: '2025-09-14' velocity_sp: 88 teams_count: 4 properties: data: type: array example: - org_unit_id: 202 sprint_id: 41 period: label: 'Sprint 17' starts_at: '2025-08-18' ends_at: '2025-08-31' velocity_sp: 81 teams_count: 4 - org_unit_id: 202 sprint_id: 42 period: label: 'Sprint 18' starts_at: '2025-09-01' ends_at: '2025-09-14' velocity_sp: 88 teams_count: 4 items: type: object properties: org_unit_id: type: integer example: 202 sprint_id: type: integer example: 41 period: type: object properties: label: type: string example: 'Sprint 17' starts_at: type: string example: '2025-08-18' ends_at: type: string example: '2025-08-31' velocity_sp: type: integer example: 81 teams_count: type: integer example: 4 tags: - 'Org Units — Velocity' parameters: - in: path name: org_unit description: 'The org unit ID.' example: 202 required: true schema: type: integer /api/v1/me/organisations: get: summary: 'List Organisation Accounts available to the authenticated user.' operationId: listOrganisationAccountsAvailableToTheAuthenticatedUser description: 'Returns the Organisation Accounts that the authenticated user can access.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 12 name: 'Acme Product' slug: acme-product plan_code: team subscription_status: trialing trial_ends_at: '2026-03-06T12:00:00Z' onboarding_completed_at: null role: owner properties: data: type: array example: - id: 12 name: 'Acme Product' slug: acme-product plan_code: team subscription_status: trialing trial_ends_at: '2026-03-06T12:00:00Z' onboarding_completed_at: null role: owner items: type: object properties: id: type: integer example: 12 name: type: string example: 'Acme Product' slug: type: string example: acme-product plan_code: type: string example: team subscription_status: type: string example: trialing trial_ends_at: type: string example: '2026-03-06T12:00:00Z' onboarding_completed_at: type: string example: null nullable: true role: type: string example: owner tags: - 'Organisation Accounts' /api/v1/organisations/current: get: summary: 'Get the current Organisation Account details.' operationId: getTheCurrentOrganisationAccountDetails description: 'Resolution follows the standard precedence (subdomain, last-used Organisation, session fallback).' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 12 name: 'Acme Product' slug: acme-product plan_code: team onboarding_choice: import onboarding_completed_at: null trial_started_at: '2026-02-21T12:00:00Z' trial_ends_at: '2026-03-06T12:00:00Z' subscription_status: trialing grace_ends_at: null locked_at: null role: owner saml_enabled: false current_token: abilities: - cli.access - actors.read - scenarios.read cli_access: true properties: data: type: object properties: id: type: integer example: 12 description: 'Organisation Account identifier.' name: type: string example: 'Acme Product' description: 'Organisation Account display name.' slug: type: string example: acme-product description: 'Organisation Account slug.' plan_code: type: string example: team description: 'Active subscription plan code.' onboarding_choice: type: string example: import description: 'Selected onboarding path (`hris`, `import`, `demo`, `empty`).' onboarding_completed_at: type: string example: null description: 'ISO-8601 onboarding completion timestamp.' trial_started_at: type: string example: '2026-02-21T12:00:00Z' description: 'ISO-8601 trial start timestamp.' trial_ends_at: type: string example: '2026-03-06T12:00:00Z' description: 'ISO-8601 trial end timestamp.' subscription_status: type: string example: trialing description: 'Current lifecycle state.' grace_ends_at: type: string example: null description: 'ISO-8601 grace-period end timestamp.' locked_at: type: string example: null description: 'ISO-8601 lock timestamp.' role: type: string example: owner description: 'Membership role for the authenticated user in this Organisation Account.' saml_enabled: type: boolean example: false description: 'Included only when the user has `sso.manage` capability and the tenant plan includes tenant-managed SAML.' current_token: type: object properties: abilities: type: array example: - cli.access - actors.read - scenarios.read description: 'Normalized abilities/scopes declared on the current bearer token.' items: type: string cli_access: type: boolean example: true description: 'Whether the current bearer token explicitly includes `cli.access`.' 404: description: '' content: application/json: schema: type: object example: code: tenant.not_resolved message: 'No Organisation Account is currently selected.' properties: code: type: string example: tenant.not_resolved message: type: string example: 'No Organisation Account is currently selected.' tags: - 'Organisation Accounts' /api/v1/organisations/current/onboarding-choice: post: summary: 'Update onboarding choice for the current Organisation Account.' operationId: updateOnboardingChoiceForTheCurrentOrganisationAccount description: 'Persists how this Organisation Account will start onboarding.' parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: data: id: 12 name: 'Acme Product' slug: acme-product onboarding_choice: import subscription_status: trialing properties: data: type: object properties: id: type: integer example: 12 description: 'Organisation Account identifier.' name: type: string example: 'Acme Product' description: 'Organisation Account display name.' slug: type: string example: acme-product description: 'Organisation Account slug.' onboarding_choice: type: string example: import description: 'Persisted onboarding choice for the current Organisation Account.' subscription_status: type: string example: trialing description: 'Current lifecycle state.' 404: description: '' content: application/json: schema: type: object example: code: tenant.not_resolved message: 'No Organisation Account is currently selected.' properties: code: type: string example: tenant.not_resolved message: type: string example: 'No Organisation Account is currently selected.' tags: - 'Organisation Accounts' requestBody: required: true content: application/json: schema: type: object properties: onboarding_choice: type: string description: 'How this Organisation Account will add data first.' example: import enum: - hris - import - demo - empty required: - onboarding_choice /api/v1/positions: get: summary: 'List positions with pagination, filters, and search.' operationId: listPositionsWithPaginationFiltersAndSearch description: "Returns baseline data when no scenario filter is supplied; pass\n`filter[scenario]=current` to reflect the active scenario." parameters: - in: query name: page description: 'The page number to return.' example: 1 required: false schema: type: integer description: 'The page number to return.' example: 1 - in: query name: per_page description: 'Results per page (max 100).' example: 25 required: false schema: type: integer description: 'Results per page (max 100).' example: 25 - in: query name: sort description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,valid_from,valid_to,fte,created_at.' example: '-valid_from' required: false schema: type: string description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,valid_from,valid_to,fte,created_at.' example: '-valid_from' - in: query name: 'filter[org_unit_team_id]' description: 'Filter by a budgeted team org unit (planning metadata only).' example: 4201 required: false schema: type: integer description: 'Filter by a budgeted team org unit (planning metadata only).' example: 4201 - in: query name: 'filter[target_employment_type]' description: 'Restrict by budgeted employment type (perm or contractor).' example: perm required: false schema: type: string description: 'Restrict by budgeted employment type (perm or contractor).' example: perm - in: query name: 'filter[active_as_of]' description: 'date Return positions active on this ISO date (YYYY-MM-DD).' example: '2025-09-20' required: false schema: type: string description: 'date Return positions active on this ISO date (YYYY-MM-DD).' example: '2025-09-20' - in: query name: 'filter[scenario]' description: 'Scenario shorthand: baseline, current, or an id.' example: current required: false schema: type: string description: 'Scenario shorthand: baseline, current, or an id.' example: current - in: query name: 'filter[scenario_id]' description: 'Scenario identifier; overrides shorthand when present.' example: 7 required: false schema: type: integer description: 'Scenario identifier; overrides shorthand when present.' example: 7 - in: query name: q description: 'Free-text search across title, team name, and actor name.' example: 'platform engineer' required: false schema: type: string description: 'Free-text search across title, team name, and actor name.' example: 'platform engineer' - in: query name: include description: 'Comma-separated relationships. Allowed: team,actor.' example: 'team,actor' required: false schema: type: string description: 'Comma-separated relationships. Allowed: team,actor.' example: 'team,actor' - in: query name: 'fields[positions]' description: 'Sparse fieldset selection.' example: 'id,title,team' required: false schema: type: string description: 'Sparse fieldset selection.' example: 'id,title,team' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: text/plain: schema: type: string example: "{\n \"data\": [\n {\n \"id\": 17,\n \"org_unit_team_id\": 4201, // planning metadata only\n \"title\": \"Staff Platform Engineer\",\n \"target_employment_type\": \"perm\",\n \"fte\": 1,\n \"target_annual_salary_cents\": 11850000,\n \"target_daily_rate_cents\": null,\n \"target_currency\": \"EUR\",\n \"valid_from\": \"2024-03-01\",\n \"valid_to\": null,\n \"scenario_id\": null,\n \"meta\": {\n \"cost_centre\": \"ENG-PLAT\",\n \"notes\": \"Supports platform migration.\"\n },\n \"team\": {\n \"id\": 8,\n \"name\": \"Platform Experience\",\n \"slug\": \"platform-experience\"\n },\n \"actor\": null,\n \"links\": {\n \"self\": \"https://tenant.orgonaut.test/api/v1/positions/17\",\n \"team\": \"https://tenant.orgonaut.test/api/v1/teams/8\",\n \"actor\": null\n },\n \"created_at\": \"2025-09-19T09:00:00Z\",\n \"updated_at\": \"2025-09-19T09:00:00Z\"\n }\n ],\n \"links\": {\n \"first\": \"https://tenant.orgonaut.test/api/v1/positions?page=1\",\n \"last\": \"https://tenant.orgonaut.test/api/v1/positions?page=3\",\n \"prev\": null,\n \"next\": \"https://tenant.orgonaut.test/api/v1/positions?page=2\"\n },\n \"meta\": {\n \"current_page\": 1,\n \"from\": 1,\n \"last_page\": 3,\n \"path\": \"https://tenant.orgonaut.test/api/v1/positions\",\n \"per_page\": 25,\n \"to\": 25,\n \"total\": 62\n }\n}" 400: description: '' content: application/json: schema: type: object example: code: query.invalid_parameter message: 'Unsupported scenario filter value: future' details: allowed: - baseline - current - '{id}' properties: code: type: string example: query.invalid_parameter message: type: string example: 'Unsupported scenario filter value: future' details: type: object properties: allowed: type: array example: - baseline - current - '{id}' items: type: string tags: - Positions post: summary: 'Create a position within the active scenario.' operationId: createAPositionWithinTheActiveScenario description: "Applies baseline defaults when the current scenario guard is baseline,\notherwise stores the record against the active scenario id." parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 201: description: '' content: text/plain: schema: type: string example: "{\n \"data\": {\n \"id\": 24,\n \"org_unit_team_id\": 4201, // planning metadata only\n \"title\": \"Staff Platform Engineer\",\n \"target_employment_type\": \"perm\",\n \"fte\": 1,\n \"target_annual_salary_cents\": 11850000,\n \"target_daily_rate_cents\": null,\n \"target_currency\": \"EUR\",\n \"valid_from\": \"2024-03-01\",\n \"valid_to\": null,\n \"scenario_id\": null,\n \"meta\": {\n \"cost_centre\": \"ENG-PLAT\",\n \"notes\": \"Supports platform migration.\"\n },\n \"team\": {\n \"id\": 8,\n \"name\": \"Platform Experience\",\n \"slug\": \"platform-experience\"\n },\n \"actor\": null,\n \"links\": {\n \"self\": \"https://tenant.orgonaut.test/api/v1/positions/24\",\n \"team\": \"https://tenant.orgonaut.test/api/v1/teams/8\",\n \"actor\": null\n },\n \"created_at\": \"2025-09-19T09:15:00Z\",\n \"updated_at\": \"2025-09-19T09:15:00Z\"\n }\n}" 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: target_annual_salary_cents: - 'Budgeted annual salary applies to permanent roles only.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: target_annual_salary_cents: type: array example: - 'Budgeted annual salary applies to permanent roles only.' items: type: string tags: - Positions requestBody: required: true content: application/json: schema: type: object properties: title: type: string description: 'The seat title.' example: ab role_type_id: type: integer description: '' example: 11 nullable: true org_unit_team_id: type: integer description: 'The budgeted team for this seat (planning metadata only; does not assign a actor).' example: 4201 nullable: true target_employment_type: type: string description: 'The budgeted employment type (e.g., perm|contractor).' example: ab nullable: true fte: type: number description: 'Full-time equivalent allocation between 0.01 and 2.0. Must be between 0.01 and 2.00.' example: 1.0 target_annual_salary_cents: type: integer description: 'Budgeted annual salary in cents for permanent roles. Must be at least 0.' example: 8500000 nullable: true target_daily_rate_cents: type: integer description: 'Budgeted daily rate in cents for contractor roles. Must be at least 0.' example: 45000 nullable: true target_currency: type: string description: 'Three-letter currency code defined in Settings → Currencies. Must be 3 characters.' example: EUR nullable: true valid_from: type: string description: 'Effective start date (YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d.' example: '2025-09-20' valid_to: type: string description: 'Effective end date (optional, YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d. Must be a date after or equal to valid_from.' example: '2026-03-31' nullable: true meta: type: object description: 'Arbitrary structured metadata for the position.' example: cost_centre: ENG-PLAT properties: { } nullable: true create_placement: type: boolean description: 'Set to false to prevent auto-creating a placement when linking this record to downstream observers.' example: true nullable: true tenant_id: type: integer description: '' example: 11 scenario_id: type: integer description: '' example: 11 nullable: true required: - title - fte - valid_from - tenant_id '/api/v1/positions/{id}': get: summary: 'Display a specific position.' operationId: displayASpecificPosition description: "Honors the active scenario guard unless `filter[scenario]`/`filter[scenario_id]`\nwere provided on the index request and cached via query options middleware." parameters: - in: query name: include description: 'Comma-separated relationships. Allowed: team,actor.' example: team required: false schema: type: string description: 'Comma-separated relationships. Allowed: team,actor.' example: team - in: query name: 'fields[positions]' description: 'Sparse fieldset.' example: 'id,title,team' required: false schema: type: string description: 'Sparse fieldset.' example: 'id,title,team' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: text/plain: schema: type: string example: "{\n \"data\": {\n \"id\": 24,\n \"org_unit_team_id\": 4201, // planning metadata only\n \"actor_id\": null,\n \"title\": \"Staff Platform Engineer\",\n \"target_employment_type\": \"perm\",\n \"fte\": 1,\n \"target_annual_salary_cents\": 11850000,\n \"target_daily_rate_cents\": null,\n \"target_currency\": \"EUR\",\n \"valid_from\": \"2024-03-01\",\n \"valid_to\": null,\n \"scenario_id\": null,\n \"meta\": {\n \"cost_centre\": \"ENG-PLAT\",\n \"notes\": \"Supports platform migration.\"\n },\n \"team\": {\n \"id\": 8,\n \"name\": \"Platform Experience\",\n \"slug\": \"platform-experience\"\n },\n \"actor\": null,\n \"links\": {\n \"self\": \"https://tenant.orgonaut.test/api/v1/positions/24\",\n \"team\": \"https://tenant.orgonaut.test/api/v1/teams/8\",\n \"actor\": null\n },\n \"created_at\": \"2025-09-19T09:15:00Z\",\n \"updated_at\": \"2025-09-19T09:15:00Z\"\n }\n}" tags: - Positions put: summary: 'Update a position.' operationId: updateAPosition description: "Validation enforces mutual exclusivity between salary and daily rate,\nmirroring the create endpoint rules." parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: text/plain: schema: type: string example: "{\n \"data\": {\n \"id\": 24,\n \"org_unit_team_id\": 4201, // planning metadata only\n \"actor_id\": null,\n \"title\": \"Principal Platform Engineer\",\n \"target_employment_type\": \"perm\",\n \"fte\": 1,\n \"target_annual_salary_cents\": 12600000,\n \"target_daily_rate_cents\": null,\n \"target_currency\": \"EUR\",\n \"valid_from\": \"2024-03-01\",\n \"valid_to\": null,\n \"scenario_id\": null,\n \"meta\": {\n \"cost_centre\": \"ENG-PLAT\",\n \"notes\": \"Role upgraded to lead migration.\"\n },\n \"team\": {\n \"id\": 8,\n \"name\": \"Platform Experience\",\n \"slug\": \"platform-experience\"\n },\n \"actor\": null,\n \"links\": {\n \"self\": \"https://tenant.orgonaut.test/api/v1/positions/24\",\n \"team\": \"https://tenant.orgonaut.test/api/v1/teams/8\",\n \"actor\": null\n },\n \"created_at\": \"2025-09-19T09:15:00Z\",\n \"updated_at\": \"2025-09-26T11:20:00Z\"\n }\n}" 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: target_daily_rate_cents: - 'Budgeted daily rate applies to contractor roles only.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: target_daily_rate_cents: type: array example: - 'Budgeted daily rate applies to contractor roles only.' items: type: string tags: - Positions requestBody: required: true content: application/json: schema: type: object properties: title: type: string description: 'The seat title.' example: ab role_type_id: type: integer description: '' example: 11 nullable: true org_unit_team_id: type: integer description: 'The budgeted team for this seat (planning metadata only; does not assign a actor).' example: 4201 nullable: true target_employment_type: type: string description: 'The budgeted employment type (e.g., perm|contractor).' example: ab nullable: true fte: type: number description: 'Full-time equivalent allocation between 0.01 and 2.0. Must be between 0.01 and 2.00.' example: 1.0 target_annual_salary_cents: type: integer description: 'Budgeted annual salary in cents for permanent roles. Must be at least 0.' example: 8500000 nullable: true target_daily_rate_cents: type: integer description: 'Budgeted daily rate in cents for contractor roles. Must be at least 0.' example: 45000 nullable: true target_currency: type: string description: 'Three-letter currency code defined in Settings → Currencies. Must be 3 characters.' example: EUR nullable: true valid_from: type: string description: 'Effective start date (YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d.' example: '2025-09-20' valid_to: type: string description: 'Effective end date (optional, YYYY-MM-DD). Must be a valid date. Must be a valid date in the format Y-m-d. Must be a date after or equal to valid_from.' example: '2026-03-31' nullable: true meta: type: object description: 'Arbitrary structured metadata for the position.' example: cost_centre: ENG-PLAT properties: { } nullable: true create_placement: type: boolean description: 'Set to false to prevent auto-creating a placement when linking this record to downstream observers.' example: true nullable: true tenant_id: type: integer description: '' example: 11 scenario_id: type: integer description: '' example: 11 nullable: true required: - title - fte - valid_from - tenant_id delete: summary: 'Soft delete a position.' operationId: softDeleteAPosition description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 204: description: '' content: application/json: schema: type: object example: { } properties: { } tags: - Positions parameters: - in: path name: id description: 'The ID of the position.' example: 11 required: true schema: type: integer - in: path name: position description: 'The ID of the position.' example: 24 required: true schema: type: integer /api/v1/scenarios/tasks: get: summary: 'List scenario tasks.' operationId: listScenarioTasks description: "Returns scenario tasks for the authenticated tenant, ordered by latest first.\nThese tasks mirror the progress shown in the Flux UI at /scenarios/tasks." parameters: - in: query name: status description: 'Filter by status (pending, running, failed, complete).' example: running required: false schema: type: string description: 'Filter by status (pending, running, failed, complete).' example: running - in: query name: type description: 'Filter by task type (clone, promotion, snapshot, all). Defaults to clone for compatibility.' example: promotion required: false schema: type: string description: 'Filter by task type (clone, promotion, snapshot, all). Defaults to clone for compatibility.' example: promotion - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: array items: type: object properties: id: type: integer example: 81 scenario_id: type: integer example: 42 scenario_name: type: string example: 'Q2 Planning' type: type: string example: clone stage: type: string example: actors status: type: string example: running percent: type: integer example: 33 message: type: string example: 'Copying Actors...' error_message: type: string example: null nullable: true created_at: type: string example: '2025-11-01T12:14:22Z' updated_at: type: string example: '2025-11-01T12:15:03Z' example: - id: 81 scenario_id: 42 scenario_name: 'Q2 Planning' type: clone stage: actors status: running percent: 33 message: 'Copying Actors...' error_message: null created_at: '2025-11-01T12:14:22Z' updated_at: '2025-11-01T12:15:03Z' tags: - 'Scenario Tasks' /api/v1/scenarios: get: summary: 'Display a paginated listing of scenarios.' operationId: displayAPaginatedListingOfScenarios description: '' parameters: - in: query name: page description: 'The page number.' example: 1 required: false schema: type: integer description: 'The page number.' example: 1 - in: query name: per_page description: 'Max 100.' example: 25 required: false schema: type: integer description: 'Max 100.' example: 25 - in: query name: sort description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,name,created_at.' example: '-created_at,name' required: false schema: type: string description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,name,created_at.' example: '-created_at,name' - in: query name: 'filter[status]' description: 'Filter by status.' example: cloning required: false schema: type: string description: 'Filter by status.' example: cloning - in: query name: 'filter[kind]' description: 'Filter by kind (planning, baseline_archive, applied_scenario).' example: planning required: false schema: type: string description: 'Filter by kind (planning, baseline_archive, applied_scenario).' example: planning - in: query name: 'fields[scenarios]' description: 'Sparse fields for scenarios.' example: 'id,name,status' required: false schema: type: string description: 'Sparse fields for scenarios.' example: 'id,name,status' - in: query name: include description: 'Comma-separated relationships. Allowed: teams,aggregate.' example: 'teams,aggregate' required: false schema: type: string description: 'Comma-separated relationships. Allowed: teams,aggregate.' example: 'teams,aggregate' - in: query name: as_of description: 'Snapshot date for aggregates. Format: YYYY-MM-DD.' example: '2025-09-01' required: false schema: type: string description: 'Snapshot date for aggregates. Format: YYYY-MM-DD.' example: '2025-09-01' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 1 name: 'FY26 Cost Optimisation' status: new links: first: null last: null prev: null next: null meta: total: 1 current_page: 1 per_page: 15 last_page: 1 properties: data: type: array example: - id: 1 name: 'FY26 Cost Optimisation' status: new items: type: object properties: id: type: integer example: 1 name: type: string example: 'FY26 Cost Optimisation' status: type: string example: new links: type: object properties: first: type: string example: null nullable: true last: type: string example: null nullable: true prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: total: type: integer example: 1 current_page: type: integer example: 1 per_page: type: integer example: 15 last_page: type: integer example: 1 400: description: '' content: application/json: schema: type: object example: code: query.invalid_parameter message: 'Unsupported sort: salary' details: allowed: - id - name - created_at properties: code: type: string example: query.invalid_parameter message: type: string example: 'Unsupported sort: salary' details: type: object properties: allowed: type: array example: - id - name - created_at items: type: string 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: per_page: - 'The per page must be at least 1.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: per_page: type: array example: - 'The per page must be at least 1.' items: type: string tags: - Scenarios post: summary: 'Create a new scenario.' operationId: createANewScenario description: 'Triggers a background clone pipeline and returns immediately with status=initialising.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 202: description: '' content: application/json: schema: type: object example: message: 'Scenario creation started. Cloning in progress.' status: initialising scenario: id: 42 name: 'Q2 Planning' status: initialising notes: 'Budget-cut planning run.' tasks_url: 'https://app.orgonaut.test/api/v1/scenarios/tasks' properties: message: type: string example: 'Scenario creation started. Cloning in progress.' status: type: string example: initialising scenario: type: object properties: id: type: integer example: 42 name: type: string example: 'Q2 Planning' status: type: string example: initialising notes: type: string example: 'Budget-cut planning run.' tasks_url: type: string example: 'https://app.orgonaut.test/api/v1/scenarios/tasks' 403: description: '' content: application/json: schema: type: object example: code: feature.unavailable message: 'Your current subscription plan does not include this feature.' properties: code: type: string example: feature.unavailable message: type: string example: 'Your current subscription plan does not include this feature.' 429: description: '' content: application/json: schema: type: object example: code: limit.scenarios message: 'Active planning scenario limit reached (1/1). Archive or apply an existing scenario, or upgrade your plan.' limit: 1 current: 1 properties: code: type: string example: limit.scenarios message: type: string example: 'Active planning scenario limit reached (1/1). Archive or apply an existing scenario, or upgrade your plan.' limit: type: integer example: 1 current: type: integer example: 1 tags: - Scenarios requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'The scenario name.' example: '"Q2 Planning"' notes: type: string description: 'Notes about the scenario.' example: '"Budget-cut planning run."' nullable: true source_scenario_id: type: integer description: 'The active scenario to clone from (null to clone from baseline).' example: 12 nullable: true required: - name '/api/v1/scenarios/{slug}': get: summary: 'Display the specified scenario.' operationId: displayTheSpecifiedScenario description: '' parameters: - in: query name: include description: 'Comma-separated relationships. Allowed: teams,aggregate.' example: aggregate required: false schema: type: string description: 'Comma-separated relationships. Allowed: teams,aggregate.' example: aggregate - in: query name: as_of description: 'Snapshot date for aggregates. Format: YYYY-MM-DD.' example: '2025-09-01' required: false schema: type: string description: 'Snapshot date for aggregates. Format: YYYY-MM-DD.' example: '2025-09-01' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 1 name: 'FY26 Cost Optimisation' status: new aggregate: as_of_date: '2025-09-01' scenario_direct_headcount: 0 scenario_subtree_headcount: 0 scenario_direct_monthly_cost_cents: 0 scenario_subtree_monthly_cost_cents: 0 velocity_completed_sp: 0 velocity_team_count: 0 computed_at: '2025-09-06T12:00:00Z' metrics: { } properties: data: type: object properties: id: type: integer example: 1 name: type: string example: 'FY26 Cost Optimisation' status: type: string example: new aggregate: type: object properties: as_of_date: type: string example: '2025-09-01' scenario_direct_headcount: type: integer example: 0 scenario_subtree_headcount: type: integer example: 0 scenario_direct_monthly_cost_cents: type: integer example: 0 scenario_subtree_monthly_cost_cents: type: integer example: 0 velocity_completed_sp: type: integer example: 0 velocity_team_count: type: integer example: 0 computed_at: type: string example: '2025-09-06T12:00:00Z' metrics: type: object properties: { } tags: - Scenarios parameters: - in: path name: slug description: 'The slug of the scenario.' example: ab required: true schema: type: string - in: path name: scenario description: 'The ID of the scenario.' example: 1 required: true schema: type: integer '/api/v1/scenarios/{scenario_slug}/delta': get: summary: 'Show computed deltas between a scenario and its source baseline.' operationId: showComputedDeltasBetweenAScenarioAndItsSourceBaseline description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: scenario: id: 7 name: 'FY26 Restructure' slug: fy26-restructure status: draft kind: planning notes: 'Budget-cut planning run.' live_from: null live_to: null locked_at: null archived_at: null is_read_only: false source_scenario_id: null created_at: '2026-03-01T09:00:00Z' updated_at: '2026-03-01T09:00:00Z' source: id: 1 name: 'Current State' slug: current-state status: published kind: planning notes: null live_from: null live_to: null locked_at: null archived_at: null is_read_only: false source_scenario_id: null created_at: '2025-12-15T09:00:00Z' updated_at: '2025-12-15T09:00:00Z' metrics: headcount: label: Headcount scenario: 118 source: 123 delta: -5 fte: label: FTE scenario: 111.4 source: 116.4 delta: -5 cost: label: 'Monthly Cost (cents)' scenario: 142500000 source: 148000000 delta: -5500000 velocity: label: 'Velocity (SP)' scenario: 89 source: 95 delta: -6 teams: - key: 'team:44' status: changed department: Engineering placements: [] badges: [] actors: - key: 'actor:98' status: changed badges: [] change_log: - entity_type: Team action: updated classification: updated identity: name: 'Platform Enablement' created_at: '2026-03-01T10:15:00Z' count: 1 properties: scenario: type: object properties: id: type: integer example: 7 name: type: string example: 'FY26 Restructure' slug: type: string example: fy26-restructure status: type: string example: draft kind: type: string example: planning notes: type: string example: 'Budget-cut planning run.' live_from: type: string example: null nullable: true live_to: type: string example: null nullable: true locked_at: type: string example: null nullable: true archived_at: type: string example: null nullable: true is_read_only: type: boolean example: false source_scenario_id: type: string example: null nullable: true created_at: type: string example: '2026-03-01T09:00:00Z' updated_at: type: string example: '2026-03-01T09:00:00Z' source: type: object properties: id: type: integer example: 1 name: type: string example: 'Current State' slug: type: string example: current-state status: type: string example: published kind: type: string example: planning notes: type: string example: null nullable: true live_from: type: string example: null nullable: true live_to: type: string example: null nullable: true locked_at: type: string example: null nullable: true archived_at: type: string example: null nullable: true is_read_only: type: boolean example: false source_scenario_id: type: string example: null nullable: true created_at: type: string example: '2025-12-15T09:00:00Z' updated_at: type: string example: '2025-12-15T09:00:00Z' metrics: type: object properties: headcount: type: object properties: label: type: string example: Headcount scenario: type: integer example: 118 source: type: integer example: 123 delta: type: integer example: -5 fte: type: object properties: label: type: string example: FTE scenario: type: number example: 111.4 source: type: number example: 116.4 delta: type: integer example: -5 cost: type: object properties: label: type: string example: 'Monthly Cost (cents)' scenario: type: integer example: 142500000 source: type: integer example: 148000000 delta: type: integer example: -5500000 velocity: type: object properties: label: type: string example: 'Velocity (SP)' scenario: type: integer example: 89 source: type: integer example: 95 delta: type: integer example: -6 teams: type: array example: - key: 'team:44' status: changed department: Engineering placements: [] badges: [] items: type: object properties: key: type: string example: 'team:44' status: type: string example: changed department: type: string example: Engineering placements: type: array example: [] badges: type: array example: [] actors: type: array example: - key: 'actor:98' status: changed badges: [] items: type: object properties: key: type: string example: 'actor:98' status: type: string example: changed badges: type: array example: [] change_log: type: array example: - entity_type: Team action: updated classification: updated identity: name: 'Platform Enablement' created_at: '2026-03-01T10:15:00Z' count: 1 items: type: object properties: entity_type: type: string example: Team action: type: string example: updated classification: type: string example: updated identity: type: object properties: name: type: string example: 'Platform Enablement' created_at: type: string example: '2026-03-01T10:15:00Z' count: type: integer example: 1 tags: - Scenarios parameters: - in: path name: scenario_slug description: 'The slug of the scenario.' example: ab required: true schema: type: string - in: path name: scenario description: 'Scenario id.' example: 7 required: true schema: type: integer '/api/v1/scenarios/{scenario_slug}/promote': post: summary: 'Promote a planning scenario to the live baseline.' operationId: promoteAPlanningScenarioToTheLiveBaseline description: 'Queues the existing promotion pipeline with type `scenario_promotion`.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 202: description: '' content: application/json: schema: type: object example: message: 'Scenario promotion started. Promotion in progress.' status: queued scenario_id: 1 tasks_url: 'https://app.orgonaut.test/api/v1/scenarios/tasks' properties: message: type: string example: 'Scenario promotion started. Promotion in progress.' status: type: string example: queued scenario_id: type: integer example: 1 tasks_url: type: string example: 'https://app.orgonaut.test/api/v1/scenarios/tasks' tags: - Scenarios requestBody: required: false content: application/json: schema: type: object properties: confirmed_promotion: type: boolean description: 'Optional confirmation flag for audit.' example: true nullable: true confirmed_archive: type: boolean description: 'Optional confirmation flag for audit.' example: true nullable: true parameters: - in: path name: scenario_slug description: 'The slug of the scenario.' example: ab required: true schema: type: string - in: path name: scenario description: 'The ID of the scenario.' example: 1 required: true schema: type: integer '/api/v1/scenarios/{scenario_slug}/submit': post: summary: 'Submit the scenario for review.' operationId: submitTheScenarioForReview description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 1 name: 'FY26 Cost Optimisation' status: under_review properties: data: type: object properties: id: type: integer example: 1 name: type: string example: 'FY26 Cost Optimisation' status: type: string example: under_review tags: - Scenarios parameters: - in: path name: scenario_slug description: 'The slug of the scenario.' example: ab required: true schema: type: string - in: path name: scenario description: 'The ID of the scenario.' example: 1 required: true schema: type: integer '/api/v1/scenarios/{scenario_slug}/approve': post: summary: 'Approve the scenario.' operationId: approveTheScenario description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 1 name: 'FY26 Cost Optimisation' status: approved properties: data: type: object properties: id: type: integer example: 1 name: type: string example: 'FY26 Cost Optimisation' status: type: string example: approved tags: - Scenarios parameters: - in: path name: scenario_slug description: 'The slug of the scenario.' example: ab required: true schema: type: string - in: path name: scenario description: 'The ID of the scenario.' example: 1 required: true schema: type: integer /api/v1/snapshots: get: summary: 'List baseline snapshots.' operationId: listBaselineSnapshots description: '' parameters: - in: query name: page description: 'The page number.' example: 1 required: false schema: type: integer description: 'The page number.' example: 1 - in: query name: per_page description: 'Max 100.' example: 25 required: false schema: type: integer description: 'Max 100.' example: 25 - in: query name: sort description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,name,created_at.' example: '-created_at,name' required: false schema: type: string description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,name,created_at.' example: '-created_at,name' - in: query name: 'filter[status]' description: 'Filter by status.' example: archived required: false schema: type: string description: 'Filter by status.' example: archived - in: query name: 'fields[scenarios]' description: 'Sparse fields for scenarios.' example: 'id,name,kind' required: false schema: type: string description: 'Sparse fields for scenarios.' example: 'id,name,kind' - in: query name: include description: 'Comma-separated relationships. Allowed: teams,aggregate.' example: aggregate required: false schema: type: string description: 'Comma-separated relationships. Allowed: teams,aggregate.' example: aggregate - in: query name: as_of description: 'Snapshot date for aggregates. Format: YYYY-MM-DD.' example: '2025-09-01' required: false schema: type: string description: 'Snapshot date for aggregates. Format: YYYY-MM-DD.' example: '2025-09-01' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 12 name: 'Baseline Snapshot 2025-11-01' kind: baseline_archive links: first: null last: null prev: null next: null meta: total: 1 current_page: 1 per_page: 15 last_page: 1 properties: data: type: array example: - id: 12 name: 'Baseline Snapshot 2025-11-01' kind: baseline_archive items: type: object properties: id: type: integer example: 12 name: type: string example: 'Baseline Snapshot 2025-11-01' kind: type: string example: baseline_archive links: type: object properties: first: type: string example: null nullable: true last: type: string example: null nullable: true prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: total: type: integer example: 1 current_page: type: integer example: 1 per_page: type: integer example: 15 last_page: type: integer example: 1 tags: - Snapshots '/api/v1/snapshots/{scenario_slug}': get: summary: 'Display a baseline snapshot.' operationId: displayABaselineSnapshot description: '' parameters: - in: query name: include description: 'Comma-separated relationships. Allowed: teams,aggregate.' example: aggregate required: false schema: type: string description: 'Comma-separated relationships. Allowed: teams,aggregate.' example: aggregate - in: query name: as_of description: 'Snapshot date for aggregates. Format: YYYY-MM-DD.' example: '2025-09-01' required: false schema: type: string description: 'Snapshot date for aggregates. Format: YYYY-MM-DD.' example: '2025-09-01' - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 12 name: 'Baseline Snapshot 2025-11-01' kind: baseline_archive status: archived properties: data: type: object properties: id: type: integer example: 12 name: type: string example: 'Baseline Snapshot 2025-11-01' kind: type: string example: baseline_archive status: type: string example: archived tags: - Snapshots parameters: - in: path name: scenario_slug description: 'The slug of the scenario.' example: ab required: true schema: type: string - in: path name: scenario description: 'Snapshot id.' example: 12 required: true schema: type: integer '/api/v1/snapshots/{scenario_slug}/restore': post: summary: 'Restore a baseline snapshot to become the live baseline.' operationId: restoreABaselineSnapshotToBecomeTheLiveBaseline description: 'Queues the existing promotion pipeline with type `snapshot_restore`.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 202: description: '' content: application/json: schema: type: object example: message: 'Snapshot restore started. Promotion in progress.' status: queued snapshot_id: 12 tasks_url: 'https://app.orgonaut.test/api/v1/scenarios/tasks' properties: message: type: string example: 'Snapshot restore started. Promotion in progress.' status: type: string example: queued snapshot_id: type: integer example: 12 tasks_url: type: string example: 'https://app.orgonaut.test/api/v1/scenarios/tasks' tags: - Snapshots requestBody: required: false content: application/json: schema: type: object properties: confirmed_promotion: type: boolean description: 'Optional confirmation flag for audit.' example: true nullable: true confirmed_archive: type: boolean description: 'Optional confirmation flag for audit.' example: true nullable: true parameters: - in: path name: scenario_slug description: 'The slug of the scenario.' example: ab required: true schema: type: string - in: path name: scenario description: 'Snapshot id.' example: 12 required: true schema: type: integer /api/v1/teams: get: summary: 'Display a paginated listing of teams.' operationId: displayAPaginatedListingOfTeams description: 'Requires `teams.read` ability.' parameters: - in: query name: page description: 'The page number.' example: 1 required: false schema: type: integer description: 'The page number.' example: 1 - in: query name: per_page description: 'Max 100.' example: 25 required: false schema: type: integer description: 'Max 100.' example: 25 - in: query name: sort description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,name,created_at.' example: '-created_at,name' required: false schema: type: string description: 'Comma-separated columns, prefix with - for DESC. Allowed: id,name,created_at.' example: '-created_at,name' - in: query name: 'filter[department_id]' description: 'Filter by department id.' example: 5 required: false schema: type: integer description: 'Filter by department id.' example: 5 - in: query name: 'filter[org_unit_id]' description: 'Filter by org unit id (department subtree).' example: 10 required: false schema: type: integer description: 'Filter by org unit id (department subtree).' example: 10 - in: query name: 'fields[teams]' description: 'Sparse fields for teams.' example: 'id,name' required: false schema: type: string description: 'Sparse fields for teams.' example: 'id,name' - in: query name: include description: 'Comma-separated relationships. Allowed: aggregate.' example: aggregate required: false schema: type: string description: 'Comma-separated relationships. Allowed: aggregate.' example: aggregate - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 15 name: 'Team CúChulainn' links: first: null last: null prev: null next: null meta: total: 1 current_page: 1 per_page: 15 last_page: 1 properties: data: type: array example: - id: 15 name: 'Team CúChulainn' items: type: object properties: id: type: integer example: 15 name: type: string example: 'Team CúChulainn' links: type: object properties: first: type: string example: null nullable: true last: type: string example: null nullable: true prev: type: string example: null nullable: true next: type: string example: null nullable: true meta: type: object properties: total: type: integer example: 1 current_page: type: integer example: 1 per_page: type: integer example: 15 last_page: type: integer example: 1 400: description: '' content: application/json: schema: type: object example: code: query.invalid_parameter message: 'Unsupported sort: salary' details: allowed: - id - name - created_at properties: code: type: string example: query.invalid_parameter message: type: string example: 'Unsupported sort: salary' details: type: object properties: allowed: type: array example: - id - name - created_at items: type: string 422: description: '' content: application/json: schema: type: object example: message: 'The given data was invalid.' errors: per_page: - 'The per page must be at least 1.' properties: message: type: string example: 'The given data was invalid.' errors: type: object properties: per_page: type: array example: - 'The per page must be at least 1.' items: type: string tags: - Teams post: summary: 'Store a newly created team.' operationId: storeANewlyCreatedTeam description: 'Requires `teams.create` ability.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 201: description: '' content: application/json: schema: type: object example: data: id: 15 name: 'Team CúChulainn' team_type_id: 3 team_type: id: 3 name: Cross-functional slug: cross-functional purpose: 'Platform delivery' parent_org_unit_id: 202 scenario_id: 1 properties: data: type: object properties: id: type: integer example: 15 name: type: string example: 'Team CúChulainn' team_type_id: type: integer example: 3 team_type: type: object properties: id: type: integer example: 3 name: type: string example: Cross-functional slug: type: string example: cross-functional purpose: type: string example: 'Platform delivery' parent_org_unit_id: type: integer example: 202 scenario_id: type: integer example: 1 tags: - Teams requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Team name. Must not be greater than 255 characters.' example: Platform team_type_id: type: integer description: 'Optional team type id. The id of an existing record in the team_types table.' example: 1 nullable: true purpose: type: string description: 'Optional purpose/mission. Must not be greater than 255 characters.' example: 'Internal platforms' nullable: true valid_from: type: string description: 'Date the team becomes active. Must be a valid date.' example: '2025-01-01' valid_to: type: string description: 'Optional date the team becomes inactive. Must be a valid date. Must be a date after or equal to valid_from.' example: '2025-12-31' nullable: true parent_org_unit_id: type: integer description: nullable example: 7 nullable: true required: - name - valid_from '/api/v1/teams/{id}': get: summary: 'Display a specific team.' operationId: displayASpecificTeam description: 'Requires `teams.read` ability.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 15 name: 'Team CúChulainn' team_type_id: 3 team_type: id: 3 name: Cross-functional slug: cross-functional purpose: 'Platform delivery' parent_org_unit_id: 202 scenario_id: 1 created_at: '2025-01-10T09:00:00Z' properties: data: type: object properties: id: type: integer example: 15 name: type: string example: 'Team CúChulainn' team_type_id: type: integer example: 3 team_type: type: object properties: id: type: integer example: 3 name: type: string example: Cross-functional slug: type: string example: cross-functional purpose: type: string example: 'Platform delivery' parent_org_unit_id: type: integer example: 202 scenario_id: type: integer example: 1 created_at: type: string example: '2025-01-10T09:00:00Z' tags: - Teams put: summary: 'Update a team.' operationId: updateATeam description: 'Requires `teams.update` ability.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 15 name: 'Team Fianna' team_type_id: 3 team_type: id: 3 name: Cross-functional slug: cross-functional purpose: 'Platform delivery' parent_org_unit_id: 202 scenario_id: 1 properties: data: type: object properties: id: type: integer example: 15 name: type: string example: 'Team Fianna' team_type_id: type: integer example: 3 team_type: type: object properties: id: type: integer example: 3 name: type: string example: Cross-functional slug: type: string example: cross-functional purpose: type: string example: 'Platform delivery' parent_org_unit_id: type: integer example: 202 scenario_id: type: integer example: 1 tags: - Teams requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Team name. Must not be greater than 255 characters.' example: Platform team_type_id: type: integer description: 'Optional team type id. The id of an existing record in the team_types table.' example: 1 nullable: true purpose: type: string description: 'Optional purpose/mission. Must not be greater than 255 characters.' example: 'Internal platforms' nullable: true valid_from: type: string description: 'Date the team becomes active. Must be a valid date.' example: '2025-01-01' valid_to: type: string description: 'Optional date the team becomes inactive. Must be a valid date. Must be a date after or equal to valid_from.' example: '2025-12-31' nullable: true parent_org_unit_id: type: integer description: nullable example: 7 nullable: true required: - name - valid_from delete: summary: 'Delete a team.' operationId: deleteATeam description: 'Requires `teams.delete` ability.' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 204: description: '' content: application/json: schema: type: object example: { } properties: { } tags: - Teams parameters: - in: path name: id description: 'The ID of the team.' example: 11 required: true schema: type: integer '/api/v1/teams/{team_id}/velocity': get: summary: 'Get velocity stats for a team in a sprint.' operationId: getVelocityStatsForATeamInASprint description: '' parameters: - in: query name: sprint_id description: 'The sprint to query.' example: 11 required: false schema: type: integer description: 'The sprint to query.' example: 11 nullable: true - in: query name: as_of description: 'date Mutually exclusive with sprint_id.' example: ab required: false schema: type: string description: 'date Mutually exclusive with sprint_id.' example: ab nullable: true - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: team_id: 12 sprint_id: 42 period: label: 'Sprint 18' starts_at: '2025-09-01' ends_at: '2025-09-14' committed_sp: 34 completed_sp: 31 scope_added_sp: 3 carried_over_sp: 5 stories_done: 12 source: jira properties: data: type: object properties: team_id: type: integer example: 12 sprint_id: type: integer example: 42 period: type: object properties: label: type: string example: 'Sprint 18' starts_at: type: string example: '2025-09-01' ends_at: type: string example: '2025-09-14' committed_sp: type: integer example: 34 completed_sp: type: integer example: 31 scope_added_sp: type: integer example: 3 carried_over_sp: type: integer example: 5 stories_done: type: integer example: 12 source: type: string example: jira tags: - 'Teams — Velocity' parameters: - in: path name: team_id description: 'The ID of the team.' example: 11 required: true schema: type: integer - in: path name: team description: 'The team ID.' example: 12 required: true schema: type: integer '/api/v1/teams/{team_id}/velocity/series': get: summary: 'Get a velocity series for a team.' operationId: getAVelocitySeriesForATeam description: '' parameters: - in: query name: from_sprint_id description: 'Starting sprint ID.' example: 11 required: false schema: type: integer description: 'Starting sprint ID.' example: 11 nullable: true - in: query name: to_sprint_id description: 'Ending sprint ID.' example: 11 required: false schema: type: integer description: 'Ending sprint ID.' example: 11 nullable: true - in: query name: from description: 'date Start date.' example: ab required: false schema: type: string description: 'date Start date.' example: ab nullable: true - in: query name: to description: 'date End date.' example: ab required: false schema: type: string description: 'date End date.' example: ab nullable: true - in: query name: limit description: 'Maximum results. Defaults to 12.' example: 11 required: false schema: type: integer description: 'Maximum results. Defaults to 12.' example: 11 nullable: true - in: query name: order description: 'asc or desc. Defaults to asc.' example: ab required: false schema: type: string description: 'asc or desc. Defaults to asc.' example: ab nullable: true - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "demo"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - team_id: 12 sprint_id: 41 period: label: 'Sprint 17' starts_at: '2025-08-18' ends_at: '2025-08-31' committed_sp: 29 completed_sp: 27 scope_added_sp: 2 carried_over_sp: 4 stories_done: 10 source: jira - team_id: 12 sprint_id: 42 period: label: 'Sprint 18' starts_at: '2025-09-01' ends_at: '2025-09-14' committed_sp: 34 completed_sp: 31 scope_added_sp: 3 carried_over_sp: 5 stories_done: 12 source: jira properties: data: type: array example: - team_id: 12 sprint_id: 41 period: label: 'Sprint 17' starts_at: '2025-08-18' ends_at: '2025-08-31' committed_sp: 29 completed_sp: 27 scope_added_sp: 2 carried_over_sp: 4 stories_done: 10 source: jira - team_id: 12 sprint_id: 42 period: label: 'Sprint 18' starts_at: '2025-09-01' ends_at: '2025-09-14' committed_sp: 34 completed_sp: 31 scope_added_sp: 3 carried_over_sp: 5 stories_done: 12 source: jira items: type: object properties: team_id: type: integer example: 12 sprint_id: type: integer example: 41 period: type: object properties: label: type: string example: 'Sprint 17' starts_at: type: string example: '2025-08-18' ends_at: type: string example: '2025-08-31' committed_sp: type: integer example: 29 completed_sp: type: integer example: 27 scope_added_sp: type: integer example: 2 carried_over_sp: type: integer example: 4 stories_done: type: integer example: 10 source: type: string example: jira tags: - 'Teams — Velocity' parameters: - in: path name: team_id description: 'The ID of the team.' example: 11 required: true schema: type: integer - in: path name: team description: 'The team ID.' example: 12 required: true schema: type: integer /api/v1/tenant-invitations: get: summary: 'List invitations for the current tenant.' operationId: listInvitationsForTheCurrentTenant description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "acme"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: - id: 17 tenant_id: 3 email: new.user@example.com role: manager state: pending invited_by: 7 expires_at: '2026-03-14T09:00:00Z' accepted_at: null accepted_by_user_id: null revoked_at: null created_at: '2026-03-07T09:00:00Z' updated_at: '2026-03-07T09:00:00Z' properties: data: type: array example: - id: 17 tenant_id: 3 email: new.user@example.com role: manager state: pending invited_by: 7 expires_at: '2026-03-14T09:00:00Z' accepted_at: null accepted_by_user_id: null revoked_at: null created_at: '2026-03-07T09:00:00Z' updated_at: '2026-03-07T09:00:00Z' items: type: object properties: id: type: integer example: 17 tenant_id: type: integer example: 3 email: type: string example: new.user@example.com role: type: string example: manager state: type: string example: pending invited_by: type: integer example: 7 expires_at: type: string example: '2026-03-14T09:00:00Z' accepted_at: type: string example: null nullable: true accepted_by_user_id: type: string example: null nullable: true revoked_at: type: string example: null nullable: true created_at: type: string example: '2026-03-07T09:00:00Z' updated_at: type: string example: '2026-03-07T09:00:00Z' tags: - 'Tenant Invitations' post: summary: 'Create a new tenant invitation.' operationId: createANewTenantInvitation description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "acme"' schema: type: string responses: 201: description: '' content: application/json: schema: type: object example: data: id: 17 tenant_id: 3 email: new.user@example.com role: manager state: pending invited_by: 7 expires_at: '2026-03-14T09:00:00Z' accepted_at: null accepted_by_user_id: null revoked_at: null created_at: '2026-03-07T09:00:00Z' updated_at: '2026-03-07T09:00:00Z' properties: data: type: object properties: id: type: integer example: 17 tenant_id: type: integer example: 3 email: type: string example: new.user@example.com role: type: string example: manager state: type: string example: pending invited_by: type: integer example: 7 expires_at: type: string example: '2026-03-14T09:00:00Z' accepted_at: type: string example: null nullable: true accepted_by_user_id: type: string example: null nullable: true revoked_at: type: string example: null nullable: true created_at: type: string example: '2026-03-07T09:00:00Z' updated_at: type: string example: '2026-03-07T09:00:00Z' tags: - 'Tenant Invitations' requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: 'Invitee email address.' example: '"new.user@example.com"' role: type: string description: 'User type to assign on acceptance. Must be one of `admin`, `manager`, `member`, or `viewer`.' example: '"manager"' required: - email - role '/api/v1/tenant-invitations/{invite}/resend': post: summary: 'Resend a pending invitation and revoke the existing pending token.' operationId: resendAPendingInvitationAndRevokeTheExistingPendingToken description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "acme"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 23 tenant_id: 3 email: new.user@example.com role: manager state: pending invited_by: 7 expires_at: '2026-03-21T09:00:00Z' accepted_at: null accepted_by_user_id: null revoked_at: null created_at: '2026-03-07T10:00:00Z' updated_at: '2026-03-07T10:00:00Z' properties: data: type: object properties: id: type: integer example: 23 tenant_id: type: integer example: 3 email: type: string example: new.user@example.com role: type: string example: manager state: type: string example: pending invited_by: type: integer example: 7 expires_at: type: string example: '2026-03-21T09:00:00Z' accepted_at: type: string example: null nullable: true accepted_by_user_id: type: string example: null nullable: true revoked_at: type: string example: null nullable: true created_at: type: string example: '2026-03-07T10:00:00Z' updated_at: type: string example: '2026-03-07T10:00:00Z' tags: - 'Tenant Invitations' parameters: - in: path name: invite description: 'Invitation id.' example: 17 required: true schema: type: integer '/api/v1/tenant-invitations/{invite}/revoke': post: summary: 'Revoke a pending invitation.' operationId: revokeAPendingInvitation description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "acme"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: id: 17 tenant_id: 3 email: new.user@example.com role: manager state: revoked invited_by: 7 expires_at: '2026-03-14T09:00:00Z' accepted_at: null accepted_by_user_id: null revoked_at: '2026-03-07T10:15:00Z' created_at: '2026-03-07T09:00:00Z' updated_at: '2026-03-07T10:15:00Z' properties: data: type: object properties: id: type: integer example: 17 tenant_id: type: integer example: 3 email: type: string example: new.user@example.com role: type: string example: manager state: type: string example: revoked invited_by: type: integer example: 7 expires_at: type: string example: '2026-03-14T09:00:00Z' accepted_at: type: string example: null nullable: true accepted_by_user_id: type: string example: null nullable: true revoked_at: type: string example: '2026-03-07T10:15:00Z' created_at: type: string example: '2026-03-07T09:00:00Z' updated_at: type: string example: '2026-03-07T10:15:00Z' tags: - 'Tenant Invitations' parameters: - in: path name: invite description: 'Invitation id.' example: 17 required: true schema: type: integer /api/v1/tenant-members: get: summary: 'List active and archived tenant memberships.' operationId: listActiveAndArchivedTenantMemberships description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "acme"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: active: - tenant_id: 3 user_id: 42 name: "Aisling O'Neill" email: aisling@example.com role: admin membership_state: active archived_at: null archived_by: null reactivated_at: null reactivated_by: null archived: - tenant_id: 3 user_id: 51 name: 'Niall Byrne' email: niall@example.com role: member membership_state: archived archived_at: '2026-02-21T09:30:00Z' archived_by: 42 reactivated_at: null reactivated_by: null properties: data: type: object properties: active: type: array example: - tenant_id: 3 user_id: 42 name: "Aisling O'Neill" email: aisling@example.com role: admin membership_state: active archived_at: null archived_by: null reactivated_at: null reactivated_by: null description: 'Active memberships.' items: type: object properties: tenant_id: type: integer example: 3 user_id: type: integer example: 42 name: type: string example: "Aisling O'Neill" email: type: string example: aisling@example.com role: type: string example: admin membership_state: type: string example: active archived_at: type: string example: null nullable: true archived_by: type: string example: null nullable: true reactivated_at: type: string example: null nullable: true reactivated_by: type: string example: null nullable: true archived: type: array example: - tenant_id: 3 user_id: 51 name: 'Niall Byrne' email: niall@example.com role: member membership_state: archived archived_at: '2026-02-21T09:30:00Z' archived_by: 42 reactivated_at: null reactivated_by: null description: 'Archived memberships.' items: type: object properties: tenant_id: type: integer example: 3 user_id: type: integer example: 51 name: type: string example: 'Niall Byrne' email: type: string example: niall@example.com role: type: string example: member membership_state: type: string example: archived archived_at: type: string example: '2026-02-21T09:30:00Z' archived_by: type: integer example: 42 reactivated_at: type: string example: null nullable: true reactivated_by: type: string example: null nullable: true tags: - 'Tenant Members' '/api/v1/tenant-members/{user}/archive': patch: summary: 'Archive a non-owner tenant membership.' operationId: archiveANonOwnerTenantMembership description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "acme"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: tenant_id: 3 user_id: 42 name: "Aisling O'Neill" email: aisling@example.com role: member membership_state: archived archived_at: '2026-03-07T10:15:00Z' archived_by: 7 reactivated_at: null reactivated_by: null properties: data: type: object properties: tenant_id: type: integer example: 3 user_id: type: integer example: 42 description: 'Membership user id.' name: type: string example: "Aisling O'Neill" email: type: string example: aisling@example.com role: type: string example: member membership_state: type: string example: archived description: 'Updated membership state.' archived_at: type: string example: '2026-03-07T10:15:00Z' archived_by: type: integer example: 7 reactivated_at: type: string example: null nullable: true reactivated_by: type: string example: null nullable: true tags: - 'Tenant Members' parameters: - in: path name: user description: 'Tenant user id.' example: 42 required: true schema: type: integer '/api/v1/tenant-members/{user}/reactivate': patch: summary: 'Reactivate an archived tenant membership.' operationId: reactivateAnArchivedTenantMembership description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "acme"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: tenant_id: 3 user_id: 42 name: "Aisling O'Neill" email: aisling@example.com role: member membership_state: active archived_at: '2026-03-01T10:15:00Z' archived_by: 7 reactivated_at: '2026-03-07T11:05:00Z' reactivated_by: 7 properties: data: type: object properties: tenant_id: type: integer example: 3 user_id: type: integer example: 42 description: 'Membership user id.' name: type: string example: "Aisling O'Neill" email: type: string example: aisling@example.com role: type: string example: member membership_state: type: string example: active description: 'Updated membership state.' archived_at: type: string example: '2026-03-01T10:15:00Z' archived_by: type: integer example: 7 reactivated_at: type: string example: '2026-03-07T11:05:00Z' reactivated_by: type: integer example: 7 tags: - 'Tenant Members' parameters: - in: path name: user description: 'Tenant user id.' example: 42 required: true schema: type: integer '/api/v1/tenant-members/{user}/role': patch: summary: 'Update a tenant member role without using ownership transfer.' operationId: updateATenantMemberRoleWithoutUsingOwnershipTransfer description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "acme"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: tenant_id: 3 user_id: 42 name: "Aisling O'Neill" email: aisling@example.com role: viewer membership_state: active archived_at: null archived_by: null reactivated_at: null reactivated_by: null properties: data: type: object properties: tenant_id: type: integer example: 3 user_id: type: integer example: 42 description: 'Membership user id.' name: type: string example: "Aisling O'Neill" email: type: string example: aisling@example.com role: type: string example: viewer description: 'Updated membership role.' membership_state: type: string example: active archived_at: type: string example: null nullable: true archived_by: type: string example: null nullable: true reactivated_at: type: string example: null nullable: true reactivated_by: type: string example: null nullable: true 422: description: '' content: application/json: schema: type: object example: message: 'Archived memberships must be reactivated before changing the user type.' code: tenancy.invalid_state properties: message: type: string example: 'Archived memberships must be reactivated before changing the user type.' code: type: string example: tenancy.invalid_state tags: - 'Tenant Members' requestBody: required: true content: application/json: schema: type: object properties: role: type: string description: 'User type to assign. Must be one of `admin`, `manager`, `member`, or `viewer`.' example: '"viewer"' required: - role parameters: - in: path name: user description: 'Tenant user id.' example: 42 required: true schema: type: integer /api/v1/tenant-members/transfer-ownership: post: summary: 'Transfer tenant ownership to an existing active admin.' operationId: transferTenantOwnershipToAnExistingActiveAdmin description: '' parameters: - in: header name: X-Tenant description: '' example: 'string required Tenant slug; required for tenant-scoped endpoints. Example: "acme"' schema: type: string responses: 200: description: '' content: application/json: schema: type: object example: data: status: ok properties: data: type: object properties: status: type: string example: ok tags: - 'Tenant Members' requestBody: required: true content: application/json: schema: type: object properties: target_user_id: type: integer description: 'Existing active admin user id.' example: 42 required: - target_user_id