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

# Create Zip

> Create a new zip archive from remote files

## Overview

The Create Zip endpoint accepts a list of URLs pointing to remote files, downloads them, bundles them into a zip archive, and uploads the result to your configured cloud storage bucket.

Zip creation is **asynchronous** - the endpoint returns immediately with a job ID, and the actual processing happens in the background.

## Endpoint

```
POST /api/v1/zips
```

## Authentication

Requires a valid access token in the `Authorization` header:

```
Authorization: Bearer YOUR_ACCESS_TOKEN
```

See [Authentication](/guides/authentication) for details.

## Request Body

<ParamField body="urls" type="array" required>
  An array of file objects to include in the zip archive. Each object specifies a URL to fetch and the desired filename in the zip.

  **Structure:** Each object must contain:

  * `url` (string, required): URL to fetch the file from. Must be publicly accessible.
  * `filename` (string, required): Desired filename for this file in the zip archive.

  **Requirements:**

  * At least 1 file object required
  * Files will be included in the zip in the order provided

  **Example:**

  ```json theme={null}
  [
    {
      "url": "https://example.com/documents/report.pdf",
      "filename": "monthly-report.pdf"
    },
    {
      "url": "https://example.com/images/photo.jpg",
      "filename": "header-image.jpg"
    }
  ]
  ```
</ParamField>

<ParamField body="bucket_name" type="string" required>
  The name of the S3 or R2 bucket where the zip archive will be stored. This must match a bucket you've configured in your project settings.
</ParamField>

<ParamField body="key" type="string" required>
  The filename for the zip archive in your bucket.

  **Example:** `"reports/monthly-report-2025-11.zip"`
</ParamField>

<ParamField body="service" type="string" optional>
  The cloud storage service to use. Defaults to `"s3"` if not specified.

  **Options:**

  * `"s3"` - AWS S3 (default)
  * `"r2"` - Cloudflare R2
</ParamField>

<ParamField body="metadata" type="object" optional>
  Custom metadata to attach to this zip. Accepts any valid JSON object. This metadata will be:

  * Stored with the zip in the database
  * Returned in all API responses (POST and GET)
  * Included in webhook payloads when the zip completes

  **Use cases:**

  * Track internal references (user IDs, order IDs, etc.)
  * Store custom identifiers for your application
  * Pass through contextual information to webhook handlers

  **Example:**

  ```json theme={null}
  {
    "user_id": "user_123",
    "order_id": "order_456",
    "reference": "Q4-2025"
  }
  ```
