{"openapi":"3.1.0","info":{"title":"POST /api/v1/people/import/preview","version":"1.0.0","description":"Preview a roster import"},"servers":[{"url":"https://api.ondayzero.com","description":"Production"}],"paths":{"/api/v1/people/import/preview":{"post":{"tags":["people"],"summary":"Preview a roster import","description":"Upload a CSV or XLSX roster. Finds the header row, maps columns by name, matches each line to an existing person by name, alias or counterparty name, and returns what applying would do plus a plan_hash. Nothing is written.","operationId":"people_import_preview","parameters":[{"name":"authorization","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Authorization"}},{"name":"x-business-id","in":"header","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"X-Business-Id"}}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"$ref":"#/components/schemas/Body_people_import_preview_api_v1_people_import_preview_post"}}}},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuccessEnvelope_PeopleImportPreviewResponse_"}}}},"400":{"description":"Bad Request - Invalid input","content":{"application/json":{"example":{"error":"validation_error","message":"Validation failed: due_date: Input should be a valid date","errors":{"due_date":"Input should be a valid date"},"code":"GEN_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Missing or expired token, an MCP OAuth token used against the REST API, or no access to this business","content":{"application/json":{"example":{"error":"unauthorized","message":"Authentication required","code":"AUTH_001","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Insufficient permissions","content":{"application/json":{"example":{"error":"forbidden","message":"You don't have permission to access this resource","code":"AUTH_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"error":"validation_error","message":"Validation failed: amount: Input should be a valid integer","errors":{"amount":"Input should be a valid integer"},"code":"GEN_002","request_id":"3f0e7c2a-9b4d-4e1a-8c6f-2d5b7a1e9c30"}}}}},"x-fastapi-operation-id":"people_import_preview_api_v1_people_import_preview_post"}}},"components":{"schemas":{"Body_people_import_preview_api_v1_people_import_preview_post":{"properties":{"file":{"type":"string","contentMediaType":"application/octet-stream","title":"File","description":"CSV or XLSX roster"}},"type":"object","required":["file"],"title":"Body_people_import_preview_api_v1_people_import_preview_post"},"PeopleImportPreviewResponse":{"properties":{"filename":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Filename"},"header_row":{"type":"integer","title":"Header Row","description":"1-based line the headers were on."},"columns_mapped":{"additionalProperties":{"type":"string"},"type":"object","title":"Columns Mapped","description":"Header -> roster field."},"columns_ignored":{"items":{"type":"string"},"type":"array","title":"Columns Ignored"},"rows":{"items":{"$ref":"#/components/schemas/PersonImportRow"},"type":"array","title":"Rows"},"counts":{"additionalProperties":{"type":"integer"},"type":"object","title":"Counts","description":"create / update / skip / issues."},"plan_hash":{"type":"string","title":"Plan Hash"}},"type":"object","required":["header_row","rows","plan_hash"],"title":"PeopleImportPreviewResponse"},"PersonFields":{"properties":{"display_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Display Name"},"first_name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"First Name"},"last_name":{"anyOf":[{"type":"string","maxLength":120},{"type":"null"}],"title":"Last Name"},"email":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Email"},"job_title":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Job Title","description":"Position."},"department":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Department"},"manager_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Manager Id","description":"Another person's id."},"manager_name":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Manager Name","description":"Manager by name; resolved to manager_id against the roster (exact name, alias or counterparty spelling). Not stored."},"status":{"anyOf":[{"type":"string","enum":["active","new","notice","inactive","terminated"]},{"type":"null"}],"title":"Status"},"employment_type":{"anyOf":[{"type":"string","enum":["w2","contractor_us","contractor_offshore"]},{"type":"null"}],"title":"Employment Type"},"pay_type":{"anyOf":[{"type":"string","enum":["flat","hourly"]},{"type":"null"}],"title":"Pay Type"},"monthly_rate_cents":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Monthly Rate Cents"},"hourly_rate_cents":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Hourly Rate Cents"},"expected_monthly_hours":{"anyOf":[{"type":"number","maximum":744.0,"minimum":0.0},{"type":"null"}],"title":"Expected Monthly Hours"},"state":{"anyOf":[{"type":"string","maxLength":32},{"type":"null"}],"title":"State"},"remote":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Remote"},"business_unit":{"anyOf":[{"type":"string","maxLength":100},{"type":"null"}],"title":"Business Unit"},"billing_entity":{"anyOf":[{"type":"string","maxLength":255},{"type":"null"}],"title":"Billing Entity","description":"Company the person bills through."},"aliases":{"anyOf":[{"items":{"type":"string"},"type":"array"},{"type":"null"}],"title":"Aliases","description":"Other names the person is paid under."},"counterparty_name":{"anyOf":[{"type":"string","maxLength":500},{"type":"null"}],"title":"Counterparty Name","description":"The counterparty name used in the books."},"vendor_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Vendor Id"},"has_insurance":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Has Insurance"},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date"},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date"},"termination_reason":{"anyOf":[{"type":"string","enum":["termed","resigned"]},{"type":"null"}],"title":"Termination Reason"},"equipment_returned":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Equipment Returned"},"final_pay_cents":{"anyOf":[{"type":"integer","minimum":0.0},{"type":"null"}],"title":"Final Pay Cents"},"notes":{"anyOf":[{"type":"string","maxLength":4000},{"type":"null"}],"title":"Notes"}},"type":"object","title":"PersonFields","description":"Everything a person row carries, all optional (shared by create/update)."},"PersonImportRow":{"properties":{"row_number":{"type":"integer","title":"Row Number","description":"1-based line in the file."},"action":{"type":"string","enum":["create","update","skip"],"title":"Action"},"person_id":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Person Id","description":"The existing person this row updates, if any."},"matched_on":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Matched On","description":"display_name, alias or counterparty_name."},"data":{"anyOf":[{"$ref":"#/components/schemas/PersonFields"},{"type":"null"}],"description":"The fields the file set for this person; a null field was not in the file and leaves an existing value alone."},"changes":{"items":{"type":"string"},"type":"array","title":"Changes","description":"Fields an update would change."},"issues":{"items":{"type":"string"},"type":"array","title":"Issues"}},"type":"object","required":["row_number","action"],"title":"PersonImportRow","description":"One input line of a roster file and what applying it would do."},"SuccessEnvelope_PeopleImportPreviewResponse_":{"properties":{"success":{"type":"boolean","title":"Success","default":true},"message":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Message"},"code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Code"},"data":{"anyOf":[{"$ref":"#/components/schemas/PeopleImportPreviewResponse"},{"type":"null"}]}},"additionalProperties":true,"type":"object","title":"SuccessEnvelope[PeopleImportPreviewResponse]"},"ErrorResponse":{"title":"ErrorResponse","type":"object","description":"Envelope returned by every non-2xx response. Branch on `code` (stable) rather than `message` (human-readable, may change).","required":["error","message","code"],"properties":{"error":{"type":"string","description":"Error category: `validation_error`, `unauthorized`, `forbidden`, `not_found`, `conflict`, `rate_limited`, `server_error`, or `service_unavailable`."},"message":{"type":"string","description":"Human-readable explanation, safe to show to end users."},"code":{"type":"string","description":"Stable machine-readable code in `CATEGORY_NNN` form (e.g. `NOT_FOUND_006`, `AUTH_010`, `GEN_002`)."},"request_id":{"type":"string","description":"Correlation id for support requests. Echoes the `x-request-id` request header when one was supplied."},"errors":{"type":"object","additionalProperties":{"type":"string"},"description":"Field-level validation messages keyed by field name. Present on 400/422 validation failures only."}}}},"securitySchemes":{"BearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"API Token","description":"API token authentication. Format: `Bearer dz_...`"}}},"security":[{"BearerAuth":[]}]}