Skip to content
Insy
For developers
Esc
navigateopen⌘Jpreview
On this page

Scopes

The two OAuth scopes Insy grants, what each one unlocks, how to request them, and why products.read is not what most people assume.

A scope is the permission a user grants your application on the consent screen. Insy has two, and that is the complete list. Each one unlocks exactly one resource endpoint.

scope is required on every authorize request. RFC 6749 makes it optional; Insy does not. A request without it will not produce an authorization code.

The two scopes

Scope Unlocks Returns
memberships.read GET /oauth/memberships The memberships flagged active on the user’s account.
products.read GET /oauth/products The digital products the user has created as a seller. Communities are not included.

GET /oauth/userinfo needs no scope at all. Any valid access token can read it, so you can identify a user without asking for either permission.

memberships.read

Returns every membership whose isActive flag is set. There is no date filter on the query — isActive is maintained by Insy’s billing and expiry jobs, so check validFrom and validUntil yourself if you need a hard boundary rather than a flag. The list is not paginated.

{
  "memberships": [
    {
      "id": "3f9a1c02-7b41-4e8d-9a63-5c0e2d81f7ab",
      "productType": "community",
      "isActive": true,
      "validFrom": "2026-01-14T09:12:44.000Z",
      "validUntil": "2026-09-14T09:12:44.000Z"
    }
  ]
}

An empty memberships array means the user has no membership flagged active — it does not mean the request failed.

products.read

Returns the digital products the user created as a seller. Communities are not products in this sense and are never returned — a seller whose catalogue is only paid communities gets an empty array.

{
  "products": [
    {
      "id": "b71e4d90-2a55-4c31-8f0d-6e9a3b12c4df",
      "name": "Trading Signals",
      "slug": "trading-signals",
      "type": "course"
    }
  ]
}

type is one of ebook, course, video, audio, document, consultation or other, and may be null if the seller never set one.

Requesting scopes

Scopes go in a single scope parameter, separated by a space or a comma — both work, and you can mix them. In a URL a space is percent-encoded as %20. The examples below use a space, which is the RFC 6749 convention.

https://api.insy.io/oauth/authorize
  ?response_type=code
  &client_id=oac_9f2c41e7a83b06d5c4e19f27
  &redirect_uri=https%3A%2F%2Fshop.example%2Fcallback
  &scope=memberships.read%20products.read
  &state=1f7c9b2e4a8d
const authorizeUrl = new URL("https://api.insy.io/oauth/authorize");
authorizeUrl.searchParams.set("response_type", "code");
authorizeUrl.searchParams.set("client_id", "oac_9f2c41e7a83b06d5c4e19f27");
authorizeUrl.searchParams.set("redirect_uri", "https://shop.example/callback");
authorizeUrl.searchParams.set("scope", "memberships.read products.read");
authorizeUrl.searchParams.set("state", crypto.randomUUID());

// URLSearchParams encodes the separating space for you.
console.log(authorizeUrl.toString());
<script
  async
  src="https://insy.io/oauth/widget.js"
  data-client-id="oac_9f2c41e7a83b06d5c4e19f27"
  data-scope="memberships.read products.read"
></script>

Check what you were granted

The token response echoes the scopes actually attached to the access token.

{
  "access_token": "…",
  "refresh_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "memberships.read"
}

Branch on the scope in the response, not on what you asked for. Refreshing a token carries the same scopes forward; there is no way to widen a token’s scope, so to gain a scope you must send the user through the authorize flow again.

Unknown or ungranted scopes

If you request a scope that does not exist, the user is redirected back to your redirect_uri with an error instead of a code:

https://shop.example/callback?error=invalid_scope&state=1f7c9b2e4a8d

Handle error on your callback route the same way you handle code. The state you sent is returned with the error, so you can still match the response to the attempt that started it.

Calling a resource endpoint with a token that lacks the required scope returns 403:

{
  "error": "insufficient_scope",
  "error_description": "memberships.read scope required"
}

An expired or revoked token returns 401 with invalid_token. Note that neither response carries a WWW-Authenticate header, so read the JSON body to tell the two cases apart.

Ask for the minimum

Request only the scopes the feature you are shipping actually needs.

  • Signing users in? Ask for the narrowest scope that works. /oauth/userinfo needs none, so a pure sign-in integration does not need memberships.read at all.
  • Gating content on an active subscription? memberships.read alone.
  • Building a creator-facing dashboard over someone’s own catalogue? That is the case products.read is for.

Every extra scope is one more line on the consent screen. You cannot widen a token later, so it is tempting to request everything up front — resist it, and re-run consent when a feature genuinely needs more.

Was this page helpful?