> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.pinnacle.sh/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.pinnacle.sh/_mcp/server.

# Upload File

POST https://api.pinnacle.sh/tools/files/upload
Content-Type: application/json

Generate presigned URLs that let you upload files directly to our storage and allow your users to download them securely.

Reference: https://docs.pinnacle.sh/api-reference/tools/upload-file

## Authentication

- `PINNACLE-API-KEY` header (required) — API Key authentication via header

## Request

### Body (application/json)

This endpoint expects a fileUploadSchema.

- `contentType` (string, required) — MIME type of your file.&#x20; Supported file types: * Audio: mp3, mp4, mpeg, ogg, aac, webm, wav, 3gpp, amr * Video: mp4, mpeg, quicktime, webm, 3gpp, H.264, m4v * Image: jpeg, png, gif, bmp, tiff, webp * Documents: pdf, csv, rtf, calendar, vcard
- `size` (integer, required) — Size of your file in bytes. Should be less than 100 MB.
- `name` (string, optional) — Name of your file.
- `options` (UploadFileOptions, optional) — Additional configurations for your file.

## Response

### 200

Successfully uploaded the file.

- `uploadUrl` (string, required) — Presigned URL for uploading your file to storage.
- `downloadUrl` (string, required) — Presigned URL for downloading your file.
- `metadata` (PinnacleFileUploadMetadata, required)

## Errors

### 400 Bad Request Error

Validation failed. The payload has missing required fields and/or invalid types.&#x20; See [https://zod.dev/error-formatting](https://zod.dev/error-formatting) for more information.

- `description` (string, required) — Human-readable summary of validation failures.
- `errors` (ZodErrorErrors, required) — Structured dictionary of issues containing two main sections: - `errors`: Array of global validation errors not tied to specific fields - `properties`: Object mapping field names to their specific validation errors, where each field contains an `errors` array

### 401 Unauthorized Error

The request lacks valid authentication credentials or the provided credentials are invalid.&#x20; Ensure you're including a valid API key in the request headers and that your account has the necessary permissions to access this endpoint.

- `error` (string, required) — Human-readable description of the error that occurred, corresponding to the HTTP status code.

### 403 Forbidden Error

Your subscription does not include access to this feature.&#x20; This occurs when attempting to use functionality that requires a higher subscription tier. Please upgrade your subscription to access this feature.

- `error` (string, required) — Human-readable description of the error that occurred, corresponding to the HTTP status code.

### 500 Internal Server Error

An unexpected error occurred on Pinnacle's servers while processing your request.&#x20; If this error persists, please contact support with the request details.

- `error` (string, required) — Human-readable description of the error that occurred, corresponding to the HTTP status code.

## Types

### UploadFileOptions

Additional configurations for your file.

- `deleteAt` (string, optional) — Set a deletion date for your file in ISO 8601 format. After this date, the file will be automatically deleted from our storage. If this field is not provided, the file will not be deleted. You can still schedule deletion or delete the file manually in the Storage page in the dashboard.
- `download` (DownloadOptions, optional) — Configure download settings for your uploaded file.

### PinnacleFileUploadMetadata

- `fileName` (string, required) — Name of the uploaded file.
- `contentType` (string, required) — MIME type of the file.
- `expiresAt` (string, required, nullable) — Expiration date in ISO 8601 format for file download access. Null indicates that `download.expiresAt` was not provided and the expiration time is defaulted to one hour after uploading.
- `deleteAt` (string, optional, nullable) — Deletion date for the file in ISO 8601 format. After this date, the file will be automatically deleted from our storage. If this field is not provided, the file will not be deleted. You can still schedule deletion or delete the file manually in the Storage page in the dashboard.

### ZodErrorErrors

Structured dictionary of issues containing two main sections: - `errors`: Array of global validation errors not tied to specific fields - `properties`: Object mapping field names to their specific validation errors, where each field contains an `errors` array

### DownloadOptions

Configure download settings for your uploaded file.

- `expiresAt` (string, optional) — Set an expiration date for file download access. If this field is not provided, then a short permalink is generated instead.

## Examples

**Request**

```json
{
  "contentType": "image/jpeg",
  "size": 1024,
  "name": "test.jpg",
  "options": {
    "deleteAt": "2025-12-31T23:59:59Z",
    "download": {
      "expiresAt": "2025-06-30T12:00:00.000Z"
    }
  }
}
```

