> ## Documentation Index
> Fetch the complete documentation index at: https://docs.goteal.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Generate presigned payslip URLs

> Generates short-lived presigned URLs that you can use to upload payslip PDFs
directly to Teal-managed storage. Use this endpoint before uploading any
files and submitting them for processing.


<Info>
  This endpoint requires a valid authorisation for the user. If no active authorisation exists, the request will be rejected.
  You can optionally pass the `x-teal-authorisation-id` header to specify which authorisation to use; otherwise the system will resolve a valid authorisation automatically.
  See [Authorisations](/authorisations) for more details.
</Info>

Request short-lived URLs to upload payslip PDFs directly to Teal managed storage.
Use the returned `presigned_url` values with HTTP `PUT`, then call
`POST /payroll/entries/payslips` to submit the uploaded documents for processing.

<RequestExample>
  ```bash cURL theme={null}
  curl --request POST \
    --url https://api.sandbox.goteal.co/payroll/entries/payslips/presign \
    --header 'Authorization: Bearer <token>' \
    --header 'Content-Type: application/json' \
    --data '{
      "files": [
        {"file_name": "payslip1.pdf", "external_id": "ext-jan-2024"},
        {"file_name": "payslip2.pdf"}
      ]
    }'
  ```

  ```python Python theme={null}
  import json
  import requests

  token = "<token>"
  url = "https://api.sandbox.goteal.co/payroll/entries/payslips/presign"

  payload = {
      "files": [
          {"file_name": "payslip1.pdf", "external_id": "ext-jan-2024"},
          {"file_name": "payslip2.pdf"},
      ]
  }

  response = requests.post(
      url,
      headers={
          "Authorization": f"Bearer {token}",
          "Content-Type": "application/json",
      },
      json=payload,
  )

  response.raise_for_status()

  print(json.dumps(response.json(), indent=2))
  ```

  ```javascript JavaScript theme={null}
  import axios from 'axios'

  const token = '<token>'
  const response = await axios.post(
    'https://api.sandbox.goteal.co/payroll/entries/payslips/presign',
    {
      files: [
        { file_name: 'payslip1.pdf', external_id: 'ext-jan-2024' },
        { file_name: 'payslip2.pdf' }
      ]
    },
    {
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json'
      }
    }
  )

  console.log(response.data)
  ```

  ```php PHP theme={null}
  <?php

  $token = '<token>';
  $payload = json_encode([
    'files' => [
      ['file_name' => 'payslip1.pdf', 'external_id' => 'ext-jan-2024'],
      ['file_name' => 'payslip2.pdf'],
    ],
  ]);

  $curl = curl_init('https://api.sandbox.goteal.co/payroll/entries/payslips/presign');
  curl_setopt_array($curl, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => 'POST',
    CURLOPT_POSTFIELDS => $payload,
    CURLOPT_HTTPHEADER => [
      'Authorization: Bearer ' . $token,
      'Content-Type: application/json',
    ],
  ]);

  $response = curl_exec($curl);
  if ($response === false) {
    throw new RuntimeException('cURL error: ' . curl_error($curl));
  }

  $statusCode = curl_getinfo($curl, CURLINFO_HTTP_CODE);
  curl_close($curl);

  if ($statusCode >= 400) {
    throw new RuntimeException('Request failed: ' . $response);
  }

  print_r(json_decode($response, true));
  ```

  ```go Go theme={null}
  package main

  import (
  	"bytes"
  	"encoding/json"
  	"fmt"
  	"io"
  	"net/http"
  )

  func main() {
  	payload := map[string]any{
  		"files": []map[string]any{
  			{"file_name": "payslip1.pdf", "external_id": "ext-jan-2024"},
  			{"file_name": "payslip2.pdf"},
  		},
  	}

  	body, _ := json.Marshal(payload)
  	req, _ := http.NewRequest(
  		"POST",
  		"https://api.sandbox.goteal.co/payroll/entries/payslips/presign",
  		bytes.NewBuffer(body),
  	)

  	req.Header.Set("Authorization", "Bearer <token>")
  	req.Header.Set("Content-Type", "application/json")

  	resp, err := http.DefaultClient.Do(req)
  	if err != nil {
  		panic(err)
  	}
  	defer resp.Body.Close()

  	if resp.StatusCode >= 400 {
  		respBody, _ := io.ReadAll(resp.Body)
  		panic(fmt.Sprintf("request failed: %s", string(respBody)))
  	}

  	respBody, _ := io.ReadAll(resp.Body)
  	fmt.Println(string(respBody))
  }
  ```

  ```java Java theme={null}
  import kong.unirest.HttpResponse;
  import kong.unirest.Unirest;

  String token = "<token>";

  HttpResponse<String> response = Unirest.post("https://api.sandbox.goteal.co/payroll/entries/payslips/presign")
      .header("Authorization", "Bearer " + token)
      .header("Content-Type", "application/json")
      .body("{\n  \"files\": [\n    {\"file_name\": \"payslip1.pdf\", \"external_id\": \"ext-jan-2024\"},\n    {\"file_name\": \"payslip2.pdf\"}\n  ]\n}")
      .asString();

  if (!response.isSuccess()) {
      throw new RuntimeException("Presign request failed: " + response.getBody());
  }

  System.out.println(response.getBody());
  ```
