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

# Create account

> Create a SideShift Connect account for one of your users.

If the same `externalId` has already been created under your integration, the existing account is returned even if a different email is supplied. Use a stable internal user ID here to prevent one creator with multiple emails from becoming multiple payable Connect accounts.

If the email already exists under your integration, the existing account is returned (idempotent). When the email belongs to a different SideShift user, `enableFallbacks` defaults to `true` and SideShift tries provider +alias and SideShift-managed fallback emails. New account creation does not link the pre-existing user.

Fallbacks are also attempted when payment-account provisioning for the original email returns a classified email collision/validation error or suspended-account error. A suspended-account fallback creates a separate managed account; it does not restore, merge, or change the suspended payment-provider account. Unrelated provider failures are not treated as suspended/email fallback signals.

A fallback run examines at most 100 numbered fallback slots and stops after 3 counted fallback payment-account provisioning failures. Each slot considers a provider +alias first. Its deterministic SideShift-managed address is considered when that alias is occupied or its provisioning failure qualifies for the managed-address retry. Occupied candidates are skipped. No usable candidate within the slot boundary returns `409 EMAIL_ALREADY_EXISTS`; the provisioning stop returns `500 PAYMENT_ACC_CREATION_FAILED`.

Use a stable `externalId` and retry the same request after a timeout or retryable server failure. A completed account is returned rather than duplicated. Store the returned `sideshiftAccountId` and resolved `email`; `emailModified` and `originalEmail` describe a fallback response, but are optional on later idempotent lookups.

Set `enableFallbacks: false` to opt out. A foreign SideShift email then returns 404, and a payment-account provisioning failure returns 500 without trying alternate emails.

**Sandbox:** The returned `paymentAccountId` is prefixed with `sim_biz_` and no real payment account is provisioned.




## OpenAPI

