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: 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}/homeBody 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 shape
Each entry in a stage’squestions 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 viaquestionRef.type(string, required) — one ofTEXT,SINGLE_SELECT,MULTI_SELECT,BOOLEAN,SCALE,NPS,RATING, orNUMERIC. Determines the shape ofoptionsand 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. Iftrue, the backfill must include an answered value for every participant pair in the stage.formattingInstructions(string, optional) —TEXTquestions only; formatting guidance shown to the respondent.options(object) — type-specific configuration. Required for everything exceptTEXT: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’sparticipants 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. ForSELFstages, passnull(it defaults to the actor) or the actor’s own id. ForMANAGER,UPWARD, andPEERstages, it’s required and must differ fromactingEmployeeId.
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’sanswers array is one submitted answer:
actingEmployeeId(string, required) — the employee who submitted the answer.targetEmployeeId(string, required) — the reviewee the answer is about. Together withactingEmployeeId, this must match one of the stage’s participant pairs.questionRef(string, required) — a questionreffrom this same stage.answer(object, required) — the submitted value; see Answer value shape.submittedAt(string, optional) — historical submission time.
Answer value shape
Theanswer 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 }(valuematches an option’svalue)MULTI_SELECT→{ "value": string[] | null }BOOLEAN→{ "value": boolean, "freeText": string | null }SCALE/NPS/RATING/NUMERIC→{ "value": number | null }
Idempotency
Pass anidempotencyKey so retries can’t create duplicate cycles. When Windmill sees a key it has processed before (for your company):
- Same key, same body — returns
201with the originaljobIdandcycleId. 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.
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
Returns201 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:
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):