Python client

TaglyDB from Python, fully typed.

The taglydb package wraps the HTTP API in typed methods that return typed objects, and turns error responses into exceptions. It needs Python 3.14 or newer.

Connecting

Tagly.build() is a context manager that opens a connection and closes it on exit. Pass your API key explicitly, or leave api_key out and set the API_KEY environment variable instead. base_url defaults to https://api.taglydb.com (or the BASE_URL environment variable), and timeout to 60 seconds.

import os

from taglydb import Tagly

with Tagly.build(api_key=os.environ["TAGLYDB_API_KEY"]) as client:
    ...

The client exposes client.indexes, client.items (items in an index), client.simple_items (standalone items) and client.sign(). Response objects use snake_case attributes — created_at, size_bytes, content_type, next_cursor — with timestamps parsed into datetimes.

Indexes and items

index = client.indexes.create(key="support-tickets", type="json", tags={"env": "prod"})
same = client.indexes.get_by_key("support-tickets")

item = client.items.create_json(
    index.id,
    key="ticket-1042",
    tags={"team": "billing", "priority": "high"},
    data={"subject": "Refund request", "amount": 42.5},
)

# Metadata only, one page at a time
page = client.items.list(index.id, tags={"team": "billing"}, limit=50)
while page.next_cursor is not None:
    page = client.items.list(
        index.id, tags={"team": "billing"}, limit=50, cursor=page.next_cursor
    )

# Every match with its data, in one call (refused past 16 MiB of matches)
tickets = client.items.list_with_data(index.id, tags={"team": "billing", "priority": "high"})
for ticket in tickets.items:
    print(ticket.key, ticket.data)

value = client.items.get_json(index.id, item.id)
value = client.items.get_json_by_key(index.id, "ticket-1042")
full = client.items.get_full(index.id, item.id)  # metadata + data

A tags= filter is a dict, so its entries are ANDed together. For OR filters (the same tag key with several values), call the HTTP API directly.

Blobs

import base64

files = client.indexes.create(key="invoices", type="blob")

with open("invoice.pdf", "rb") as f:
    invoice = client.items.create_blob(
        files.id,
        key="invoice-1042",
        data=f.read(),
        content_type="application/pdf",
        tags={"team": "billing"},
    )

pdf: bytes = client.items.get_blob(files.id, invoice.id)

# In get_full / list_with_data, a blob's bytes arrive base64-encoded
full = client.items.get_full(files.id, invoice.id)
pdf = base64.b64decode(full.data["b64"])

client.indexes

Methods

create(*, type, key=None, tags=None)→ Index
Create an index. type is "json" or "blob".
get(index_id)→ Index
Get an index by id.
get_by_key(key)→ Index
Get an index by key.
list(*, limit=None, cursor=None, tags=None, key=None)→ IndexPage
One page of your indexes: .items and .next_cursor.

client.items

Methods

create_json(index_id, *, data, tags=None, key=None)→ Item
Store a JSON value in a json index.
create_blob(index_id, *, data, content_type=…, tags=None, key=None)→ Item
Store bytes in a blob index. content_type defaults to application/octet-stream.
list(index_id, *, limit=None, cursor=None, tags=None, key=None)→ ItemPage
One page of item metadata: .items and .next_cursor.
list_with_data(index_id, *, tags=None, key=None)→ ItemWithDataPage
Every matching item with its data. Not paginated; raises PayloadTooLargeError past 16 MiB of matches.
get_json(index_id, item_id)→ JSON value
A JSON item's value.
get_blob(index_id, item_id)→ bytes
A blob item's bytes.
get_json_by_key(index_id, key)→ JSON value
A JSON item's value, by key.
get_blob_by_key(index_id, key)→ bytes
A blob item's bytes, by key.
get_full(index_id, item_id)→ ItemWithData
Metadata plus data for one item.

Standalone items

client.simple_items stores and fetches items that belong to no index — no index to create first, and keys unique across your standalone items. Items created here aren't reachable through client.items, and vice versa.

note = client.simple_items.create_json(
    key="latest-report", tags={"kind": "report"}, data={"ok": True}
)

client.simple_items.get_json(note.id)
client.simple_items.get_json_by_key("latest-report")
client.simple_items.get_full_by_key("latest-report")

logo = client.simple_items.create_blob(key="logo", data=png_bytes, content_type="image/png")
client.simple_items.get_blob_by_key("logo")

Methods

create_json(*, data, tags=None, key=None)→ Item
Store a standalone JSON value.
create_blob(*, data, content_type=…, tags=None, key=None)→ Item
Store standalone bytes.
get_json(item_id) / get_blob(item_id)→ JSON value / bytes
The payload, by id.
get_json_by_key(key) / get_blob_by_key(key)→ JSON value / bytes
The payload, by key.
get_full(item_id) / get_full_by_key(key)→ ItemWithData
Metadata plus data.

Signed URLs

client.sign(item_id, *, view=None, expires_in=None) mints a time-limited URL for any item you own, indexed or standalone. view is "data" (default) or "full"; expires_in is in seconds, default one hour, at most seven days. See Signed URLs for how the URL behaves.

signed = client.sign(invoice.id, view="data", expires_in=86_400)
print(signed.url, signed.expires_at)

Errors

Every error response raises TaglyError or, for the statuses below, one of its subclasses. For the exact cause, read error.detail.type, which ends in one of the error types. Network failures raise HTTPConnectionError or HTTPTimeoutError, also importable from taglydb.

from taglydb import ConflictError, TaglyError

try:
    client.indexes.create(key="support-tickets", type="json")
except ConflictError:
    index = client.indexes.get_by_key("support-tickets")
except TaglyError as error:
    print(error.status, error.detail.type, error.detail.detail, error.detail.request_id)
    raise

Exceptions

TaglyErrorbase class
Every API error. .status is the HTTP status; .detail is the problem details (type, title, status, detail, request_id).
ValidationError400 / 415 / 422
The request was malformed or had invalid values.
AuthenticationError401
Missing, invalid or revoked API key.
CreditsExhaustedError402
Your plan's credits are used up until Free's monthly reset, or until more credits are added.
NotFoundError404
No such index, item or key in your account.
ConflictError409
That key is already taken.
PayloadTooLargeError413
An item is too large, a list_with_data query matches more than 16 MiB, or a write would pass your plan's storage limit.

Managing API keys and reading usage aren't wrapped by the client yet — use the HTTP API or your dashboard.