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

# Create a client session

> Says who Link acts for, and what it may reach, for 60 minutes. Call it from the one endpoint your app's
`fetchClientSecret` calls and return `client_secret` to the SDK, or send a browser to `web_url` (append
`&action=…&charger_id=…` for an action other than `add`). Link opens any number of link sessions with it
until it expires, then asks your app for a new one.

Without `external_user_id` the client session is for your own chargers. With it, it is for one of your
users: the first client session for an `external_user_id` creates that user, and their first link
session shows them a consent screen with your name and logo. A Plugchoice user calling with their own
token acts for themselves.

The **`scope`** (required) is everything its link sessions may reach, so the secret can't be used on the
rest of your fleet: the chargers to work on, the sites to add chargers to (and work on any charger at
them), one address to add chargers at (`location`), and whether `add` may make a new site at an address
the hosted flow asks your user for (`new_site`). Name at least one (`422 scope-required`), at most 100 ids
per list. Every id is checked now (`404 not-found` for one you may not use), so a scope can only narrow
your access; there is no "everything". What you may name:

| Caller | Chargers and sites | `location` or `new_site` |
|---|---|---|
| Integration, without `external_user_id` (your own chargers) | At sites your team owns | Creates a site in your team |
| Integration, with `external_user_id` (one of your users) | At that user's sites | Reuses the user's site with the same `reference`, else creates one in your team |
| Plugchoice user | At sites they have Sensitive access to | Creates a site in their current team |

Once an `add` link session has made the site of `location` (when it starts) or a new site (when the
hosted flow has the address), later link sessions of the same client session add to that site.

The other fields are defaults for every link session: the language, the colour scheme, the brand and
methods offered, and your label for the charger.




## OpenAPI

````yaml api-reference/sdk/sdk-v1.openapi.yaml POST /client-sessions
openapi: 3.1.0
info:
  title: Plugchoice SDK API
  version: '2026-10-07'
  summary: Connect EV chargers to Plugchoice from your own app.
  description: >
    The API of the Plugchoice SDK. Your server creates client sessions, which
    say who Link (the SDK's

    onboarding feature) acts for and what it may reach; it reads the link
    sessions Link opened with them, and

    the chargers and what each can do. The `/session` endpoints are used only by
    Link's hosted flow at

    `connect.plugchoice.com`.


    Errors are RFC 9457 problem details; times are RFC 3339 UTC; ids are UUIDs.


    **Versions** are dates. Pin one with the `Plugchoice-Version` request
    header; without it you get the newest.

    Every response says which version answered in the same header. An unknown
    version is

    `400 unknown-version`. The only version is `2026-10-07`.
  contact:
    name: Plugchoice
    url: https://developer.plugchoice.com/sdk
    email: support@plugchoice.com
servers:
  - url: https://api.plugchoice.com/sdk/v1
    description: Production
security: []
tags:
  - name: Client sessions
    description: >-
      Say who Link acts for, and what it may reach, for an hour. Integrations
      and Plugchoice users.
  - name: Link sessions
    description: >-
      Read the outcome of a link session, one time through Link. Integrations
      and Plugchoice users.
  - name: Chargers
    description: >-
      Read chargers and what they can do (`capabilities`). Integrations and
      Plugchoice users.
  - name: Session
    description: Link's hosted flow only. Opens and runs one link session.
