Python SDK and API
Move from a quick query to notebooks, scripts, and applications.
Install and authenticate
Create an API key in Access → SDK & API, then set PERIPLUS_API_KEY in your environment. Do not put keys in notebooks, source control, or browser code. API keys authenticate the SDK and REST API only; AI clients connect to MCP by signing in with OAuth.
python -m pip install --upgrade periplus-python-sdkRun your first query
Client reads PERIPLUS_API_KEY from the environment. Connect to the API origin, not the marketing site. Parameters are positional; pass values separately from SQL.
Python
from periplus_sdk import Client
with Client("https://api.periplus.dev") as client:
result = client.execute(
"SELECT capture_id, url FROM captures LIMIT ?", [10]
)
print(result.columns)
for row in result.rows:
print(row)cURL
curl https://api.periplus.dev/api/v1/sql \
-H "Authorization: Bearer $PERIPLUS_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"sql":"SELECT capture_id, url FROM captures LIMIT ?","parameters":[10]}'Beyond the first query
Client and AsyncClient expose SQL alongside captures, crawls, pins, schedules, projects, members, invitations, saved queries, keys, usage, query history, audit, bug reports and feedback. SQL is read-only; resource methods create and manage organization resources. The SDK covers everything the MCP tools do: captures.inspect(capture_id, view) reads one capture's summary, metrics, text, outline, structure or links (page with the cursor in next_steps), bug_reports.create(title=..., body_markdown=...) reports Periplus behaving incorrectly, and feedback.create(model=..., task=..., agent_friction=..., user_friction=..., suggestion=...) says where it worked but was harder than it should have been. Lists return items and next_cursor; pass it back as cursor, or use iter().
Client and AsyncClient automatically retry buffered SQL execution and preparation on HTTP 502, 503 or 504, up to twice by default. Set sql_retries=0 when creating the client to disable these retries, or choose a value from 0 to 3. Retries honor Retry-After up to 30 seconds; longer delays return the failure to the caller. Without that header, the delay grows from one second. Transport errors, streaming queries and other methods are not retried automatically.
Use request_timeout for resource calls and timeout for SQL and SQL-selected pins and schedules. Check truncated before treating a buffered result as complete. Streaming raises on truncation unless allow_partial=True; a missing completion frame always fails.
Check your access
The key selects its user and organization. Changing PERIPLUS_API_URL is only needed for local development or a different deployment.
Python
from periplus_sdk import Client
with Client() as client:
identity = client.identity.get()
print(identity.organization_id, identity.permissions)cURL
curl --fail-with-body "${PERIPLUS_API_URL:-https://api.periplus.dev}/identity" \
-H "Authorization: Bearer $PERIPLUS_API_KEY"Inspect the SQL catalogue
Schema discovery returns column types, descriptions, helpers, and documented relationships. It does not execute a corpus SELECT.
Python
from periplus_sdk import Client
with Client() as client:
catalogue = client.schema()
for table in catalogue.relations:
print(table.name, [(c.name, c.data_type) for c in table.columns])
entry = client.schema("capture_links") # one relation or helper, with full notes and examples
print(entry.entry.notes, entry.entry.examples)cURL
curl --fail-with-body "${PERIPLUS_API_URL:-https://api.periplus.dev}/api/v1/schema" \
-H "Authorization: Bearer $PERIPLUS_API_KEY"
curl --fail-with-body "${PERIPLUS_API_URL:-https://api.periplus.dev}/api/v1/schema?name=capture_links" \
-H "Authorization: Bearer $PERIPLUS_API_KEY"Use native SQL cells in Marimo
Install periplus-python-sdk[notebook], set PERIPLUS_API_KEY, and create one engine in a Python cell. Marimo recognizes it as a SQL data source; expand periplus → public_v1 to browse tables, views, typed columns, and comments. Each SQL execution uses its own snapshot, not a notebook-wide transaction.
Use sql_api.bind(engine, name=value) to pass Python values—including lists—into native SQL cells without string interpolation. A SQLAlchemy Inspector caches schema metadata; call inspector.clear_cache() when you need to refresh it. Function documentation is available through Client.schema().helpers, independently of Marimo’s table browser.
import marimo as mo
from periplus_sdk import sql_api
engine = sql_api.create_engine()
selected = sql_api.bind(engine, host="example.com")
# In a SQL cell, choose selected as the data source.
captures = mo.sql(
"SELECT capture_id, url, observed_at FROM captures "
"WHERE domain(url) = :host LIMIT 20",
engine=selected,
)
# Dispose engines when the notebook is finished.Stream asynchronously
Use the stream as an async context manager, including when stopping early. Normal completion validates delivered row counts. Transport interruptions remain errors; allow_partial only permits a server-declared result limit.
from periplus_sdk import AsyncClient
async def read_captures():
async with AsyncClient() as client:
async with await client.stream(
"SELECT capture_id, url FROM captures LIMIT ?", [100]
) as stream:
async for batch in stream:
for row in batch:
print(row)
print(stream.result.row_count)
# In an async notebook cell: await read_captures()
# In a script: asyncio.run(read_captures())