Skip to content

Connect partner onboarding

This guide walks a new Connect partner through end-to-end integration. Read the Connect overview first if you haven’t.

  1. 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)
  2. 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.

  3. 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.)

  4. 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.

For platforms whose customers are existing Survey Coder users.

  1. 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=code

    The state parameter is mandatory for CSRF protection — generate a random per-session value and verify it in step 3.

  2. Handle the callback at your redirect_uri. Survey Coder appends ?code=...&state=... on Approve, or ?error=access_denied&state=... on Deny.

  3. 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/callback

    Response shape:

    {
    "access_token": "scp_at_...",
    "refresh_token": "scp_rt_...",
    "token_type": "Bearer",
    "expires_in": 3600,
    "scope": "projects:read coding:write refinement:apply"
    }
  4. Store the tokens in your DB keyed by the end-customer id. Access tokens expire in 1 hour; refresh tokens in 90 days.

  5. Make authenticated requests with Authorization: Bearer <access_token>:

    Terminal window
    curl https://api.surveycoder.io/v1/projects \
    -H "Authorization: Bearer scp_at_..."
  6. 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

For platforms whose customers do NOT have Survey Coder accounts. You operate on their behalf via managed sub-orgs.

  1. 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"
    }
  2. 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_id keyed by your end-customer id. The external_ref is idempotent — passing the same value returns 409 if the org already exists.

  3. Per-customer requests include the X-Connect-Org header 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.

  4. Refresh by calling client_credentials again 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 = same flows, different credentials. Replace client_id / client_secret with the _test versions:

  • All requests are tagged environment=sandbox server-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_orgs rows, 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.

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_authorizations with revoked_at set; you can see this via GET /v2/connect/authorizations.

Partners can also revoke their own authorizations programmatically:

Terminal window
curl -X DELETE https://api.surveycoder.io/v2/connect/authorizations/<authorization-id> \
-H "Authorization: Bearer scp_at_..."

Query your own usage aggregated per data-org:

Terminal window
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):

Terminal window
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.

Common errors specific to Connect:

CodeWhen
OAUTH_TOKEN_INVALIDBearer token is malformed, expired, or revoked. Refresh or re-consent.
OAUTH_ORG_REQUIREDExpress token without X-Connect-Org on a non-managed-orgs endpoint.
OAUTH_ORG_NOT_MANAGEDX-Connect-Org references an org that isn’t yours.
INSUFFICIENT_SCOPEToken doesn’t include the granular scope this endpoint requires.
CONNECT_SPEND_CAP_REACHEDYour monthly partner cap is hit. Raise it or wait for the 1st.
CONNECT_PER_ORG_CAP_REACHEDOne of your sub-orgs hit its per-org cap.
CONFIRMATION_REQUIREDDestructive endpoints (bulk-delete) need { confirm: true }.

Full error catalog at Error Reference.