
# Archive tools

Three in-process archive operations on [fflate](https://github.com/101arrowz/fflate) (MIT, pure-JS) — the same open-source library you could run yourself. Every op is a `POST https://api.relaystation.ai/v1/archive/<op>`. Everything happens **in memory**: cputools never writes an entry to a filesystem, so there is no path-traversal ("zip-slip") surface — and the extraction is bomb-guarded (below).

## Inputs and outputs

Same uniform shape as the [PDF tools](/docs/pdf-tools): a `file` input source is `{ "inline": "<base64>" }` (≤ 4 MB) or `{ "inputKey": "..." }` (from `POST /v1/cputools/upload-url`, ≤ 50 MB). `zip` takes a `files` **array**. `unzip` returns a **manifest** — `{ entries: [{ name, output }] }` — where each `name` is a sanitized basename and each `output` is the uniform envelope (inline ≤ 4 MB, else a presigned `outputUrl`). `zip`/`gzip` return a single output envelope.

## Billing

Per MB of **input** (compressed) bytes, **minimum 1 MiB**, at `cputools.price.archive.<op>.per_mb_micros` (launch default $0.0002 / MB). Billing is on the input you send — so `unzip` bills the compressed archive, not its (larger) expansion. The live `402` challenge is authoritative.

| Op | Billed on | Rate |
|---|---|---|
| `zip` | total input MB | $0.0002 / MB |
| `unzip` | input MB | $0.0002 / MB |
| `gzip` | input MB | $0.0002 / MB |

## zip

Zip one or more files into a single archive. `files` is an array of input sources (≥ 1, ≤ `cputools.archive.max_files_per_zip`, default 1000); optional `filenames` must match `files.length`. Filenames are reduced to a **sanitized basename** (path separators, `..`, drive prefixes, and null bytes stripped); collisions get a `-N` suffix.

```json
POST /v1/archive/zip
{ "files": [{"inline":"<b64>"}, {"inline":"<b64>"}], "filenames": ["a.txt", "b.txt"] }
```

## unzip

Extract a zip to a manifest of entries. **Zip-bomb-guarded** in two layers (see below). A nested `.zip` entry is returned as **raw bytes — never recursively extracted** (one level only).

```json
POST /v1/archive/unzip
{ "file": {"inline":"<b64 zip>"} }
```

## gzip

Compress or decompress a single file. `mode` is `compress` (→ `application/gzip`) or `decompress` (→ `application/octet-stream`). Decompress is guarded by the same uncompressed-size cap (gzip bombs too).

```json
POST /v1/archive/gzip
{ "file": {"inline":"<b64>"}, "mode": "compress" }
```

## The zip-bomb defense (documented limits)

A decompression bomb — a tiny archive that expands to gigabytes — is stopped by **layered, operator-tunable caps**, and the rejection is a feature:

1. **Pre-charge directory check.** Before any charge, cputools reads the archive's central directory and rejects — with a `422`, **no charge** — an archive whose declared uncompressed total exceeds `cputools.archive.max_uncompressed_bytes` (default **128 MiB**), whose entry count exceeds `cputools.archive.max_entries` (default 10000), or whose per-entry declared compression ratio exceeds `cputools.archive.max_compression_ratio` (default 1000:1).
2. **Authoritative running-total during extraction.** Declared sizes can lie, so extraction streams and sums the **actual** decompressed bytes, aborting the instant it crosses the cap — memory never balloons (and the charge is refunded).
3. **Encrypted/password-protected zips are rejected** (`422 UNSUPPORTED_ENCRYPTED_ZIP`) — cputools doesn't decrypt.

## Sample

```bash
curl -X POST https://api.relaystation.ai/v1/archive/unzip \
  -H 'X-Payment: <base64 EIP-3009 auth>' \
  -H 'Idempotency-Key: unzip-bundle-20260609' \
  -H 'Content-Type: application/json' \
  -d '{"file":{"inline":"<b64 zip>"}}'
```

## Errors

- `402 PAYMENT_REQUIRED` — no valid payment.
- `422 ARCHIVE_TOO_LARGE` — declared (pre-charge) or actual (in-flight) uncompressed output over the cap.
- `422 ARCHIVE_TOO_MANY_ENTRIES` / `422 ARCHIVE_RATIO_EXCEEDED` — over the entry-count or compression-ratio cap (pre-charge).
- `422 UNSUPPORTED_ENCRYPTED_ZIP` — an encrypted/password-protected entry.
- `422 ARCHIVE_INVALID` — not a valid zip/gzip (or a ZIP64 archive, which is unsupported).
- `422 ARCHIVE_TOO_MANY_FILES` / `422 FILENAME_COUNT_MISMATCH` — zip input over the file cap, or a `filenames` length that doesn't match `files`.

## Next

[PDF tools](/docs/pdf-tools) · [CSV tools](/docs/csv-tools) · [Image tools](/docs/image-tools) · [Utils tools](/docs/utils-tools) · [Generate tools](/docs/generate-tools) · [Pricing](/pricing) · [API reference](/api-reference)
