Integration prompts
Prompt: Python backend
A prompt to paste into a coding agent that sends trusted events and reads customer state from a Python backend with a secret key.
Paste the prompt below into your coding agent from the root of your Python service. Store the
test secret key in the service's secret store first. The prompt uses httpx; requests
works the same way.
Integrate this Python backend with Orishare (loyalty and engagement API).
Read first, and use only what they define:
- https://docs.orishare.com/docs/agent-guide, especially the credential matrix at
https://docs.orishare.com/docs/agent-guide#credential-matrix and "Stable retry ids"
- https://docs.orishare.com/capabilities.json (supported operations and credential kinds)
- https://docs.orishare.com/openapi.json (the only source of routes and schemas)
Do not invent routes, fields or rule actions.
Credentials:
- Secret key from os.environ["ORISHARE_SECRET_KEY"] (osk_test_... in a test environment). Server only;
never in templates, JavaScript, logs or the repository.
- No personal access token (opat_...) at runtime.
Build a module orishare_client.py:
1. API = "https://api.orishare.com"; one httpx.Client with
headers={"Authorization": f"Bearer {key}"} and a timeout.
2. send_event(record): POST /v1/events with {"id", "name", "customer_id", "occurred_at",
"properties"}. customer_id is OUR user id (the external id), never an Orishare cust_ id. The
id comes from the business record (f"order_{order.id}") and is the only dedupe: a secret-key
POST /v1/events ignores Idempotency-Key, so resending the same id is the retry. Call it after
our transaction commits (for example from an outbox worker), never inside it.
Example call: client.post(f"{API}/v1/events", json=body)
3. send_batch(records): {"events": [...]} with at most 100 items. The response is 207 with
results[i].status in accepted, evaluated, duplicate, rejected, matched by results[i].index
(a rejected item has error and no id); resend only rejected items after fixing them, same ids.
4. customer_state(customer_id): client.get(f"{API}/v1/customers/{customer_id}/state").
5. mint_customer_session(customer_id): POST /v1/customer_sessions with
{"grant_type": "secret_key", "customer_id": customer_id} (the internal cust_ id, from
POST /v1/customers or GET /v1/customers?external_id=...) and one Idempotency-Key per mint
intent (reused on its retries); return only { customer_token } to our signed-in frontend. If
a retry is answered by replay (customer_token: None), mint again with a new Idempotency-Key. The key needs customer_sessions:write, which the operator must grant (403
insufficient_scope otherwise).
6. Async: 202 means stored. To learn the outcome poll client.get(f"{API}/v1/events/{event_id}")
with the evt_ id from the admission result (not our business id) until status is evaluated.
7. Repeating requests, exactly as https://docs.orishare.com/docs/agent-guide#when-a-request-may-be-repeated:
after 429, or 409 with Retry-After, wait and resend unchanged. After a lost answer (timeout,
connection error, 5xx) resend only when the operation's retry class in capabilities.json is
atomic_replay or keyed_resume (rest_operations[].retry in capabilities.json), with the
same Idempotency-Key and body. POST /v1/events is outcome_unknown but deduplicated by event
id: resend the same event with the same id. Any other outcome_unknown write: read the
resource first. No blanket retry loops for writes; never resend after 409 event_id_conflict
or 422 idempotency_key_reused.
8. Webhooks: verify Orishare-Signature (t=...,v1=... HMAC-SHA256 of f"{t}.{raw_body}" with the
endpoint secret, constant-time compare, 300 second tolerance).
9. Hosted widget (if I ask for it): https://docs.orishare.com/docs/hosted-widget, backend path.
Expose mint_customer_session (step 5) as an authenticated endpoint of ours that returns only
{ customer_token }, minted fresh on each call, and add to our page the script tag for
<widget origin>/v1/embed.js (the widget origin is in the dashboard snippet; ask me for it) plus
orishare.push(['mount', { portal: <ptl_...>, publishableKey: <opk_...>, getSession }]),
plus target: '#element' only when the portal's layout is inline (omit it for a launcher),
where getSession posts to that endpoint (same origin as the page) and returns its JSON. When
our user signs out, push ['signOut'], wait for the signed_out event, then end our own session;
push ['signIn'] after sign-in. The minted session is ours:
the widget never revokes it; it expires on its own (15 minutes by default). The secret key
never leaves this service; the publishable key in the snippet is fine.
Write pytest tests with a mocked transport (same id on retry; 207 handling). Verify
against the test environment and report changed files and anything unverified.