An install is one customer’s grant of access to your platform app: which scopes they approved, whether they consented to Work Email sharing, and the tokens minted under that grant. Every Platform API token (ot_ptk_…) belongs to exactly one install.
The Consent Deep Link
Send your customer (an OpenTrain employer — specifically the organization owner) to:
https://app.opentrain.ai/integrations/{partnerSlug}/connect?external_project_id=<id>&redirect_uri=<url>&state=<opaque>
| Query parameter | Required | Purpose |
|---|
external_project_id | No | Your project identifier. Echoed back on the return redirect so you know which of your projects initiated the connection. |
redirect_uri | No | Where to send the customer after consent. Must be http(s) and match a redirect URI registered for your app: same origin, and its path must start with the registered base path (so dynamic sub-paths under a registered base work). |
state | No | Opaque value echoed back unchanged on the return redirect — use it to correlate the flow on your side. |
On the consent screen the customer sees your app’s name, the scopes you requested, and — if you requested participants:email — a separate, explicit PII consent checkbox for Work Email sharing. They can approve or decline.
The One-Time Token
When the customer approves, OpenTrain mints an install-scoped ot_ptk_… token and displays it once, on the consent screen only. The customer copies it into your platform’s integration settings.
The token never travels in the redirect back to your platform. The return redirect carries only opentrain_install_id, plus your echoed state and external_project_id — never credentials. If the token is lost, the customer must reconnect to mint a new one.
Scopes
Your app requests a subset of these seven scopes when you register it; the customer sees each one described on the consent screen:
| Scope | Grants |
|---|
project-links:read | Read links between your projects and OpenTrain jobs |
project-links:write | Create and remove project links (implies project-links:read) |
contracts:read | Read contracts (status, dates, budget) on linked jobs |
participants:read | Read hired participants on linked contracts: OpenTrain user ID, display name, profile URL, country |
participants:email | Read the participant’s @opentrain.work Work Email. Requires the explicit PII consent checkbox at install time; personal email addresses are never shared |
usage:write | Report cumulative per-day work usage (time, tasks, labels) for contracts on linked jobs |
webhooks:manage | Create, list, update, and delete webhook endpoints |
participants:email is double-gated: the scope must be granted and the install must have piiConsent: true. If the customer granted the scope but left the consent box unchecked, requests that would return Work Email are refused with 403 at request time — and webhook payloads simply omit the workEmail field.
Inspecting Your Install
GET /installs/current returns the install your token belongs to — useful as a connectivity check and to confirm which scopes and consent you actually have:
curl -sS https://app.opentrain.ai/api/partner/v1/installs/current \
-H "Authorization: Bearer $OT_PARTNER_TOKEN"
{
"install": {
"id": "<INSTALL_ID>",
"status": "ACTIVE",
"scopes": [
"project-links:read",
"project-links:write",
"contracts:read",
"participants:read",
"participants:email",
"webhooks:manage"
],
"piiConsent": true,
"organizationId": "<ORG_ID>",
"createdAt": "2026-06-12T10:00:00.000Z",
"partnerApp": { "id": "<APP_ID>", "slug": "your-app", "name": "Your App" },
"token": { "id": "<TOKEN_ID>", "name": "Your App connection", "scopes": ["..."] }
}
}
What Every Request Requires
Beyond the right scope, every Platform API request checks four things. Any of them failing yields 401 or 403:
- A live token — not revoked or expired
- An
ACTIVE install
- An
ACTIVE platform app
- The annotation-platform feature enabled for the granting employer’s account
Reconnecting
If a customer runs the consent flow again for an app they already installed, OpenTrain revokes all previous tokens for that install before minting the fresh one. A scope reduction on reconnect therefore cannot be bypassed by holding on to an older, broader token. Treat any 401 as a signal to ask the customer to reconnect.
Revocation
Customers can disconnect your app at any time from their OpenTrain integrations page. Disconnecting immediately:
- Flips the install to
REVOKED
- Revokes every access token — your next request returns
401
- Emits an
install.revoked event to webhook deliveries already queued
- Stops all new event fan-out for that install
Handle install.revoked by ceasing work for that customer and marking the connection as disconnected in your UI.