Skip to main content
Imports one completed historical performance-review cycle into Windmill: the cycle itself, its reviewees, its stages with questions and submitted answers, and optionally calibration data and manager-review releases. Use it to bring reviews you ran in a previous tool (or on spreadsheets) into Windmill, so managers and employees see their full review history in one place.
The call validates the request schema and its references, then runs the import asynchronously. The asynchronous import can find additional errors. A successful request returns 201 with a jobId and the pre-assigned cycleId — poll Get backfill status with the jobId to follow the import through to completion. Once the cycle is imported, you can read it back out with Export cycle packets.

Authorization

This endpoint enforces two rules beyond a valid API key:
Admins and HR Admins only. The member who created the API key must hold the performance_reviews.manage_cycles capability, which the Admin and HR Admin roles grant. This is a company-wide role check — unlike the packets export, it isn’t tied to admin access on any particular cycle. A valid key without the capability returns 403 Forbidden.
All employee ids in the payload must be existing Windmill employees in your company (and in your primary org). Import your people first — via an HRIS integration or the Dashboard — then backfill their reviews.

Path parameters

string
required
Your company’s slug. Find it in your Dashboard URL, right after /s/:https://app.gowindmill.com/s/{companySlug}/home

Body parameters

The payload describes the entire historical cycle in one document. Stages, questions, and answers are wired together with refs — short consumer-local identifiers you choose (like "self" or "q-overall-rating"). Refs only need to be unique within their scope (stage refs within the request, question refs within their stage) and never leave the payload; Windmill mints its own ids during import.
string
Optional caller-provided idempotency key for this import. Strongly recommended — see Idempotency. Without it, every call creates a new job and a new cycle.
object
required
Metadata for the cycle to import.
string[]
default:"[]"
Employee ids granted write access to the imported cycle (cycle admins).
object[]
required
The employees enrolled in the cycle as reviewees. At least one is required, each employee may appear only once, and every reviewee must be the target of at least one stage. Manager relationships cannot contain a circular reporting chain.
object[]
required
The cycle’s stages, in payload order. At least one is required. Each stage is discriminated by type: SELF, MANAGER, UPWARD, or PEER. A request can include at most one MANAGER stage and one UPWARD stage; a reviewee can appear in at most one SELF stage and one PEER stage.
object
Optional calibration data — overrides applied to manager-stage answers during a historical calibration process.
object[]
default:"[]"
Historical manager-review packet releases — what was shared back with each reviewee. At most one entry per reviewee, and all question refs must point at MANAGER-stage questions.
Question visibility is cycle-wide, not per release. Whether a question is hidden from employees is configured once per question for the entire cycle, so every release that includes a given question must agree on whether it appears in hiddenFromEmployeeQuestionRefs. This consistency check spans releases of every statusPENDING releases count just as much as ENABLED and SHARED ones. If two releases disagree about a question’s visibility, the request fails with 400 Bad Request.

Question shape

Each entry in a stage’s questions array is a question definition plus a ref:
  • ref (string, required) — consumer-local question identifier, unique within the stage. Stage answers, calibration entries, and releases point back at it via questionRef.
  • type (string, required) — one of TEXT, SINGLE_SELECT, MULTI_SELECT, BOOLEAN, SCALE, NPS, RATING, or NUMERIC. Determines the shape of options and of matching answers.
  • label (string, required) — the question prompt shown to the respondent.
  • description (string, required) — supporting context beneath the prompt; pass an empty string when there is none.
  • helpText (string, optional) — tooltip/help text.
  • required (boolean, required) — whether the question had to be answered. If true, the backfill must include an answered value for every participant pair in the stage.
  • formattingInstructions (string, optional) — TEXT questions only; formatting guidance shown to the respondent.
  • options (object) — type-specific configuration. Required for everything except TEXT:
    • TEXT → optional { "maxWordCount": number }.
    • SINGLE_SELECT{ "options": [{ "value": string, "label": string, "description"?: string }], "enableFreeText"?: boolean } — at least one option.
    • MULTI_SELECT{ "choices": [{ "value": string, "label": string }], "minSelections"?: number, "maxSelections"?: number }.
    • BOOLEAN{ "defaultValue": boolean | null, "freeTextIfTrue"?: boolean, "freeTextIfFalse"?: boolean }.
    • SCALE{ "min"?: number, "max"?: number, "step"?: number, "integer"?: boolean, "labels"?: { "low"?: string, "high"?: string } }.
    • NPS{ "labels"?: { "low"?: string, "high"?: string } }.
    • RATING{ "points": [{ "value": number, "label"?: string }] } — at least two points.
    • NUMERIC{ "min"?: number | null, "max"?: number | null, "integer"?: boolean, "unit"?: string | null }.

