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

# Start Contact Document Upload

> Reserves a document slot on the contact — and the declared bytes against your company's contact-document storage — and returns a presigned URL to upload the file to. The file never passes through this API.

1. `POST` this endpoint with the file's name, MIME type and size.
2. `PUT` the raw file bytes to the returned `uploadUrl`, sending `Content-Type` exactly as returned in `contentType`. Do **not** send your API key with this request — the URL carries its own authorisation.
3. `POST /contacts/{id}/documents/{documentId}/complete` to confirm the upload.

The document stays `PENDING`, and is never used on a call, until step 3 succeeds. Uploads that are never completed are cleaned up automatically after 24 hours and release both the slot and the bytes they reserved.

A contact holds at most 5 uploaded documents. The company's total contact-document storage is capped by its plan; a request that would exceed it is refused with a `400` naming the bytes in use, the ceiling and the size requested.

This endpoint has a lower rate limit than the rest of the API.

Reserves a document slot on a contact and returns a presigned URL to upload the file to.

## Overview

The same three-step flow as an [agent knowledge upload](/api-reference/agents/knowledge/start-upload), because the file bytes go straight to storage and never pass through this API.

<Steps>
  <Step title="Reserve a slot">
    `POST /contacts/{id}/documents/uploads` with the file's name, MIME type and size. You get back a `documentId` and an `uploadUrl`.

    <Note>
      `sizeBytes` must be the file's **exact** byte count. It is signed into the upload URL, and it is
      also what is reserved against your company's contact-document storage — so a larger file is
      refused at completion, not quietly admitted.
    </Note>
  </Step>

  <Step title="Upload the file">
    `PUT` the raw file bytes to `uploadUrl`, sending `Content-Type` exactly as returned in `contentType`.

    <Warning>
      Do **not** send your API key with this request. The upload URL carries its own authorisation.
    </Warning>
  </Step>

  <Step title="Complete the upload">
    `POST /contacts/{id}/documents/{documentId}/complete`. The stored file is verified, and only then does the document become usable on calls.
  </Step>
</Steps>

## Example

```bash theme={null}
# 1. Reserve a slot
curl -X POST https://api.instaview.sk/contacts/$CONTACT_ID/documents/uploads \
  -H "Authorization: Bearer $INSTAVIEW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "fileName": "cover-letter.pdf",
    "mimeType": "application/pdf",
    "sizeBytes": 482133,
    "title": "Cover letter"
  }'

# 2. Upload the bytes (no API key on this request)
curl -X PUT "$UPLOAD_URL" \
  -H "Content-Type: application/pdf" \
  --data-binary @cover-letter.pdf

# 3. Confirm
curl -X POST https://api.instaview.sk/contacts/$CONTACT_ID/documents/$DOCUMENT_ID/complete \
  -H "Authorization: Bearer $INSTAVIEW_API_KEY"
```

## Supported File Types

PDF, DOC, DOCX, TXT, MD, CSV, TSV, JSON, XML and YAML — the same set as agent knowledge.

## Limits

| Limit                                | Value                                                  |
| ------------------------------------ | ------------------------------------------------------ |
| Maximum file size                    | 20 MiB (20,971,520 bytes)                              |
| Uploaded documents per contact       | 5 (the `CV` document does not count)                   |
| Contact-document storage per company | Plan-tiered; the `400` names the figures when exceeded |
| Upload URL validity                  | 15 minutes                                             |
| Unfinished uploads reclaimed after   | 24 hours                                               |

<Info>
  Both the slot and the bytes are **reserved** the moment this call succeeds, pending uploads included.
  That is what stops several parallel requests from each passing the same check. An abandoned upload
  releases both automatically after 24 hours — or immediately, if you `DELETE` the document.
</Info>

The storage ceiling is per company and per plan. A contact is not a document library, and the aggregate is the number that actually costs money — every byte here is stored and, once the contact is on a call, sent to the voice provider. When a reservation would take the company past its ceiling, the response is a `400` of the form:

```text theme={null}
Contact document storage limit reached for this company: 248MB of 250MB in use, and this file needs 3MB more.
Delete contact documents or upgrade the plan.
```