**Response**

```json
{
  "uploadUrl": "https://server.trypinnacle.app/storage/v2/object/upload/sign/vault/3/test.jpg?token=example",
  "downloadUrl": "https://server.trypinnacle.app/storage/v2/object/sign/vault/3/test.jpg?token=example",
  "metadata": {
    "fileName": "test.jpg",
    "contentType": "image/jpeg",
    "expiresAt": "2025-08-30T12:00:00.000Z",
    "deleteAt": "2025-12-31T23:59:59Z"
  }
}
```

**SDK Code**

```python Upload Result
import requests

url = "https://api.pinnacle.sh/tools/files/upload"

payload = {
    "contentType": "image/jpeg",
    "size": 1024,
    "name": "test.jpg",
    "options": {
        "deleteAt": "2025-12-31T23:59:59Z",
        "download": { "expiresAt": "2025-06-30T12:00:00.000Z" }
    }
}
headers = {
    "PINNACLE-API-KEY": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript Upload Result
const url = 'https://api.pinnacle.sh/tools/files/upload';
const options = {
  method: 'POST',
  headers: {'PINNACLE-API-KEY': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"contentType":"image/jpeg","size":1024,"name":"test.jpg","options":{"deleteAt":"2025-12-31T23:59:59Z","download":{"expiresAt":"2025-06-30T12:00:00.000Z"}}}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Upload Result
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.pinnacle.sh/tools/files/upload"

	payload := strings.NewReader("{\n  \"contentType\": \"image/jpeg\",\n  \"size\": 1024,\n  \"name\": \"test.jpg\",\n  \"options\": {\n    \"deleteAt\": \"2025-12-31T23:59:59Z\",\n    \"download\": {\n      \"expiresAt\": \"2025-06-30T12:00:00.000Z\"\n    }\n  }\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("PINNACLE-API-KEY", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Upload Result
require 'uri'
require 'net/http'

url = URI("https://api.pinnacle.sh/tools/files/upload")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["PINNACLE-API-KEY"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"contentType\": \"image/jpeg\",\n  \"size\": 1024,\n  \"name\": \"test.jpg\",\n  \"options\": {\n    \"deleteAt\": \"2025-12-31T23:59:59Z\",\n    \"download\": {\n      \"expiresAt\": \"2025-06-30T12:00:00.000Z\"\n    }\n  }\n}"

response = http.request(request)
puts response.read_body
```

```java Upload Result
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.pinnacle.sh/tools/files/upload")
  .header("PINNACLE-API-KEY", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"contentType\": \"image/jpeg\",\n  \"size\": 1024,\n  \"name\": \"test.jpg\",\n  \"options\": {\n    \"deleteAt\": \"2025-12-31T23:59:59Z\",\n    \"download\": {\n      \"expiresAt\": \"2025-06-30T12:00:00.000Z\"\n    }\n  }\n}")
  .asString();
```

```php Upload Result
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.pinnacle.sh/tools/files/upload', [
  'body' => '{
  "contentType": "image/jpeg",
  "size": 1024,
  "name": "test.jpg",
  "options": {
    "deleteAt": "2025-12-31T23:59:59Z",
    "download": {
      "expiresAt": "2025-06-30T12:00:00.000Z"
    }
  }
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'PINNACLE-API-KEY' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp Upload Result
using RestSharp;

var client = new RestClient("https://api.pinnacle.sh/tools/files/upload");
var request = new RestRequest(Method.POST);
request.AddHeader("PINNACLE-API-KEY", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"contentType\": \"image/jpeg\",\n  \"size\": 1024,\n  \"name\": \"test.jpg\",\n  \"options\": {\n    \"deleteAt\": \"2025-12-31T23:59:59Z\",\n    \"download\": {\n      \"expiresAt\": \"2025-06-30T12:00:00.000Z\"\n    }\n  }\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift Upload Result
import Foundation

let headers = [
  "PINNACLE-API-KEY": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "contentType": "image/jpeg",
  "size": 1024,
  "name": "test.jpg",
  "options": [
    "deleteAt": "2025-12-31T23:59:59Z",
    "download": ["expiresAt": "2025-06-30T12:00:00.000Z"]
  ]
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.pinnacle.sh/tools/files/upload")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```