Participant shape

Each entry in a stage’s participants array declares one actor-to-reviewee assignment:
  • actingEmployeeId (string, required) — the employee who performed the stage work (the self-reviewer, manager, report, or peer).
  • revieweeEmployeeId (string | null, required) — the employee being reviewed. For SELF stages, pass null (it defaults to the actor) or the actor’s own id. For MANAGER, UPWARD, and PEER stages, it’s required and must differ from actingEmployeeId.
Duplicate (actor, reviewee) pairs aren’t allowed within a stage. A MANAGER stage allows only one participant per reviewee, and an UPWARD stage allows each actor to appear only once. Every reviewee targeted here must also be declared in the top-level reviewees array.

Answer shape

Each entry in a stage’s answers array is one submitted answer:
  • actingEmployeeId (string, required) — the employee who submitted the answer.
  • targetEmployeeId (string, required) — the reviewee the answer is about. Together with actingEmployeeId, this must match one of the stage’s participant pairs.
  • questionRef (string, required) — a question ref from this same stage.
  • answer (object, required) — the submitted value; see Answer value shape.
  • submittedAt (string, optional) — historical submission time.
At most one answer per (actor, reviewee, question) triple.

Answer value shape

The answer object’s shape depends on the referenced question’s type, and each value is validated against the question’s options (select values must match declared options, scale/numeric values must respect bounds, NPS must be 0–10, ratings must match a defined point value):
  • TEXT{ "value": string | null }
  • SINGLE_SELECT{ "value": string | null, "freeText": string | null } (value matches an option’s value)
  • MULTI_SELECT{ "value": string[] | null }
  • BOOLEAN{ "value": boolean, "freeText": string | null }
  • SCALE / NPS / RATING / NUMERIC{ "value": number | null }

Idempotency

Pass an idempotencyKey so retries can’t create duplicate cycles. When Windmill sees a key it has processed before (for your company):
  • Same key, same body — returns 201 with the original jobId and cycleId. If the earlier attempt never started importing, it’s re-queued; if it’s already importing, completed, or failed, nothing re-runs.
  • Same key, different body — returns 409 Conflict. One key maps to exactly one payload.
  • To re-run a failed import, send a new request with a different idempotencyKey — a failed job isn’t retried under its original key.
If the request validates and saves but cannot be queued, the endpoint returns 503 Service Unavailable. If the request has an idempotencyKey, retry with the same key. If the request has no key, do not retry it because a retry can create a duplicate cycle. Contact Windmill Support.

Response

Returns 201 Created after validation. Windmill queues new and VALIDATED jobs. An idempotent replay of an IMPORTING, COMPLETED, or FAILED job returns the original IDs but does not queue the job again.
string
required
Id of the import job tracking this backfill. Poll it with Get backfill status.
string
required
The pre-assigned id of the cycle being imported. The cycle materializes in Windmill as the import runs; treat it as final only once the job reports COMPLETED.

Errors

Most error responses carry a single code, message, and requestId:
Validation failures (400) use the code VALIDATION_ERROR and add a details array with one entry per issue. Each entry’s path locates the offending field in your payload (array indices included):