````yaml /openapi/connect.yaml post /accounts/create
openapi: 3.0.4
info:
  title: SideShift Connect
  version: 1.0.0
  description: >
    Embed payment infrastructure directly into your platform. Create accounts
    for your users, transfer funds in any direction, and drop in pre-built
    payout and pay-in widgets — all through a single API.


    ## Setup Guide


    ### 1. Generate an API Key


    Go to [Settings → Connect](https://app.sideshift.app/settings?tab=embed) and
    click **Generate API Key**.


    Copy it immediately — keys are only shown once.


    - `sk_live_*` — Production (real money)

    - `sk_test_*` — Sandbox (isolated balances, simulated payouts)


    > **Never expose your API key in client-side code.** All API calls must be
    made from your backend.


    ### 2. Add Allowed Domains


    In Settings → Connect, add the domains where you'll embed widgets:


    | Pattern | Matches |

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

    | `app.example.com` | Exact match |

    | `*.example.com` | All subdomains |

    | `localhost` | Any port (auto-allowed for development) |


    ### 3. Create User Accounts


    Every user who needs access to payments needs a SideShift Connect account:


    ```bash

    curl -X POST https://app.sideshift.app/api/embed/accounts/create \
      -H "x-api-key: sk_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "email": "jane@example.com", "name": "Jane Creator", "externalId": "usr_123" }'
    ```


    Store the returned `sideshiftAccountId` — you'll need it for everything
    else.


    ### 4. Generate a Widget Token


    Tokens authenticate embedded widget sessions. Generate them server-side:


    ```bash

    curl -X POST https://app.sideshift.app/api/embed/auth/token \
      -H "x-api-key: sk_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{ "sideshiftAccountId": "acct_abc123", "widgetType": "both" }'
    ```


    The response includes `widgetUrls.payout` and `widgetUrls.payin` — use these
    as iframe sources or pass them to the SDK.


    ### 5. Embed the Widget


    **iframe (recommended):**

    ```html

    <iframe
      src="WIDGET_URL"
      width="100%" height="500"
      frameborder="0"
      allow="payment; camera; microphone"
      style="border:0; border-radius:12px"
    ></iframe>

    ```

    The iframe is the most reliable method — it works in any framework, needs no
    build tooling, and avoids dependency conflicts. The `allow="payment; camera;
    microphone"` attribute is required for KYC/identity verification inside the
    widget.


    **npm SDK (alternative):**

    ```jsx

    import { SideShiftPayout } from '@sideshiftapp/connect/react';


    <SideShiftPayout
      token={token}
      theme={{ theme: 'light', primaryColor: '#3D8CFA', borderRadius: 12 }}
      onWithdrawCompleted={(data) => console.log('Withdrew', data.amountCents)}
      onSessionExpired={() => refreshToken()}
    />

    ```

    The SDK is a thin wrapper around the same iframe — you get typed props,
    auto-resize, and event callbacks. Also available as
    `@sideshiftapp/connect/vanilla`.


    **iOS (SwiftUI):**

    ```swift

    SideShiftConnect.configure(apiKey: "sk_live_YOUR_KEY")

    SideShiftPayoutView(accountId: "acct_abc123", currency: "USD")

    ```

    The iOS SDK manages tokens internally — no server-side token generation
    needed.


    ### 6. Transfer Funds


    Move money between your company and user accounts:


    ```bash

    curl -X POST https://app.sideshift.app/api/embed/accounts/transfer \
      -H "x-api-key: sk_live_YOUR_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "toAccountId": "acct_abc123",
        "amountCents": 5000,
        "idempotencyKey": "payout-001",
        "metadata": {
          "obligationType": "creator_agreement",
          "obligationReference": "agreement-001",
          "description": "Approved payment for completed creator deliverable",
          "approvalReference": "approval-001"
        }
      }'
    ```


    ### 7. Set Up Webhooks


    Configure a webhook endpoint in Settings → Connect to receive
    `transfer.completed`, `deposit.*`, and `withdrawal.*` events. Always verify
    the signature:


    ```js

    const crypto = require("crypto");

    function verify(payload, timestamp, signature, secret) {
      const expected = crypto.createHmac("sha256", secret).update(`${timestamp}.${payload}`).digest("hex");
      return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
    }

    ```


    ---


    ## Authentication


    Include your API key in the `x-api-key` header on every request:


    ```

    x-api-key: sk_live_your_key_here

    ```


    Rotate your key anytime from Settings → Connect. The old key is invalidated
    immediately.


    ## Sandbox


    Use `sk_test_*` keys to test without moving real money. Sandbox balances are
    fully isolated from live. Webhooks still fire so you can validate your full
    pipeline. All API endpoints behave identically (same validation, same error
    codes, same response shapes).


    ### Sandbox behavior


    - `paymentAccountId` values are prefixed with `sim_biz_*` (simulated)

    - Company → User transfers are simulated — no real payout is executed, but
    the internal ledger is updated normally

    - User → Company and User → User transfers work identically to production

    - Webhook events are delivered to your configured endpoint

    - Leaderboard and notification side effects are **not** triggered


    ### Payout widget in sandbox


    When a widget token is generated with a `sk_test_*` key, the payout widget
    automatically uses SideShift's sandbox payout environment. No additional
    configuration is required.


    > **Important:** The payout widget shows "Pending Balance from your
    platform" instead of the actual balance in sandbox mode. This is expected.


    When you transfer funds with a `sk_test_*` key, the sandbox wallet is
    credited correctly on the internal ledger. However, the payout widget's
    withdrawal UI cannot display the real balance because the company ID is
    simulated (`sim_biz_*`) and the widget relies on the real payments
    infrastructure to resolve balances.


    - The `passedInBalance` field (if set) appears as a display-only "Pending
    Balance" label

    - The withdrawal flow (bank account linking, payout initiation) is not fully
    functional in sandbox


    **In production** with `sk_live_*` keys, transfers call the real payments
    API, funds land in the creator's real wallet, and the balance + withdrawal
    UI works normally.


    **To verify sandbox transfers are working**, use the balance API — this is
    the source of truth:


    ```bash

    GET /accounts/balance?sideshiftAccountId=ACCOUNT_ID

    ```


    The `balanceCents` and `transactions` in the response accurately reflect all
    sandbox transfers.


    ### Testing checklist


    1. Generate `sk_test_*` key and store securely

    2. Create at least two sandbox accounts

    3. Test all three transfer directions (company→user, user→company,
    user→user)

    4. Verify balances via the balance API after each transfer

    5. Replay a transfer with the same `idempotencyKey` — confirm no duplicate

    6. Confirm webhook arrives and signature verification passes

    7. Trigger error cases (insufficient balance, invalid account) and verify
    your handling

    8. Test widget token generation and embedding


    ### Go-live checklist


    Before switching to `sk_live_*`:


    1. Store your live API key in a production secrets manager

    2. Confirm production domains in Settings → Connect (remove dev wildcards)

    3. Verify webhook endpoint uses HTTPS and validates signatures

    4. Add retry handling with idempotency keys in your backend

    5. Attach commercial evidence metadata to every transfer

    6. Run a small live test (e.g. $0.50 transfer) before full volume


    ## Idempotency


    Always include an `idempotencyKey` on transfer requests. Replaying a request
    with the same key returns the original successful result instead of creating
    a duplicate.


    ## Rate Limits


    | Endpoint | Limit |

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

    | Account creation | 100/hour |

    | Token generation | 30/min per account |

    | Transfers | 60/min (configurable) |

    | General | 100/min |


    Exceeding limits returns `429` with a `Retry-After` header.


    ## Base URL


    `https://app.sideshift.app/api/embed`
  contact:
    name: SideShift Support
    url: https://app.sideshift.app
servers:
  - url: https://app.sideshift.app/api/embed
    description: Production / Sandbox (determined by API key prefix)
security:
  - apiKeyAuth: []
tags:
  - name: Accounts
    description: >-
      Create and manage user accounts. Each user gets a `sideshiftAccountId` and
      a wallet for receiving and sending funds.
  - name: Verifications
    description: >-
      Read identity verification status and required actions for connected
      accounts.
  - name: Transfers
    description: >-
      Move funds between your company and user accounts. Supports company→user,
      user→company, and user→user directions.
  - name: Tokens
    description: >-
      Generate short-lived access tokens that authenticate embedded widget
      sessions.
  - name: Checkout
    description: >-
      Create public hosted checkout links that settle into your SideShift
      wallet.
  - name: Webhooks
    description: >
      Receive real-time event notifications when transfers, deposits, and
      withdrawals complete or fail. Configure endpoints in Settings → Connect.


      **Available events:** `deposit.pending`, `deposit.confirmed`,
      `deposit.failed`, `transfer.completed`, `withdrawal.created`,
      `withdrawal.updated`, `withdrawal.completed`, `account.risk_flagged`


      `withdrawal.completed` is a derived alias of `withdrawal.updated` filtered
      to `status === "completed"`. Subscribe to it if you only want the
      terminal-success event; subscribe to `withdrawal.updated` for the full
      lifecycle (`requested → awaiting_payment → in_transit → completed | failed
      | canceled | denied`).


      All webhook deliveries include `x-sideshift-signature` and
      `x-sideshift-timestamp` headers for signature verification. Non-2xx
      responses are retried with exponential backoff (up to 5 attempts).
      Webhooks fire in both sandbox and production modes.
paths:
  /accounts/create:
    post:
      tags:
        - Accounts
      summary: Create account
      description: >
        Create a SideShift Connect account for one of your users.


        If the same `externalId` has already been created under your
        integration, the existing account is returned even if a different email
        is supplied. Use a stable internal user ID here to prevent one creator
        with multiple emails from becoming multiple payable Connect accounts.


        If the email already exists under your integration, the existing account
        is returned (idempotent). When the email belongs to a different
        SideShift user, `enableFallbacks` defaults to `true` and SideShift tries
        provider +alias and SideShift-managed fallback emails. New account
        creation does not link the pre-existing user.


        Fallbacks are also attempted when payment-account provisioning for the
        original email returns a classified email collision/validation error or
        suspended-account error. A suspended-account fallback creates a separate
        managed account; it does not restore, merge, or change the suspended
        payment-provider account. Unrelated provider failures are not treated as
        suspended/email fallback signals.


        A fallback run examines at most 100 numbered fallback slots and stops
        after 3 counted fallback payment-account provisioning failures. Each
        slot considers a provider +alias first. Its deterministic
        SideShift-managed address is considered when that alias is occupied or
        its provisioning failure qualifies for the managed-address retry.
        Occupied candidates are skipped. No usable candidate within the slot
        boundary returns `409 EMAIL_ALREADY_EXISTS`; the provisioning stop
        returns `500 PAYMENT_ACC_CREATION_FAILED`.


        Use a stable `externalId` and retry the same request after a timeout or
        retryable server failure. A completed account is returned rather than
        duplicated. Store the returned `sideshiftAccountId` and resolved
        `email`; `emailModified` and `originalEmail` describe a fallback
        response, but are optional on later idempotent lookups.


        Set `enableFallbacks: false` to opt out. A foreign SideShift email then
        returns 404, and a payment-account provisioning failure returns 500
        without trying alternate emails.


        **Sandbox:** The returned `paymentAccountId` is prefixed with `sim_biz_`
        and no real payment account is provisioned.
      operationId: createAccount
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - email
              properties:
                email:
                  type: string
                  format: email
                  description: User's email address
                  example: jane@example.com
                name:
                  type: string
                  description: Display name (max 100 characters)
                  example: Jane Creator
                externalId:
                  type: string
                  description: >-
                    Your platform's stable internal user ID. SideShift checks it
                    within your integration before email lookup and returns the
                    existing account when found. Do not send concurrent create
                    requests for the same value.
                  example: usr_123
                profileImageUrl:
                  type: string
                  format: uri
                  description: Profile image URL
                enableFallbacks:
                  type: boolean
                  default: true
                  description: >-
                    When true (default), try provider +alias and
                    SideShift-managed fallback emails on email collision,
                    classified email validation/collision failures, or a
                    payment-provider suspended-account response. Set to false to
                    disable alternate-email provisioning.
                passedInBalance:
                  type: integer
                  minimum: 0
                  description: Display-only balance in cents (does not add real funds)
            example:
              email: jane@example.com
              name: Jane Creator
              externalId: usr_123
      responses:
        '200':
          description: Account already exists — existing account returned
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Account'
        '201':
          description: Account created
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                    example: true
                  data:
                    $ref: '#/components/schemas/Account'
              example:
                success: true
                data:
                  sideshiftAccountId: acct_a1b2c3d4e5f6
                  paymentAccountId: biz_x7y8z9
                  email: jane@example.com
                  name: Jane Creator
                  created: true
        '400':
          description: Invalid request (missing or malformed email)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmbedErrorEnvelope'
              example:
                success: false
                error:
                  code: INVALID_EMAIL
                  message: Invalid email format
        '401':
          description: Missing or invalid API key
        '403':
          description: Domain not allowed or Connect not enabled
        '404':
          description: >-
            The email belongs to another SideShift user and alternate-email
            provisioning is disabled
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmbedErrorEnvelope'
        '409':
          description: >-
            No usable fallback candidate was found within the 100 numbered slots
            without reaching the provisioning-failure limit
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmbedErrorEnvelope'
        '500':
          description: >-
            Payment-account provisioning failed outside the retryable fallback
            boundary or reached the fallback failure limit
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmbedErrorEnvelope'
components:
  schemas:
    Account:
      type: object
      properties:
        sideshiftAccountId:
          type: string
          description: Unique account identifier — use this across all endpoints
          example: acct_a1b2c3d4e5f6
        paymentAccountId:
          type: string
          description: Internal payment account ID (`sim_biz_*` in sandbox)
          example: biz_x7y8z9
        email:
          type: string
          format: email
          example: jane@example.com
        name:
          type: string
          nullable: true
          example: Jane Creator
        created:
          type: boolean
          description: Whether the account was newly created (false if it already existed)
        alreadyExists:
          type: boolean
          description: >-
            True when an account owned by this integration was reused instead of
            created
        emailModified:
          type: boolean
          description: >-
            Whether this response was produced through a provider +alias or
            SideShift-managed fallback email
        originalEmail:
          type: string
          format: email
          description: >-
            The normalized original request email (only present when
            `emailModified` is true)
    EmbedErrorEnvelope:
      type: object
      required:
        - success
        - error
      properties:
        success:
          type: boolean
          enum:
            - false
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: Machine-readable error code
            message:
              type: string
              description: Human-readable error message
  securitySchemes:
    apiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: >-
        Your SideShift Connect API key (`sk_live_*` or `sk_test_*`). Generate
        from Settings → Connect.

````