Skip to main content
Tax planning (the advisor) reads a client’s binder and produces an AdvisorPlan: a global summary plus a list of AdvisorStrategy entries, each one a discrete tax-saving recommendation with evidence, an implementation plan, an estimated savings range, and a status you can drive. It runs as a background task: you start it with a trigger mutation and follow the resulting task to completion with the tasks API. Planning operations are reached through the me query resolved as a WorkspaceUser, so they all require a workspaceToken (see Authentication). All requests go to:
https://router.apps.filed.com/graphql
The advisor run is a polled background task. This page documents how to start a run, read the resulting plan, and update a strategy’s status. For the polling pattern itself (listing tasks, reading status, and the TaskResult union), see Tasks; this page does not re-explain it.

The AdvisorPlan type

AdvisorPlan is the shape returned by the advisorPlan field on Client. It carries the run identifier, a global summary, aggregate savings and skills-applied breakdowns, and the individual strategies.
type AdvisorPlan {
  runId: ID!
  taxYear: Int
  returnType: String
  strategies: [AdvisorStrategy!]!
  byDomain: JSON!
  bySavingsHorizon: JSON!
  estimatedSavingsCentsByHorizon: JSON!
  globalSummary: String!
  skillsApplied: AppliedSkills!
}

type AppliedSkills {
  workspace: [String!]!
  user: [String!]!
}
runId
ID!
The advisor run this plan belongs to. Pass it back to setAdvisorStrategyStatus when updating a strategy from this plan.
taxYear
Int
The tax year the plan was prepared for, for example 2025. May be null when the run has not finished populating the plan.
returnType
String
The return form as a free-form string (for example "F1040"). Note this is a String, not the ReturnType enum used by the trigger inputs.
strategies
[AdvisorStrategy!]!
The strategy recommendations. See AdvisorStrategy for the field shape.
byDomain
JSON!
Aggregate counts of strategies grouped by domain (for example retirement, income_shifting). The exact keys depend on which strategies the run produced.
bySavingsHorizon
JSON!
Aggregate counts of strategies grouped by savings horizon. Horizon keys match the SavingsHorizon enum values (CURRENT_YEAR, MULTI_YEAR, LIFETIME, EVENT_DRIVEN).
estimatedSavingsCentsByHorizon
JSON!
Estimated total savings in USD cents, keyed by savings horizon. Treat the values as estimates, not guarantees.
globalSummary
String!
A human-readable summary of the whole plan, suitable to show at the top of a plan view.
skillsApplied
AppliedSkills!
Which workspace and user skills were applied to this run. Each field is a list of skill names.
type AppliedSkills {
  workspace: [String!]!
  user: [String!]!
}

The AdvisorStrategy type

Each entry in AdvisorPlan.strategies is an AdvisorStrategy: one recommendation the advisor surfaced from the binder, with the evidence it built on, a step-by-step implementation plan, an optional savings estimate, and a status you control with setAdvisorStrategyStatus.
type AdvisorStrategy {
  id: ID!
  strategyId: String!
  domain: String!
  title: String!
  summary: String!
  applicabilityEvidence: String!
  sourceSubdocIds: [String!]!
  implementationPlan: [String!]!
  estimatedSavingsCents: Int
  savingsMethod: String
  savingsHorizon: SavingsHorizon!
  assumptions: String
  status: AdvisorStrategyStatus!
}

enum AdvisorStrategyStatus {
  PROPOSED
  SELECTED
  DISMISSED
}