paths:
  /client-sessions:
    post:
      tags:
        - Client sessions
      summary: Create a client session
      description: >
        Says who Link acts for, and what it may reach, for 60 minutes. Call it
        from the one endpoint your app's

        `fetchClientSecret` calls and return `client_secret` to the SDK, or send
        a browser to `web_url` (append

        `&action=…&charger_id=…` for an action other than `add`). Link opens any
        number of link sessions with it

        until it expires, then asks your app for a new one.


        Without `external_user_id` the client session is for your own chargers.
        With it, it is for one of your

        users: the first client session for an `external_user_id` creates that
        user, and their first link

        session shows them a consent screen with your name and logo. A
        Plugchoice user calling with their own

        token acts for themselves.


        The **`scope`** (required) is everything its link sessions may reach, so
        the secret can't be used on the

        rest of your fleet: the chargers to work on, the sites to add chargers
        to (and work on any charger at

        them), one address to add chargers at (`location`), and whether `add`
        may make a new site at an address

        the hosted flow asks your user for (`new_site`). Name at least one (`422
        scope-required`), at most 100 ids

        per list. Every id is checked now (`404 not-found` for one you may not
        use), so a scope can only narrow

        your access; there is no "everything". What you may name:


        | Caller | Chargers and sites | `location` or `new_site` |

        |---|---|---|

        | Integration, without `external_user_id` (your own chargers) | At sites
        your team owns | Creates a site in your team |

        | Integration, with `external_user_id` (one of your users) | At that
        user's sites | Reuses the user's site with the same `reference`, else
        creates one in your team |

        | Plugchoice user | At sites they have Sensitive access to | Creates a
        site in their current team |


        Once an `add` link session has made the site of `location` (when it
        starts) or a new site (when the

        hosted flow has the address), later link sessions of the same client
        session add to that site.


        The other fields are defaults for every link session: the language, the
        colour scheme, the brand and

        methods offered, and your label for the charger.
      operationId: createClientSession
      parameters:
        - $ref: '#/components/parameters/PlugchoiceVersion'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateClientSessionRequest'
            examples:
              integrationUser:
                summary: Integration, a user's first charger at their address
                value:
                  external_user_id: acme-user-1
                  scope:
                    location:
                      reference: acme-address-123
                      address:
                        street: Stationsplein
                        house_number: 1
                        postal_code: 1012 AB
                        city: Amsterdam
                        country: NL
                      timezone: Europe/Amsterdam
                  language: nl
                  charger_reference: acme-device-987
              integrationOwnChargers:
                summary: Integration, its own chargers at one of its sites
                value:
                  scope:
                    sites:
                      - 7d0f6f3e-2b0b-4c55-9b1e-3c6b8f0a9e21
                  charger_reference: depot-bay-4
              user:
                summary: Plugchoice user, the network settings of one charger
                value:
                  scope:
                    chargers:
                      - 2f1c8e47-5a4b-4c3d-9e2f-1a0b9c8d7e6f
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreatedClientSession'
        '401':
          $ref: '#/components/responses/Unauthenticated'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          description: >-
            A charger or site in `scope` is not one you may use, or doesn't
            exist (`not-found`).
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
        '422':
          description: >-
            The body is invalid (`validation-failed`), the scope names nothing
            (`scope-required`), or `device_type` is not `charger`
            (`unsupported-device-type`).
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/Problem'
        '429':
          $ref: '#/components/responses/RateLimited'
      security:
        - integrationToken: []
        - userToken: []
