{"openapi":"3.0.0","info":{"title":"Purple Suite ATS API","version":"1.0.0","description":"\n\n---\n\n**Acting as a user (`X-PS-Impersonate-User`).** Every endpoint accepts an optional `X-PS-Impersonate-User` request header whose value is the email of the instance user to act as. The effective user resolves in this order: the header if present, else the instance's configured default MCP user, else none. User-scoped paths additionally accept the literal `me` in place of an email, and Graph-style apps also serve `/me/…` twins of their `/users/{email}/…` paths that resolve the same way. This header is honored by all operations but is intentionally omitted from each operation's parameter list so it is not surfaced as a per-tool argument by MCP clients; see the `ImpersonateUser` entry under `components.parameters`."},"servers":[{"url":"https://marketplace.moveworks.com/api/purple-suite/ats"}],"security":[{"patAuth":[]}],"components":{"securitySchemes":{"patAuth":{"type":"http","scheme":"bearer"}},"parameters":{"InstanceId":{"in":"header","name":"X-Instance-ID","required":true,"schema":{"type":"string"}},"ImpersonateUser":{"in":"header","name":"X-PS-Impersonate-User","required":false,"schema":{"type":"string","format":"email"},"description":"Optional. Act as a specific user in the target instance — the value is that user's email (must exist in the instance). Resolution order for the effective user: (1) this header, if set; (2) the instance's configured default MCP user; (3) otherwise no user is bound. User-scoped paths (e.g. /users/{email}/…) also accept the literal \"me\", and Graph-style apps serve /me/… twins of those paths (Graph: /me ≡ /users/{signed-in-user}) — both resolve to whatever this header (or the default MCP user) points at. An explicit path email always wins over the header. The unified MCP server does not send this header — it relies on the per-instance default — so it is primarily for direct REST/HTTP callers."}},"schemas":{"Requisition":{"type":"object","properties":{"id":{"type":"string","readOnly":true},"title":{"type":"string","example":"Software Engineer"},"department":{"type":"string","enum":["Engineering","Sales","Marketing","Finance","HR","Product"],"example":"Engineering"},"status":{"type":"string","enum":["open","in_progress","on_hold","closed","filled","cancelled"],"example":"open"},"headcount":{"type":"integer","example":1},"hiringManager":{"type":"string","nullable":true,"example":"alice@example.com","description":"Hiring manager email. Alias: hiring_manager (snake_case accepted)."},"createdBy":{"type":"string","nullable":true,"example":"recruiter@example.com","description":"Email of the user who created this requisition. Alias: created_by (snake_case accepted)."},"responsibilities":{"type":"string","nullable":true,"example":"Design, build, and maintain backend services and APIs; participate in code review and on-call rotations.","description":"Short prose description of what the role does."},"required_skills":{"type":"array","description":"Skills the role requires, each with a target proficiency. Alias: requiredSkills (camelCase accepted).","items":{"type":"object","properties":{"skill":{"type":"string","example":"Python"},"proficiency":{"type":"string","enum":["beginner","intermediate","advanced"],"example":"advanced"}}}},"level":{"type":"string","nullable":true,"example":"Sr","description":"Seniority label — e.g. IC1/IC2/IC3/Sr/Manager/Director, or any free-text level."},"target_role_for":{"type":"array","description":"IDs of requisitions/roles this one is a common next step from (career pathing). Alias: targetRoleFor (camelCase accepted).","items":{"type":"string","example":"REQ-0001"}},"openedAt":{"type":"string","format":"date-time","readOnly":true},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"Candidate":{"type":"object","properties":{"id":{"type":"string","readOnly":true},"firstName":{"type":"string","example":"Jordan","description":"Alias: first_name (snake_case accepted)."},"lastName":{"type":"string","example":"Patel","description":"Alias: last_name (snake_case accepted)."},"email":{"type":"string","format":"email","example":"jordan.patel0@email.com"},"phone":{"type":"string","nullable":true,"example":"+1-415-555-1234"},"requisitionId":{"type":"string","nullable":true,"example":"REQ-0001","description":"Alias: requisition_id (snake_case accepted)."},"status":{"type":"string","enum":["applied","screening","phone_screen","interview","offer","hired","rejected","withdrawn"],"example":"applied","description":"Current pipeline stage. Alias: stage (accepted on create/update)."},"source":{"type":"string","nullable":true,"enum":["LinkedIn","Referral","Indeed","Company Website","Glassdoor"],"example":"LinkedIn"},"resumeUrl":{"type":"string","nullable":true,"example":"https://storage.example.com/resumes/jordan-patel.pdf","description":"Alias: resume_url (snake_case accepted)."},"appliedAt":{"type":"string","format":"date-time","readOnly":true},"stageHistory":{"type":"array","readOnly":true,"items":{"type":"object","properties":{"status":{"type":"string"},"notes":{"type":"string","nullable":true},"recordedAt":{"type":"string","format":"date-time"}}}},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"Interview":{"type":"object","properties":{"id":{"type":"string","readOnly":true},"candidateId":{"type":"string","nullable":true,"example":"CAND-0001","description":"Alias: candidate_id (snake_case accepted)."},"requisitionId":{"type":"string","nullable":true,"example":"REQ-0001","description":"Alias: requisition_id (snake_case accepted)."},"interviewer":{"type":"string","nullable":true,"example":"alice@example.com"},"scheduledAt":{"type":"string","format":"date-time","nullable":true,"example":"2026-06-10T14:00:00.000Z","description":"Alias: scheduled_at (snake_case accepted)."},"type":{"type":"string","nullable":true,"enum":["phone_screen","technical","behavioral","panel","final"],"example":"technical"},"status":{"type":"string","enum":["scheduled","completed","cancelled","no_show"],"example":"scheduled"},"feedback":{"type":"string","nullable":true,"example":"Strong problem-solving skills, good communication."},"rating":{"type":"integer","nullable":true,"minimum":1,"maximum":5,"example":4},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"Offer":{"type":"object","properties":{"id":{"type":"string","readOnly":true},"candidateId":{"type":"string","nullable":true,"example":"CAND-0001","description":"Alias: candidate_id (snake_case accepted)."},"requisitionId":{"type":"string","nullable":true,"example":"REQ-0001","description":"Alias: requisition_id (snake_case accepted)."},"baseSalary":{"type":"number","nullable":true,"example":120000,"description":"Base salary amount. Alias: salary (accepted on create/update)."},"equity":{"type":"string","nullable":true,"example":"0.25%"},"startDate":{"type":"string","format":"date-time","nullable":true,"example":"2026-07-01T00:00:00.000Z","description":"Alias: start_date (snake_case accepted)."},"status":{"type":"string","enum":["draft","sent","accepted","declined","expired","withdrawn"],"example":"draft"},"sentAt":{"type":"string","format":"date-time","nullable":true,"example":"2026-06-01T00:00:00.000Z","description":"Timestamp when the offer was dispatched to the candidate. Client-settable."},"expiresAt":{"type":"string","format":"date-time","nullable":true,"example":"2026-06-15T00:00:00.000Z","description":"Alias: expires_at (snake_case accepted)."},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}},"JobPosting":{"type":"object","properties":{"id":{"type":"string","readOnly":true},"requisitionId":{"type":"string","nullable":true,"example":"REQ-0001","description":"Alias: requisition_id (snake_case accepted)."},"title":{"type":"string","example":"Software Engineer"},"department":{"type":"string","nullable":true,"example":"Engineering"},"description":{"type":"string","nullable":true,"example":"We are looking for a talented Software Engineer to join our team."},"location":{"type":"string","nullable":true,"example":"Remote"},"type":{"type":"string","nullable":true,"enum":["full_time","part_time","contract","internship"],"example":"full_time"},"status":{"type":"string","enum":["draft","active","paused","closed"],"example":"draft"},"applicantCount":{"type":"integer","nullable":true,"example":42,"description":"Running applicant count. Defaults to 0 on create; may be set by the client and is not server-managed."},"postedAt":{"type":"string","format":"date-time","nullable":true,"example":"2026-06-01T00:00:00.000Z","description":"Alias: posted_at (snake_case accepted)."},"createdAt":{"type":"string","format":"date-time","readOnly":true},"updatedAt":{"type":"string","format":"date-time","readOnly":true}}}}},"paths":{"/requisitions":{"get":{"summary":"List Purple ATS job requisitions","description":"Approved openings to hire against in Purple ATS — title, department, headcount, hiring manager, required skills and status (open → in_progress/on_hold → filled/closed/cancelled). Candidates and job postings link back to a requisition by requisitionId. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Requisition"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Open a job requisition","description":"Creates a new requisition (a request to hire) in Purple ATS. This opens headcount only — it does not publish an advert (create a job posting for that) and does not add applicants.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Requisition"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Requisition"}}}}}}},"/requisitions/{id}":{"get":{"summary":"Get requisition","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Requisition"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Update requisition","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Requisition"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Requisition"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete requisition","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/candidates":{"get":{"summary":"List Purple ATS job applicants","description":"External people who applied to a Purple ATS requisition, with their pipeline stage (applied → screening → phone_screen → interview → offer → hired, or rejected/withdrawn), source and resume link. These are applicants, not employees (see the hris app) or chat/identity users. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Candidate"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Add an applicant to the hiring pipeline","description":"Registers a new candidate against a Purple ATS requisition, starting at the 'applied' stage unless a status is supplied.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Candidate"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Candidate"}}}}}}},"/candidates/{id}":{"get":{"summary":"Get candidate","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Candidate"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Update a candidate record","description":"Partially updates an applicant in Purple ATS (contact details, source, resume). This is also how a candidate is rejected or withdrawn — set status accordingly. Forward pipeline moves should use the candidate advance tool, which validates the transition and records stage history.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Candidate"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Candidate"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete candidate","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/candidates/{id}/advance":{"post":{"summary":"Advance a candidate to the next hiring stage","description":"Moves a Purple ATS candidate forward through applied → screening → phone_screen → interview → offer → hired (auto-picks the next stage when no target is given) and appends an entry to stageHistory. Forward-only: it refuses backwards moves and candidates who are rejected, withdrawn or already hired — use the candidate update tool to reject or withdraw someone.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","enum":["applied","screening","phone_screen","interview","offer","hired"],"description":"Target stage to advance to. Omit to auto-advance to the next stage. Alias: stage (snake_case/legacy accepted).","example":"interview"},"notes":{"type":"string","nullable":true,"example":"Passed phone screen with flying colors."}}}}}},"responses":{"200":{"description":"Candidate advanced","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Candidate"}}}},"400":{"description":"Invalid stage value"},"404":{"description":"Candidate not found"},"422":{"description":"Stage transition not allowed (rejected/withdrawn/hired/backwards move)"}}}},"/candidates/{id}/generate-offer":{"post":{"summary":"Generate a draft offer for a candidate","description":"Creates an Offer record in draft status for a Purple ATS candidate and moves that candidate to the 'offer' stage in one step — prefer this over creating an offer record directly. Rejected if the candidate is already hired or already has a draft/sent offer.","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"salary":{"type":"number","nullable":true,"example":130000,"description":"Base salary. Alias: baseSalary (camelCase accepted). Stored as baseSalary on the created Offer document."},"equity":{"type":"string","nullable":true,"example":"0.25%","description":"Equity grant string (e.g. '0.25%')."},"start_date":{"type":"string","format":"date-time","nullable":true,"example":"2026-07-14T00:00:00.000Z","description":"Proposed start date. Alias: startDate (camelCase accepted)."},"expires_at":{"type":"string","format":"date-time","nullable":true,"example":"2026-06-20T00:00:00.000Z","description":"Offer expiry date. Alias: expiresAt (camelCase accepted)."},"sentAt":{"type":"string","format":"date-time","nullable":true,"example":"2026-06-01T00:00:00.000Z","description":"Timestamp when offer was sent, if already dispatched."}}}}}},"responses":{"201":{"description":"Offer created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Offer"}}}},"404":{"description":"Candidate not found"},"422":{"description":"Candidate already hired or already has an active offer"}}}},"/interviews":{"get":{"summary":"List Purple ATS candidate interviews","description":"Interview sessions booked or completed for Purple ATS candidates — interviewer email, type (phone_screen | technical | behavioral | panel | final), scheduledAt, status and the written feedback plus 1–5 rating. These are hiring loops, not general calendar meetings (see the calendar app). Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Interview"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Create interview","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Interview"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Interview"}}}}}}},"/interviews/{id}":{"get":{"summary":"Get interview","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Interview"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Update interview","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Interview"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Interview"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete interview","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/offers":{"get":{"summary":"List Purple ATS job offers","description":"Employment offers extended to Purple ATS candidates — base salary, equity, start date, expiry and status (draft → sent → accepted/declined/expired/withdrawn). Check here for an existing draft/sent offer before generating another for the same candidate. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Offer"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Create offer","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Offer"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Offer"}}}}}}},"/offers/{id}":{"get":{"summary":"Get offer","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Offer"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Update offer","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Offer"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Offer"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete offer","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/job-postings":{"get":{"summary":"List Purple ATS job postings","description":"Externally-facing job adverts in Purple ATS — title, location, employment type, applicant count and status (draft | active | paused | closed) — usually linked to a requisition. The posting is the advert; the requisition is the approved headcount behind it. Supports server-side querying with _filter/_search/_orderby/_select/_top/_skip/_count (OData $-params, renamed with a leading underscore for MCP).","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"query","name":"limit","schema":{"type":"integer","default":50},"description":"Legacy page size (default 50). Prefer $top. Ignored when $top/$skip are used."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"Legacy keyset cursor — pass the previous response's nextCursor. Prefer $skip/$top."},{"in":"query","name":"$filter","schema":{"type":"string"},"description":"OData filter expression. Comparison operators: eq, ne, gt, ge, lt, le. Logical: and, or, not, with parentheses for grouping. String functions: contains(field,'x'), startswith(field,'x'), endswith(field,'x'). Membership: field in ('a','b'). Case-insensitive matching via tolower(field) / toupper(field). Nested fields use a slash, e.g. pto_balance/vacation_days. String literals use single quotes; dates are ISO-8601. Examples: \"status eq 'active'\"; \"salary gt 50000 and department eq 'Engineering'\"; \"contains(tolower(name),'acme')\"; \"closeDate gt 2026-01-01\"."},{"in":"query","name":"$select","schema":{"type":"string"},"description":"Comma-separated list of fields to return, e.g. \"id,firstName,email\". Nested fields via slash (\"pto_balance/vacation_days\"). The id field is always included."},{"in":"query","name":"$orderby","schema":{"type":"string"},"description":"Sort clause(s), comma-separated, each \"field [asc|desc]\" (asc is the default). Example: \"createdAt desc, lastName asc\". Null values sort last."},{"in":"query","name":"$search","schema":{"type":"string"},"description":"Free-text, case-insensitive substring match across the resource's searchable fields."},{"in":"query","name":"$top","schema":{"type":"integer"},"description":"Maximum number of items to return (page size)."},{"in":"query","name":"$skip","schema":{"type":"integer"},"description":"Number of matching items to skip before returning results (offset paging). Combine with $top."},{"in":"query","name":"$count","schema":{"type":"boolean"},"description":"When true, include @odata.count (total matches after $filter/$search, before paging) in the response."}],"responses":{"200":{"description":"Paginated list","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/JobPosting"}},"nextCursor":{"type":"string","nullable":true,"description":"Cursor for the next page, or null on the last page. With $top/$skip this is the next $skip value; otherwise a keyset cursor."},"total":{"type":"integer","description":"Total documents in the collection."},"@odata.count":{"type":"integer","description":"Present only when $count=true: total matches after $filter/$search."},"@odata.truncated":{"type":"boolean","description":"Present and true only when the collection exceeded the in-memory query cap and results may be incomplete."}}}}}},"400":{"description":"Invalid OData query (e.g. malformed $filter)"}}},"post":{"summary":"Create job posting","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPosting"}}}},"responses":{"201":{"description":"Created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPosting"}}}}}}},"/job-postings/{id}":{"get":{"summary":"Get job posting","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPosting"}}}},"404":{"description":"Not found"}}},"patch":{"summary":"Update job posting","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"description":"Fields to update (partial)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPosting"}}}},"responses":{"200":{"description":"Updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JobPosting"}}}},"404":{"description":"Not found"}}},"delete":{"summary":"Delete job posting","parameters":[{"$ref":"#/components/parameters/InstanceId"},{"in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"}}}}}},"404":{"description":"Not found"}}}},"/referrals":{"get":{"x-odata":false,"summary":"List Purple ATS employee referrals","description":"Candidates put forward by existing employees, with a per-status tally. Each referral records who referred whom (referrer_email → candidate_name/candidate_email), the position, and its status (pending | reviewing | hired | rejected). A referral is a lead, not yet a candidate in the pipeline — see ats__list__candidates for candidates under active consideration. Returns the whole collection with counts; it does not take OData query params.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"responses":{"200":{"description":"Referral list","content":{"application/json":{"schema":{"type":"object","properties":{"referrals":{"type":"array","items":{"type":"object"}},"totalCount":{"type":"integer"},"statusCounts":{"type":"object"}}}}}}}},"post":{"summary":"Submit an employee referral","description":"Records a candidate referred by an existing employee and stores it in the instance's referrals collection with status `pending`. referrer_email, candidate_name and candidate_email are required. This creates a referral lead only — it does not add anyone to the hiring pipeline; use ats__create__candidates for that.","parameters":[{"$ref":"#/components/parameters/InstanceId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"referrer_email":{"type":"string","format":"email","example":"employee@example.com"},"candidate_name":{"type":"string","example":"Jane Smith"},"candidate_email":{"type":"string","format":"email","example":"jane.smith@email.com"},"position":{"type":"string","example":"Software Engineer"}},"required":["referrer_email","candidate_name","candidate_email"]}}}},"responses":{"200":{"description":"Referral submitted","content":{"application/json":{"schema":{"type":"object","properties":{"success":{"type":"boolean"},"referral":{"type":"object","properties":{"id":{"type":"string"},"referrer_email":{"type":"string"},"candidate_name":{"type":"string"},"candidate_email":{"type":"string"},"position":{"type":"string"},"status":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}}}}}}},"400":{"description":"Missing required fields"}}}}}}