enum SavingsHorizon {
  CURRENT_YEAR
  MULTI_YEAR
  LIFETIME
  EVENT_DRIVEN
}
id
ID!
The strategy’s stable row identifier for this plan.
strategyId
String!
The logical strategy key shared across runs and clients (for example accelerate_charitable_contributions). Use this, together with domain and runId, to address a strategy in setAdvisorStrategyStatus.
domain
String!
The strategy’s domain (for example retirement, income_shifting, entity_selection). Used together with strategyId to address a strategy.
title
String!
A short, human-readable strategy title.
summary
String!
A one-paragraph summary of the strategy and its expected effect.
applicabilityEvidence
String!
The evidence from the binder that made the advisor surface this strategy. Quote or paraphrase this when explaining a recommendation to a client.
sourceSubdocIds
[String!]!
The binder sub-document IDs the evidence was drawn from. Cross-reference these with the clients API to surface the source documents.
implementationPlan
[String!]!
Ordered, human-readable steps to implement the strategy.
estimatedSavingsCents
Int
Optional estimated tax savings in USD cents. null when the strategy does not produce a direct dollar estimate.
savingsMethod
String
How the estimate was computed, when estimatedSavingsCents is present.
savingsHorizon
SavingsHorizon!
When the savings are expected to land: CURRENT_YEAR, MULTI_YEAR, LIFETIME, or EVENT_DRIVEN.
assumptions
String
Free-text assumptions behind the estimate, when relevant.
status
AdvisorStrategyStatus!
The strategy’s workflow status: PROPOSED (the advisor surfaced it, no action taken), SELECTED (the firm accepted it), or DISMISSED (the firm rejected it). Drive it with setAdvisorStrategyStatus.

Start an advisor run

There are two trigger mutations for an advisor run. Both return a taskId you poll as a TAX_ADVISOR task, both require a workspaceToken, and both create a task whose result member is TaskTaxAdvisorResult. Pick the one that matches how you stage documents:
  • triggerTaxAdvisor (the ai subgraph) takes the client, return type, and tax year directly. Use it when the documents are already in the client’s binder.
  • initiateTaxAdvisor (the platform subgraph) also takes uploadIds, ingesting them into the binder in the same call. This is the mutation the Filed web app’s /planning route actually uses.
The Filed web app’s planning flow (src/routes/.../planning/) calls initiateTaxAdvisor, not triggerTaxAdvisor, because the in-app flow stages fresh uploads at the same moment it kicks off the run. Both mutations exist live and resolve to the same TAX_ADVISOR task type; pick the one that matches your ingestion path.

Trigger via triggerTaxAdvisor

triggerTaxAdvisor starts an advisor run for a client whose binder is already populated. It lives in the ai subgraph and requires a workspaceToken.
mutation TriggerTaxAdvisor($input: TriggerTaxAdvisorInput!) {
  triggerTaxAdvisor(input: $input) {
    taskId
  }
}

Input: TriggerTaxAdvisorInput

input TriggerTaxAdvisorInput {
  clientId: ID!
  returnType: ReturnType!
  taxYear: Int!
  skills: RunSkillSelectionInput
}

"""
Per-run selection of tenant (firm + user) skills. Omitted = all active skills
apply; an empty list censors every skill in that scope.
"""
input RunSkillSelectionInput {
  workspace: [String!]
  user: [String!]
}
clientId
ID!
required
The client to plan for.
returnType
ReturnType!
required
The return form: F1040, F1041, F1065, F1120, F1120S, or F990 (see clients).
taxYear
Int!
required
The tax year to plan for, for example 2025.
skills
RunSkillSelectionInput
Optional. Override which workspace and user skills apply to this run. Omit to apply all active skills; pass an empty list for a scope to censor every skill in that scope.

Returns: TriggerTaskResult

type TriggerTaskResult {
  taskId: ID!
}
taskId
ID!
The ID of the started TAX_ADVISOR task. Poll it until status is no longer RUNNING, then read advisorPlan and, optionally, the task’s result as TaskTaxAdvisorResult.
curl -X POST https://router.apps.filed.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
  -d '{
    "query": "mutation TriggerTaxAdvisor($input: TriggerTaxAdvisorInput!) { triggerTaxAdvisor(input: $input) { taskId } }",
    "variables": {
      "input": {
        "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
        "returnType": "F1040",
        "taxYear": 2025
      }
    }
  }'
{
  "data": {
    "triggerTaxAdvisor": {
      "taskId": "018f9c2c-4a1b-7e20-8b33-7c4d5e6f7080"
    }
  }
}

Trigger via initiateTaxAdvisor

initiateTaxAdvisor starts an advisor run and attaches already-staged uploads to the client’s binder in the same call. It lives in the platform subgraph and requires a workspaceToken. Stage the files first with the upload endpoint; this is the mutation the Filed web app uses for the /planning route.
mutation InitiateTaxAdvisor($input: InitiateTaxAdvisorInput!) {
  initiateTaxAdvisor(input: $input) {
    taskId
  }
}

Input: InitiateTaxAdvisorInput

input InitiateTaxAdvisorInput {
  clientId: ID!
  uploadIds: [String!]!
  skills: RunSkillSelectionInput
}
clientId
ID!
required
The client to plan for.
uploadIds
[String!]!
required
One or more upload IDs from the upload endpoint. The advisor ingests these into the client’s binder as part of starting the run.
skills
RunSkillSelectionInput
Optional. Override which workspace and user skills apply to this run. Same shape as triggerTaxAdvisor.

Returns: InitiateTaxAdvisorResult

type InitiateTaxAdvisorResult {
  taskId: ID
}
taskId
ID
The ID of the started TAX_ADVISOR task. Poll it until status is no longer RUNNING, then read advisorPlan. null when the ingestion accepted the upload but did not start a task; treat that as a soft error and retry.
curl -X POST https://router.apps.filed.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
  -d '{
    "query": "mutation InitiateTaxAdvisor($input: InitiateTaxAdvisorInput!) { initiateTaxAdvisor(input: $input) { taskId } }",
    "variables": {
      "input": {
        "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
        "uploadIds": ["018f9c2a-7b1e-7c3d-9a4e-2f6b1c8d0e5a"]
      }
    }
  }'
{
  "data": {
    "initiateTaxAdvisor": {
      "taskId": "018f9c2c-4a1b-7e20-8b33-7c4d5e6f7080"
    }
  }
}

Poll the task to completion

There is no task(id:) query. Poll the task you just started by listing the client’s TAX_ADVISOR tasks and reading the entry whose id matches the taskId returned above. The polling mechanics are documented on Tasks; the short version:
query PollTaxAdvisor($clientId: ID!) {
  me {
    ... on WorkspaceUser {
      workspace {
        clients(filters: { ids: [$clientId] }) {
          tasks(type: TAX_ADVISOR, limit: 1) {
            id
            status
            startedAt
            completedAt
            errorMessage
            subTasks {
              type
              status
            }
          }
        }
      }
    }
  }
}
curl -X POST https://router.apps.filed.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
  -d '{
    "query": "query PollTaxAdvisor($clientId: ID!) { me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { tasks(type: TAX_ADVISOR, limit: 1) { id status startedAt completedAt errorMessage subTasks { type status } } } } } } }",
    "variables": { "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c" }
  }'
{
  "data": {
    "me": {
      "workspace": {
        "clients": [
          {
            "tasks": [
              {
                "id": "018f9c2c-4a1b-7e20-8b33-7c4d5e6f7080",
                "status": "RUNNING",
                "startedAt": "2026-07-04T11:02:00.000Z",
                "completedAt": null,
                "errorMessage": null,
                "subTasks": [
                  { "type": "BUILD_ADVISOR_MANIFEST", "status": "COMPLETED" },
                  { "type": "RUN_ADVISOR_AGENT", "status": "RUNNING" }
                ]
              }
            ]
          }
        ]
      }
    }
  }
}
Poll on an interval (for example every few seconds) until status is no longer RUNNING. COMPLETED means the run succeeded and advisorPlan is now readable; FAILED means it did not, and errorMessage (plus subTasks[].errorMessage) explains which stage failed. Typical advisor sub-task types are BUILD_ADVISOR_MANIFEST, RUN_ADVISOR_AGENT, LOCATE_ADVISOR_REFERENCES, and EXPORT_ADVISOR.

Read the plan

