> Fetch the index of every Tonbo docs page as Markdown at https://tonbo.dev/llms.txt

# open() and aopen()

Last updated 3 November 2026

`open()` opens a database by its URL and makes this process its writer; `aopen()` is the same call for asyncio. This page lists every parameter, what they return and what they raise.

A database’s URL comes from `tonbo database show <name> --json`, the console or the Management API. [Connect from Python](https://tonbo.dev/docs/databases/python.md) walks through a first open.

## open()

```python
def open(
    url: str,
    *,
    token: str | None = None,
    dir: str | os.PathLike[str] | None = None,
    read_only: bool = False,
    recreate: bool = False,
    max_pending_bytes: int | None = None,
    timeout: float = DEFAULT_OPEN_S,
    busy_timeout: float = DEFAULT_BUSY_S,
    **connect_kwargs: Any,
) -> UrsulaConnection: ...
```

```python
import os

import sqlite_ursula

db = sqlite_ursula.open(
    os.environ["NOTES_URL"],
    token=os.environ["TONBO_API_KEY"],
    dir="./notes-cache",
    timeout=10,
)
```

The first open of a database in the process binds its local cache, catches it up from Tonbo and claims the database, so any earlier writer is fenced: from then on its statements raise [`fenced`](https://tonbo.dev/docs/reference/python/errors.md#reset-fenced). Later opens of the database in the process return another connection on the same binding, with no network, and concurrent opens, from any thread, share one bind and its outcome. It blocks the calling thread for at most `timeout`.

### Parameters

| Parameter | Type | Default | What it does |
| --- | --- | --- | --- |
| `url` | `str` | Required | The database’s URL, such as `https://…/acme/notes`, as `tonbo database show --json` prints it. A string; anything else raises `invalid_argument`, and a string that is not a database’s URL `invalid_url`. |
| `token` | `str \| None` | `None`: the token the process holds, else `URSULA_VFS_TOKEN_FILE`’s | The bearer token every request of the process carries from now on: a key that acts as you, or an access token exchanged from a key (see [Credentials](https://tonbo.dev/docs/reference/python/open.md#auth)). It is applied with [set_token()](https://tonbo.dev/docs/reference/python/gate-and-import.md#set-token) before the open, so it replaces the token of every database the process has open. A string only: for a token that changes, call `set_token()` with each new one. |
| `dir` | `str \| os.PathLike[str] \| None` | `None`: `<user cache>/sqlite-ursula/0.8` | The cache directory, where the local copy lives; created when missing. Use a local disk, not a network filesystem. A database is open under one `dir` at a time in the process: an open under another waits for it to retire, and raises `bound_elsewhere` while it has connections open. The user cache is `$XDG_CACHE_HOME`, else `~/.cache` on Linux and `~/Library/Caches` on macOS. |
| `read_only` | `bool` | `False` | Open without becoming the writer, so the process writing the database is left alone; writes fail with `SQLITE_READONLY`. See [Read-only opens](https://tonbo.dev/docs/reference/python/open.md#read-only). A boolean, and not with `recreate`. |
| `recreate` | `bool` | `False` | When the database’s stream is missing and the cache holds data, create the stream again and move the cache into `salvage/` (see [Salvage](https://tonbo.dev/docs/databases/backups.md#salvage)), instead of raising `not_found`. Tonbo creates a database’s stream itself, so on Tonbo create the database first and leave this `False`. |
| `max_pending_bytes` | `int \| None` | `None`: 64 MiB | How many bytes of compressed commits may wait for Tonbo before writes raise `backlog_full`. A whole number of bytes, zero or more. |
| `timeout` | `float` | `30`: seconds | How many seconds the open may take: a positive number, at most `threading.TIMEOUT_MAX`. Past it the open cancels its request and raises `UrsulaUnavailableError`: the last condition it met, or `cancelled`. Ctrl-C cancels it too. |
| `busy_timeout` | `float` | `5`: seconds | `sqlite3.connect`’s `timeout`: how long a statement waits for another connection’s lock, zero or more seconds. The connections of a database share its files, hence the default. |
| `**connect_kwargs` | `Any` | None | Every other keyword goes to `sqlite3.connect`, such as `isolation_level` or `detect_types`. `check_same_thread` is `False` unless given. A `factory` must be a subclass of [UrsulaConnection](https://tonbo.dev/docs/reference/python/connection.md#ursula-connection). `database`, `uri` and `autocommit=False` raise `invalid_argument`. |

### Read-only opens

In a process that owns the database, `read_only=True` returns a read-only connection on the owner’s binding: it sees the owner’s latest commits, and its `synced()` waits as the owner’s does. In any other process it builds or catches up a reader cache of its own to the database as of the open, claims nothing and does not follow later commits, and its `synced()` returns at once. Close every read-only connection of the process and open again to read newer commits.

### Credentials

`token` is a key that acts as you, sent as it is (see [Sending the key directly](https://tonbo.dev/docs/platform/api-keys.md#direct)), or an access token exchanged from a key (see [The token exchange](https://tonbo.dev/docs/platform/api-keys.md#exchange)). A key that acts as an Agent must be exchanged first: sent as it is, the open raises [`unauthorized`](https://tonbo.dev/docs/reference/python/errors.md#unavailable-unauthorized) once its `timeout` passes (see [an Agent’s key](https://tonbo.dev/docs/databases/python.md#agent-key)). The key must reach the database, and a read-only key opens it only with `read_only=True`, since claiming the database writes to it.

### Returns

An [UrsulaConnection](https://tonbo.dev/docs/reference/python/connection.md#ursula-connection), a `sqlite3.Connection` subclass, or the `factory` class given. Its `status()["state"]` is `live`, or `provisional` when Tonbo was unreachable and a trusted cache that claimed the database before served the open: reads come from the cache, commits wait, and the state turns `live` once Tonbo answers (see [state](https://tonbo.dev/docs/reference/nodejs/status.md#field-state)).

### Errors

- [`UrsulaSetupError`](https://tonbo.dev/docs/reference/python/errors.md#ursula-setup-error) when the open cannot succeed as asked, such as [`invalid_argument`](https://tonbo.dev/docs/reference/python/errors.md#setup-invalid-argument), [`invalid_url`](https://tonbo.dev/docs/reference/python/errors.md#setup-invalid-url), [`not_found`](https://tonbo.dev/docs/reference/python/errors.md#setup-not-found), [`bound_elsewhere`](https://tonbo.dev/docs/reference/python/errors.md#setup-bound-elsewhere), [`host_unsupported`](https://tonbo.dev/docs/reference/python/errors.md#setup-host-unsupported) or [`forked`](https://tonbo.dev/docs/reference/python/errors.md#setup-forked). Its action is `fix`, or `retry` for [`in_use`](https://tonbo.dev/docs/reference/python/errors.md#setup-in-use) and [`claim_races`](https://tonbo.dev/docs/reference/python/errors.md#setup-claim-races). A database you have not created is [on Troubleshooting](https://tonbo.dev/docs/databases/troubleshooting/open.md#not-created).
- [`UrsulaUnavailableError`](https://tonbo.dev/docs/reference/python/errors.md#ursula-unavailable-error) when `timeout` passed first: the last condition the open met, such as [`unreachable`](https://tonbo.dev/docs/reference/python/errors.md#unavailable-unreachable) or [`unauthorized`](https://tonbo.dev/docs/reference/python/errors.md#unavailable-unauthorized), or [`cancelled`](https://tonbo.dev/docs/reference/python/errors.md#unavailable-cancelled) when it met none (see [Troubleshooting](https://tonbo.dev/docs/databases/troubleshooting/open.md#unavailable)).
- `KeyboardInterrupt` on Ctrl-C, once the open cancelled its request. A Ctrl-C in the open’s first instant can miss the cancel, and that request then runs on in the background.

### Retries

An open is safe to call again: it never creates a database (unless `recreate` recreates a missing one), and each call either binds, joins a bind under way or returns a connection on the binding there is. Call it again after an error whose action is `retry`, with a backoff. Never call it in a loop after `fenced`: each open takes the database back from the other writer.

```python
import time

import sqlite_ursula

for attempt in range(5):
    try:
        db = sqlite_ursula.open(url, token=key)
        break
    except sqlite_ursula.UrsulaError as error:
        if error.action != "retry":
            raise  # fix, reopen or stop: retrying the same call cannot help
        time.sleep(2**attempt)
```

## aopen()

```python
async def aopen(
    url: str,
    *,
    token: str | None = None,
    dir: str | os.PathLike[str] | None = None,
    read_only: bool = False,
    recreate: bool = False,
    max_pending_bytes: int | None = None,
    timeout: float = DEFAULT_OPEN_S,
    busy_timeout: float = DEFAULT_BUSY_S,
    **connect_kwargs: Any,
) -> UrsulaConnection: ...
```

```python
import asyncio
import os

import sqlite_ursula


async def main() -> None:
    async with asyncio.timeout(10):  # cancels the open, too
        db = await sqlite_ursula.aopen(
            os.environ["NOTES_URL"], token=os.environ["TONBO_API_KEY"]
        )
    db.execute("INSERT INTO notes(body) VALUES (?)", ("hello",))
    db.commit()
    await db.asynced()
```

[open()](#open) for asyncio: the same [parameters](#open-parameters), the same connection and the same errors, and it waits for the bind on the event loop instead of blocking it. Cancelling it, as `asyncio.timeout` around it does, cancels the bind and raises `CancelledError`, so no bind goes on to claim the database after the caller gave up. Statements on the connection it returns still run on the calling thread, as they do with `sqlite3`; wait for Tonbo with [asynced()](https://tonbo.dev/docs/reference/python/connection.md#asynced) or [agate()](https://tonbo.dev/docs/reference/python/gate-and-import.md#agate).
