---
title: Sign in with Insy
description: Let Insy users grant your application access to their memberships and products using the OAuth 2.0 authorization code flow.
---

Sign in with Insy is an OAuth 2.0 authorization code flow. Your application sends a user to Insy,
the user signs in and approves the scopes you asked for, and you receive an access token that lets
you read that user's data on their behalf.

Use OAuth when the data belongs to a user who is not you. Use an [API key](/developers/api/authentication)
when your own backend acts on your own community.

> **OAuth 2.0, not OpenID Connect**
>
> This is plain OAuth 2.0. There is no `id_token`, no `/.well-known` discovery document and no
> UserInfo claims beyond `sub` and `email`. Configure your client library as a generic OAuth 2.0
> provider with explicit endpoint URLs — an OIDC-only library will not work.

## Pick an integration

<CardGroup cols={2}>
  <Card title="Login widget" href="/developers/oauth/widget" icon="plug">
    One script tag, no backend. A public client that runs PKCE in the browser and hands you tokens
    through a callback or a DOM event.
  </Card>
  <Card title="Server-side flow" href="/developers/oauth/server-side" icon="arrow-right-left">
    The full redirect and token exchange for a confidential client, where the secret and the tokens
    stay on your server.
  </Card>
  <Card title="PKCE" href="/developers/oauth/pkce" icon="shield-check">
    How to generate the verifier and the S256 challenge. Required for public clients, optional for
    confidential ones.
  </Card>
  <Card title="Scopes" href="/developers/oauth/scopes" icon="list">
    The two scopes Insy grants, what each unlocks, and how to ask for the minimum.
  </Card>
  <Card title="Reference" href="/developers/oauth/reference" icon="code">
    Every OAuth endpoint, parameter, response shape and error code.
  </Card>
</CardGroup>

## How the flow works

1. **Send the user to Insy**

    Redirect the browser to `GET https://api.insy.io/oauth/authorize` with your `client_id`, a
    registered `redirect_uri`, the `scope` you need and a `state` value you can verify later.

2. **The user consents**

    Insy authenticates the user and shows a consent screen naming your application. If the user
    declines you get redirected back with `error=access_denied`.

3. **Insy redirects back with a code**

    Your `redirect_uri` receives `?code=...&state=...`. The authorization code is valid for
    60 seconds and can be exchanged once.

4. **Exchange the code for tokens**

    `POST https://api.insy.io/oauth/token` returns an access token valid for one hour and a refresh
    token valid for 30 days. The response status is `201`, not `200`.

5. **Call the resource endpoints**

    Send `Authorization: Bearer <access_token>` to `/oauth/userinfo`, `/oauth/memberships` or
    `/oauth/products`.

## Register your application

Register your own client at
[**Account → Developer → OAuth clients**](https://insy.io/account/developer/oauth-clients) and
press **Create client**. Credentials come back on the spot; nothing is provisioned by hand. The
same screen edits, rotates and deletes a client afterwards.

### What to fill in

| Item | Required | Notes |
| --- | --- | --- |
| Application name | Yes | Shown to the user on the consent screen. |
| Client type | Yes | `confidential` or `public` — see below. |
| Redirect URIs | Yes | Exact URIs. Anything not registered is rejected. List every environment you use. One exception: widget clients do not register the hosted callback — `https://insy.io/oauth/widget-callback` is accepted automatically for any client that has at least one web origin registered. |
| Scopes | Yes | `memberships.read`, `products.read`, or both. Ask for the minimum. |
| Logo | Optional | Shown next to your application name on the consent screen. |
| Web origins | Widgets only | The origins the login widget may run on. See [web origins](/developers/oauth/widget). |

### Confidential or public

The type is a property of the client, never inferred from a request — but you can change it on
the client's own screen if you picked wrong.

| | Confidential | Public |
| --- | --- | --- |
| Has a `client_secret` | Yes | No |
| Where it runs | Your server | A browser or a native app |
| `client_secret` in the token request | Required | Must be omitted — sending one returns `invalid_client` |
| PKCE | Optional | Required |

Choose **confidential** if you have a backend that can hold a secret. Choose **public** if the flow
completes in a browser or a mobile app, including anything built on the
[login widget](/developers/oauth/widget).

### What you get back

| Credential | Format | Issued to |
| --- | --- | --- |
| `client_id` | `oac_` followed by 24 hex characters | Every client |
| `client_secret` | `ocs_` followed by 48 hex characters | Confidential clients only |

> **The secret is shown once**
>
> `client_secret` is displayed a single time, when your client is created. Store it in your secret
> manager immediately. If it is lost, press **Rotate secret** on the client to mint a new one — the
> previous secret stops working at that moment, so swap it in your application first.

The `client_id` is not a secret. It appears in browser URLs and, for widgets, in your page source.

## Token lifetimes

| Token | Lifetime | Behaviour |
| --- | --- | --- |
| Authorization code | 60 seconds | Single use. Exchange it immediately. |
| Access token | 1 hour (`expires_in: 3600`) | Opaque string. Do not parse it. |
| Refresh token | 30 days, sliding | Each refresh issues a new pair with a fresh 30-day window. |

Refreshing revokes the old refresh token **and** the old access token immediately. Replace both in
storage as a single atomic write, or an in-flight request will start failing with `invalid_token`.

## Known deviations from the RFCs

<Accordion>
  <AccordionItem title="The token endpoint returns 201, not 200" description="RFC 6749 §5.1 mandates 200">
    `POST /oauth/token` answers with HTTP `201` on success. Strict client libraries assert `200` and
    will throw on an otherwise valid response. Either configure the library to accept `201` or make
    the token request with plain HTTP.
  </AccordionItem>
  <AccordionItem title="HTTP Basic client authentication is not supported">
    Credentials go in the request **body** (`client_secret_post`). `client_secret_basic` — the
    default in most libraries — is not accepted. Set the token endpoint authentication method
    explicitly.
  </AccordionItem>
  <AccordionItem title="scope is required on the authorize request">
    RFC 6749 makes `scope` optional. Insy requires it. A request without `scope` will not produce a
    code.
  </AccordionItem>
  <AccordionItem title="No WWW-Authenticate header on 401 or 403">
    Resource endpoints return the error as a JSON body only. Libraries that discover the error from
    the `WWW-Authenticate` header will see nothing. Read the response body.
  </AccordionItem>
  <AccordionItem title="No refresh token reuse detection">
    Replaying a revoked refresh token fails, but it does not invalidate the rest of the token family.
    Treat refresh tokens as high-value secrets on your side.
  </AccordionItem>
  <AccordionItem title="There are no official SDKs">
    Every example in these docs is plain HTTP against `https://api.insy.io`. There is no sandbox
    environment and no version prefix in the URL.
  </AccordionItem>
</Accordion>

## Response shapes

The OAuth token, revoke, userinfo, memberships and products endpoints return bare, RFC-shaped JSON —
not the `BaseResponse` envelope used elsewhere in the API.

```json title="POST /oauth/token — 201"
{
  "access_token": "…",
  "refresh_token": "…",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "memberships.read"
}
```

```json title="Any OAuth error"
{
  "error": "invalid_grant",
  "error_description": "Authorization code has expired"
}
```

The one exception is `GET /oauth/client/{clientId}`, the public endpoint for rendering your own
consent screen, which does use the envelope.