components:
  parameters:
    PlugchoiceVersion:
      name: Plugchoice-Version
      in: header
      required: false
      description: >-
        The API version to answer with (`400 unknown-version` for one this API
        does not know). Every response carries the version that answered in the
        same header.
      schema:
        type: string
        enum:
          - '2026-10-07'
  schemas:
    CreateClientSessionRequest:
      type: object
      required:
        - scope
      properties:
        external_user_id:
          $ref: '#/components/schemas/ExternalUserId'
          description: >-
            Integrations only. For this user of yours; turns on the consent
            screen. Without it, your own chargers.
        device_type:
          $ref: '#/components/schemas/DeviceType'
          default: charger
        scope:
          $ref: '#/components/schemas/ClientSessionScope'
        language:
          type: string
          description: >-
            BCP 47 tag for the hosted flow. Unsupported languages fall back to
            English.
          default: en
          examples:
            - nl
        color_scheme:
          type: string
          enum:
            - light
            - dark
            - system
          default: system
        vendor:
          type: string
          description: A catalog vendor id (for example `peblar`). Skips the brand picker.
        methods:
          type: array
          uniqueItems: true
          description: Limit the onboarding methods offered. Default all.
          items:
            $ref: '#/components/schemas/OnboardingMethod'
        charger_reference:
          type: string
          maxLength: 255
          description: >-
            Integrations only. Your own label for the charger being added;
            returned as `reference`.
        redirect_uri:
          type: string
          format: uri
          description: >-
            Browser only. Where the hosted flow sends your user when a link
            session is done; it must be on your integration's allow-list (for a
            Plugchoice user, a Plugchoice or custom domain of theirs). The SDK
            ignores it and returns to your app.
    CreatedClientSession:
      type: object
      required:
        - id
        - client_secret
        - expires_at
        - web_url
      properties:
        id:
          type: string
          format: uuid
        client_secret:
          type: string
          description: >
            Hand it to the SDK (your `fetchClientSecret` returns it). `cs_test_`
            for a test integration,

            `cs_live_` otherwise, then 32 random bytes base64url. Opaque; never
            log it.
          examples:
            - cs_live_Hq3k9d0mR2xYw7pLf8sVb1nC4eTgJ6aZuQ5iKoXyM0w
        expires_at:
          type: string
          format: date-time
          description: Link can open link sessions with it until then (60 minutes).
        web_url:
          type: string
          format: uri
          description: >-
            Link in a browser, with the client secret in the fragment (never
            sent to a server). Append `&action=…&charger_id=…` for an action
            other than `add`.
          examples:
            - >-
              https://connect.plugchoice.com/#cs=cs_live_Hq3k9d0mR2xYw7pLf8sVb1nC4eTgJ6aZuQ5iKoXyM0w
    Problem:
      type: object
      description: RFC 9457 problem details.
      required:
        - type
        - title
        - status
      properties:
        type:
          type: string
          format: uri
          description: >-
            Stable identifier,
            `https://developer.plugchoice.com/sdk/errors/{code}`.
          examples:
            - https://developer.plugchoice.com/sdk/errors/identity-taken
        title:
          type: string
          examples:
            - Identity already registered
        status:
          type: integer
        detail:
          type: string
        code:
          $ref: '#/components/schemas/ErrorCode'
        errors:
          type: object
          description: Only for `validation-failed`. Field path → messages.
          additionalProperties:
            type: array
            items:
              type: string
        site:
          $ref: '#/components/schemas/SiteSummary'
          description: Only for `charger-at-other-site`.
        claimable:
          type: boolean
          description: >-
            Only for `identity-taken`. Whether `POST /session/claims` can be
            tried.
        vendor_charger:
          type: object
          description: >-
            Only for problems connecting a Zaptec installation's chargers in the
            hosted flow. Which charger of the installation it is about.
          required:
            - id
            - serial
          properties:
            id:
              type: string
              description: The brand's id (`ZaptecCharger.id`).
            serial:
              type: string
    ExternalUserId:
      type: string
      description: Your own id for one of your users.
      minLength: 1
      maxLength: 200
      pattern: ^[A-Za-z0-9._:@-]+$
    DeviceType:
      type: string
      description: >-
        The kind of device. Only chargers for now; another type is `422
        unsupported-device-type`.
      enum:
        - charger
    ClientSessionScope:
      type: object
      description: >
        Everything the client session's link sessions may reach. At least one of
        them (`422 scope-required`); every id must be one

        you may use (`404 not-found`).
      additionalProperties: false
      properties:
        chargers:
          type: array
          description: Chargers to work on (`network`, `setup`, `reconnect`, …).
          maxItems: 100
          uniqueItems: true
          items:
            type: string
            format: uuid
        sites:
          type: array
          description: Sites to add chargers to, and work on any charger at.
          maxItems: 100
          uniqueItems: true
          items:
            type: string
            format: uuid
        location:
          $ref: '#/components/schemas/Location'
          description: >-
            One address to add chargers at. The first `add` link session creates
            its site (or, with `external_user_id` and `reference`, finds that
            user's), and later link sessions add to it.
        new_site:
          type: boolean
          default: false
          description: >-
            `add` may make a new site at an address the hosted flow asks your
            user for. Later link sessions add to the site the first one made.
            Enough on its own as a scope.
    OnboardingMethod:
      type: string
      description: >
        How a charger gets connected:


        - `local_hotspot`: the SDK joins the charger's own Wi-Fi and configures
        it (Peblar).

        - `local_lan`: the SDK finds the charger on the home network, or joins
        its setup hotspot, signs in to it
          over the brand's pinned HTTPS and configures it (Alfen). The identity is read from the charger.
        - `manual_ocpp`: the user types the identity and configures the charger
        themselves; any OCPP charger,
          works in a browser.
        - `vendor_cloud`: Plugchoice switches the charger over in its brand's
        cloud after the user signs in
          there with the charger owner's account (Zaptec); works in a browser.

        More may be added.
      enum:
        - local_hotspot
        - local_lan
        - manual_ocpp
        - vendor_cloud
    ErrorCode:
      type: string
      description: >-
        The last path segment of `type`, for switch statements. New codes may be
        added.
      enum:
        - unauthenticated
        - forbidden
        - not-found
        - validation-failed
        - rate-limited
        - unknown-version
        - unsupported-device-type
        - scope-required
        - client-session-expired
        - site-required
        - session-ended
        - consent-required
        - consent-not-needed
        - location-required
        - location-already-set
        - method-not-allowed
        - identity-taken
        - identity-busy
        - identity-not-taken
        - charger-at-other-site
        - pincode-invalid
        - claim-exists
        - claim-closed
        - charger-offline
        - plug-in-not-available
        - nothing-to-complete
        - vendor-sign-in-failed
        - vendor-sign-in-required
        - vendor-not-permitted
        - vendor-charger-offline
        - vendor-rate-limited
        - vendor-unavailable
        - vendor-error
        - bad-request
        - server-error
    SiteSummary:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: string
          format: uuid
        name:
          type: string
    Location:
      type: object
      description: >-
        An address. Becomes a site; `timezone` is used for schedules and
        history.
      required:
        - address
        - timezone
      properties:
        reference:
          type: string
          maxLength: 200
          description: >-
            Integrations only, with `external_user_id`. Your own id for this
            address; a later session with the same reference reuses the site.
        name:
          type: string
          maxLength: 255
          default: Home
        address:
          $ref: '#/components/schemas/Address'
        timezone:
          type: string
          description: IANA time zone.
          examples:
            - Europe/Amsterdam
    Address:
      type: object
      description: The address of a site.
      required:
        - street
        - house_number
        - postal_code
        - city
        - country
      properties:
        street:
          type: string
          maxLength: 255
        house_number:
          type: integer
          minimum: 0
        house_number_addition:
          type: string
          maxLength: 255
        postal_code:
          type: string
          maxLength: 255
        city:
          type: string
          maxLength: 255
        country:
          type: string
          pattern: ^[A-Z]{2}$
          description: ISO 3166-1 alpha-2.
  responses:
    Unauthenticated:
      description: Missing, invalid or expired token (`unauthenticated`).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    Forbidden:
      description: >-
        The caller may not do this, for example an integration that is inactive
        (`forbidden`).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
    RateLimited:
      description: >-
        Too many requests (`rate-limited`); wait `Retry-After` seconds.
        Registering and claiming chargers also count toward Plugchoice's limits
        on claim attempts.
      headers:
        Retry-After:
          schema:
            type: integer
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/Problem'
  securitySchemes:
    integrationToken:
      type: oauth2
      description: >
        Your integration's client credentials (test or live). The token acts as
        your integration's team and

        lasts 1 hour; request a new one when it expires. Send `client_id`,
        `client_secret` and

        `grant_type=client_credentials`. No OAuth scopes are needed.
      flows:
        clientCredentials:
          tokenUrl: https://app.plugchoice.com/oauth/token
          scopes: {}
    userToken:
      type: http
      scheme: bearer
      description: >
        A Plugchoice user's OAuth access token or personal access token, acting
        for that user.

````

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