Read the plan through Client.advisorPlan. There is no top-level advisorPlan query; reach it through me { ... on WorkspaceUser { workspace { clients(...) { advisorPlan } } } } (see clients). Call it without a runId to read the client’s current plan, or pass the runId from a specific task to read that run’s plan.
query ClientAdvisorPlan($clientId: ID!, $runId: ID) {
  me {
    ... on WorkspaceUser {
      workspace {
        clients(filters: { ids: [$clientId] }) {
          id
          advisorPlan(runId: $runId) {
            runId
            taxYear
            returnType
            globalSummary
            estimatedSavingsCentsByHorizon
            skillsApplied {
              workspace
              user
            }
            strategies {
              id
              strategyId
              domain
              title
              summary
              applicabilityEvidence
              sourceSubdocIds
              implementationPlan
              estimatedSavingsCents
              savingsMethod
              savingsHorizon
              assumptions
              status
            }
          }
        }
      }
    }
  }
}
curl -X POST https://router.apps.filed.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
  -d '{
    "query": "query ClientAdvisorPlan($clientId: ID!) { me { ... on WorkspaceUser { workspace { clients(filters: { ids: [$clientId] }) { id advisorPlan { runId taxYear returnType globalSummary estimatedSavingsCentsByHorizon skillsApplied { workspace user } strategies { id strategyId domain title summary applicabilityEvidence sourceSubdocIds implementationPlan estimatedSavingsCents savingsMethod savingsHorizon assumptions status } } } } } } }",
    "variables": { "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c" }
  }'
{
  "data": {
    "me": {
      "workspace": {
        "clients": [
          {
            "id": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
            "advisorPlan": {
              "runId": "018f9c2c-4a1b-7e20-8b33-7c4d5e6f7080",
              "taxYear": 2025,
              "returnType": "F1040",
              "globalSummary": "5 strategies surfaced across retirement, income shifting, and entity selection. Estimated 3-year savings of $18,400.",
              "estimatedSavingsCentsByHorizon": {
                "CURRENT_YEAR": 420000,
                "MULTI_YEAR": 1840000,
                "LIFETIME": 0,
                "EVENT_DRIVEN": 0
              },
              "skillsApplied": {
                "workspace": ["advisor_evidence_qa"],
                "user": []
              },
              "strategies": [
                {
                  "id": "019a1b2c-3d4e-7f10-aa12-1c2d3e4f5060",
                  "strategyId": "accelerate_charitable_contributions",
                  "domain": "charitable",
                  "title": "Bunch charitable contributions into 2025",
                  "summary": "Combine two years of charitable giving into 2025 to exceed the standard deduction and itemize this year.",
                  "applicabilityEvidence": "Client has donated $4,200 and $4,800 in each of the last two years per binder sub-doc sd_8842 and sd_8843.",
                  "sourceSubdocIds": ["sd_8842", "sd_8843"],
                  "implementationPlan": [
                    "Confirm intended 2025 and 2026 giving totals with the client.",
                    "Move 2026 contributions into December 2025.",
                    "Rebuild the itemized deduction worksheet."
                  ],
                  "estimatedSavingsCents": 82000,
                  "savingsMethod": "marginal_rate_x_deduction_delta",
                  "savingsHorizon": "CURRENT_YEAR",
                  "assumptions": "Marginal rate stays at 24%; no further AGI-limit changes.",
                  "status": "PROPOSED"
                },
                {
                  "id": "019a1b2c-3d4e-7f10-aa12-1c2d3e4f5061",
                  "strategyId": "roth_conversion_window",
                  "domain": "retirement",
                  "title": "Roth convert up to the 24% bracket cap",
                  "summary": "Convert traditional IRA funds to Roth up to the top of the 24% bracket this year.",
                  "applicabilityEvidence": "Client has $120,000 in traditional IRA assets and taxable income is temporarily lower this year per sd_7101.",
                  "sourceSubdocIds": ["sd_7101"],
                  "implementationPlan": [
                    "Model the bracket headroom for the current year.",
                    "Convert up to the cap.",
                    "Withhold or pay estimated tax on the conversion."
                  ],
                  "estimatedSavingsCents": null,
                  "savingsMethod": null,
                  "savingsHorizon": "MULTI_YEAR",
                  "assumptions": "Future marginal rate is 32% or higher.",
                  "status": "PROPOSED"
                }
              ]
            }
          }
        ]
      }
    }
  }
}
advisorPlan returns null while the run is still RUNNING, or when the client has no advisor run yet. Treat null as “no plan to show”, and keep polling the task until status is COMPLETED before re-reading.

Update a strategy’s status

setAdvisorStrategyStatus moves a strategy between PROPOSED, SELECTED, and DISMISSED. It lives in the ai subgraph and requires a workspaceToken. Identify the strategy with clientId plus the strategy’s domain and strategyId (both from AdvisorStrategy), and pass the plan’s runId so the status change is recorded against the right run.
mutation SetAdvisorStrategyStatus($input: SetAdvisorStrategyStatusInput!) {
  setAdvisorStrategyStatus(input: $input) {
    id
    status
  }
}

