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
Create a client
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 anorder 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.
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: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.
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
Useget_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
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: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
Passbase_url or an httpx.Timeout when creating the client: