# Postgres

> Create a Postgres database, connect an app or your laptop to it, branch it for an experiment, fork it from a point in time, restore it in place, and set when it sleeps.

A Rytiv database is PostgreSQL 16 on a machine of its own, with its data on a volume and a continuous archive of its write-ahead log. Only apps you attach can reach it, always over TLS. Every database can be **branched** (a copy-on-write copy of its disk, ready in seconds) and **forked** (a full copy from its archive, from now or from a moment in the past), so an agent can change a schema without touching the original.

## 1. Create a database

**CLI**

```bash
ry postgres create appdb
ry postgres create appdb --size 20 --locale en-US
```

**curl**

```bash
curl -X POST "https://api.rytiv.app/v1/resources" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "appdb", "kind": "postgres"}'
```

**TypeScript**

```ts
const res = await fetch("https://api.rytiv.app/v1/resources", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.RYTIV_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "appdb", kind: "postgres" }),
});
const db = await res.json(); // db.url is shown in this answer only
```

**Python**

```python
import os, requests

db = requests.post(
    "https://api.rytiv.app/v1/resources",
    headers={"Authorization": f"Bearer {os.environ['RYTIV_TOKEN']}"},
    json={"name": "appdb", "kind": "postgres"},
).json()  # db["url"] is shown in this answer only
```

Your agent uses the tool [`create_postgres`](https://docs.rytiv.com/reference/mcp-tools.md#create_postgres), which never returns the password.

| Field | Type | Default | Notes |
| --- | --- | --- | --- |
| `name` | string | required | Like an [app name](https://docs.rytiv.com/apps.md#names-and-addresses), unique across Rytiv |
| `kind` | string | required | `postgres` |
| `memoryMb` | integer | `1024` | 128 to 16384 |
| `sizeGb` | integer | `10` | Size of the data volume, 4 to 500 |
| `locale` | string | `de-DE` | ICU locale for sorting and comparing text, such as `en-US`. Fixed once the database exists. |
| `durability` | string | `standard` | `standard`: one machine. `host-redundant`: a standby on a second host (API only; a redundant database cannot be branched or forked). |
| `suspendPolicy` | object | suspend after 300 s | When the database sleeps, see [Sleep](#5-sleep). `{"mode": "off"}` keeps it always on. |

`POST /v1/resources` answers `201` with `name`, `kind`, `port`, `secretKey` (`DATABASE_URL`) and `url`. **The `url` contains the password and is returned only here**; the CLI shows it only with `--json`. You do not need to store it if you attach the database to an app: the app gets it as a secret.

## 2. Connect from an app

```bash
ry attach appdb --to web
ry deploy web
```

`POST /v1/apps/:name/attach` with `{"resource": "appdb"}` gives the app a managed secret `DATABASE_URL`. A running machine keeps the environment it started with, so deploy the app again (or stop and start its machine) after attaching. The URL looks like this:

```text
postgres://postgres:<password>@appdb.internal:5432/appdb?sslmode=verify-full&sslrootcert=/etc/rytiv/ca/postgres.crt
```

- `appdb.internal` resolves only inside apps that are attached to the database. Everyone else cannot reach it.
- The database accepts TLS connections only. The certificate authority is at `/etc/rytiv/ca/postgres.crt` in every machine, so `verify-full` works without setup.
- The database name is the resource name with `-` replaced by `_`.

## 3. Connect from your computer

A database has no public address. Open a tunnel with the CLI and connect to the local end:

```bash
ry proxy appdb 15432:5432
psql -h 127.0.0.1 -p 15432 -U postgres appdb
```

The first connection wakes a sleeping database.

## 4. Branch, fork, restore

### Branch a database

A branch is a copy-on-write copy of the database **as it is now**, taken on its host. It is ready in seconds, takes almost no space at first, and is a database of its own with its own password: writes to the branch never reach the source.

**CLI**

```bash
ry postgres branch appdb appdb-try --expires-in 1d
```

**curl**

```bash
curl -X POST "https://api.rytiv.app/v1/resources/appdb/branch" \
  -H "Authorization: Bearer $RYTIV_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "appdb-try", "ttlSeconds": 86400}'
```

**TypeScript**

```ts
await fetch("https://api.rytiv.app/v1/resources/appdb/branch", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.RYTIV_TOKEN}`, "Content-Type": "application/json" },
  body: JSON.stringify({ name: "appdb-try", ttlSeconds: 86400 }),
});
```

**Python**

```python
import os, requests

