# curl and the HTTP API

> The CLI and the MCP tool both call the HTTP API at https://app.tmphost.io/api/v1. An agent with only `curl` can call it directly: with a Bearer token, or, with tight limits, without signing in.

HTML version: https://tmphost.io/docs/api/

## Prefer the CLI when possible

**If the agent can install the [CLI](https://tmphost.io/docs/cli.md), use it instead of curl.** `tmphost file upload dist/` uploads a whole directory in one command, keeps its token fresh, checks paths and credits before sending anything, and prints the URLs. With curl the agent does each of those by hand, one request per file. Install it with one line ([install](https://tmphost.io/docs/cli/#install)), then run `tmphost login`.

Use curl only when installing a binary is not an option, or for a quick one-file upload without signing in. Like the CLI, curl sends files from disk, so their bytes never pass through the model's context.

## With or without a token

| | With a token | Without a token |
| --- | --- | --- |
| How | `Authorization: Bearer <token>` | `Origin: https://tmphost.io`, and no Authorization header |
| TTL | Default 30 minutes; the maximum is in `/limits` | At most 10 minutes |
| File size | The account's `maxUploadBytes` | 1 MB per file |
| Quota | Credits: 250 every 30 days | Per IP address, per 30 minutes: 5 new folders, 5 file writes, 4 MB in all |
| Calls | All of them | Only `/limits`, create a folder, write a file |
| Cost | Credits per file written | Free |

## Get a token

Tokens come from the OAuth sign-in at https://app.tmphost.io, which needs a browser once. The simplest way to get one is the CLI: `tmphost login` saves an access token to its config file, and `tmphost whoami` refreshes it when it is about to expire.

```sh
tmphost login --server https://app.tmphost.io
tmphost whoami > /dev/null
TOKEN=$(jq -r .accessToken ~/.config/tmphost/config.json)
API=https://app.tmphost.io/api/v1
```

The config file is `$TMPHOSTIO_CONFIG`, or `tmphost/config.json` in the user config directory: `~/.config` on Linux, `~/Library/Application Support` on macOS, `%AppData%` on Windows. An access token lasts 1 hour. When a call answers 401, run `tmphost whoami` again and re-read the token; don't trade the refresh token yourself, or the CLI's saved one may stop working.

## With a token

Every call below sends `-H "Authorization: Bearer $TOKEN"`. Responses are JSON; ids, times and sizes here are examples.

### Check the limits

```sh
curl -sS -H "Authorization: Bearer $TOKEN" $API/limits
```

Returns `defaultTtlMinutes`, `maxTtlMinutes`, `maxUploadBytes`, `billingBlockMinutes` and `billingUnitBytes` for the account.

### Create a folder

```sh
curl -sS -X POST -H "Authorization: Bearer $TOKEN" $API/folders
```

```json
{"folder":{"id":"k7q2m9xa","createdAt":"2026-10-21T07:15:20Z","expiresAt":"2026-10-21T07:45:20Z","url":"https://k7q2m9xa.file.tmphost.io/","title":"","defaultTitle":"k7q2m9xa","agent":"tmphost CLI"}}
```

Without a body the folder lives 30 minutes, the recommended default: send `{"ttlMinutes":N}` only when the agent knows it needs longer. `agent` is the app the token was issued to.

### Upload a file

```sh
curl -sS -X PUT -H "Authorization: Bearer $TOKEN" \
    --data-binary @dist/index.html $API/folders/k7q2m9xa/files/index.html
curl -sS -X PUT -H "Authorization: Bearer $TOKEN" \
    --data-binary @dist/assets/app.js $API/folders/k7q2m9xa/files/assets/app.js
```

```json
{"file":{"path":"index.html","size":1342,"updatedAt":"2026-10-21T07:15:21Z","url":"https://k7q2m9xa.file.tmphost.io/index.html","agent":"tmphost CLI"},"creditsSpent":1}
{"file":{"path":"assets/app.js","size":20480,"updatedAt":"2026-10-21T07:15:22Z","url":"https://k7q2m9xa.file.tmphost.io/assets/app.js","agent":"tmphost CLI"},"creditsSpent":1}
```

One `PUT` per file; the raw request body is the file and the URL path after `/files/` is its path in the folder. Use `--data-binary @file`: `-d` strips newlines and would change the file. A file already at that path is replaced in place, same URL, same expiry. Each write costs 1 credit per started MB per started 30 minutes left in the folder.

### List a folder, and all folders

```sh
curl -sS -H "Authorization: Bearer $TOKEN" $API/folders/k7q2m9xa
curl -sS -H "Authorization: Bearer $TOKEN" $API/folders
```

`GET /folders/{id}` returns `{"folder":{...},"files":[{"path","size","updatedAt","url"},...]}`; `GET /folders` returns `{"folders":[...]}`. The folder's `openUrl` is what to share: the folder URL when `index.html` sits at its root, else the URL of its only file.

### TTL: 30 minutes, then extend

Start every folder with the 30 minute default and extend it while it is still needed, instead of guessing a long TTL up front. Credits are charged by lifetime, so a folder that turns out to be needed for 10 minutes costs a quarter of one created for 2 hours, and nothing stays public longer than it has to. Before `expiresAt`, add time with `POST /folders/{id}/extend`; once a folder has expired it is gone, so extend a few minutes early.

### Download, extend, rename, delete

```sh
curl -sS -H "Authorization: Bearer $TOKEN" -o app.js $API/folders/k7q2m9xa/files/assets/app.js
curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
    -H 'Content-Type: application/json' -d '{"minutes":60}' $API/folders/k7q2m9xa/extend
curl -sS -X PATCH -H "Authorization: Bearer $TOKEN" \
    -H 'Content-Type: application/json' -d '{"title":"build preview"}' $API/folders/k7q2m9xa
curl -sS -X DELETE -H "Authorization: Bearer $TOKEN" $API/folders/k7q2m9xa/files/assets/app.js
curl -sS -X DELETE -H "Authorization: Bearer $TOKEN" $API/folders/k7q2m9xa
```

A download is always sent as an attachment (`application/octet-stream`); the public URL serves the file with its real type. `extend` returns `{"folder":{...},"creditsSpent":N}` and charges each file for the added time. The deletes answer `204` with no body.

## Without a token

The upload box on the home page works without signing in, and curl can make the same calls. An anonymous request must carry `Origin: https://tmphost.io` (or `https://app.tmphost.io`) and no `Authorization` header. This is a real session, run on 2026-10-21:

```sh
API=https://app.tmphost.io/api/v1
curl -sS -H 'Origin: https://tmphost.io' $API/limits
# {"defaultTtlMinutes":10,"maxTtlMinutes":10,"maxUploadBytes":1048576,"billingBlockMinutes":30,"billingUnitBytes":1048576,"anonymous":true,"fileOps":5,"fileBytes":4194304,"fileOpsWindowMinutes":30}

curl -sS -X POST -H 'Origin: https://tmphost.io' \
    -H 'Content-Type: application/json' -d '{"ttlMinutes":10}' $API/folders
# {"folder":{"id":"g7znawfm","createdAt":"2026-10-21T07:15:20Z","expiresAt":"2026-10-21T07:25:20Z","url":"https://g7znawfm.file.tmphost.io/","title":"","defaultTitle":"g7znawfm","agent":"Web"}}

curl -sS -X PUT -H 'Origin: https://tmphost.io' \
    --data-binary @index.html $API/folders/g7znawfm/files/index.html
# {"file":{"path":"index.html","size":12,"updatedAt":"2026-10-21T07:15:20Z","url":"https://g7znawfm.file.tmphost.io/index.html","agent":"Web"},"creditsSpent":0}
```

Keep the folder `id` and the URLs from these responses: without a token there is no call to look them up again.

## Pitfalls without a token

- **No Origin header, no access.** Plain `curl $API/limits` answers `401 {"error":"unauthorized"}`. Every call needs `Origin: https://tmphost.io` or `https://app.tmphost.io` (a `Referer` on one of them also works). Any other origin is 401 too.
- **A bad token does not fall back.** An expired or wrong `Authorization` header is 401 even with the Origin header. Send no Authorization header at all.
- **10 minutes, at most.** The folder and its URLs are gone after 10 minutes, and `{"ttlMinutes":60}` answers `400 {"error":"invalid ttl: must be between 1 and 10 minutes"}`. A folder cannot be extended without a token.
- **1 MB per file.** A bigger file answers `413 {"error":"upload too large: \"big.bin\" is over the 1 MB file limit"}`.
- **A small quota per IP address.** In any 30 minutes: 5 new folders, 5 file writes and 4 MB written. Retries and replacements count. Past it, calls answer `429`, such as `{"error":"anonymous quota exceeded: at most 5 files per 30 minutes"}`. A 5-file site uses the whole window.
- **Write-only.** Listing, reading, downloading, renaming, extending and deleting all need a token (401 without one). A file stays public until its folder expires; there is no way to take it down early.
- **The IP address is the account.** Everyone behind the same address (an office NAT, a VPN, a shared CI runner) shares one quota and one set of folders, so anyone there who knows a folder id can overwrite its files. If the agent's IP address changes, it can no longer add files to its folders.
- **Meant for the web upload box.** Anonymous access exists for people dropping files on the site; its limits may tighten. For anything an agent does repeatedly, install the [CLI](https://tmphost.io/docs/cli.md) and sign in, or [send a token](https://tmphost.io/docs/api/#token) with curl.

## Errors

Errors are JSON, `{"error":"<message>"}`, except a malformed request, which answers a plain `Bad Request`. Use `curl -sS` without `-f` to see the message.

| Status | Meaning |
| --- | --- |
| 400 | A TTL out of range, an invalid path or title, or a malformed body or folder id. |
| 401 | `unauthorized`: no valid token, and not an anonymous request from an allowed Origin; or the call needs a token. |
| 402 | `insufficient credits: N needed`: the account can't pay for the write or the extension. |
| 404 | `not found`: the folder or file doesn't exist, has expired, or belongs to someone else. |
| 413 | `upload too large`: the file is over `maxUploadBytes`. |
| 429 | `anonymous quota exceeded`: wait for the 30 minute window, or sign in. |

## Endpoints

All under https://app.tmphost.io/api/v1.

| Call | Without a token | Description |
| --- | --- | --- |
| `GET /limits` | Yes | TTL, size and billing limits for the caller. |
| `POST /folders` | Yes | Create a folder. Body `{"ttlMinutes":N}`, optional. |
| `PUT /folders/{id}/files/{path}` | Yes | Write a file; the raw body is its content. |
| `GET /folders` | No | List live folders. |
| `GET /folders/{id}` | No | A folder and its files. |
| `PATCH /folders/{id}` | No | Rename a folder. Body `{"title":"..."}`. |
| `POST /folders/{id}/extend` | No | Make a folder live longer. Body `{"minutes":N}`. |
| `DELETE /folders/{id}` | No | Delete a folder and its files. |
| `GET /folders/{id}/files/{path}` | No | Download a file. |
| `DELETE /folders/{id}/files/{path}` | No | Delete one file. |
| `GET /users/me` | No | The account, its credits and renewal time. |

Paths are at most 5 directories deep, with no `.` or `..` segments and no backslashes; percent-encode spaces and other special characters in the URL.
