Initiate an upload.

Add MCP server to your AI tool

Allow AI tools and LLMs to interact with the API documentation portal through MCP.

MCP server URL

https://docs.getpenfold.dev/mcp

Standard setup for AI tools providing an mcp.json file

mcp.json
"Penfold Partner API MCP server": {
  "url": "https://docs.getpenfold.dev/mcp"
}
Close
POST /uploads

Initiate an upload of a Contribution or Enrolment file. Returns a URL where the file should be sent. The employer the upload is for should be defined in the "EmployerId" column, or other known alias for non-PAPDIS files, with a value of "external_reference" from the employer record, returned on GET of an employer.

Path parameters

  • employer_id string Required
application/json

Body Required

  • filename string Required

    The filename of the upload.

  • purpose string Required

    The purpose of the file upload. If "contribution", both contributions and enrolments will be processed. If "enrolment", only enrolments will be processed.

    Values are contribution or enrolment.

Responses

  • 200 application/json

    Successfully created the upload. The response contains the ID of the upload, that can be used in querying /uploads/{upload_id} endpoints.

    Hide response attributes Show response attributes object
    • id string Required

      ID of the upload.

    • employer_id string | null

      ID of the employer the upload was made for.

    • put_destination_url string | null Required

      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.

    • created_at string(date-time) Required

      Datetime the upload was created.

    • updated_at string(date-time) Required

      Datetime the upload was last updated.

    • processing_started string(date-time) | null Required

      Datetime of when processing of the file began.

    • processing_ended string(date-time) | null Required

      Datetime of when processing of the file ended, or null if it has not ended.

    • processing_time number | null Required

      The number of seconds it took to process the file end-to-end (if applicable).

    • total_errors number Required

      The number of errors produced in processing the file. Zero if there are none or processing is not finished. Use the {upload_id}/errors endpoint to retrieve detailed information of the errors.

    • contributions_created number Required

      The number of contributions created during processing. Zero if processing is not finished.

    • contributions_unprocessed number

      The number of contributions that failed to be created during processing. Zero if processing is not finished.

    • contributions_already_existed number Required

      The number of contributions that already existed prior to processing, and which have been skipped during processing of the upload. Zero if processing is not finished.

    • employer_contributions number Required

      The total of employer contributions created for the upload, excluding contributions that already existed. Zero if processing is not finished.

    • employee_contributions number Required

      The total of employee contributions created for the upload, excluding contributions that already existed. Zero if processing is not finished.

    • total_contributions number Required

      The total of contributions created for the upload, excluding contributions that already existed. Zero if processing is not finished.

    • filename string | null Required

      The filename of the uploaded file (if applicable).

    • status string Required

      The status of the upload. Error indicates the file was not able to be processed. PartiallyProcessed indicates that validation passed and processing ran eagerly, but some records succeeded while others produced errors — treat this status as incomplete and re-submit the failing records (or the entire file) to recover. Processing is idempotent per pay period, so successfully-processed records will be skipped on re-submission. To retrieve the errors for an Error or PartiallyProcessed upload, use the /uploads/{upload_id}/errors endpoint.

      Values are AwaitingFile, ReceivedFile, Processing, Processed, Error, Timeout, or PartiallyProcessed.

  • 400 application/json

    Bad request, the request is malformed or contains invalid data.

    Hide response attributes Show response attributes object
    • error string

      A descriptive error message.

    • validation_errors array[object]
      Hide validation_errors attributes Show validation_errors attributes object
      • field string Required

        The name of the field that failed validation.

      • message string Required

        A descriptive error message.

  • 401 application/json

    Unauthorized, the request requires authentication, and the provided credentials are either missing or incorrect.

    Hide response attribute Show response attribute object
    • error string

      A descriptive error message.

  • 404 application/json

    The employer was not found.

    Hide response attribute Show response attribute object
    • error string

      A descriptive error message.

POST /uploads
curl \
 --request POST 'https://payroll-api.getpenfold.dev/v4/uploads' \
 --header "Authorization: Bearer $ACCESS_TOKEN" \
 --header "Content-Type: application/json" \
 --data '{"filename":"papdis.csv","purpose":"contribution"}'
Request examples
{
  "filename": "papdis.csv",
  "purpose": "contribution"
}
Response examples (200)
{
  "id": "string",
  "employer_id": "string",
  "put_destination_url": "string",
  "created_at": "2026-05-04T09:42:00Z",
  "updated_at": "2026-05-04T09:42:00Z",
  "processing_started": "2023-03-01T11:00:00Z",
  "processing_ended": "2023-03-01T12:00:00Z",
  "processing_time": 47,
  "total_errors": 10,
  "contributions_created": 5,
  "contributions_unprocessed": 0,
  "contributions_already_existed": 0,
  "employer_contributions": "500.34",
  "employee_contributions": "734.11",
  "total_contributions": "1234.45",
  "filename": "papdis.csv",
  "status": "Processed"
}
Response examples (400)
{
  "error": "Bad request: invalid data provided.",
  "validation_errors": [
    {
      "field": "email",
      "message": "Email address is invalid."
    }
  ]
}
Response examples (401)
{
  "error": "Bad request: invalid data provided."
}
Response examples (404)
{
  "error": "Bad request: invalid data provided."
}