## Rate Limiting

Shares the agent knowledge upload limit: **6 requests per minute and 60 per hour** per API key for this endpoint, with completion limited separately and more generously. A `429` carries a `Retry-After` header.

## Error Scenarios

* **400 Bad Request**: Unsupported file type, a size over 20 MB, the contact already holds 5 documents, or the company is at its storage limit
* **404 Not Found**: No contact with that id belongs to your API key's company
* **429 Too Many Requests**: Upload rate limit exceeded — wait for `Retry-After` seconds

## Related Resources

* [Complete Contact Document Upload](/api-reference/contacts/documents/complete-upload)
* [List Contact Documents](/api-reference/contacts/documents/list-documents)


## OpenAPI

````yaml POST /contacts/{id}/documents/uploads
openapi: 3.0.0
info:
  title: InstaView API
  description: |-
    InstaView API Documentation

    ## Authentication

    All endpoints require API key authentication using Bearer token:
    ```
    Authorization: Bearer sk_your_api_key_here
    ```

    ## API Key Management

    The API Key module provides comprehensive key management for:
    - **Direct Client Keys**: Company-scoped keys for your applications
    - **ATS Partner Keys**: Resource-scoped keys for ATS integrations

    ### Key Features
    - HMAC-SHA256 hashing for API key storage
    - Configurable rate limiting
    - Comprehensive audit logging
    - Company-level isolation
    - Resource scoping for ATS partners
  version: 1.0.0
  contact: {}
servers:
  - url: https://api.instaview.sk
    description: Production API Gateway
security: []
tags: []
paths:
  /contacts/{id}/documents/uploads:
    post:
      tags:
        - Contacts
      summary: Start a contact document upload
      description: >-
        Reserves a document slot on the contact — and the declared bytes against
        your company's contact-document storage — and returns a presigned URL to
        upload the file to. The file never passes through this API.


        1. `POST` this endpoint with the file's name, MIME type and size.

        2. `PUT` the raw file bytes to the returned `uploadUrl`, sending
        `Content-Type` exactly as returned in `contentType`. Do **not** send
        your API key with this request — the URL carries its own authorisation.

        3. `POST /contacts/{id}/documents/{documentId}/complete` to confirm the
        upload.


        The document stays `PENDING`, and is never used on a call, until step 3
        succeeds. Uploads that are never completed are cleaned up automatically
        after 24 hours and release both the slot and the bytes they reserved.


        A contact holds at most 5 uploaded documents. The company's total
        contact-document storage is capped by its plan; a request that would
        exceed it is refused with a `400` naming the bytes in use, the ceiling
        and the size requested.


        This endpoint has a lower rate limit than the rest of the API.
      operationId: PublicContactDocumentsController_requestUpload_v1
      parameters:
        - name: id
          required: true
          in: path
          description: Contact the document belongs to.
          schema:
            type: string
            format: uuid
        - name: companyId
          required: false
          in: query
          description: >-
            Required for ATS API keys to specify which company to access.
            Ignored for standard company API keys.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/PublicCreateKnowledgeUploadDto'
      responses:
        '201':
          description: Slot and bytes reserved, upload URL issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicKnowledgeUploadTicketDto'
        '400':
          description: >-
            Unsupported file type, a size over the limit, the contact already
            holds 5 documents, or the company is at its contact-document storage
            limit (the message names the figures).
        '404':
          description: No contact with that id belongs to the API key's company.
        '429':
          description: >-
            Upload rate limit exceeded. This endpoint has a lower limit than the
            rest of the API — wait for the number of seconds in the
            `Retry-After` header before retrying.
      security:
        - bearer: []
