Connect partner onboarding
This guide walks a new Connect partner through end-to-end integration. Read the Connect overview first if you haven’t.
Onboarding
Section titled “Onboarding”-
Request partner registration. Email jorge@surveycoder.io with:
- Platform name + URL
- Privacy policy + Terms of Service URLs
- Redirect URI(s) for Standard flow (e.g.
https://app.yourplatform.io/oauth/callback) — required only if you’ll use Standard - Whether you want Express flow (managed sub-orgs)
- Expected scopes (from the scope catalog)
-
Receive your credentials. You’ll get four values, shown once and never again:
client_id(live)client_secret(live)client_id_test(sandbox)client_secret_test(sandbox)
Store all four in your secret manager immediately. Survey Coder only keeps bcrypt hashes; lost secrets must be rotated by us.
-
Add a Stripe payment method to your Survey Coder partner master org. This is the org Survey Coder will invoice monthly. (Sandbox usage is not billed.)
-
Start integrating against sandbox first. Every code example below works against sandbox by using
client_id_test/client_secret_test. Switch to live credentials only after end-to-end tests pass.
Standard flow integration
Section titled “Standard flow integration”For platforms whose customers are existing Survey Coder users.
-
Build the consent URL and redirect the end-customer to it.
https://surveycoder.io/oauth/authorize?client_id=cc_xxxx&redirect_uri=https://app.yourplatform.io/oauth/callback&scope=projects:read+coding:write+refinement:apply&state=<opaque-CSRF-token>&response_type=codeThe
stateparameter is mandatory for CSRF protection — generate a random per-session value and verify it in step 3. -
Handle the callback at your
redirect_uri. Survey Coder appends?code=...&state=...on Approve, or?error=access_denied&state=...on Deny. -
Exchange the code for tokens. Server-to-server (never from the browser — client_secret would leak):
Terminal window curl -X POST https://api.surveycoder.io/oauth/token \-d grant_type=authorization_code \-d code=scp_ac_xxxx \-d client_id=cc_xxxx \-d client_secret=cs_xxxx \-d redirect_uri=https://app.yourplatform.io/oauth/callbackconst r = await fetch('https://api.surveycoder.io/oauth/token', {method: 'POST',headers: { 'Content-Type': 'application/x-www-form-urlencoded' },body: new URLSearchParams({grant_type: 'authorization_code',code,client_id: process.env.SC_CLIENT_ID!,client_secret: process.env.SC_CLIENT_SECRET!,redirect_uri: process.env.SC_REDIRECT_URI!,}),});const { access_token, refresh_token, expires_in } = await r.json();import httpxr = httpx.post('https://api.surveycoder.io/oauth/token', data={'grant_type': 'authorization_code','code': code,'client_id': os.environ['SC_CLIENT_ID'],'client_secret': os.environ['SC_CLIENT_SECRET'],'redirect_uri': os.environ['SC_REDIRECT_URI'],})r.raise_for_status()tokens = r.json()Response shape:
{"access_token": "scp_at_...","refresh_token": "scp_rt_...","token_type": "Bearer","expires_in": 3600,"scope": "projects:read coding:write refinement:apply"} -
Store the tokens in your DB keyed by the end-customer id. Access tokens expire in 1 hour; refresh tokens in 90 days.
-
Make authenticated requests with
Authorization: Bearer <access_token>:Terminal window curl https://api.surveycoder.io/v1/projects \-H "Authorization: Bearer scp_at_..." -
Refresh expired access tokens using the refresh token. The refresh token rotates on each use (single-use; the response includes a new one to store).
Terminal window curl -X POST https://api.surveycoder.io/oauth/token \-d grant_type=refresh_token \-d refresh_token=scp_rt_... \-d client_id=cc_xxxx \-d client_secret=cs_xxxx
Express flow integration
Section titled “Express flow integration”For platforms whose customers do NOT have Survey Coder accounts. You operate on their behalf via managed sub-orgs.
-
Get a partner-level access token via
client_credentials:Terminal window curl -X POST https://api.surveycoder.io/oauth/token \-d grant_type=client_credentials \-d client_id=cc_xxxx \-d client_secret=cs_xxxx \-d "scope=connect:manage coding:write refinement:apply analytics:read"Response (no refresh token in Express):
{"access_token": "scp_at_...","token_type": "Bearer","expires_in": 3600,"scope": "connect:manage coding:write refinement:apply analytics:read"} -
Create a managed sub-org for each end-customer the first time they use your platform:
Terminal window curl -X POST https://api.surveycoder.io/v2/connect/managed-orgs \-H "Authorization: Bearer scp_at_..." \-H "Content-Type: application/json" \-d '{"external_ref": "yourplatform_customer_42","display_name": "Acme Coffee Tracker"}'Response:
{"managed_org_id": "managed-uuid","child_org_id": "org-uuid","external_ref": "yourplatform_customer_42"}Store the
child_org_idkeyed by your end-customer id. Theexternal_refis idempotent — passing the same value returns 409 if the org already exists. -
Per-customer requests include the
X-Connect-Orgheader naming the target sub-org:Terminal window curl -X POST https://api.surveycoder.io/v1/code \-H "Authorization: Bearer scp_at_..." \-H "X-Connect-Org: org-uuid" \-H "Content-Type: application/json" \-d '{"question_text": "What did you like about the product?","responses": [{"id":"r1","text":"Great taste"}, ...]}'The request operates on the sub-org’s data. RLS guarantees you can’t accidentally cross-contaminate between your end-customers.
-
Refresh by calling
client_credentialsagain before the 1-hour expiry. Connect Bearer tokens are cheap to re-issue — many partners just request a fresh one before every batch of API calls rather than caching.
Sandbox testing
Section titled “Sandbox testing”Sandbox = same flows, different credentials. Replace client_id / client_secret with the _test versions:
- All requests are tagged
environment=sandboxserver-side. - Sandbox traffic is hard-capped at 1,000 credits/month by default. The cap is configurable per partner.
- Sandbox usage never appears on your live Stripe invoice.
- Sandbox data lives in real
connect_authorizations/connect_managed_orgsrows, so you can verify your audit-log queries work correctly against real shapes.
When you’re ready for production, switch to live credentials. No code changes beyond the credentials.
Revocation
Section titled “Revocation”End-customers can revoke their Standard-flow authorization at any time via surveycoder.io/settings/connected-apps. After revocation:
- Subsequent calls with the revoked access token return
401 OAUTH_TOKEN_INVALID. - Your refresh token also stops working — you’d need a fresh consent.
- The revoked authorization stays in
oauth_authorizationswithrevoked_atset; you can see this viaGET /v2/connect/authorizations.
Partners can also revoke their own authorizations programmatically:
curl -X DELETE https://api.surveycoder.io/v2/connect/authorizations/<authorization-id> \ -H "Authorization: Bearer scp_at_..."Monitoring usage
Section titled “Monitoring usage”Query your own usage aggregated per data-org:
curl "https://api.surveycoder.io/v2/connect/usage?environment=live&from=2026-05-01&to=2026-06-01" \ -H "Authorization: Bearer scp_at_..."Response:
{ "environment": "live", "from": "2026-05-01T00:00:00.000Z", "to": "2026-06-01T00:00:00.000Z", "by_data_org": [ { "data_org_id": "org-uuid", "total_credits": 4250, "total_cost_cents": 26350, "request_count": 53 } ]}And see every action you took (paginated):
curl "https://api.surveycoder.io/v2/connect/audit?limit=50" \ -H "Authorization: Bearer scp_at_..."Set up a daily cron in your platform to pull /v2/connect/usage and alert if you’re approaching your monthly spend cap.
Error reference
Section titled “Error reference”Common errors specific to Connect:
| Code | When |
|---|---|
OAUTH_TOKEN_INVALID | Bearer token is malformed, expired, or revoked. Refresh or re-consent. |
OAUTH_ORG_REQUIRED | Express token without X-Connect-Org on a non-managed-orgs endpoint. |
OAUTH_ORG_NOT_MANAGED | X-Connect-Org references an org that isn’t yours. |
INSUFFICIENT_SCOPE | Token doesn’t include the granular scope this endpoint requires. |
CONNECT_SPEND_CAP_REACHED | Your monthly partner cap is hit. Raise it or wait for the 1st. |
CONNECT_PER_ORG_CAP_REACHED | One of your sub-orgs hit its per-org cap. |
CONFIRMATION_REQUIRED | Destructive endpoints (bulk-delete) need { confirm: true }. |
Full error catalog at Error Reference.
Reference
Section titled “Reference”- Connect overview
- OAuth scopes catalog
- API reference:
/oauth/*and/v2/connect/*endpoints in the API Reference.