Skip to content
PeriplusDocs
Open app
Python SDK and API

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.

Shell
python -m pip install --upgrade periplus-python-sdk

Run 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

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

Shell
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

Python
from periplus_sdk import Client

with Client() as client:
    identity = client.identity.get()
    print(identity.organization_id, identity.permissions)

cURL

Shell
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

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

Shell
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.

Python
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.

Python
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())

Create an API key