Connected Apps Partner Guide
Let players securely connect their PB Vision account to your product.
Connected apps let your product ask a PB Vision user for permission to read data from their account. The flow uses standard OAuth 2.1.
Connected Apps are separate from the PB Vision Partner API. Connected Apps provide user-authorized, read-only account access. Partner API access is a separate capability for uploading video, paying for processing, and receiving completion webhooks. A product may use both, but Connected App credentials do not enable Partner API uploads and a Partner API key does not replace user authorization. For upload and processing integrations, see the PB Vision API Partner Guide.
1. Request credentials from PB Vision
Start at pb.vision/apps and choose Become a Partner, or email support@pb.vision. Send PB Vision:
Display name: what users see on the consent screen and in their "Connected apps" list (e.g.
Acme Pickleball Tracker).Product description: what you are building and how you plan to use PB Vision data.
Redirect URIs: exact callback URLs, including scheme, host, port, and path. Web redirects must use HTTPS. Native and desktop apps may register an
http://127.0.0.1loopback URI for local development. PB Vision ignores the loopback port, so the app can bind any available ephemeral port at runtime. Use the literal127.0.0.1, notlocalhost.Client type: confidential or public. Use a confidential client when your application has a secure backend that can store a Client Secret and perform the token exchange server-side. Use a public client when the application cannot securely store a secret, such as a browser-only, desktop, native, or mobile application.
Homepage URL (optional): linked from the consent screen.
Icon URL (optional): a direct HTTPS URL for a small square icon shown on the consent screen.
You'll get back:
{ "clientId": "partner-acmexyz", "clientSecret": "5f3a1b...32 chars total..." }Public clients receive a Client ID without a Client Secret and authenticate with PKCE alone. Confidential clients should store the Client Secret immediately and securely. Never expose it in browser code, shipped application code, public repositories, logs, or unencrypted messages.
2. Send the user to consent
For each authorization request:
Generate a cryptographically random
code_verifiercontaining 43 to 128 unreserved characters.Generate
code_challenge = BASE64URL(SHA256(code_verifier)).Generate a cryptographically random
statevalue and associate it with the user's session.Store the
code_verifiertemporarily for the token exchange.
Only S256 is supported. Redirect the user's browser to:
https://pb.vision/oauth/authorize ?client_id=<your Client ID> &redirect_uri=<one registered redirect URI> &scope=library%3Aread%20trends%3Aread &state=<random CSRF token> &code_challenge=<PKCE code challenge> &code_challenge_method=S256 URL-encode every parameter. If a Client ID contains +, URL-encode it as %2B.
After the user approves, PB Vision redirects to:
<your redirect URI>?code=<authorization code>&state=<state> If the user denies access, PB Vision redirects to <your redirect URI>?error=access_denied&state=<state>.
Verify that the returned state exactly matches the value stored for that authorization request. Do not continue if it does not match.
3. Exchange the code for an access token
Authorization codes are single-use and expire after 60 seconds. Exchange the code immediately.
Confidential clients should make this request from their backend. The Client Secret must never be available to an end user. Public clients omit client_secret and may perform the exchange from the app itself.
POST https://api-2o2klzx4pa-uc.a.run.app/oauth/token Content-Type: application/json { "grant_type": "authorization_code", "code": "<the code from step 2>", "code_verifier": "<the original PKCE code verifier>", "client_id": "<your Client ID>", "client_secret": "<your Client Secret>", "redirect_uri": "<the same redirect URI used during authorization>" } The redirect_uri must exactly match the value used in the authorization request. A successful response looks like:
{ "access_token": "pbv_pat_<uid>_<secret>", "token_type": "Bearer", "scope": "library:read trends:read" }The scope response field is space-separated and reflects the access actually granted. Access tokens do not currently expire by default, and PB Vision does not issue refresh tokens. Store access tokens securely and continue using them until the user or your app revokes access.
If the same user authorizes a set of scopes the existing grant already covers, PB Vision returns the existing token. Otherwise PB Vision issues a new token carrying exactly the newly authorized scopes and invalidates the previous token for that app. Request every scope your product needs in each authorization request, even scopes the user granted before.
4. Call authorized endpoints
Send the access token as a Bearer credential. Do not send it as x-token or combine Bearer authentication with PB Vision's x-uid/x-token headers.
Read the user's library
curl -sS -X POST https://api-2o2klzx4pa-uc.a.run.app/library/get \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{}'The response includes folders and video entries. The main fields are:
{ "folders": [ { "fid": 123456789, "name": "League Night", "epoch": 1784235600 } ], "videos": [ { "vid": "abc123def456", "name": "Game 1", "epoch": 1784235600, "secs": 698, "fid": 123456789 } ] }vidis the PB Vision video ID.epochis when the game was played, or when the upload finished if the game time is unavailable.secsis the video duration in seconds, rounded up to the nearest integer.fidis the folder ID associated with this video or session.Multi-game videos include a
sessionsarray with session-level names, timestamps, durations, and folders. A session may benullwhen the user is only entitled to some sessions in the video.Optional fields such as
hidden,starred,new, andprocessingWorkflowIdsdescribe the user's library state. Respect hidden videos and sessions rather than presenting them as active library items.
Stream a library video
For a vid, the whole-video media and root preview assets are:
Video: https://storage.googleapis.com/pbv-pro/<vid>/max.mp4 Poster: https://storage.googleapis.com/pbv-pro/<vid>/poster.jpg Thumbnail: https://storage.googleapis.com/pbv-pro/<vid>/thumbnail.jpg Preview: https://storage.googleapis.com/pbv-pro/<vid>/preview.gif Current processing also generates preview assets for each successfully processed session. Use the session's zero-based position in the sessions array as sessionIdx:
Poster: https://storage.googleapis.com/pbv-pro/<vid>/<sessionIdx>/poster.jpg Thumbnail: https://storage.googleapis.com/pbv-pro/<vid>/<sessionIdx>/thumbnail.jpg Preview: https://storage.googleapis.com/pbv-pro/<vid>/<sessionIdx>/preview.gif max.mp4 is the streamable H.264 file used by the PB Vision player. For a multi-session upload, it contains the complete video rather than a separate file for each session. Per-session and animated preview assets are generated on a best-effort basis and may be unavailable for older videos, videos that are still processing, or sessions whose previews could not be rendered. Use the root poster or thumbnail as a fallback. No additional Connected App scope or Partner API export setting is required to stream max.mp4 after library:read returns its vid.
OAuth protects discovery of the user's library, but the media asset URLs are not separately OAuth-authenticated.
Read the user's trends
POST /trend/get requires the authorizing user's PB Vision user ID (UID):
curl -sS -X POST https://api-2o2klzx4pa-uc.a.run.app/trend/get \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{"forUID":"<PB Vision user UID>"}'Current access tokens use the format pbv_pat_<uid>_<32-character-secret>. To obtain forUID, remove the pbv_pat_ prefix and the final underscore plus 32-character secret. PB Vision UIDs may contain underscores, so parse from the fixed-length suffix instead of splitting on every underscore. Treat the entire token as a credential and do not log or expose it.
Read the user's original referral code
curl -sS -X POST https://api-2o2klzx4pa-uc.a.run.app/user/referrals/get \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{}'The response is { "code": "..." } when the user signed up through a referral code, or {} otherwise.
5. Revoke access
Users can disconnect an app from Settings > Account > Connected apps in PB Vision. Partners should also revoke a token when a user removes the integration from the partner product:
POST https://api-2o2klzx4pa-uc.a.run.app/oauth/revoke Content-Type: application/json { "token": "<access token>", "client_id": "<your Client ID>", "client_secret": "<your Client Secret>" } Public clients omit client_secret.
The revocation endpoint returns 200 whether or not the token was active. A user must complete authorization again to reconnect after revocation.
Public, native, and desktop clients
A browser-only, desktop, native, or mobile app cannot protect a Client Secret because users can extract secrets from code shipped to them. It must be registered as a public client.
Public clients use PKCE without a Client Secret.
Store access tokens in the operating system's secure credential store.
Desktop apps may register an
http://127.0.0.1loopback redirect URI.PB Vision ignores the loopback port during matching, so the app can bind an available ephemeral port at runtime.
Use the literal
127.0.0.1, notlocalhost, for loopback redirects.Mobile apps should use an HTTPS app link or universal link as their production redirect URI.
Security checklist
Generate a new PKCE pair and
statevalue for every authorization request.Verify
statebefore exchanging the authorization code.Keep Client Secrets and access tokens out of source code, URLs, analytics, logs, and support messages.
Store confidential-client secrets and access tokens in a secrets manager or encrypted server-side store.
Store public-client access tokens in the operating system's secure credential store.
Request only the scopes your product needs.
Revoke the access token when a user disconnects the integration in your product.
Treat access tokens as credentials.
Scopes
The public scope-discovery endpoint is the source of truth for currently available scopes:
POST https://api-2o2klzx4pa-uc.a.run.app/oauth/scopes Request multiple scopes by separating them with spaces, for example scope=library:read trends:read. URL-encode spaces as %20 in the authorization URL.
Connected App scopes are currently read-only. They do not provide unrestricted account access or enable video uploads, processing webhooks, DUPR submissions, or the Partner API's raw per-video insights, stats, and computer-vision exports. Those capabilities use PB Vision's product UI, Partner API, or separately approved export paths.
When an eligible new user authorizes a Connected App, PB Vision may attribute that user to the app's partner account for referral rewards under the current referral-program rules.
Error responses
PB Vision wraps standard OAuth error fields under data:
{ "code": "BadRequestException", "message": "invalid_grant: PKCE code_verifier does not match code_challenge", "data": { "error": "invalid_grant", "error_description": "PKCE code_verifier does not match code_challenge" } }Read body.data.error and body.data.error_description.
A request with a missing or malformed body parameter is rejected before OAuth processing with a different shape, with no data field:
{ "code": "InvalidInputException", "message": "Body Validation Failure: body must have required property 'code_verifier'" }Handle this shape in addition to the OAuth error envelope.
Do not send PB Vision a Client Secret, access token, authorization code, or PKCE verifier in a support message. Send the sanitized authorization URL or request body, HTTP status, and sanitized error response.
Worked example (curl)
# 1. Generate a PKCE pair and state CODE_VERIFIER=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=') CODE_CHALLENGE=$(printf '%s' "$CODE_VERIFIER" \ | openssl dgst -sha256 -binary | base64 | tr '+/' '-_' | tr -d '=') STATE=$(openssl rand -hex 16) # 2. Open the consent URL in a browser echo "https://pb.vision/oauth/authorize?\ client_id=partner-acmexyz&\ redirect_uri=https%3A%2F%2Facme.example.com%2Fcb&\ scope=library%3Aread%20trends%3Aread&\ state=$STATE&\ code_challenge=$CODE_CHALLENGE&\ code_challenge_method=S256" # 3. After consent, PB Vision redirects to: # https://acme.example.com/cb?code=<CODE>&state=<STATE> # Verify STATE before continuing, then paste the authorization code. CODE="<paste the code from the redirect>" # 4. Exchange the code immediately RESPONSE=$(curl -sS https://api-2o2klzx4pa-uc.a.run.app/oauth/token \ -H 'Content-Type: application/json' \ -d '{ "grant_type": "authorization_code", "code": "'"$CODE"'", "code_verifier": "'"$CODE_VERIFIER"'", "client_id": "partner-acmexyz", "client_secret": "<your Client Secret>", "redirect_uri": "https://acme.example.com/cb" }') ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r .access_token) # 5. Call a scoped API curl -sS -X POST https://api-2o2klzx4pa-uc.a.run.app/library/get \ -H "Authorization: Bearer $ACCESS_TOKEN" \ -H 'Content-Type: application/json' \ -d '{}'