---
title: "Write Setup Plan"
method: PUT
path: "/api/autopilot/setup-plan"
tags: ["autopilot"]
---

# Write Setup Plan

`PUT /api/autopilot/setup-plan`

## Request

- Base URL: `https://api.taxcompass.it` (the document declares no server; this is the origin it was published from)
- URL: `https://api.taxcompass.it/api/autopilot/setup-plan`
- Auth: HTTP bearer

Save the intake answers for this user, replacing whatever was there.

A whole-object PUT rather than the profile's partial patch, because the two
are different shapes of edit: the fiscal profile is a wizard that sends one
step at a time, while this is one form the user submits complete. Merging here
would make "I cleared that optional box" mean "leave it alone", which is the
wrong reading for every optional field on the intake.

Validated against ``PlanRequest`` — the same model ``POST /api/plan`` takes —
so nothing can be stored that the plan builder would then refuse to read. That
is the whole of the validation: this row is answers, not a tax position, so
there is no scheme or regime gate like the profile's.

## Request body

- PlanRequest: Structured intake answers → one-shot Setup Plan. Five of the Italy pack's six ``critical`` slots are required here; the rest are optional but improve the cross-border risk flags + accountant match. ``regulated_profession`` is the exception — it is **derived** from the activity, not asked (see the field comment), so the intake never poses a question a foreign founder can't answer.
  - `locale` 'en' | 'pl' | 'it' | 'de' | 'fr' | 'es'
  - `current_residency` string, required
  - `target_setup` 'moving_to_italy' | 'staying_abroad' | 'already_in_italy', required
  - `expected_annual_revenue_eur` number, required
  - `activity_description` string, required
  - `impatriate_eligibility` boolean, required
  - `regulated_profession` boolean, nullable
  - `expected_annual_costs_eur` number, nullable
  - `business_type` 'professionista' | 'artigiano' | 'commerciante', nullable
  - `passport_country` string, nullable
  - `permit_status` 'eu_no_permit_needed' | 'has_self_employment' | 'has_other_permit' | 'needs_permit' | 'unsure', nullable
  - `ateco_chosen` string, nullable
  - `single_client_is_employer` boolean, nullable
  - `existing_business` string, nullable
  - `client_situation` string, nullable
  - `family_situation` string, nullable
  - `pension_situation` string, nullable
  - `prior_employment_income_eur` number, nullable
  - `social_security_coverage` 'none' | 'eu_a1' | 'non_eu_totalization' | 'unsure', nullable
  - `days_in_italy_this_year` integer, nullable
  - `urgency` string, nullable

## Response `200`

Successful Response

- SetupPlanRead: ``GET/PUT /api/autopilot/setup-plan`` — the Setup Plan's saved *inputs*. The inputs, not the rendered plan, and the distinction is the whole design: :func:`app.api.plan.build_plan` is a pure function of a :class:`PlanRequest` (no LLM, no DB, no HTTP), so the plan can always be recomputed from what is stored here — whereas a stored *output* would go on disagreeing with a recomputed one after the first tax-engine change, and there would be no way to tell which of the two the user had actually read. ``updated_at`` is the DB clock, and it is here for the same reason ``RedditiSessionRead`` carries one: this row is written from whichever device the user happens to be on, and "whose copy is newer" is a question the client has to be able to answer without guessing.
  - `inputs` PlanRequest, required: Structured intake answers → one-shot Setup Plan. Five of the Italy pack's six ``critical`` slots are required here; the rest are optional but improve the cross-border risk flags + accountant match. ``regulated_profession`` is the exception — it is **derived** from the activity, not asked (see the field comment), so the intake never poses a question a foreign founder can't answer.
    - `locale` 'en' | 'pl' | 'it' | 'de' | 'fr' | 'es'
    - `current_residency` string, required
    - `target_setup` 'moving_to_italy' | 'staying_abroad' | 'already_in_italy', required
    - `expected_annual_revenue_eur` number, required
    - `activity_description` string, required
    - `impatriate_eligibility` boolean, required
    - `regulated_profession` boolean, nullable
    - `expected_annual_costs_eur` number, nullable
    - `business_type` 'professionista' | 'artigiano' | 'commerciante', nullable
    - `passport_country` string, nullable
    - `permit_status` 'eu_no_permit_needed' | 'has_self_employment' | 'has_other_permit' | 'needs_permit' | 'unsure', nullable
    - `ateco_chosen` string, nullable
    - `single_client_is_employer` boolean, nullable
    - `existing_business` string, nullable
    - `client_situation` string, nullable
    - `family_situation` string, nullable
    - `pension_situation` string, nullable
    - `prior_employment_income_eur` number, nullable
    - `social_security_coverage` 'none' | 'eu_a1' | 'non_eu_totalization' | 'unsure', nullable
    - `days_in_italy_this_year` integer, nullable
    - `urgency` string, nullable
  - `updated_at` string, date-time, nullable

## Other responses

- `422`: Validation Error

---

[API](https://skmtc.dev/taxcompass/apis/taxcompass-api.md) · [All operations](https://skmtc.dev/taxcompass/apis/taxcompass-api/llms.txt) · [OpenAPI document](https://skmtc.dev/taxcompass/apis/taxcompass-api/revisions/fef5fc6a9b91?raw)