</ParamField>

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://api.zipkit.io/v1/zips \
    -H "Authorization: Bearer your-access-token" \
    -H "Content-Type: application/json" \
    -d '{
      "urls": [
        {
          "url": "https://app.zipkit.io/onboarding/success.jpg",
          "filename": "success.jpg"
        },
        {
          "url": "https://app.zipkit.io/onboarding/welcome.txt",
          "filename": "welcome.txt"
        }
      ],
      "bucket_name": "my-bucket",
      "key": "example.zip",
      "service": "s3",
      "metadata": {
        "user_id": "user_123",
        "order_id": "order_456"
      }
    }'
  ```

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

  url = "https://api.zipkit.io/v1/zips"
  headers = {
      "Authorization": "Bearer your-access-token",
      "Content-Type": "application/json"
  }
  data = {
      "urls": [
          {
              "url": "https://app.zipkit.io/onboarding/success.jpg",
              "filename": "success.jpg"
          },
          {
              "url": "https://app.zipkit.io/onboarding/welcome.txt",
              "filename": "welcome.txt"
          }
      ],
      "bucket_name": "my-bucket",
      "key": "example.zip",
      "service": "s3",
      "metadata": {
          "user_id": "user_123",
          "order_id": "order_456"
      }
  }

  response = requests.post(url, json=data, headers=headers)
  zip_data = response.json()
  print(f"zip UUID: {zip_data['data']['uuid']}")
  print(f"Status: {zip_data['data']['status']}")
  ```

  ```javascript Javascript theme={null}
  const response = await fetch('https://api.zipkit.io/v1/zips', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer your-access-token',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      urls: [
        {
          url: 'https://app.zipkit.io/onboarding/success.jpg',
          filename: 'success.jpg'
        },
        {
          url: 'https://app.zipkit.io/onboarding/welcome.txt',
          filename: 'welcome.txt'
        }
      ],
      bucket_name: 'my-bucket',
      key: 'example.zip',
      service: 's3',
      metadata: {
        user_id: 'user_123',
        order_id: 'order_456'
      }
    })
  });

  const zipData = await response.json();
  console.log(`zip UUID: ${zipData.data.uuid}`);
  console.log(`Status: ${zipData.data.status}`);
  ```

  ```php PHP theme={null}
  <?php
  $ch = curl_init();

  curl_setopt($ch, CURLOPT_URL, 'https://api.zipkit.io/v1/zips');
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'Authorization: Bearer your-access-token',
      'Content-Type: application/json'
  ]);

  $data = [
      'urls' => [
          [
              'url' => 'https://app.zipkit.io/onboarding/success.jpg',
              'filename' => 'success.jpg'
          ],
          [
              'url' => 'https://app.zipkit.io/onboarding/welcome.txt',
              'filename' => 'welcome.txt'
          ]
      ],
      'bucket_name' => 'my-bucket',
      'key' => 'example.zip',
      'service' => 's3',
      'metadata' => [
          'user_id' => 'user_123',
          'order_id' => 'order_456'
      ]
  ];

  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

  $response = curl_exec($ch);
  $zipData = json_decode($response, true);

  echo "zip UUID: " . $zipData['data']['uuid'] . "\n";
  echo "Status: " . $zipData['data']['status'] . "\n";

  curl_close($ch);
  ```

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

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

  func main() {
      url := "https://api.zipkit.io/v1/zips"

      data := map[string]interface{}{
          "urls": []map[string]string{
              {
                  "url":      "https://app.zipkit.io/onboarding/success.jpg",
                  "filename": "success.jpg",
              },
              {
                  "url":      "https://app.zipkit.io/onboarding/welcome.txt",
                  "filename": "welcome.txt",
              },
          },
          "bucket_name": "my-bucket",
          "key":         "example.zip",
          "service":     "s3",
          "metadata": map[string]string{
              "user_id":  "user_123",
              "order_id": "order_456",
          },
      }

      jsonData, _ := json.Marshal(data)

      req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
      req.Header.Set("Authorization", "Bearer your-access-token")
      req.Header.Set("Content-Type", "application/json")

      client := &http.Client{}
      resp, _ := client.Do(req)
      defer resp.Body.Close()

      body, _ := io.ReadAll(resp.Body)

      var result map[string]interface{}
      json.Unmarshal(body, &result)

      zipData := result["data"].(map[string]interface{})
      fmt.Printf("zip UUID: %s\n", zipData["uuid"])
      fmt.Printf("Status: %s\n", zipData["status"])
  }
  ```

  ```java Java theme={null}
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.net.URI;
  import org.json.JSONObject;
  import org.json.JSONArray;

  public class CreateZip {
      public static void main(String[] args) throws Exception {
          String url = "https://api.zipkit.io/v1/zips";

          JSONArray urls = new JSONArray()
              .put(new JSONObject()
                  .put("url", "https://app.zipkit.io/onboarding/success.jpg")
                  .put("filename", "success.jpg"))
              .put(new JSONObject()
                  .put("url", "https://app.zipkit.io/onboarding/welcome.txt")
                  .put("filename", "welcome.txt"));

          JSONObject metadata = new JSONObject()
              .put("user_id", "user_123")
              .put("order_id", "order_456");

          JSONObject data = new JSONObject()
              .put("urls", urls)
              .put("bucket_name", "my-bucket")
              .put("key", "example.zip")
              .put("service", "s3")
              .put("metadata", metadata);

          HttpClient client = HttpClient.newHttpClient();
          HttpRequest request = HttpRequest.newBuilder()
              .uri(URI.create(url))
              .header("Authorization", "Bearer your-access-token")
              .header("Content-Type", "application/json")
              .POST(HttpRequest.BodyPublishers.ofString(data.toString()))
              .build();

          HttpResponse<String> response = client.send(request,
              HttpResponse.BodyHandlers.ofString());

          JSONObject zipData = new JSONObject(response.body())
              .getJSONObject("data");

          System.out.println("zip UUID: " + zipData.getString("uuid"));
          System.out.println("Status: " + zipData.getString("status"));
      }
  }
  ```

  ```ruby Ruby theme={null}
  require 'net/http'
  require 'json'

  uri = URI('https://api.zipkit.io/v1/zips')
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(uri.path, {
    'Authorization' => 'Bearer your-access-token',
    'Content-Type' => 'application/json'
  })

  request.body = {
    urls: [
      {
        url: 'https://app.zipkit.io/onboarding/success.jpg',
        filename: 'success.jpg'
      },
      {
        url: 'https://app.zipkit.io/onboarding/welcome.txt',
        filename: 'welcome.txt'
      }
    ],
    bucket_name: 'my-bucket',
    key: 'example.zip',
    service: 's3',
    metadata: {
      user_id: 'user_123',
      order_id: 'order_456'
    }
  }.to_json

  response = http.request(request)
  zip_data = JSON.parse(response.body)
  puts "zip UUID: #{zip_data['data']['uuid']}"
  puts "Status: #{zip_data['data']['status']}"
  ```
</RequestExample>

<ResponseExample>
  ```json Response theme={null}
  {
    "data": {
      "uuid": "clx7g2m4p0000xyz123abc",
      "status": "none",
      "metadata": {
        "user_id": "user_123",
        "order_id": "order_456"
      }
    }
  }
  ```
</ResponseExample>

<ResponseField name="data" type="object">
  The response data object containing zip information.
</ResponseField>

<ResponseField name="data.uuid" type="string">
  Unique identifier (UUID) for this zip job. Use this to retrieve the zip status via GET /api/v1/zips/:id.
</ResponseField>

<ResponseField name="data.status" type="string">
  Current status of the zip job. Will be `"none"` when first created.

  **Possible values:**

  * `none` - Job created, not yet started processing
  * `processing` - Currently downloading files and creating zip
  * `succeeded` - zip successfully created and uploaded to your bucket
  * `failed` - Job failed during processing
</ResponseField>

<ResponseField name="data.metadata" type="object">
  The custom metadata object provided in the request. Returns the exact metadata you sent, or an empty object `{}` if none was provided.
</ResponseField>

## Zip Creation Process

After you create a zip job, ZipKit performs these steps asynchronously:

1. **Validates** the bucket ID and URLs
2. **Downloads** each file from the provided URLs
3. **Creates** a zip archive containing all downloaded files
4. **Uploads** the zip to your configured cloud storage bucket
5. **Sends** a webhook notification (if configured)

The entire process typically takes a few seconds to a few minutes, depending on:

* Number of files
* Size of files
* Network speed when downloading from source URLs
* Upload speed to your cloud storage

## Checking Status

The Create Zip endpoint returns immediately - it doesn't wait for the zip to be created. To check the status:

**Use webhooks (recommended)**: Configure a webhook to receive automatic notifications when the zip completes

## Error Responses

All errors follow a consistent JSON format:

```json theme={null}
{
  "error": {
    "type": "error_type",
    "code": "error_code",
    "message": "Human-readable description",
    "param": "field_name or null"
  }
}
```

### 401 Unauthorized

Returned when authentication fails.

#### Missing Authorization Header

```json theme={null}
{
  "error": {
    "type": "authentication_error",
    "code": "authentication_required",
    "message": "No API key provided. Include your API key in the Authorization header using Bearer auth.",
    "param": null
  }
}
```

#### Invalid API Key

```json theme={null}
{
  "error": {
    "type": "authentication_error",
    "code": "invalid_api_key",
    "message": "Invalid API key provided.",
    "param": null
  }
}
```

**Solution**: Check that you're including a valid `Authorization` header.

### 404 Not Found

Returned when the specified bucket doesn't exist or doesn't belong to your project.

**Solution**: Verify the `bucket_name` matches a bucket you've configured in your project.

### 422 Unprocessable Entity

Returned when a required parameter is missing or invalid.

#### Missing Required Parameter

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_missing",
    "message": "Missing required parameter: bucket_name",
    "param": "bucket_name"
  }
}
```

