Skip to main content
plainrouter contains the generated Python SDK and the plainrouter command-line interface. The SDK exposes synchronous and asynchronous Conversion API operations, with ten convenience imports at the package root. The CLI exposes nine API commands. The current PyPI release is 0.6.0 and targets signed API contract 0.5.0. The source is in plainrouter/sdk.

Requirements

  • Python 3.11 or newer.
  • A Signals workspace secret.
  • Server-side code. Do not put the secret in browser or distributed client code.

Install

For terminal commands, follow the CLI quickstart for an isolated installation, authentication, and configuration. This page covers the Python application client.

Create a client

The default API base is https://plainrouter.com/api/v1 and the default request timeout is 30 seconds.

Use async operations

Each operation module also provides an asynchronous method:

Send a Meta conversion

Send a server-side purchase through Plainrouter to your connected Meta destination. This is a Plainrouter SDK recipe, not a direct Meta Graph API tutorial. A successful create response confirms ingestion; inspect destination delivery separately. Before sending, confirm settlement on your server, load the order’s recorded advertising consent, and select the backend as the purchase owner. This request can create a real conversion. Use the sandbox for synthetic validation or a controlled destination test during setup.

Send a purchase synchronously

The example accepts an order mapping from your application: The local consent check requires three recorded grants. It does not obtain consent or establish permission by itself. Do not replace an unknown decision with granted, or an old purchase or consent time with the current time.
Create the authenticated client once in your service, loading PLAINROUTER_SIGNAL_SECRET from your secret manager. After loading order from your database, build event = purchase_event(order) and call send_purchase(client, event). The returned receipt exposes event_id and duplicate; it is not a Meta delivery receipt. CreateEventBodyType0.from_dict constructs the published consented request model. It does not prove that input meets every server validation rule. Plainrouter normalizes and hashes supported raw identity fields before Meta delivery. Include only actual browser identifiers; see identifier formats.

Send the same model asynchronously

Reuse the same event model and receipt check in an async service:
Await send_purchase_async(client, event) from your application’s async context. The client reuses an httpx session. Reuse a client per service configuration and close it at shutdown; the generated client supports with for synchronous use and async with for asynchronous use.

Retry safely

Persist one payload per paid order before sending, including the event ID, original purchase time, amount and applicable consent snapshot. Reconstruct the generated model from that stored payload for a later job. Do not rebuild it with new timestamps or identity values. Recheck that advertising use is still permitted before retrying.
  • HTTP 202, duplicate: false: a new event was accepted for processing.
  • HTTP 200, duplicate: true: the event was already accepted. Inspect the original event; the retry does not update or enrich it.
  • A timeout or lost response leaves acceptance uncertain. Retry the same stored ID and payload rather than minting a new ID.
You can use the generated idempotency_key argument instead of a body event_id. When both are present, they must match. Your application owns durable job retries and concurrent-order guards; the SDK does not implement that workflow for you. A separate browser signalq("track", ...) call generates its own ID and cannot accept this recipe’s order ID. Choose one purchase owner, and avoid sending the same checkout through SDK, managed browser tracking and Stripe verified revenue. See event-ID deduplication.

Confirm Meta delivery

Use get_event.sync_detailed(client=client, event=receipt.event_id) for the receipt returned by send_purchase. Check HTTP 200 before reading its parsed event and deliveries. The async equivalent is get_event.asyncio_detailed. Import get_event from plainrouter. A delivery marked accepted confirms Meta acceptance. queued, sent and retrying are pending; an empty delivery list is not a success receipt. Follow delivery statuses and Meta troubleshooting for failed or skipped deliveries. Acceptance does not guarantee attribution.

Troubleshoot a purchase request

Verify Signal ingestion

The operation is idempotent. A repeated verification returns the existing event with duplicate: true. The event confirms ledger receipt but is not real arrival evidence and does not complete onboarding.

Available operation modules

List events by cursor

Cursor-based listing is available through the generated module rather than a package-root convenience import:
Omit cursor on the first request. For the next page, pass the opaque next_cursor returned in the response’s event pagination data. The CLI does not expose this operation.

Configure another API origin or timeout

Pass base_url or an httpx.Timeout when creating the client:
Override the production origin only for a Plainrouter-provided region or a controlled test environment.

Handle credentials safely

The Python SDK accepts a Signals workspace secret. It does not accept an MCP workspace execution token or OAuth management credential. Keep the secret in a server-side secret manager. Never include it in prompts, URLs, logs, source control, or client-side code. For request and response fields, use the API reference.

Implementation references