{"openapi":"3.0.3","info":{"title":"Penfold Partner API","version":"1.0","description":"Unified API for payroll partners to onboard employers, manage employees,\ncontributions, and file uploads.\n\n## Authentication\n\nOAuth2 client credentials via AWS Cognito.\n\n1. POST to the Cognito token endpoint with `grant_type=client_credentials`\n   and scope `penfold-partner-api/read`.\n2. Include the access token as a Bearer token in the Authorization header.\n\nThe token's `client_id` is mapped to your organisation, which scopes all\nrequests to your employers only.\n\n## Identifier conventions\n\n- **Employer ID** (`id`): UUID assigned by Penfold when the employer is\n  created. Used as the `{employerId}` path parameter.\n- **External reference** (`externalReference`): The Penfold employer\n  reference (e.g. `PEN12345678`). This is `PEN` + the Companies House\n  number. Returned alongside `id` in employer responses for cross-referencing.\n- **Employee ID**: Unique identifier assigned by Penfold when the employee\n  is enrolled.\n"},"servers":[{"url":"https://partner-api.getpenfold.com/v1","description":"Production"},{"url":"https://partner-api.getpenfold.dev/v1","description":"Staging"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Health"},{"name":"Employers"},{"name":"Payments"},{"name":"Employees"},{"name":"Documents"},{"name":"Transfers"},{"name":"Payroll Contributions"},{"name":"Payroll Uploads"}],"paths":{"/healthz":{"get":{"summary":"Health check","operationId":"getHealth","security":[],"tags":["Health"],"responses":{"200":{"description":"Service is healthy","content":{"application/json":{"schema":{"type":"object","properties":{"status":{"type":"string","example":"ok"}}}}}}}}},"/employers":{"get":{"summary":"List employers","operationId":"getEmployers","tags":["Employers"],"description":"Returns all employers belonging to your organisation.\nEach employer includes both `id` (UUID) and `externalReference`\n(PEN + company number) for cross-referencing.\n","responses":{"200":{"description":"List of employers","content":{"application/json":{"schema":{"type":"object","required":["employers"],"properties":{"employers":{"type":"array","items":{"$ref":"#/components/schemas/Employer"}}}},"example":{"employers":[{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","name":"Acme Ltd","externalReference":"PEN12345678","companyNumber":"12345678","status":"Active","defaultEmployeeContributionsPercent":5,"defaultEmployerContributionsPercent":3,"contributionBasis":"QualifyingEarnings","allowsSalarySacrifice":false,"paymentMethod":"BankTransfer","createdAt":"2025-01-15T10:00:00Z"},{"id":"b2c3d4e5-f6a7-8901-bcde-f12345678901","name":"Widgets Inc","externalReference":"PEN87654321","companyNumber":"87654321","status":"AwaitingAgreement","defaultEmployeeContributionsPercent":5,"defaultEmployerContributionsPercent":3,"contributionBasis":"QualifyingEarnings","allowsSalarySacrifice":false,"paymentMethod":"BankTransfer","createdAt":"2025-03-01T10:00:00Z"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"summary":"Create employer","operationId":"createEmployer","tags":["Employers"],"description":"Creates a new employer under your organisation.\n\nThe company number is validated against Companies House. If an employer\nwith the same company number already exists in your organisation, a 409\nis returned with the existing employer's `externalReference`.\n\n## Onboarding lifecycle\n\nEmployer creation triggers an onboarding process:\n\n1. **AwaitingAgreement** — Employer record created, company validated\n   against Companies House. The employer must complete onboarding on\n   Penfold's platform (sign agreement, AML/KYB verification).\n2. **Pending** — Onboarding complete, awaiting first payroll submission.\n3. **Active** — First payroll processed. Employer is fully operational.\n\nPoll `GET /employers` or `GET /employers/{employerId}` to track\nstatus progression. The employer cannot process payroll until\nstatus is `Active`.\n","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateEmployerRequest"},"example":{"companyNumber":"12345678","companyName":"Acme Ltd","primaryContactEmail":"payroll@acme.com","primaryContactName":"Jane Smith","primaryContactRole":"PayrollManager","payrollFrequencies":["Monthly"],"expectedFirstPayPeriodStartDate":"2025-03-01","expectedPayPeriodCadence":"Monthly","defaultEmployeeContributionsPercent":5,"defaultEmployerContributionsPercent":3,"contributionBasis":"QualifyingEarnings","allowsSalarySacrifice":false,"paymentMethod":"BankTransfer","numberOfEmployees":50,"directorName":"John Smith","directorDateOfBirth":"1980-05-15"}}}},"responses":{"201":{"description":"Employer created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatedEmployer"},"example":{"id":"a1b2c3d4-e5f6-7890-abcd-ef1234567890","name":"Acme Ltd","externalReference":"PEN12345678","companyNumber":"12345678","status":"AwaitingAgreement","defaultEmployeeContributionsPercent":5,"defaultEmployerContributionsPercent":3,"contributionBasis":"QualifyingEarnings","allowsSalarySacrifice":false,"paymentMethod":"BankTransfer","createdAt":"2025-03-01T10:00:00Z"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"description":"Company not found in Companies House","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"company not found in Companies House"}}}},"409":{"description":"Employer already exists for this company number","content":{"application/json":{"schema":{"type":"object","required":["error","externalReference"],"properties":{"error":{"type":"string"},"externalReference":{"type":"string","description":"The existing employer's Penfold reference"}}},"example":{"error":"employer already exists","externalReference":"PEN12345678"}}}},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}":{"patch":{"summary":"Update employer","operationId":"updateEmployer","tags":["Employers"],"description":"Update an employer's configuration. All fields are optional — only\nprovided fields are updated.\n","parameters":[{"$ref":"#/components/parameters/employerId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmployerPatchBody"},"examples":{"updateContributions":{"summary":"Update contribution rates","value":{"defaultEmployeeContributionsPercent":6,"defaultEmployerContributionsPercent":4}},"updateContact":{"summary":"Update primary contact","value":{"primaryContactEmail":"new.contact@acme.com","primaryContactName":"John Doe","primaryContactRole":"Finance"}}}}}},"responses":{"200":{"description":"Employer updated successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Employer"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}/payments":{"get":{"summary":"List payments","operationId":"getEmployerPayments","tags":["Payments"],"description":"Returns all bank transfer payments for the specified employer, including\npast (received) and upcoming (outstanding) payments. Use the `status`\nfield to distinguish between them.\n","parameters":[{"$ref":"#/components/parameters/employerId"}],"responses":{"200":{"description":"List of payments","content":{"application/json":{"schema":{"type":"object","required":["payments"],"properties":{"payments":{"type":"array","items":{"$ref":"#/components/schemas/Payment"}}}},"example":{"payments":[{"id":"p1a2b3c4-d5e6-7890-abcd-ef1234567890","amountPence":850000,"paymentReference":"M25030112345678-ZG","payPeriodStart":"2025-03-01","payPeriodEnd":"2025-03-31","status":"Outstanding","createdAt":"2025-03-15T10:00:00Z"},{"id":"p2b3c4d5-e6f7-8901-bcde-f12345678902","amountPence":920000,"paymentReference":"M25020112345678-AB","payPeriodStart":"2025-02-01","payPeriodEnd":"2025-02-28","status":"Received","createdAt":"2025-02-15T10:00:00Z"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}/employees":{"get":{"summary":"List employees","operationId":"getEmployees","tags":["Employees"],"description":"Retrieve a paginated list of employees for a specified employer.","parameters":[{"$ref":"#/components/parameters/employerId"},{"in":"query","name":"pageSize","schema":{"type":"integer","minimum":100,"maximum":500,"default":200},"description":"The maximum number of employees to return per page."},{"in":"query","name":"pageNumber","schema":{"type":"integer","minimum":1,"default":1},"description":"The page number to return."}],"responses":{"200":{"description":"A paginated list of employees for the specified employer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedEmployees"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"summary":"Create employees","operationId":"createEmployees","tags":["Employees"],"description":"Enrol new employees for a specified employer. Employees are processed\nasynchronously — the response returns an upload ID that can be used\nto track processing status via the Uploads endpoints.\n","parameters":[{"$ref":"#/components/parameters/employerId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/EmployeeCreateBody"}}}}},"responses":{"201":{"description":"Submission accepted for processing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Submission"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}/employees/{employeeId}/transfers":{"post":{"summary":"Initiate pension transfer","operationId":"initiateTransfer","tags":["Transfers"],"description":"Initiate a pension transfer into Penfold for an employee. This\nrequests the transfer of an existing pension pot from another\nprovider into the employee's Penfold workplace pension.\n\nThe transfer will be submitted to the previous provider (via Origo\nwhere supported) and progress through standard transfer statuses.\n","parameters":[{"$ref":"#/components/parameters/employerId"},{"$ref":"#/components/parameters/employeeId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitiateTransferRequest"},"example":{"providerName":"Scottish Widows","policyNumber":"SW12345678","estimatedAmountPence":2500000}}}},"responses":{"201":{"description":"Transfer initiated successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PensionTransfer"},"example":{"id":"t1a2b3c4-d5e6-7890-abcd-ef1234567890","reference":"PEN76432-1","providerName":"Scottish Widows","policyNumber":"SW12345678","estimatedAmountPence":2500000,"status":"Requested","createdAt":"2025-03-15T14:30:00Z"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"get":{"summary":"List pension transfers","operationId":"getEmployeeTransfers","tags":["Transfers"],"description":"List all pension transfers for a specified employee.","parameters":[{"$ref":"#/components/parameters/employerId"},{"$ref":"#/components/parameters/employeeId"}],"responses":{"200":{"description":"List of pension transfers for the employee.","content":{"application/json":{"schema":{"type":"object","required":["transfers"],"properties":{"transfers":{"type":"array","items":{"$ref":"#/components/schemas/PensionTransfer"}}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}/employees/{employeeId}":{"patch":{"summary":"Update employee","operationId":"updateEmployee","tags":["Employees"],"description":"Update an employee's information. This can be used to mark them as a\nleaver. Note that once marked as a leaver the employee record will no\nlonger be accessible under the employer.\n","parameters":[{"$ref":"#/components/parameters/employerId"},{"$ref":"#/components/parameters/employeeId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmployeePatchBody"},"examples":{"markAsLeaver":{"value":{"exitDate":"2023-12-31"}}}}}},"responses":{"204":{"description":"Successfully updated the employee's information."},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}/employees/{employeeId}/pension-summary":{"get":{"summary":"Get employee pension summary","operationId":"getEmployeePensionSummary","tags":["Employees"],"description":"Returns pension summary for a specific employee.\n\nThe employer must belong to your organisation. Returns 404 only if the\nemployee is genuinely not found. Opted-out employees return 200 with\n`\"status\": \"optedOut\"` and null data fields.\n","parameters":[{"$ref":"#/components/parameters/employerId"},{"$ref":"#/components/parameters/employeeId"}],"responses":{"200":{"description":"Employee pension summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PensionSummary"},"examples":{"active":{"summary":"Active employee","value":{"provider":"Penfold","status":"active","balance":{"currentValuePence":150000,"totalContributionsPence":140000,"totalGainsPence":10000,"gainPercentage":7.14},"latestContributions":{"employeePence":5000,"employerPence":3000,"period":"2024-01"},"deepLink":"https://penfold.go.link"}},"optedOut":{"summary":"Opted-out employee","value":{"provider":"Penfold","status":"optedOut","balance":null,"latestContributions":null,"deepLink":null}}}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}/employees/{employeeId}/documents":{"get":{"summary":"List employee documents","operationId":"getEmployeeDocuments","tags":["Documents"],"description":"Returns all documents for a specific employee, including contract notes,\nvaluation statements, and direct debit notices.\n\nEach document includes a `downloadUrl` that returns a pre-signed S3 URL\nvalid for 5 minutes. To download the PDF, follow the URL returned by\n`downloadUrl`.\n","parameters":[{"$ref":"#/components/parameters/employerId"},{"$ref":"#/components/parameters/employeeId"},{"in":"query","name":"type","required":false,"description":"Filter documents by type.","schema":{"type":"string","enum":["AnnualBenefitStatement","ContractNote","DirectDebitMandateConfirmation","DirectDebitMandateNotice"]}},{"in":"query","name":"pageSize","schema":{"type":"integer","minimum":1,"maximum":500,"default":200},"description":"The maximum number of documents to return per page."},{"in":"query","name":"pageNumber","schema":{"type":"integer","minimum":1,"default":1},"description":"The page number to return."}],"responses":{"200":{"description":"A paginated list of documents for the employee.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedDocuments"},"example":{"pageNumber":1,"pageSize":200,"totalItems":3,"items":[{"id":"d2b3c4d5-f6a7-8901","type":"AnnualBenefitStatement","title":"Annual Benefit Statement - 2025","createdAt":"2025-06-15T10:00:00Z","downloadUrl":"/v1/employers/emp123/employees/ee456/documents/d2b3c4d5-f6a7-8901/download"},{"id":"d3c4d5e6-a7b8-9012","type":"ContractNote","title":"Contract Note - Buy PenfoldLifetime","createdAt":"2025-06-20T14:30:00Z","downloadUrl":"/v1/employers/emp123/employees/ee456/documents/d3c4d5e6-a7b8-9012/download"}]}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}/employees/{employeeId}/documents/{documentId}/download":{"get":{"summary":"Download document","operationId":"downloadEmployeeDocument","tags":["Documents"],"description":"Returns a pre-signed URL for downloading the document PDF.\nThe URL is valid for 5 minutes.\n\nThe response body is a plain text URL. Redirect the user or make a\nGET request to the returned URL to download the PDF.\n","parameters":[{"$ref":"#/components/parameters/employerId"},{"$ref":"#/components/parameters/employeeId"},{"in":"path","name":"documentId","required":true,"description":"Unique identifier for the document","schema":{"type":"string"}}],"responses":{"200":{"description":"Pre-signed S3 URL for the document PDF (valid for 5 minutes).","content":{"text/plain":{"schema":{"type":"string","format":"uri"},"example":"https://s3.eu-west-1.amazonaws.com/penfold-client-comms/..."}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}/employees/{employeeId}/contributions":{"get":{"summary":"List employee contributions","operationId":"getEmployeeContributions","tags":["Payroll Contributions"],"description":"Retrieve the contributions for a specified employee.","parameters":[{"$ref":"#/components/parameters/employerId"},{"$ref":"#/components/parameters/employeeId"},{"in":"query","name":"pageSize","schema":{"type":"integer","minimum":100,"maximum":500,"default":200},"description":"The maximum number of contributions to return per page."},{"in":"query","name":"pageNumber","schema":{"type":"integer","minimum":1,"default":1},"description":"The page number to return."},{"in":"query","name":"sortBy","description":"Sort contributions by date field.","required":false,"schema":{"type":"string","enum":["payPeriodStartDate","payPeriodEndDate","createdAt"],"default":"createdAt"}},{"in":"query","name":"sortOrder","description":"Sort order (ascending or descending).","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"A paginated list of contributions for the specified employee.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedContributions"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}/contributions":{"get":{"summary":"List employer contributions","operationId":"getEmployerContributions","tags":["Payroll Contributions"],"description":"Retrieve a paginated list of contributions for a specified employer.","parameters":[{"$ref":"#/components/parameters/employerId"},{"in":"query","name":"pageSize","schema":{"type":"integer","minimum":100,"maximum":500,"default":200},"description":"The maximum number of contributions to return per page."},{"in":"query","name":"pageNumber","schema":{"type":"integer","minimum":1,"default":1},"description":"The page number to return."},{"in":"query","name":"sortBy","description":"Sort contributions by date field.","required":false,"schema":{"type":"string","enum":["payPeriodStartDate","payPeriodEndDate","createdAt"],"default":"createdAt"}},{"in":"query","name":"sortOrder","description":"Sort order (ascending or descending).","required":false,"schema":{"type":"string","enum":["asc","desc"],"default":"desc"}}],"responses":{"200":{"description":"A paginated list of contributions for the specified employer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedContributions"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"post":{"summary":"Create contributions","operationId":"createContributions","tags":["Payroll Contributions"],"description":"Create multiple contributions for a specified employer in a single request.\nContributions are processed asynchronously — the response returns an upload\nID that can be used to track processing status via the Uploads endpoints.\n","parameters":[{"$ref":"#/components/parameters/employerId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ContributionBody"}}}}},"responses":{"201":{"description":"Submission accepted for processing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Submission"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}/uploads":{"post":{"summary":"Initiate file upload","operationId":"initiateUpload","tags":["Payroll Uploads"],"description":"Initiate an upload of a Contribution or Enrolment file. Returns a\npre-signed URL where the file should be sent via PUT request. We\nrecommend using the AWS S3 client SDK to perform the upload.\n","parameters":[{"$ref":"#/components/parameters/employerId"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InitiateUploadBody"}}}},"responses":{"200":{"description":"Successfully initiated the upload.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Upload"}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}},"get":{"summary":"List uploads","operationId":"getUploads","tags":["Payroll Uploads"],"description":"Get uploads for the specified employer.","parameters":[{"$ref":"#/components/parameters/employerId"},{"in":"query","name":"pageNumber","schema":{"type":"integer","minimum":1,"default":1},"description":"The page number to return."}],"responses":{"200":{"description":"Retrieved the uploads successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedUploads"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}/uploads/{uploadId}":{"get":{"summary":"Get upload status","operationId":"getUpload","tags":["Payroll Uploads"],"description":"Get the details of a file upload, including the realtime status of its processing.","parameters":[{"$ref":"#/components/parameters/employerId"},{"$ref":"#/components/parameters/uploadId"}],"responses":{"200":{"description":"Retrieved the upload successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Upload"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}/uploads/{uploadId}/errors":{"get":{"summary":"Get upload errors","operationId":"getUploadErrors","tags":["Payroll Uploads"],"description":"Get the errors of an upload. Will be an empty array unless upload\nstatus is \"Error\" or \"PartiallyProcessed\".\n","parameters":[{"$ref":"#/components/parameters/employerId"},{"$ref":"#/components/parameters/uploadId"},{"in":"query","name":"pageSize","schema":{"type":"integer","minimum":100,"maximum":500,"default":200},"description":"The maximum number of records to return per page."},{"in":"query","name":"pageNumber","schema":{"type":"integer","minimum":1,"default":1},"description":"The page number to return."}],"responses":{"200":{"description":"Retrieved the upload errors successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedUploadErrors"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}/uploads/{uploadId}/contributions":{"get":{"summary":"Get upload contributions","operationId":"getUploadContributions","tags":["Payroll Uploads"],"description":"Get the contributions created for an upload.","parameters":[{"$ref":"#/components/parameters/employerId"},{"$ref":"#/components/parameters/uploadId"},{"in":"query","name":"pageSize","schema":{"type":"integer","minimum":100,"maximum":500,"default":200},"description":"The maximum number of records to return per page."},{"in":"query","name":"pageNumber","schema":{"type":"integer","minimum":1,"default":1},"description":"The page number to return."}],"responses":{"200":{"description":"Retrieved the upload contributions successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedContributions"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}},"/employers/{employerId}/uploads/{uploadId}/enrolments":{"get":{"summary":"Get upload enrolments","operationId":"getUploadEnrolments","tags":["Payroll Uploads"],"description":"Get the enrolments created for an upload, in the form of employee records.","parameters":[{"$ref":"#/components/parameters/employerId"},{"$ref":"#/components/parameters/uploadId"},{"in":"query","name":"pageSize","schema":{"type":"integer","minimum":100,"maximum":500,"default":200},"description":"The maximum number of records to return per page."},{"in":"query","name":"pageNumber","schema":{"type":"integer","minimum":1,"default":1},"description":"The page number to return."}],"responses":{"200":{"description":"Retrieved the upload enrolments successfully.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PaginatedEmployees"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"500":{"$ref":"#/components/responses/InternalServerError"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Cognito OAuth2 access token with scope `penfold-partner-api/read`"}},"parameters":{"employerId":{"in":"path","name":"employerId","required":true,"description":"Penfold employer UUID","schema":{"type":"string","format":"uuid"}},"employeeId":{"in":"path","name":"employeeId","required":true,"description":"Unique identifier for the employee","schema":{"type":"string"}},"uploadId":{"in":"path","name":"uploadId","required":true,"description":"Unique identifier for the upload","schema":{"type":"string"}}},"schemas":{"PensionSummary":{"type":"object","required":["provider","status","balance","latestContributions","deepLink"],"properties":{"provider":{"type":"string","enum":["Penfold"],"description":"Pension provider name"},"status":{"type":"string","enum":["active","optedOut"],"description":"- `active`: employee is enrolled, balance and contributions are populated\n- `optedOut`: employee opted out of the scheme, all data fields are null\n"},"balance":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/Balance"}]},"latestContributions":{"nullable":true,"allOf":[{"$ref":"#/components/schemas/LatestContributions"}],"description":"Most recent contribution breakdown. Null if opted out or no contributions yet."},"deepLink":{"nullable":true,"type":"string","format":"uri","description":"URL for \"View \u0026 manage your pension\" CTA. Null if opted out.","example":"https://penfold.go.link"}}},"Balance":{"type":"object","required":["currentValuePence","totalContributionsPence","totalGainsPence","gainPercentage"],"properties":{"currentValuePence":{"type":"integer","description":"Current total pot value in pence","example":150000},"totalContributionsPence":{"type":"integer","description":"Total contributions paid in, in pence","example":140000},"totalGainsPence":{"type":"integer","description":"Investment gains (or losses if negative) in pence","example":10000},"gainPercentage":{"type":"number","format":"double","description":"Gain as percentage of total contributions, rounded to 2dp","example":7.14}}},"LatestContributions":{"type":"object","required":["employeePence","employerPence","period"],"properties":{"employeePence":{"type":"integer","description":"Employee contribution in pence","example":5000},"employerPence":{"type":"integer","description":"Employer contribution in pence","example":3000},"period":{"type":"string","pattern":"^\\d{4}-\\d{2}$","description":"Pay period (YYYY-MM)","example":"2024-01"}}},"Employer":{"type":"object","required":["id","name","externalReference","companyNumber","status","defaultEmployeeContributionsPercent","defaultEmployerContributionsPercent","contributionBasis","allowsSalarySacrifice","paymentMethod","createdAt"],"properties":{"id":{"type":"string","format":"uuid","description":"Unique identifier for the employer","example":"a1b2c3d4-e5f6-7890-abcd-ef1234567890"},"name":{"type":"string","description":"Employer name","example":"Acme Ltd"},"externalReference":{"type":"string","description":"Penfold employer reference (`PEN` + Companies House number)","example":"PEN12345678"},"companyNumber":{"type":"string","description":"Companies House number","example":"12345678"},"status":{"type":"string","enum":["AwaitingAgreement","Pending","Active","Closed"],"description":"Current status of the employer:\n- `AwaitingAgreement`: Employer must complete onboarding on Penfold's platform (agreement, AML/KYB)\n- `Pending`: Onboarding complete, awaiting first payroll submission\n- `Active`: Fully operational, can process payroll\n- `Closed`: Employer account closed\n","example":"Active"},"defaultEmployeeContributionsPercent":{"type":"number","description":"Default employee contribution percentage","example":5},"defaultEmployerContributionsPercent":{"type":"number","description":"Default employer contribution percentage","example":3},"contributionBasis":{"type":"string","enum":["QualifyingEarnings","TotalPay","BasicPay"],"description":"Basis on which pension contributions are calculated","example":"QualifyingEarnings"},"allowsSalarySacrifice":{"type":"boolean","description":"Whether the employer allows salary sacrifice","example":false},"paymentMethod":{"type":"string","enum":["BankTransfer"],"description":"Payment method for pension contributions","example":"BankTransfer"},"createdAt":{"type":"string","format":"date-time","description":"When the employer was created (ISO 8601)","example":"2025-03-01T10:00:00Z"}}},"CreateEmployerRequest":{"type":"object","required":["companyNumber","companyName","primaryContactEmail","primaryContactName","payrollFrequencies","expectedFirstPayPeriodStartDate","expectedPayPeriodCadence","defaultEmployeeContributionsPercent","defaultEmployerContributionsPercent","contributionBasis","paymentMethod","numberOfEmployees","directorName","directorDateOfBirth"],"properties":{"companyNumber":{"type":"string","description":"Companies House registration number","example":"12345678"},"companyName":{"type":"string","description":"Registered company name","example":"Acme Ltd"},"primaryContactEmail":{"type":"string","format":"email","description":"Email address of the primary contact at the employer","example":"payroll@acme.com"},"primaryContactName":{"type":"string","description":"Full name of the primary contact at the employer","example":"Jane Smith"},"primaryContactRole":{"type":"string","enum":["CompanyDirector","Finance","HR","PayrollManager"],"description":"Role of the primary contact at the employer","example":"PayrollManager"},"directorName":{"type":"string","description":"Full name of a company director. Required for AML verification.","example":"John Smith"},"directorDateOfBirth":{"type":"string","format":"date","description":"Date of birth of the company director (YYYY-MM-DD). Required for AML verification.","example":"1980-05-15"},"payrollFrequencies":{"type":"array","items":{"type":"string","enum":["Weekly","Fortnightly","FourWeekly","Monthly"]},"description":"How often the employer runs payroll","example":["Monthly"]},"expectedFirstPayPeriodStartDate":{"type":"string","format":"date","description":"Expected start date of the first pay period (YYYY-MM-DD)","example":"2025-03-01"},"expectedPayPeriodCadence":{"type":"string","enum":["Weekly","Fortnightly","FourWeekly","Monthly"],"description":"Expected cadence for pay periods","example":"Monthly"},"defaultEmployeeContributionsPercent":{"type":"number","minimum":0,"maximum":100,"description":"Default employee contribution percentage","example":5},"defaultEmployerContributionsPercent":{"type":"number","minimum":0,"maximum":100,"description":"Default employer contribution percentage","example":3},"contributionBasis":{"type":"string","enum":["QualifyingEarnings","TotalPay","BasicPay"],"description":"Basis on which pension contributions are calculated","example":"QualifyingEarnings"},"allowsSalarySacrifice":{"type":"boolean","description":"Whether the employer allows salary sacrifice arrangements","default":false,"example":false},"paymentMethod":{"type":"string","enum":["BankTransfer"],"description":"How the employer will pay pension contributions","example":"BankTransfer"},"numberOfEmployees":{"type":"integer","minimum":1,"description":"Approximate number of employees to be enrolled","example":50}}},"EmployerPatchBody":{"type":"object","description":"All fields are optional. Only provided fields are updated.","properties":{"primaryContactEmail":{"type":"string","format":"email","description":"Email address of the primary contact at the employer","example":"payroll@acme.com"},"primaryContactName":{"type":"string","description":"Full name of the primary contact at the employer","example":"Jane Smith"},"primaryContactRole":{"type":"string","enum":["CompanyDirector","Finance","HR","PayrollManager"],"description":"Role of the primary contact at the employer","example":"PayrollManager"},"defaultEmployeeContributionsPercent":{"type":"number","minimum":0,"maximum":100,"description":"Default employee contribution percentage","example":5},"defaultEmployerContributionsPercent":{"type":"number","minimum":0,"maximum":100,"description":"Default employer contribution percentage","example":3},"contributionBasis":{"type":"string","enum":["QualifyingEarnings","TotalPay","BasicPay"],"description":"Basis on which pension contributions are calculated","example":"QualifyingEarnings"},"allowsSalarySacrifice":{"type":"boolean","description":"Whether the employer allows salary sacrifice arrangements","example":false},"payrollFrequencies":{"type":"array","items":{"type":"string","enum":["Weekly","Fortnightly","FourWeekly","Monthly"]},"description":"How often the employer runs payroll","example":["Monthly"]}}},"CreatedEmployer":{"description":"Alias for Employer — returned from POST /employers.","allOf":[{"$ref":"#/components/schemas/Employer"}]},"Payment":{"type":"object","required":["id","amountPence","paymentReference","payPeriodStart","payPeriodEnd","status","createdAt"],"properties":{"id":{"type":"string","description":"Unique identifier for the payment","example":"p1a2b3c4-d5e6-7890-abcd-ef1234567890"},"amountPence":{"type":"integer","description":"Total payment amount in pence","example":850000},"paymentReference":{"type":"string","description":"Bank transfer reference to use when making the payment. Format: {frequencyPrefix}{yyMMdd}{employerNumber}-{suffix} where frequencyPrefix is M(onthly)/W(eekly)/4(weekly)/2(weekly), date is the pay period start, and employer number is the external reference without the PEN prefix. Max 18 characters.","example":"M25030112345678-ZG"},"payPeriodStart":{"type":"string","format":"date","description":"Start date of the pay period this payment covers (ISO 8601 date)","example":"2025-03-01"},"payPeriodEnd":{"type":"string","format":"date","description":"End date of the pay period this payment covers (ISO 8601 date)","example":"2025-03-31"},"status":{"type":"string","description":"Whether Penfold has received this payment.","enum":["Outstanding","Received"],"example":"Outstanding"},"createdAt":{"type":"string","format":"date-time","description":"When the payment record was created (ISO 8601)","example":"2025-03-15T10:00:00Z"}}},"Document":{"type":"object","required":["id","type","title","createdAt","downloadUrl"],"properties":{"id":{"type":"string","description":"Unique identifier for the document.","example":"d1a2b3c4-e5f6-7890"},"type":{"type":"string","enum":["AnnualBenefitStatement","ContractNote","DirectDebitMandateConfirmation","DirectDebitMandateNotice"],"description":"The type of document:\n- `AnnualBenefitStatement`: Annual benefit statement (SMPI)\n- `ContractNote`: Generated when funds are bought, sold, or switched\n- `DirectDebitMandateConfirmation`: Direct debit setup confirmation\n- `DirectDebitMandateNotice`: Direct debit advance notice\n","example":"AnnualBenefitStatement"},"title":{"type":"string","description":"Human-readable document title.","example":"Contract Note - Buy PenfoldLifetime"},"createdAt":{"type":"string","format":"date-time","description":"When the document was created (ISO 8601).","example":"2025-06-15T10:00:00Z"},"downloadUrl":{"type":"string","description":"Relative URL to download the document PDF. Returns a pre-signed\nS3 URL valid for 5 minutes.\n","example":"/v1/employers/emp123/employees/ee456/documents/d1a2b3c4-e5f6-7890/download"}}},"PaginatedDocuments":{"type":"object","required":["pageNumber","pageSize","totalItems","items"],"properties":{"pageNumber":{"type":"integer","description":"The current page number.","example":1},"pageSize":{"type":"integer","description":"The number of items per page.","example":200},"totalItems":{"type":"integer","description":"The total number of items available.","example":3},"items":{"type":"array","items":{"$ref":"#/components/schemas/Document"},"description":"An array of Document objects on the current page."}}},"InitiateTransferRequest":{"type":"object","required":["providerName","policyNumber","estimatedAmountPence"],"properties":{"providerName":{"type":"string","description":"Name of the pension provider the pot is being transferred from.","example":"Scottish Widows"},"policyNumber":{"type":"string","description":"Policy or plan number at the previous provider.","example":"SW12345678"},"estimatedAmountPence":{"type":"integer","description":"Estimated value of the pension pot in pence.","example":2500000}}},"PensionTransfer":{"type":"object","required":["id","reference","providerName","policyNumber","estimatedAmountPence","status","createdAt"],"properties":{"id":{"type":"string","description":"Unique identifier for the transfer.","example":"t1a2b3c4-d5e6-7890-abcd-ef1234567890"},"reference":{"type":"string","description":"Penfold transfer reference (e.g. PEN76432-1).","example":"PEN76432-1"},"providerName":{"type":"string","description":"Name of the previous pension provider.","example":"Scottish Widows"},"policyNumber":{"type":"string","description":"Policy or plan number at the previous provider.","example":"SW12345678"},"estimatedAmountPence":{"type":"integer","description":"Estimated value of the pension pot in pence.","example":2500000},"finalAmountPence":{"type":"integer","nullable":true,"description":"Actual amount received in pence. Null until funds arrive.","example":null},"status":{"type":"string","description":"Simplified status of the transfer. Internally Penfold tracks ~25\ngranular statuses; these are collapsed here assuming most transfers\nfollow the happy path. Rejected transfers will typically need to\nbe resolved on the Penfold platform directly.\n\n- `Requested`: Transfer has been submitted\n- `InProgress`: Transfer is being processed with the previous provider\n- `FundsReceived`: Funds have arrived at Penfold\n- `Complete`: Funds invested in the employee's pension pot\n- `Rejected`: Previous provider rejected the transfer — resolve via Penfold platform\n- `Cancelled`: Transfer was cancelled\n","enum":["Requested","InProgress","FundsReceived","Complete","Rejected","Cancelled"],"example":"Requested"},"rejectionReason":{"type":"string","nullable":true,"description":"Reason the transfer was rejected (if applicable).","example":null},"createdAt":{"type":"string","format":"date-time","description":"When the transfer was initiated (ISO 8601).","example":"2025-03-15T14:30:00Z"}}},"Employee":{"type":"object","required":["id","email","forename","surname","title","dateOfBirth","nationalInsuranceNumber","employmentStartDate","status","createdAt","updatedAt"],"properties":{"id":{"type":"string","description":"Unique identifier for the employee.","example":"e1234-abcd-5678-efgh","readOnly":true},"email":{"type":"string","description":"Email address of the employee.","example":"john.doe@example.com","format":"email"},"forename":{"type":"string","description":"The employee's first name.","example":"John"},"surname":{"type":"string","description":"The employee's last name.","example":"Doe"},"title":{"type":"string","description":"The employee's title, e.g., Mr., Mrs., Dr., etc.","example":"Mr."},"dateOfBirth":{"type":"string","description":"The employee's date of birth, in YYYY-MM-DD format.","example":"1990-01-01","format":"date"},"addressLine1":{"type":"string","description":"The first line of the employee's address.","example":"123 Main Street"},"postcode":{"type":"string","description":"The postal code of the employee's address.","example":"AB12 3CD"},"nationalInsuranceNumber":{"type":"string","description":"The employee's National Insurance number.","example":"AA123456C"},"employmentStartDate":{"type":"string","description":"The date the employee started their employment, in YYYY-MM-DD format.","example":"2023-01-01","format":"date"},"status":{"type":"string","description":"The employee's current status on the Penfold platform:\n- `Enrolled`: Employee has been enrolled but has not yet activated their Penfold account\n- `Active`: Employee has activated their Penfold account\n- `OptedOut`: Employee opted out of the pension scheme\n- `Left`: Employee has left employment\n","enum":["Enrolled","Active","OptedOut","Left"],"example":"Active"},"exitDate":{"type":"string","nullable":true,"description":"The date the employee's employment ended, in YYYY-MM-DD format (if applicable).","example":null,"format":"date"},"optOutDate":{"type":"string","nullable":true,"description":"The date the employee opted out of the pension scheme, in YYYY-MM-DD format (if applicable).","example":null,"format":"date"},"optInDate":{"type":"string","nullable":true,"description":"The date the employee opted into the pension scheme, in YYYY-MM-DD format (if applicable).","example":null,"format":"date"},"optOutWindowStartDate":{"type":"string","nullable":true,"description":"The date the employee's opt out window begins.","example":"2023-01-01","format":"date"},"optOutWindowEndDate":{"type":"string","nullable":true,"description":"The date the employee's opt out window ends.","example":"2023-01-31","format":"date"},"createdAt":{"type":"string","description":"The date and time the employee record was created, in ISO 8601 format.","example":"2023-03-01T12:00:00Z","format":"date-time","readOnly":true},"updatedAt":{"type":"string","description":"The date and time the employee record was last updated, in ISO 8601 format.","example":"2023-03-15T12:00:00Z","format":"date-time","readOnly":true}}},"EmployeeCreateBody":{"type":"object","required":["email","dateOfBirth","forename","surname","title","addressLine1","postcode","nationalInsuranceNumber","employmentStartDate"],"properties":{"email":{"type":"string","description":"Email address of the employee.","example":"john.doe@example.com","format":"email"},"forename":{"type":"string","description":"The employee's first name.","example":"John"},"surname":{"type":"string","description":"The employee's last name.","example":"Doe"},"title":{"type":"string","description":"The employee's title, e.g. Mr., Mrs., Dr., etc.","example":"Mr."},"dateOfBirth":{"type":"string","description":"The employee's date of birth, in YYYY-MM-DD format.","example":"1990-01-01","format":"date"},"addressLine1":{"type":"string","description":"The first line of the employee's address.","example":"123 Main Street"},"postcode":{"type":"string","description":"The postal code of the employee's address.","example":"AB12 3CD"},"nationalInsuranceNumber":{"type":"string","description":"The employee's National Insurance number.","example":"AA123456C"},"employmentStartDate":{"type":"string","description":"The date the employee started their employment, in YYYY-MM-DD format.","example":"2023-01-01","format":"date"}}},"EmployeePatchBody":{"type":"object","properties":{"exitDate":{"type":"string","description":"Date the employee's employment ended, in YYYY-MM-DD format.","example":"2023-12-31","format":"date"}}},"PaginatedEmployees":{"type":"object","required":["pageNumber","pageSize","totalItems","items"],"properties":{"pageNumber":{"type":"integer","description":"The current page number.","example":1},"pageSize":{"type":"integer","description":"The number of items per page.","example":200},"totalItems":{"type":"integer","description":"The total number of items available.","example":1},"items":{"type":"array","items":{"$ref":"#/components/schemas/Employee"},"description":"An array of Employee objects on the current page."}}},"Submission":{"type":"object","required":["id","status","createdAt"],"properties":{"id":{"type":"string","description":"Upload ID for tracking the submission. Use the Uploads endpoints to check processing status.","example":"u1234-abcd-5678-efgh"},"status":{"type":"string","description":"Initial status of the submission.","example":"ReceivedFile","enum":["ReceivedFile","Processing"]},"createdAt":{"type":"string","format":"date-time","description":"When the submission was created (ISO 8601).","example":"2023-03-01T12:00:00Z"}}},"Contribution":{"type":"object","required":["id","employeeId","employerContributionsAmount","employeeContributionsAmount","createdAt","payPeriodStartDate","payPeriodEndDate","status"],"properties":{"id":{"type":"string","description":"Unique identifier for the contribution record.","example":"c1234-abcd-5678-efgh"},"uploadId":{"type":"string","description":"ID of the file upload from which the contribution was created (if applicable)."},"employeeId":{"type":"string","description":"Identifier for the employee associated with the contribution.","example":"e9876-wxyz-4321-stuv"},"employerContributionsAmount":{"type":"number","description":"The amount of the employer's contribution for the given pay period.","example":1000.0},"employeeContributionsAmount":{"type":"number","description":"The amount of the employee's contribution for the given pay period.","example":250.0},"createdAt":{"type":"string","format":"date-time","description":"The date and time the contribution record was created, in ISO 8601 format.","example":"2023-03-22T12:00:00Z","readOnly":true},"payPeriodStartDate":{"type":"string","format":"date","description":"The start date of the pay period for which the contributions were made, in YYYY-MM-DD format.","example":"2023-03-01"},"payPeriodEndDate":{"type":"string","format":"date","description":"The end date of the pay period for which the contributions were made, in YYYY-MM-DD format.","example":"2023-03-15"},"status":{"type":"string","description":"The status of the contribution. Possible values are: - `Cancelled`: The contribution has been cancelled. - `NotSubmitted`: The contribution has not been submitted. - `Deleted`: The contribution has been deleted. - `Pending`: The contribution is currently being processed. - `Completed`: The contribution has been successfully processed.\n","enum":["Cancelled","NotSubmitted","Deleted","Pending","Completed"],"example":"Pending"}}},"ContributionBody":{"type":"object","required":["employeeId","employerContributionsAmount","employeeContributionsAmount","payPeriodStartDate","payPeriodEndDate"],"properties":{"employeeId":{"type":"string","description":"Identifier for the employee associated with the contribution.","example":"e9876-wxyz-4321-stuv"},"employerContributionsAmount":{"type":"number","description":"The amount of the employer's contribution for the given pay period.","example":1200.0},"employeeContributionsAmount":{"type":"number","description":"The amount of the employee's contribution for the given pay period.","example":300.0},"payPeriodStartDate":{"type":"string","format":"date","description":"The start date of the pay period, in YYYY-MM-DD format.","example":"2023-03-01"},"payPeriodEndDate":{"type":"string","format":"date","description":"The end date of the pay period, in YYYY-MM-DD format.","example":"2023-03-15"}}},"PaginatedContributions":{"type":"object","required":["pageNumber","pageSize","totalItems","items"],"properties":{"pageNumber":{"type":"integer","description":"The current page number.","example":1},"pageSize":{"type":"integer","description":"The number of items per page.","example":200},"totalItems":{"type":"integer","description":"The total number of items available.","example":1},"items":{"type":"array","items":{"$ref":"#/components/schemas/Contribution"},"description":"An array of Contribution objects on the current page."}}},"Upload":{"type":"object","required":["id","putDestinationUrl","status","updatedAt","createdAt","processingStarted","processingEnded","processingTime","totalErrors","contributionsCreated","contributionsAlreadyExisted","employerContributions","employeeContributions","totalContributions","filename"],"properties":{"id":{"type":"string","description":"ID of the upload."},"employerId":{"type":"string","description":"ID of the employer the upload was made for.","nullable":true},"putDestinationUrl":{"type":"string","description":"Destination URL where the actual file should be sent, using a PUT request. Only present when status is AwaitingFile. We recommend using the AWS S3 client SDK to perform the upload.\n","nullable":true},"createdAt":{"type":"string","format":"date-time","description":"Datetime the upload was created."},"updatedAt":{"type":"string","format":"date-time","description":"Datetime the upload was last updated."},"processingStarted":{"type":"string","nullable":true,"format":"date-time","description":"Datetime of when processing of the file began.","example":"2023-03-01T11:00:00Z"},"processingEnded":{"type":"string","format":"date-time","nullable":true,"description":"Datetime of when processing of the file ended, or null if it has not ended.","example":"2023-03-01T12:00:00Z"},"processingTime":{"type":"number","description":"The number of seconds it took to process the file end-to-end (if applicable).","example":47,"nullable":true},"totalErrors":{"type":"number","description":"The number of errors produced in processing the file. Zero if there are none or processing is not finished. Use the `{uploadId}/errors` endpoint to retrieve detailed information.\n","example":10},"contributionsCreated":{"type":"number","description":"The number of contributions created during processing. Zero if processing is not finished.","example":5},"contributionsUnprocessed":{"type":"number","description":"The number of contributions that failed to be created during processing. Zero if processing is not finished.","example":0},"contributionsAlreadyExisted":{"type":"number","description":"The number of contributions that already existed prior to processing and were skipped. Zero if processing is not finished.","example":0},"employerContributions":{"type":"number","description":"The total of employer contributions created for the upload, excluding those that already existed. Zero if processing is not finished.","example":500.34},"employeeContributions":{"type":"number","description":"The total of employee contributions created for the upload, excluding those that already existed. Zero if processing is not finished.","example":734.11},"totalContributions":{"type":"number","description":"The total of all contributions created for the upload, excluding those that already existed. Zero if processing is not finished.","example":1234.45},"filename":{"type":"string","description":"The filename of the uploaded file (if applicable).","example":"papdis.csv","nullable":true},"status":{"type":"string","description":"The status of the upload. Error indicates the file was not able to be processed. PartiallyProcessed indicates that some but not all contributions or enrolments were created.\n","example":"Processed","enum":["AwaitingFile","ReceivedFile","Processing","Processed","Error","Timeout","PartiallyProcessed"]}}},"InitiateUploadBody":{"type":"object","required":["filename","purpose"],"properties":{"filename":{"type":"string","description":"The filename of the upload.","example":"papdis.csv"},"purpose":{"type":"string","description":"The purpose of the file upload. If \"contribution\", both contributions and enrolments will be processed. If \"enrolment\", only enrolments will be processed.\n","example":"contribution","enum":["contribution","enrolment"]}}},"PaginatedUploads":{"type":"object","properties":{"pageNumber":{"type":"integer","description":"The current page number.","example":1},"pageSize":{"type":"integer","description":"The number of items per page.","example":200},"totalItems":{"type":"integer","description":"The total number of items available.","example":1},"items":{"type":"array","items":{"$ref":"#/components/schemas/Upload"},"description":"An array of Upload objects on the current page."}}},"UploadError":{"type":"object","properties":{"uploadId":{"type":"string","description":"ID of the upload where the error occurred."},"scope":{"type":"string","enum":["File","AllRows","Row"],"description":"The scope of the error. Row indicates an error in a specific row. AllRows indicates an error that applies to all rows. File indicates an error with the file itself.\n"},"rowIndex":{"type":"number","description":"The row in the file where the error occurred. Starting at row 1, the first non-header row. Set when scope=Row.","nullable":true},"code":{"type":"string","description":"An identifier for the class of the error.","example":"NationalInsuranceNumberInvalid"},"message":{"type":"string","description":"Human readable error message to help diagnose the issue.","example":"The provided National Insurance Number is invalid."}}},"PaginatedUploadErrors":{"type":"object","properties":{"pageNumber":{"type":"integer","description":"The current page number.","example":1},"pageSize":{"type":"integer","description":"The number of items per page.","example":200},"totalItems":{"type":"integer","description":"The total number of items available.","example":1},"items":{"type":"array","items":{"$ref":"#/components/schemas/UploadError"},"description":"An array of UploadError objects on the current page."}}},"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string"}}},"ValidationErrorDetail":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"A descriptive error message."},"validationErrors":{"type":"array","items":{"type":"object","required":["field","message"],"properties":{"field":{"type":"string","description":"The name of the field that failed validation."},"message":{"type":"string","description":"A descriptive error message."}}}}}}},"responses":{"Unauthorized":{"description":"Missing or invalid authentication token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"unauthorized"}}}},"ValidationError":{"description":"Request body failed validation","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidationErrorDetail"},"example":{"error":"validation failed","validationErrors":[{"field":"companyNumber","message":"companyNumber is required"},{"field":"primaryContactEmail","message":"must be a valid email address"}]}}}},"NotFound":{"description":"Resource not found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"not found"}}}},"InternalServerError":{"description":"Internal server error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":"internal server error"}}}}}}}