Repository navigation
Conversation
A tenant client built with api_key can create and get managed users through client.platform.managed_users, with typed ManagedUser values and PlatformError failures that never carry upstream bodies. Adding managed_user acts as that user: REST requests send the key and a Pine-Managed-User header, the Socket.IO handshake sends token and managed_user, and connect() resolves the Pine user ID through auth.me(). client_name adds a Pine-Client header to every REST request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
client_name now also goes to the Socket.IO handshake as a Pine-Client header, where the backend reads it for attribution. The identity headers become a method of a base shared by both HTTP transports instead of a module function reading their private fields. The README states that the backend records Pine-Client only for tenant-key requests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The managed-user header is sent only with the API key it was configured with, never after set_token() or with a per-request token. Identity headers (Authorization, Pine-Managed-User, Pine-Client) are applied after caller headers, as on main, so http.* callers cannot override them. API keys must start with pine_sk_live_ or pine_sk_test_. API_KEY_PREFIXES, validate_external_id, MANAGED_USER_HEADER and CLIENT_HEADER are exported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mint_ticket() always fails with 403 platform_route_not_allowed as AuthError, and while the backend's access switch is off user-scoped calls fail with 403 platform_managed_user_access_disabled as the resource's usual error type. A test pins both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
Pine's B2B Platform API lets an enterprise (tenant) hold a secret key (
pine_sk_live_.../pine_sk_test_...) and create managed users identified by its ownexternal_id. This PR makes the SDK the complete enterprise integration: create the managed user, then act as them over REST and Socket.IO.Public API
AsyncPineAI(api_key=...)/PineAI(api_key=...)is a tenant client. Keys must start withpine_sk_live_orpine_sk_test_.client.platform.managed_users.create(external_id, *, email, name, phone=None)is idempotent: 201 on create, 200 with the stored user unchanged afterwards.client.platform.managed_users.get(external_id). Here theexternal_idgoes into the path by design: it is validated first, then percent-encoded.ManagedUser(id,external_id,email,name,phone,created_at) and raisePlatformErrorwith a stablecodeandstatus_code, never the upstream body.Pine-Managed-User.AsyncPineAI(api_key=..., managed_user=external_id)acts as that managed user.Authorization: Bearer <key>andPine-Managed-User, and the Socket.IO handshake sends{"token": key, "managed_user": external_id}.connect()resolves the Pine user ID that envelopes need, once per client, throughauth.me()(GET /api/v2/auth/me). Noexternal_idgoes into a URL path in managed-user mode.set_token()/auth.verify_code()or a per-requesttoken=.client_name="..."sendsPine-Clienton REST requests and on the Socket.IO handshake. The backend records it only for tenant-key requests.Authorization,Pine-Managed-User,Pine-Client) are applied after an injected client's defaults and after callerheaders=, so neither can override them.API_KEY_PREFIXES,validate_external_id,MANAGED_USER_HEADER,CLIENT_HEADER,PlatformAPI,ManagedUsersAPI(and their sync variants),ManagedUserandPlatformError.ValueErrorwithout echoing the key or theexternal_id.The README gains "Integrating as an enterprise (Platform API)", and the CHANGELOG gains an Unreleased entry. Versions are bumped only in release commits (RELEASING.md), so the version is unchanged here.
Backend contract
email,nameandphoneto the managed user, key +Pine-Managed-Userauth on/api/v2(including/api/v2/auth/me), and the/api/v2/socket.io/handshake. Where it differs from RunVid/webapp-backend#2022, this PR follows #2024.GET /api/v2/auth/me.GET /v2/usersis not a backend route.Rollout order (canonical — identical in all Platform API PRs)
tenants.managed_user_access_enabledstaysfalse.tenants.managed_user_access_enabled: truein the target environment's config (and updatingTestCheckedInManagedUserAccessSwitch), deploy, then create tenants.Constraints: #2022 before #2024; SDK #6 released before MCP #13 merges; MCP deploys after #2024; end-to-end testing of SDK/MCP in staging requires staging's own steps 1–6 first.
Tests
tests/test_platform.pyruns against a fake backend on 127.0.0.1 (aiohttp + python-socketio). It covers:Pine-Clienton the handshake, and a singleauth.me()across reconnects;platform_route_not_allowedand 429 onsessions.list(), with no leakage;set_token, caller headers that cannot override identity, the key-prefix rule, and the public names.HOME/PATHand proxies pointed at a dead local port.🤖 Generated with Claude Code