<!-- section: Start here · status: building · source: docs/start/01-quickstart.md -->

> From a new account to your first successful API call, in about two minutes.


# Quickstart

Two minutes from nothing to a working call. You need an account and a project — if you
have one open in the console, you already have everything below.

## 1. Your base URL and key

Every request goes to the same base and carries the same kind of key:

```bash
export BASE="https://<your-deployment>"
export KEY="sk_live_…"          # the secret key from your project page
```

Your project page shows both. The key is displayed **once**, when it is issued — if you
have lost it, issue another; keys are free and revoking the old one takes a click.

Two kinds exist and the difference matters:

| | Where it goes | What it can do |
|---|---|---|
| `sk_…` **secret** | On your server, only | Everything |
| `pk_…` **publishable** | In your app's frontend | Sign users in, and nothing else |

A publishable key is safe in a browser *because* it can't reach the management APIs. A
secret key in a browser is a breach, so we make it impossible rather than discouraged —
the endpoints that matter answer no preflight at all.

## 2. Your first call

```bash
curl "$BASE/v1/files" -H "authorization: Bearer $KEY"
```

```json
{ "files": [], "total": 0, "page": 1, "totalPages": 0, "nextCursor": null }
```

That's a working integration. An empty list is the correct answer for a new project, and
the response shape is the one you'll get with a thousand files in it.

If you got `401`, the key is wrong or revoked. If you got nothing at all, `$BASE` is
unset — the most common first mistake, and the reason it is spelled out above.

## 3. Upload something

Uploading is three calls, and the middle one doesn't touch us at all:

```bash
# a) Ask where to put it
curl -X POST "$BASE/v1/files/upload-intent" \
  -H "authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"filename":"hello.txt","contentType":"text/plain","size":11}'
```

```bash
# b) Send the bytes straight to storage, using the url that came back
curl -X PUT "<uploadUrl>" -H "content-type: text/plain" --data-binary "hello world"
```

```bash
# c) Tell us it landed
curl -X POST "$BASE/v1/files/confirm" \
  -H "authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"id":"<id from step a>"}'
```

The bytes never pass through us, which is why a 500 MB upload costs you one request
rather than a timeout. [Upload files](/docs/files-api/upload-files) has the full shape.

## 4. Sign someone in

Auth is enabled per project, because it gives you a Postgres database of your own — open
**Auth** on your project page and turn it on. Then:

```bash
curl -X POST "$BASE/v1/auth/sign-up/email" \
  -H "authorization: Bearer $KEY" -H "content-type: application/json" \
  -d '{"email":"ada@example.com","password":"correct-horse-battery","name":"Ada"}'
```

A session cookie comes back, and that user now exists in a database no other project can
reach. [Auth](/docs/auth-api) covers social login, passkeys, two-factor, organizations
and SSO — all of it on the same key.

## What to read next

- [Files API](/docs/files-api) — every endpoint, one page each
- [Auth](/docs/auth-api) — sessions, tokens, and the rest of it
- [Usage and billing](/docs/platform/usage-and-billing) — what any of this costs

Nothing here assumes a client library. There is no SDK yet, so this HTTP API is the whole
interface, and every example above is a request you can paste into a terminal.