Input: SetAdvisorStrategyStatusInput

input SetAdvisorStrategyStatusInput {
  clientId: ID!
  domain: String!
  strategyId: String!
  status: AdvisorStrategyStatus!
  runId: ID
}

enum AdvisorStrategyStatus {
  PROPOSED
  SELECTED
  DISMISSED
}
clientId
ID!
required
The client the plan belongs to.
domain
String!
required
The strategy’s domain (from AdvisorStrategy.domain).
strategyId
String!
required
The strategy’s logical key (from AdvisorStrategy.strategyId), not the row id.
status
AdvisorStrategyStatus!
required
The new status: PROPOSED, SELECTED, or DISMISSED. Use SELECTED for strategies the firm accepts, DISMISSED for those it rejects, and PROPOSED to revert either back to the advisor’s original state.
runId
ID
The plan’s runId (from AdvisorPlan.runId). Optional in the schema but recommended: it pins the status change to a specific run, which matters when a client has more than one advisor run on file.

Returns: AdvisorStrategy

The mutation returns the updated AdvisorStrategy, typically just id and status. The full type is documented above.
id
ID!
The strategy row identifier that was updated.
status
AdvisorStrategyStatus!
The strategy’s new status.
curl -X POST https://router.apps.filed.com/graphql \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_WORKSPACE_TOKEN" \
  -d '{
    "query": "mutation SetAdvisorStrategyStatus($input: SetAdvisorStrategyStatusInput!) { setAdvisorStrategyStatus(input: $input) { id status } }",
    "variables": {
      "input": {
        "clientId": "018f9c2a-3d5f-7a10-b2c4-9e8d7f6a5b4c",
        "domain": "charitable",
        "strategyId": "accelerate_charitable_contributions",
        "runId": "018f9c2c-4a1b-7e20-8b33-7c4d5e6f7080",
        "status": "SELECTED"
      }
    }
  }'
{
  "data": {
    "setAdvisorStrategyStatus": {
      "id": "019a1b2c-3d4e-7f10-aa12-1c2d3e4f5060",
      "status": "SELECTED"
    }
  }
}
After a successful setAdvisorStrategyStatus, re-read advisorPlan to get the refreshed strategies[].status values. The Filed web app does this by including ClientAdvisorPlan in the mutation’s refetchQueries.

Task result member: TaskTaxAdvisorResult

When a TAX_ADVISOR task reaches status: COMPLETED, its result field resolves to TaskTaxAdvisorResult. This is the same data the advisorPlan field exposes as AdvisorPlan, just delivered through the task poll. Most callers prefer advisorPlan for its richer strategies list; TaskTaxAdvisorResult is useful when you are already polling the task and want the high-level summary in the same response.
type TaskTaxAdvisorResult {
  taxYear: Int!
  returnType: ReturnType!
  summary: String!
  strategyTotal: Int!
  byDomain: JSON!
  bySavingsHorizon: JSON!
  estimatedSavingsCentsByHorizon: JSON!
}
Select it with an inline fragment on the task’s result, alongside the TaskUnknownResult fallback:
query TaxAdvisorResult($clientId: ID!) {
  me {
    ... on WorkspaceUser {
      workspace {
        clients(filters: { ids: [$clientId] }) {
          tasks(type: TAX_ADVISOR, limit: 1) {
            id
            status
            completedAt
            result {
              __typename
              ... on TaskTaxAdvisorResult {
                taxYear
                returnType
                summary
                strategyTotal
                byDomain
                bySavingsHorizon
                estimatedSavingsCentsByHorizon
              }
              ... on TaskUnknownResult {
                message
              }
            }
          }
        }
      }
    }
  }
}
TaskTaxAdvisorResult is one member of the TaskResult union. The other tax members are TaskTaxPrepResult (returned for TAX_PREP tasks, see tax prep) and TaskTaxReviewResult (returned for TAX_REVIEW tasks). See Tasks, task result for the full union and the inline-fragment pattern used to select it.