</RequestExample>

<ResponseExample>
  ```json Response theme={null}
  {
    "presigned_urls": [
      {
        "file_name": "payslip1.pdf",
        "external_id": "ext-jan-2024",
        "presigned_url": "https://s3.amazonaws.com/bucket/presigned-uploads/payslip1.pdf?signature=abc123",
        "path": "client/acct123/users/user456/payslip1.pdf"
      },
      {
        "file_name": "payslip2.pdf",
        "presigned_url": "https://s3.amazonaws.com/bucket/presigned-uploads/payslip2.pdf?signature=def456",
        "path": "client/acct123/users/user456/payslip2.pdf"
      }
    ]
  }
  ```
</ResponseExample>


## OpenAPI

````yaml POST /payroll/entries/payslips/presign
openapi: 3.0.1
info:
  title: Payroll API
  description: A full flagged payroll api provided by Teal
  version: 1.0.0
servers:
  - url: https://api.sandbox.goteal.co
    description: Sandbox server for experiments
  - url: https://api.goteal.co
    description: Production server
security:
  - ApiKeyAuth: []
  - MemberBearerAuth: []
paths:
  /payroll/entries/payslips/presign:
    post:
      summary: Generate presigned URLs for payslip uploads
      description: >
        Generates short-lived presigned URLs that you can use to upload payslip
        PDFs

        directly to Teal-managed storage. Use this endpoint before uploading any

        files and submitting them for processing.
      parameters:
        - $ref: '#/components/parameters/XAuthorisationId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EntriesPayslipsPresignRequest'
      responses:
        '200':
          description: Presigned URLs generated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EntriesPayslipsPresignResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/UnauthorizedBearer'
      security:
        - BearerAuth: []
components:
  parameters:
    XAuthorisationId:
      name: x-teal-authorisation-id
      in: header
      required: false
      description: >-
        Optional authorisation ID to associate with this request. If not
        provided, the system will attempt to resolve a valid authorisation for
        the user automatically.
      schema:
        type: string
        format: uuid
        example: 7f3b8c2a-1d4e-4f6a-8b8c-9a0b1c2d3e4f
  schemas:
    EntriesPayslipsPresignRequest:
      type: object
      required:
        - files
      properties:
        files:
          type: array
          description: Payslip files that require presigned upload URLs
          minItems: 1
          items:
            $ref: '#/components/schemas/EntriesPayslipsPresignRequestFile'
    EntriesPayslipsPresignResponse:
      type: object
      required:
        - presigned_urls
      properties:
        presigned_urls:
          type: array
          description: Generated URLs and metadata for uploading the requested files.
          items:
            $ref: '#/components/schemas/EntriesPayslipsPresignResponseFile'
    EntriesPayslipsPresignRequestFile:
      type: object
      required:
        - file_name
      properties:
        file_name:
          type: string
          description: File name including extension. Must be a PDF.
          example: payslip1.pdf
        external_id:
          type: string
          description: Optional identifier echoed back in the presign response.
          example: ext-payslip-2024-05
    EntriesPayslipsPresignResponseFile:
      type: object
      required:
        - file_name
        - presigned_url
        - path
      properties:
        file_name:
          type: string
          description: The requested file name.
          example: payslip1.pdf
        external_id:
          type: string
          description: Optional identifier provided in the presign request.
          example: ext-payslip-2024-05
        presigned_url:
          type: string
          format: uri
          description: The URL to upload the file content to via HTTP PUT.
          example: >-
            https://s3.amazonaws.com/bucket/presigned-upload-key?signature=abc123
        path:
          type: string
          description: The storage path that must be echoed when submitting the payslips.
          example: client/acct123/users/user456/payslip1.pdf
    Error:
      required:
        - errors
      type: object
      properties:
        errors:
          type: array
          items:
            type: string
          description: An array of messages describing the errors
          example:
            - The request is missing the required field `name`
    UnauthorizedBarer:
      type: object
      properties:
        errors:
          type: array
          items:
            type: string
          description: An array of messages describing the errors
          example:
            - 'Not Authorization: Bearer <token> provided or wrong value'
  responses:
    BadRequest:
      description: Bad request if one of the required parameters is missing
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    UnauthorizedBearer:
      description: Unauthorized if the X-API-KEY is not provided or is wrong
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/UnauthorizedBarer'
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-KEY
    MemberBearerAuth:
      type: http
      scheme: bearer
      description: >-
        Bearer token for authentication. The token should be the one returned by
        the "/members/signin"
    BearerAuth:
      type: http
      scheme: bearer
      description: >-
        Bearer token for authentication. The token should be the one returned by
        the /user-tokens endpoint.

````