Skip to main content

pagesystems

Page Systems gives your Python agent hosted web pages (pickers, forms, tables and more). The agent sends the person a link, the person answers on the page, and the answer arrives in your agent's conversation as a new turn. The agent does not poll, and the person does not have to say "done".

The package is a thin client. It bundles no page types, schemas or rendering: your agent reads them from the Page Systems API at runtime, so new page types work without an update. The core uses only the standard library.

1. Install

pip install pagesystems                      # client and callback handler
pip install "pagesystems[openai-agents]" fastapi uvicorn   # or langchain, crewai, pydantic-ai, google-adk, claude-agent-sdk
export PAGE_SYSTEMS_API_KEY=ap_live_...           # create a key in the Page Systems dashboard
export PAGE_SYSTEMS_WEBHOOK_SECRET=whsec_...      # the signing secret on the dashboard Overview page

Python 3.10 or newer. PAGE_SYSTEMS_BASE_URL defaults to https://pagesystems.ai.

2. Add the tools and a callback route

Give your agent the five tools (list_page_types, get_page_type_schema, create_page, get_page_result, cancel_page). Pass the public HTTPS URL of your callback route and an agent_ref that identifies the conversation, so each page calls back with its own conversation id.

from fastapi import BackgroundTasks, FastAPI, Request
from fastapi.responses import JSONResponse
from agents import Agent, Runner, SQLiteSession
from pagesystems import CallbackError, handle_callback_async
from pagesystems.openai_agents import page_systems_tools

app = FastAPI()
CALLBACK_URL = "https://your-app.example.com/page-systems"

def make_agent(conversation_id: str) -> Agent:
    return Agent(
        name="Assistant",
        instructions="Use Page Systems pages when the person must choose or fill in something.",
        tools=page_systems_tools(
            callback_url=CALLBACK_URL,
            agent_ref={"framework": "python", "sessionId": conversation_id},
        ),
    )

async def take_turn(conversation_id: str, visible_text: str, model_text: str):
    # The session stores the conversation's history, so the same id continues it. model_text rides in the user message, never in instructions.
    # The session stores both together; show visible_text to the person.
    session = SQLiteSession(conversation_id, "conversations.db")
    result = await Runner.run(make_agent(conversation_id), f"{visible_text}\n\n{model_text}", session=session)
    await send_to_user(conversation_id, result.final_output)  # your code: however your app delivers a reply

@app.post("/page-systems")
async def page_systems_callback(request: Request, background: BackgroundTasks):
    # Page Systems waits 10 seconds for this response, so answer now and run the turn after.
    def resume(event):
        if not event.agent_ref:  # a page this app did not create (for example over MCP): decline it, the answer stays readable with get_page_result
            raise CallbackError(410, {"error": "no_conversation"})
        # event.visible_text is one short line for the chat, e.g. "✓ Pick a desk: FlexFrame 55": store and show it as the person's message.
        # event.model_text is the answer for the model, as unverified data: give it to the model in a user message, never in the instructions.
        background.add_task(take_turn, event.agent_ref["sessionId"], event.visible_text, event.model_text)

    status, body = await handle_callback_async(await request.body(), headers=request.headers, on_submitted=resume, on_new_page_requested=resume)
    return JSONResponse(body, status_code=status)

Page Systems gives up on a request after 10 seconds and retries it with backoff (first after 30 seconds, then doubling, at most an hour apart). A slow handler can therefore run the same turn twice. So the handler only schedules the turn and returns; FastAPI's BackgroundTasks runs it after the response. send_to_user is your own code. Use the same resume for on_new_page_requested, or Page Systems gets a 410 no_handler for that event.

Every adapter has the same shape:

Framework Import Returns
OpenAI Agents SDK pagesystems.openai_agents.page_systems_tools(...) function_tools
LangChain pagesystems.langchain.page_systems_tools(...) StructuredTools
CrewAI pagesystems.crewai.page_systems_tools(...) CrewAI tools
Pydantic AI pagesystems.pydantic_ai.page_systems_tools(...) Tools
Google ADK pagesystems.google_adk.page_systems_tools(...) FunctionTools
Claude Agent SDK pagesystems.claude_agent_sdk.page_systems_server(...) in-process MCP server, for mcp_servers={"pages": server}

Each takes callback_url=None, agent_ref=None, client=None.

3. The answer arrives as a new turn

When the person submits, Page Systems calls your route with {"type": "page.submitted", "pageId": "..."}. handle_callback reads the answer with your API key, calls your handler with an event (page_id, title, result, agent_ref, summary when Page Systems has one, visible_text, model_text, message), and then claims the answer. Your handler starts the next turn of that conversation, and should return within 10 seconds (start the turn in the background; see above).

