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

# Create a portal session

> Returns a single-use URL that drops this user straight into the connection
portal with no sign-in code — use it when they are already authenticated in
your own app. Valid once, for a short window.

This is how managed users create connections: `POST /finance/connections`
refuses a managed user's own token. Pass `institution_id` when the user has
already picked their bank in your app, and the portal skips its picker and
starts that bank's consent flow itself. Pass `connection_id` instead to
renew one of the user's existing connections the same way: it keeps its id
and a `connection.reauthorized` webhook follows. A user who has not yet accepted the
portal terms is asked to accept first; nothing starts on its own for them.



## OpenAPI

````yaml /openapi/managed.json post /users/{userId}/portal-sessions
openapi: 3.1.0
info:
  title: Synci
  version: '1.0'
  description: >-
    Register and manage the end users your Managed app pays for. Authenticate
    with HTTP Basic using your app's client id and client secret. See the
    Managed apps guide for the full flow.
servers:
  - url: https://api.synci.io/managed/v1
security:
  - basicAuth: []
tags:
  - name: Managed Users
  - name: Managed Connections
  - name: Managed Webhooks
paths:
  /users/{userId}/portal-sessions:
    post:
      tags:
        - Managed Users
      summary: Create a portal session
      description: >-
        Returns a single-use URL that drops this user straight into the
        connection

        portal with no sign-in code — use it when they are already authenticated
        in

        your own app. Valid once, for a short window.


        This is how managed users create connections: `POST
        /finance/connections`

        refuses a managed user's own token. Pass `institution_id` when the user
        has

        already picked their bank in your app, and the portal skips its picker
        and

        starts that bank's consent flow itself. Pass `connection_id` instead to

        renew one of the user's existing connections the same way: it keeps its
        id

        and a `connection.reauthorized` webhook follows. A user who has not yet
        accepted the

        portal terms is asked to accept first; nothing starts on its own for
        them.
      operationId: managed.users.portal-sessions
      parameters:
        - name: userId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePortalSessionRequest'
      responses:
        '200':
          description: ''
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: object
                    properties:
                      url:
                        type: string
                        description: >-
                          The client id rides along so an EXPIRED link degrades
                          to a scoped

                          sign-in rather than a dead end: the portal needs it to
                          issue a code,

                          and after a 10-minute TTL "opened it after lunch" is a
                          likely path.

                          Not a secret — it appears in every OAuth flow.
                      expires_at:
                        type: string
                    required:
                      - url
                      - expires_at
                required:
                  - data
        '403':
          $ref: '#/components/responses/AuthorizationException'
        '422':
          $ref: '#/components/responses/ValidationException'
components:
  schemas:
    CreatePortalSessionRequest:
      type: object
      properties:
        return_url:
          type:
            - string
            - 'null'
          format: uri
          description: |-
            Where "Continue to ‹App›" sends this user when they finish. Omit to
            use the app's configured portal return URL. With `institution_id`
            or `connection_id`, a successful bank flow also returns here
            directly, with `status=completed` and `connection`, even when the
            bank finishes in its own app and opens the return in another
            browser tab. Any `https://` URL is accepted; it does not have to be
            registered on the app.
          maxLength: 512
        institution_id:
          type:
            - integer
            - 'null'
          description: >-
            The institution the user already picked in your app. When set, the

            portal shows a short "‹App› uses Synci" interlude and then starts

            that institution's consent flow by itself, instead of asking the

            user to pick their bank a second time. Ids come from

            `GET /finance/institutions`. This always starts a new connection,
            even when the user already has

            one at that bank; connecting the same login twice gives the user a

            second connection with its own copy of the accounts, billed

            separately. To renew an existing connection, send `connection_id`

            instead. An institution this app may not connect is rejected with

            `422` when the session is minted.
          pattern: ^\d+$
          minimum: 1
          maximum: 9007199254740991
        connection_id:
          type:
            - integer
            - 'null'
          description: >-
            One of this user's existing connections that needs renewing (an

            expiring consent, or a bank that asks the user to sign in again).

            The portal shows the same short interlude as for `institution_id`

            and then starts the renewal of THIS connection: it keeps its id,

            its accounts and their history, and a `connection.reauthorized`

            webhook follows. Send it as a string. Mutually exclusive with

            `institution_id`, which starts a new connection. Checked when the
            session is minted: a connection that is not this

            user's, or whose provider cannot renew, is a `422`.
          pattern: ^\d+$
          minimum: 1
          maximum: 9007199254740991
      title: CreatePortalSessionRequest
  responses:
    AuthorizationException:
      description: Authorization error
      content:
        application/json:
          schema:
            type: object
            properties:
              message:
                type: string
                description: Error overview.
            required:
              - message
    ValidationException:
      description: Validation error
      content:
        application/json:
          schema:
            type: object
            properties:
              message:
                type: string
                description: Errors overview.
              errors:
                type: object
                description: A detailed description of each field that failed validation.
                additionalProperties:
                  type: array
                  items:
                    type: string
            required:
              - message
              - errors
  securitySchemes:
    basicAuth:
      type: http
      description: |-
        **Username = your client id. Password = your client secret.**

        So the base64-encoded `username:password` described below is
        `client_id:client_secret`:

        ```
        Authorization: Basic base64(YOUR_CLIENT_ID:YOUR_CLIENT_SECRET)
        ```

        Most HTTP clients build that header for you:

        ```bash
        curl -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
             https://api.synci.io/managed/v1/users
        ```

        Both values come from **Developers → Apps → your app**. The
        secret is shown once at creation and is prefixed `oac_`.
      scheme: basic

````

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