requests.post(
    "https://api.rytiv.app/v1/resources/appdb/branch",
    headers={"Authorization": f"Bearer {os.environ['RYTIV_TOKEN']}"},
    json={"name": "appdb-try", "ttlSeconds": 86400},
)
```

| Field | Type | Notes |
| --- | --- | --- |
| `name` | string | Name of the new database |
| `ttlSeconds` | integer | Delete the branch after this long, 60 to 2592000 (30 days) |
| `expiresAt` | string | Or: delete it at this moment (ISO 8601). Not together with `ttlSeconds`. |
| `ephemeral` | boolean | Ready faster, without an archive of its own, and lost if its host fails |

The answer is `202` with `state: "restoring"`. Poll `GET /v1/resources/:name` until `state` is `ready`; the CLI waits for you. A database can have up to 15 branches.

This is where an agent changes a schema or tries a migration. Through MCP, [`create_branch`](https://docs.rytiv.com/reference/mcp-tools.md#create_branch) makes one, and [`execute_sql`](https://docs.rytiv.com/reference/mcp-tools.md#execute_sql) only writes when you name a branch of the database; on the database itself it runs read-only.

### Fork from a point in time

A fork is a full copy made from the database's archive, on any host. It takes longer than a branch, minutes for a large database, and can start from any moment the archive covers (7 days):

```bash
ry postgres fork appdb appdb-yesterday --at 2026-10-19T09:00:00Z
```

`POST /v1/resources/:name/fork` takes `name`, an optional `asOf` (ISO 8601) and the same `ttlSeconds` or `expiresAt` as a branch.

### Connection URL of a copy

A branch or fork has its own password. Attach it to an app like any database, or fetch its URL **once**:

```bash
curl -X POST "https://api.rytiv.app/v1/resources/appdb-try/url" \
  -H "Authorization: Bearer $RYTIV_TOKEN"
```

The answer is `{name, url}`. A second call answers `409` with `url-handed-out`: the URL is handed out exactly once. For the original database, the URL came with the create answer and this route answers `409` with `not-a-copy`. The CLI asks for it right after a branch or fork is ready and prints it with `--json`.

### Restore in place

```bash
ry postgres restore appdb --at 2026-10-19T09:00:00Z
```

`POST /v1/resources/:name/restore` with `{asOf}` puts a database back to that moment. Name, URL and password stay the same; open connections end once, at the switch. The state before the restore is kept as a branch named `<name>-old-<timestamp>`, or the name you pass as `backupName`. The CLI asks before it does this; pass `--yes` in scripts.

### Expiry

`PATCH /v1/resources/:name` with `ttlSeconds` or `expiresAt` sets when a branch or fork is deleted; `"expiresAt": null` keeps it. Only copies can expire.

```bash
ry postgres expire appdb-try --in 12h
ry postgres expire appdb-try --never
```

## 5. Sleep

A standalone database carries a sleep policy: by default it is suspended after 300 seconds without a running query, an open transaction or traffic, and stopped a day after that; the next connection wakes it. A redundant database never sleeps.

```bash
ry postgres sleep appdb --after 10m
ry postgres sleep appdb --off
ry postgres create reports --always-on
```

`PATCH /v1/resources/:name` with `suspendPolicy` changes the rule; `{"mode": "off"}` keeps the database always on. The policy is stored with the database and shown by `ry postgres list`; whether a region's hosts act on it is part of the region's configuration, so do not build on a database being asleep.

## List and delete

| Method | Path | What it does |
| --- | --- | --- |
| `GET` | `/v1/resources` | Your databases, with `state` (`restoring`, `ready`, `failed`), `sourceName` and `expiresAt` for copies. Never a URL. |
| `GET` | `/v1/resources/:name` | One database |
| `DELETE` | `/v1/resources/:name` | Deletes the database, its machine and its volume, and detaches it from every app. It cannot be undone. |

```bash
ry postgres list
ry postgres destroy appdb-try
```

## Errors you will meet

| Status | Code | When |
| --- | --- | --- |
| `409` | `name-taken` | The name is used somewhere on Rytiv |
| `400` | `name-reserved` | The name is reserved for the platform |
| `503` | `no-capacity` | No room right now, try again |
| `409` | `url-handed-out` | The URL of this copy was handed out already |
| `409` | `not-ready` | The copy is still restoring, or failed |
| `409` | `conflict` | The database already has 15 branches: delete one, or fork |

See [Postgres](https://docs.rytiv.com/reference/api/postgres.md) in the API reference for every field, and the [CLI reference](https://docs.rytiv.com/reference/cli/postgres.md) for every flag of `ry postgres`.