Use visible_text and model_text, not message. visible_text is one short line for the chat. model_text is the answer for the model. See Handling answers safely below. The per-framework install guides show where model_text goes for each framework.

Status codes returned to Page Systems: 200 done or already handled; 503 reading failed or your handler raised (Page Systems retries, and the answer is not claimed until the handler succeeds); 410 cannot be delivered (also when you pass no handler for the event type); 400 bad body. Raise pagesystems.CallbackError(410 or 503, body) in a handler to choose the response. A retry after a lost response can deliver the same answer twice, so make the handler safe to repeat.

on_new_page_requested runs when the person presses "Request a new page" on an expired, unanswered page. Nothing was created; your agent decides whether to send another page.

Pass the request headers and the raw body so the handler can verify the signature (handle_callback(raw_body, headers=request.headers, ...)). With PAGE_SYSTEMS_WEBHOOK_SECRET set (or webhook_secret=), a missing or wrong signature returns 401, which Page Systems does not retry; with no secret, callbacks are accepted unverified and a warning is logged once.

Use handle_callback(body, ...) (body: a dict, or the raw request bytes or string) with synchronous handlers (Flask, Django). Use handle_callback_async when handlers are async def.

Handling answers safely

Parts of an answer can be text typed by whoever opened the link, who is not verified. Treat the answer as data, never as instructions.

  • visible_text: one short line (at most 160 characters) that your chat stores and shows as the person's message. By default it is ✓ <title>: <summary>. Pass chat_line= to handle_callback or handle_callback_async to change it: "summary" (default), "title" (✓ Answered: <title>) or "plain" (✓ Answered). Any other value raises ValueError.
  • summary: a short readable description of the answer, sent by Page Systems when it has one. It never includes free text the person typed, but it can include the page title and the labels your agent wrote, such as option or row names. Log it only if those hold nothing sensitive.
  • model_text: what the model reads. It names the page, includes the summary, says the answer is unverified page data, and puts the answer JSON between <page_answer> and </page_answer> tags. A </ inside the JSON is escaped so typed text cannot close the tag. Give it to the model as a user-role message or a tool result, never in a system prompt or instructions.
  • message: the older text with the raw JSON inline and no trust note. Do not use it.
  • Whether your agent keeps model_text depends on the framework; the install guides say which. Page Systems deletes its copy at expiry but cannot delete copies saved in your agent's history.
  • Do not log model_text or result. Log the page_id, and the summary only if your page labels hold nothing sensitive. In shared channels, use chat_line 'plain' if the title or labels are sensitive.

More detail: https://pagesystems.ai/docs/handling-answers

Client

from pagesystems import PageSystems

ps = PageSystems()   # api_key and base_url default to the environment, read at call time
ps.list_page_types(); ps.get_page_type("form"); ps.get_result(page_id)
ps.create_page({"title": "...", "page": {"type": "form", ...}}, callback_url=..., agent_ref=..., idempotency_key=...)
ps.claim(page_id); ps.cancel(page_id); ps.get_skill()

create_page adds version: 1. An invalid spec or paused creation (payment_required, spend_cap_reached, burst_limit) returns a dict with error and message instead of raising. Other API failures raise PageSystemsError.

Pages collect an answer; they never book, buy or pay. Do not use them for card numbers, passwords or identity documents.

Metadata

Release files for pagesystems 0.4.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pagesystems 0.4.2
File Size Uploaded Provenance
pagesystems-0.4.2.tar.gz 14.0 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pagesystems 0.4.2
File Interpreter ABI Platform
pagesystems-0.4.2-py3-none-any.whl Python 3 none any Details

Total release size: 32.7 kB

Release files / pagesystems-0.4.2.tar.gz

Download URL pagesystems-0.4.2.tar.gz
Size 14.0 kB
Tags Source
SHA-256 checksum
How to use checksums
bc16494831ffc8e88eb5e7bd5f43186c0a81f7a632e0e2c7ed3a486db7062192
BLAKE2b-256 checksum
How to use checksums
7a48a214b042c2daf8c413609f6c53fa477bd90be22a1354395f608c9b462edc
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / pagesystems-0.4.2-py3-none-any.whl

Download URL pagesystems-0.4.2-py3-none-any.whl
Size 18.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
08ee98ff466f0f1b8468afdca328d143df89dec111eb468ec8215d43acf3b7c6
BLAKE2b-256 checksum
How to use checksums
08b54274781d78fcfe70b90a12dcf2cfbe97f7111fb052ac67f99985bf5111ba
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.14 {"installer":{"name":"uv","version":"0.11.14","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for pagesystems 0.4.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.4.2 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page