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

# Create Portal Session

> Return a single-use URL that opens the borrower portal already signed in, for a borrower your platform has already authenticated. Call this from your backend and redirect the borrower to the returned URL. The URL is a short-lived bearer credential: anyone holding it can exchange it once for that borrower's session, so it must not be logged, stored, or placed anywhere it can be read back. Redeeming it starts an ordinary portal session, which is not cut short by the URL's own expiry. Available only to explicitly approved integrations; contact Finosu to have it enabled for your account.

## Overview

Returns a single-use URL that opens the borrower portal already signed in, for a
borrower your platform has already authenticated. Your backend requests the URL;
you redirect the borrower to it.

This exists so a borrower who has just signed in on your side is not asked to
sign in a second time. No passwords, cookies or session tokens are shared
between your platform and Finosu.

> [!WARNING]
>
> This feature is available only to explicitly approved integrations. Until it is
> enabled for your account, an authenticated request returns `403`. Contact Finosu
> to have it enabled and to obtain your API key.

## Flow

1. A borrower signs in on your platform.
2. Your **backend** calls this endpoint with your own identifier for that
   borrower.
3. You redirect the borrower's browser to the returned `url`.
4. They land on the portal dashboard, signed in.

The call is server-to-server. Never make it from the browser: the response
contains a credential that would then be visible to the borrower and to anything
else running on the page.

## The URL is a credential

The returned `url` is a **short-lived bearer credential**. Anyone holding it can
exchange it once for that borrower's session.

* Do not write it to logs, analytics, error trackers, or support tickets.
* Do not store it, email it, or include it in a redirect you record.
* Redirect the borrower to it immediately and discard your copy.

It is single-use: the first redemption spends it, and every later attempt fails.
It also expires shortly after issuance — see `expiresIn`.

Responses are served with `Cache-Control: no-store`.

## Expiry does not limit the session

`expiresIn` is how long the **URL** remains redeemable. Once redeemed, the
borrower gets an ordinary portal session governed by the portal's normal session
policy, and that session is not cut short when the URL expires.

Request a URL at the moment you are about to redirect, rather than issuing one
ahead of time and holding it.

## Identifying the borrower

`externalCustomerId` is your own identifier for the borrower, the same reference
you send Finosu elsewhere. The lookup is scoped to the company your API key
authenticates as, so a reference belonging to another company returns `404`.

`externalLoanId` is optional. A borrower's loans at one company share a single
portal account, so naming a loan does not change what the session can reach. Send
it when a borrower's loans span more than one of your brands, so the link opens
the right branded portal.

If the loan reference does not identify a live loan belonging to that borrower —
whether it names another borrower's loan, a loan that has been removed, or a
reference we have never seen — the request is rejected with `409` and no URL is
issued. That check exists to catch a mis-keyed mapping before it hands a borrower
someone else's account, so treat a `409` as a data problem on your side rather
than something to retry.

## Example

```bash theme={null}
curl -X POST "https://api.finosu.com/sso/portal-sessions" \
     -H "X-API-Key: your_api_key_here" \
     -H "Content-Type: application/json" \
     -d '{"externalCustomerId": "example-customer-1"}'
```

```json theme={null}
{
  "url": "https://portal.example-lender.com/sso?t=hJ4k...",
  "expiresAt": "2026-01-15T18:04:21+00:00",
  "expiresIn": 60
}
```

Redirect the borrower to `url`. Nothing else in the response needs to be stored.

## Errors

| Status | Meaning | What to do |
| - | - | - |
| `401` | Missing, invalid, or inactive API key | Provide an active key |
| `403` | Partner sign-on is not enabled for your account | Contact Finosu |
| `404` | No borrower matches that reference in the company your key authenticates as | Check the reference; confirm the borrower was sent to Finosu |
| `409` | The loan reference does not identify a live loan belonging to that borrower | Fix the mapping; do not retry |
| `422` | Malformed request body | Check the request schema |
| `429` | Issuance quota exceeded | Wait for the `Retry-After` interval |
| `503` | Sign-on is temporarily unavailable | Retry; no URL was issued |

A `404` and a `409` are different failures. `404` means no borrower matches that
reference **within the company your key authenticates as** — a borrower belonging
to another company is not visible to your key. `409` means the borrower was
found, but the loan reference does not resolve to a live loan of theirs.

## Redemption failures

If the borrower opens a URL that has already been used, has expired, or was never
issued, the portal shows one neutral message asking them to return to your
platform and open a new link. Those three cases are deliberately
indistinguishable, so a URL cannot be probed to learn whether it ever existed.

If a borrower reports that a link did not work, issue a new one rather than
asking them to retry the old one — a spent URL can never work again.


## OpenAPI

````yaml POST /sso/portal-sessions
openapi: 3.1.0
info:
  title: Finosu API
  description: Finosu API
  license:
    name: MIT
  version: 1.0.0
servers:
  - url: https://api.finosu.com
security:
  - apiKeyAuth: []
paths:
  /sso/portal-sessions:
    post:
      summary: Create a borrower portal sign-in URL
      description: >-
        Return a single-use URL that opens the borrower portal already signed
        in, for a borrower your platform has already authenticated. Call this
        from your backend and redirect the borrower to the returned URL. The URL
        is a short-lived bearer credential: anyone holding it can exchange it
        once for that borrower's session, so it must not be logged, stored, or
        placed anywhere it can be read back. Redeeming it starts an ordinary
        portal session, which is not cut short by the URL's own expiry.
        Available only to explicitly approved integrations; contact Finosu to
        have it enabled for your account.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreatePortalSessionRequest'
            example:
              externalCustomerId: example-customer-1
      responses:
        '201':
          description: 'Single-use sign-in URL. Served with Cache-Control: no-store.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PortalSessionResponse'
        '401':
          description: Missing, invalid, or inactive API key
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Partner sign-on is not enabled for the authenticated company
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '404':
          description: >-
            No customer matches that external reference in the authenticated
            company
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '409':
          description: >-
            The loan reference does not identify a live loan belonging to that
            customer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Issuance quota exceeded; retry after the Retry-After interval
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '503':
          description: Sign-on is temporarily unavailable; no link was issued
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    CreatePortalSessionRequest:
      type: object
      required:
        - externalCustomerId
      properties:
        externalCustomerId:
          type: string
          minLength: 1
          maxLength: 255
          description: Your identifier for the borrower, as sent to Finosu.
        externalLoanId:
          type: string
          minLength: 1
          maxLength: 255
          nullable: true
          description: >-
            Optional. A borrower's loans at one company share a single portal
            account, so this does not change what the session can reach. Send it
            when a borrower's loans span more than one of your brands, so the
            link opens the right branded portal. If it does not identify a live
            loan belonging to that borrower — another borrower's loan, a removed
            loan, or an unknown reference — the request is rejected with 409.
    PortalSessionResponse:
      type: object
      required:
        - url
        - expiresAt
        - expiresIn
      properties:
        url:
          type: string
          description: >-
            Single-use sign-in URL. Treat as a secret; redirect the borrower to
            it.
        expiresAt:
          type: string
          format: date-time
          description: Absolute time the URL stops working.
        expiresIn:
          type: integer
          description: >-
            Seconds until the URL stops working. The borrower's session is not
            limited by this value.
    Error:
      type: object
      required:
        - detail
      description: >-
        HTTP error envelope. Most errors contain a message; structured
        application errors may contain an object or array.
      properties:
        detail:
          oneOf:
            - type: string
            - additionalProperties: true
              type: object
            - type: array
              items: {}
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key

````

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