> ## Documentation Index
> Fetch the complete documentation index at: https://developer.plugchoice.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Client sessions and scopes

> What your server creates for Link, what it may reach, and how the SDK gets it.

export const Screen = ({name, alt, caption, wide = false}) => {
  const style = wide ? {
    width: '100%'
  } : {
    width: '280px',
    maxWidth: '100%'
  };
  return <figure style={{
    margin: '1.5rem 0',
    textAlign: 'center'
  }}>
      <img className="block dark:hidden" src={`/images/sdk/${name}-light.webp`} alt={alt} style={{
    ...style,
    margin: '0 auto',
    borderRadius: '12px',
    border: '1px solid rgba(128, 128, 128, 0.25)'
  }} />
      <img className="hidden dark:block" src={`/images/sdk/${name}-dark.webp`} alt={alt} style={{
    ...style,
    margin: '0 auto',
    borderRadius: '12px',
    border: '1px solid rgba(128, 128, 128, 0.25)'
  }} />
      {caption ? <figcaption style={{
    marginTop: '0.5rem',
    fontSize: '0.875rem',
    opacity: 0.7
  }}>{caption}</figcaption> : null}
    </figure>;
};

A **client session** says who Link acts for and what it may reach, for 60 minutes. Your server creates it with [Create a client session](/sdk/api/create-client-session) and hands its **client secret** to your app. Link opens any number of [link sessions](/sdk/concepts/link-sessions) with it until it expires.

## One endpoint on your server

You write one endpoint, and the SDK calls it through your app:

1. Your app creates the SDK with a `fetchClientSecret` callback, once.
2. When your app opens Link for an action, the SDK calls `fetchClientSecret` with that action (`add`, `network`, …, plus its `chargerId` or `siteId`), while the hosted flow loads.
3. Your callback asks your endpoint, which creates a client session scoped to that action and returns its `client_secret`.
4. When the secret runs out while Link is still open, the SDK calls `fetchClientSecret` again with the same action. Your user can take as long as they need.

If the callback throws, returns an empty string or takes longer than 30 seconds, the hosted flow shows an error screen with **Try again**. If your user gives up there, Link closes with the error code `clientSecretUnavailable`.

<Screen name="secret-unavailable" alt="Link's error screen when the app couldn't give it a client secret" />

[Get started](/sdk/get-started) has a complete endpoint in Node.

## Who it's for

| You send | The client session is for | Consent screen |
| - | - | - |
| `external_user_id` | One of your users. The first client session for an `external_user_id` creates that user at Plugchoice. | In their first link session |
| No `external_user_id` | Your integration's own chargers | No |

`external_user_id` is your own id for the user: 1 to 200 letters, digits and `._:@-`. A Plugchoice user who calls the SDK API with their own token always acts for themselves.

## The scope

The `scope` is required. It's everything the client session's link sessions may reach, so a client secret can't be used on the rest of your fleet. Name at least one of these:

| Field | What it allows |
| - | - |
| `chargers` | Work on these chargers: `network`, `setup`, `reconnect`. Up to 100 ids. |
| `sites` | Add chargers to these sites, and work on any charger at them. Up to 100 ids. |
| `location` | Add chargers at this address. The first `add` link session creates its site. |
| `new_site` | `add` may create a new site at an address the hosted flow asks your user for. |

* Every id is checked when you create the client session. One that you may not use, or that doesn't exist, is [`404 not-found`](/sdk/errors/not-found). A scope can only narrow your access.
* A scope that names nothing is [`422 scope-required`](/sdk/errors/scope-required). There is no "everything": to reach a lot, list the sites.
* With `external_user_id`, the chargers and sites must be that user's. Without it, they must be at sites your integration's team owns.
* If your app opens an action for a charger or site outside the scope, Link can't start. It shows an error, and closes with the code `not-found`.

<Screen name="scope-error" alt="Link's error screen when the charger isn't in the client session's scope" />

### Scope each action

Scope the client session to exactly what the action is about:

| Action | Scope |
| - | - |
| `add` with a `siteId` | `{ "sites": [siteId] }` |
| `add` without one, address known | `{ "location": { … } }` |
| `add` without one, address unknown | `{ "new_site": true }` |
| `network`, `setup`, `reconnect` | `{ "chargers": [chargerId] }` |

An `add` link session goes to the site the app opened it for (`siteId`, which must be in `scope.sites`). Without a `siteId` it goes to the site of `location`, else to a new site, else to the scope's only site. A scope with several sites and nothing else needs a `siteId` ([`site-required`](/sdk/errors/site-required)). A scope of chargers only can't `add`.

### Locations

Send a `location` when you know where your user's charger is:

```json theme={null}
{
  "external_user_id": "user-123",
  "scope": {
    "location": {
      "reference": "address-987",
      "name": "Home",
      "address": {
        "street": "Stationsplein",
        "house_number": 1,
        "postal_code": "1012 AB",
        "city": "Amsterdam",
        "country": "NL"
      },
      "timezone": "Europe/Amsterdam"
    }
  }
}
```

* `reference` is your own id for the address. A later client session for the same user with the same `reference` adds to the same site.
* `timezone` is an IANA time zone, used for schedules and history.
* `name` is `Home` when you leave it out.

With `new_site` instead, the hosted flow asks your user for the address before it adds the charger.

<Screen name="location" alt="Link asking for the address of the charger" />

Once an `add` link session has made a site, later link sessions of the same client session add to it.

## Defaults for every link session

The other fields of a client session apply to all of its link sessions:

| Field | |
| - | - |
| `language` | The hosted flow's language, as a BCP 47 tag: `de`, `en`, `es`, `fi`, `fr`, `nl` or `zh`. Others fall back to English. |
| `color_scheme` | `light`, `dark` or `system` (the default). |
| `vendor` | A brand, such as `peblar`, to skip the brand picker. |
| `methods` | Only offer these [onboarding methods](/sdk/chargers). |
| `charger_reference` | Your own label for the charger being added, returned as its `reference`. |
| `redirect_uri` | In a browser only: where to send your user afterwards. See [In a browser](/sdk/web). |
| `device_type` | `charger`, the only device type today. |

## The client secret

* It starts with `cs_test_` for a test integration and `cs_live_` for a live one. Otherwise it's opaque.
* It works for 60 minutes (`expires_at`), for any number of link sessions.
* Plugchoice stores only a hash of it. Don't log it, and don't put it in a URL.
* The SDK never puts it in a URL either: it hands it to the hosted flow directly. In a browser, `web_url` carries it in the URL fragment, which never reaches a server.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.