components:
  schemas:
    PublicCreateKnowledgeUploadDto:
      type: object
      properties:
        fileName:
          type: string
          maxLength: 255
          example: benefits-and-perks.pdf
          description: Name of the file being uploaded, including its extension.
        mimeType:
          type: string
          enum:
            - application/pdf
            - application/msword
            - >-
              application/vnd.openxmlformats-officedocument.wordprocessingml.document
            - text/plain
            - text/markdown
            - text/csv
            - text/tab-separated-values
            - application/json
            - application/xml
            - text/xml
            - application/yaml
            - text/yaml
          example: application/pdf
          description: >-
            MIME type of the file. The returned upload URL is signed for this
            exact type, so the PUT must send it back as its `Content-Type`
            header verbatim.
        sizeBytes:
          type: integer
          minimum: 1
          maximum: 20971520
          example: 482133
          description: >-
            Exact size of the file in bytes. This is binding, not an estimate:
            it is signed into the upload URL, so the `PUT` must send precisely
            this many bytes and storage rejects anything else with a 403. Send
            the real byte count of the file you are about to upload; most HTTP
            clients set `Content-Length` for you from the body. Checked again
            against the stored object at completion.
        title:
          type: string
          maxLength: 255
          example: Benefits and perks
          description: Human-readable label for the document. Defaults to `fileName`.
      required:
        - fileName
        - mimeType
        - sizeBytes
    PublicKnowledgeUploadTicketDto:
      type: object
      properties:
        document:
          description: >-
            The reserved document, in PENDING state until the upload is
            completed.
          allOf:
            - $ref: '#/components/schemas/PublicKnowledgeDocumentDto'
        uploadUrl:
          type: string
          example: >-
            https://instaview-agent-knowledge-production.s3.eu-central-1.amazonaws.com/agent-knowledge/...
          description: >-
            Presigned storage URL to PUT the file to. Carries its own
            authorisation — do not send your API key with it.
        uploadMethod:
          type: string
          enum:
            - PUT
          example: PUT
          description: HTTP method the upload URL expects.
        contentType:
          type: string
          example: application/pdf
          description: >-
            The `Content-Type` header the PUT must send. It is part of the URL's
            signature, so any other value is rejected by storage.
        expiresAt:
          format: date-time
          type: string
          example: '2026-08-08T10:30:00.000Z'
          description: >-
            When the upload URL stops working. The document slot outlives it —
            request a new upload if this expires.
        maxBytes:
          type: number
          example: 20971520
          description: >-
            Maximum bytes the stored object may have. A larger file is rejected
            at completion and deleted.
      required:
        - document
        - uploadUrl
        - uploadMethod
        - contentType
        - expiresAt
        - maxBytes
    PublicKnowledgeDocumentDto:
      type: object
      properties:
        id:
          type: string
          format: uuid
          example: 123e4567-e89b-12d3-a456-426614174000
          description: Document ID
        title:
          type: string
          example: Benefits and perks
          description: Human-readable label for the document
        fileName:
          type: string
          example: benefits-and-perks.pdf
          description: Original file name as uploaded
        mimeType:
          type: string
          example: application/pdf
          description: MIME type of the stored file
        sizeBytes:
          type: number
          example: 482133
          description: >-
            Size of the stored file in bytes, as verified against storage.
            Absent until the upload is READY.
        status:
          type: string
          enum:
            - PENDING
            - READY
          example: READY
          description: >-
            `PENDING` while waiting for the file to be uploaded to the presigned
            URL and the upload to be completed; `READY` once the file is
            confirmed stored. Only READY documents are attached to calls.
        isActive:
          type: boolean
          example: true
          description: >-
            Whether the agent may consult this document during a call. Always
            false while the upload is PENDING.
        source:
          type: string
          enum:
            - UPLOAD
            - CV
          example: UPLOAD
          description: >-
            `UPLOAD` for a file attached through the API or the dashboard. `CV`
            for the document the platform produces from a contact's processed CV
            (contacts only); a `CV` document can be deactivated but not renamed
            or deleted. Always `UPLOAD` on an agent's knowledge base.
        createdAt:
          format: date-time
          type: string
          example: '2026-08-08T10:15:00.000Z'
          description: When the document was created
      required:
        - id
        - title
        - status
        - isActive
        - source
        - createdAt
  securitySchemes:
    bearer:
      type: http
      scheme: bearer
      description: API key for authentication using Bearer scheme

````