**Common causes:**

* Missing `bucket_name`, `key`, or `urls` parameter

#### Invalid Parameter

```json theme={null}
{
  "error": {
    "type": "invalid_request_error",
    "code": "parameter_invalid",
    "message": "is invalid",
    "param": "urls"
  }
}
```

**Common causes:**

* `urls` is not an array or is empty
* Invalid URL format
* Invalid data types

## Best Practices

<AccordionGroup>
  <Accordion title="Validate URLs before sending" icon="check">
    Make sure all URLs are accessible and return the expected files. ZipKit will attempt to download from each URL - if a URL is inaccessible, the zip job may fail.
  </Accordion>

  <Accordion title="Use descriptive keys" icon="file">
    Specify a meaningful `key` parameter to organize your zips in your bucket (e.g., `"reports/monthly-2025-11.zip"` instead of letting ZipKit auto-generate one).
  </Accordion>

  <Accordion title="Consider file sizes" icon="database">
    Very large files or many files can take longer to process. Use webhooks to track completion.
  </Accordion>

  <Accordion title="Use descriptive filenames" icon="tag">
    Provide clear, descriptive filenames for each file in the zip (e.g., `monthly-report-2025-11.pdf` instead of `file.pdf`). This makes the zip contents easier to understand for end users.
  </Accordion>

  <Accordion title="Configure buckets properly" icon="bucket">
    Ensure your destination bucket exists and has proper write permissions for the credentials configured in your project.
  </Accordion>
</AccordionGroup>

## Zip File Location

When the zip is complete, it will be stored in your specified bucket at the `key` you provided.

**Example:**
If you set `"key": "reports/monthly-2025-11.zip"`, the zip will be stored at:

```
my-bucket/reports/monthly-2025-11.zip
```

You can then access the file using your cloud storage provider's SDK, console, or direct URL.

## Limits and Quotas

<Note>
  **Rate Limits**: API rate limits may apply depending on your plan. Contact support for details on your specific limits.
</Note>
