# First administrator setup

Source: https://docs.settlemint.com/docs/developers/platform-setup/first-admin-setup
Create the first administrator, organisation, wallet security, identity, system, currency, and token factories by API.



This guide bootstraps a DALP instance by API instead of the web interface. The flow creates the first administrator, binds that user to an organisation, and secures the signing wallet. The final steps create the on-chain identity, initialize the system, and enable asset factories. That account becomes the starting point for later API keys, role assignments, asset configuration, and operational workflows. For the web interface approach, see the [user guide](/docs/operators/platform-setup/first-admin-setup).

<Mermaid
  chart="`
flowchart TD
Founder[&#x22;First administrator&#x22;] --> API[&#x22;DALP setup API&#x22;]
API --> Organisation[&#x22;Organisation record&#x22;]
API --> System[&#x22;System infrastructure&#x22;]
API --> Roles[&#x22;Initial platform roles&#x22;]
Organisation --> Users[&#x22;Future administrators and operators&#x22;]
System --> Assets[&#x22;Asset creation and system operations&#x22;]
Roles --> Keys[&#x22;API keys and automation scopes&#x22;]
`"
/>

## Prerequisites [#prerequisites]

You need three things before starting. Have your platform URL ready (for example, `https://your-platform.example.com`). On a public chain, the wallet must hold ETH or the chain's native token for gas fees. You also need the email address for the new administrator account.

<Callout type="warning" title="Critical security warning">
  When you create the account by API, keep the password out of scripts, logs,
  and version control. Load it from a secrets manager or a protected environment
  variable. Rotate it after setup if automation handled it. For manual
  setup, prefer the web interface because the password stays in the browser.
</Callout>

## Overview [#overview]

You complete the bootstrap in this order:

1. Create the administrator account.
2. Create and activate the organisation.
3. Create an API key for the remaining setup calls.
4. Create a blockchain wallet, PIN, and recovery codes.
5. Create the administrator's on-chain identity.
6. Initialize the system infrastructure.
7. Configure the base currency and token factories.

<Callout type="info" title="First user privileges">
  Only the first user can initialize the system. That setup deploys the identity
  registry, access manager, system registries, and compliance framework used by
  later platform operations.
</Callout>

<Callout type="info" title="Single sign-on changes how admin is granted">
  When the deployment uses an external OIDC provider, local email/password
  sign-up is disabled and platform admin is derived from the provider's admin
  claim rather than from sign-up order. See [SSO sign-in
  (OIDC)](/docs/developers/sso-oidc).
</Callout>

## Steps [#steps]

<Steps>
  <Step>
    ### Create administrator account [#create-administrator-account]

    Register the first administrator account with an email address and password. The platform returns a session token and user record:

    ```bash
    curl -i -c cookies.txt -X POST "https://your-platform.example.com/api/auth/sign-up/email" \
      -H "Content-Type: application/json" \
      -H "Origin: https://your-platform.example.com" \
      -d '{
        "name": "Platform Admin",
        "email": "admin@example.com",
        "password": "YOUR_SECURE_PASSWORD"
      }'
    ```

    ```json
    {
      "token": "1CL....UdI",
      "user": {
        "name": "Platform Admin",
        "email": "admin@example.com",
        "emailVerified": false,
        "image": null,
        "createdAt": "2024-01-15T10:00:00.000Z",
        "updatedAt": "2024-01-15T10:00:00.000Z",
        "role": "admin",
        "banned": false,
        "banReason": null,
        "banExpires": null,
        "twoFactorEnabled": false,
        "wallet": "0x0000000000000000000000000000000000000000",
        "lastLoginAt": null,
        "walletPincodeEnabled": false,
        "walletPincodeVerificationId": null,
        "walletTwoFactorEnabled": false,
        "walletTwoFactorVerificationId": null,
        "walletSecretCodeVerificationId": null,
        "walletSecretCodesConfirmed": false,
        "id": "usr_abc123"
      }
    }
    ```

    The session is now stored in `cookies.txt` and used for all subsequent authenticated requests. Save the `user.id` value, such as `usr_abc123`. You need it for the KYC profile step later. Retrieve your user record at any time using:

    ```bash
    curl -X GET "https://your-platform.example.com/api/user/me" \
      -H "X-Api-Key: YOUR_API_KEY"
    ```

    <Callout type="warning" title="Password security">
      If automating this step, ensure the password is sourced from a secure secrets
      manager and never persisted in logs, scripts, or version control. Consider
      deleting the password from memory immediately after use.
    </Callout>
  </Step>

  <Step>
    ### Name your organisation [#name-your-organisation]

    Enter the name of the primary entity operating the platform. The example creates `Financial Institution S.A.` and uses its slug as the organisation context:

    ```bash
    curl -b cookies.txt -X POST "https://your-platform.example.com/api/auth/organization/create" \
      -H "Content-Type: application/json" \
      -H "Origin: https://your-platform.example.com" \
      -d '{
        "name": "Financial Institution S.A.",
        "slug": "financial-institution-sa"
      }'
    ```

    ```json
    {
      "name": "Financial Institution S.A.",
      "slug": "financial-institution-sa",
      "logo": null,
      "createdAt": "2024-01-15T10:00:00.000Z",
      "id": "org_abc123",
      "members": [
        {
          "organizationId": "org_abc123",
          "userId": "usr_abc123",
          "role": "owner",
          "createdAt": "2024-01-15T10:00:00.000Z",
          "id": "mem_abc123"
        }
      ]
    }
    ```

    After creating the organisation, set it as your active context. The platform returns the organisation record:

    ```bash
    curl -b cookies.txt -X POST "https://your-platform.example.com/api/auth/organization/set-active" \
      -H "Content-Type: application/json" \
      -H "Origin: https://your-platform.example.com" \
      -d '{
        "organizationId": "org_abc123"
      }'
    ```

    ```json
    {
      "name": "Financial Institution S.A.",
      "slug": "financial-institution-sa",
      "logo": null,
      "createdAt": "2024-01-15T10:00:00.000Z",
      "metadata": null,
      "id": "org_abc123"
    }
    ```

    Save the `id` from the response. You need it when creating API keys.
  </Step>

  <Step>
    ### Create API key [#create-api-key]

    Create an API key to authenticate subsequent requests. The full key appears only in this response, so store it immediately:

    ```bash
    curl -b cookies.txt -X POST "https://your-platform.example.com/api/auth/api-key/create" \
      -H "Content-Type: application/json" \
      -H "Origin: https://your-platform.example.com" \
      -d '{
        "name": "Platform Setup Key",
        "expiresIn": 86400,
        "metadata": {
          "organizationId": "org_abc123"
        }
      }'
    ```

    ```json
    {
      "name": "Platform Setup Key",
      "start": "sm_atk",
      "prefix": "sm_dalp_",
      "key": "sm_dalp_xxxxxxxxxxxxxxxx",
      "userId": "usr_abc123",
      "enabled": true,
      "rateLimitEnabled": true,
      "rateLimitTimeWindow": 60000,
      "rateLimitMax": 10000,
      "requestCount": 0,
      "expiresAt": "2024-01-16T10:00:00.000Z",
      "createdAt": "2024-01-15T10:00:00.000Z",
      "updatedAt": "2024-01-15T10:00:00.000Z",
      "permissions": {
        "user": [
          "create",
          "list",
          "set-role",
          "ban",
          "impersonate",
          "delete",
          "set-password",
          "get",
          "update"
        ],
        "session": ["list", "revoke", "delete"],
        "organization": ["update", "delete"],
        "member": ["create", "update", "delete"],
        "invitation": ["create", "cancel"],
        "team": ["create", "update", "delete"],
        "ac": ["create", "read", "update", "delete"],
        "setting": ["read", "list", "upsert", "remove"],
        "system": ["read", "list", "create"],
        "exchangeRates": ["read", "list", "remove", "sync", "update"]
      },
      "metadata": {
        "organizationId": "org_abc123"
      },
      "id": "key_abc123"
    }
    ```

    <Callout type="warning" title="Store API key securely">
      The full API key (`sm_dalp_...`) is shown only once. Store it immediately in a
      secure location. Use this key in the `X-Api-Key` header for all subsequent
      requests.
    </Callout>
  </Step>

  <Step>
    ### Create blockchain wallet [#create-blockchain-wallet]

    Create a wallet for the administrator. The platform assigns a blockchain address and returns it:

    ```bash
    curl -X POST "https://your-platform.example.com/api/user/create-wallet" \
      -H "X-Api-Key: YOUR_API_KEY" \
      -H "Content-Type: application/json"
    ```

    ```json
    {
      "wallet": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb"
    }
    ```

    Save the wallet address for reference. The address is useful when you query wallet state or debug transaction signing.
  </Step>

  <Step>
    ### Secure with PIN [#secure-with-pin]

    Enable PIN-based wallet verification. The platform requires this PIN to authorize on-chain transactions. Choose a 6-digit code you can remember and store it in a secrets manager.

    ```bash
    curl -X POST "https://your-platform.example.com/api/auth/wallet/pincode/enable" \
      -H "Content-Type: application/json" \
      -H "X-Api-Key: YOUR_API_KEY" \
      -d '{
        "pincode": "123456"
      }'
    ```

    ```json
    {
      "success": true
    }
    ```

    <Callout type="warning" title="PIN security">
      Choose a unique 6-digit PIN that you can remember. This PIN is required to
      authorize blockchain transactions. Store it securely and never share it.
    </Callout>
  </Step>

  <Step>
    ### Save backup codes [#save-backup-codes]

    Generate backup recovery codes. These codes let you recover wallet access if you lose your PIN:

    ```bash
    curl -X POST "https://your-platform.example.com/api/auth/wallet/secret-codes/generate" \
      -H "Content-Type: application/json" \
      -H "X-Api-Key: YOUR_API_KEY" \
      -d '{}'
    ```

    ```json
    {
      "secretCodes": [
        "ABCD-1234-EFGH",
        "IJKL-5678-MNOP",
        "QRST-9012-UVWX",
        "YZAB-3456-CDEF",
        "GHIJ-7890-KLMN"
      ],
      "rotated": false,
      "verificationId": "018f7b0a-1234-7890-abcd-ef0123456789"
    }
    ```

    <Callout type="warning" title="Critical: Save recovery codes securely">
      These codes are your only way to recover wallet access if you forget your PIN.
      Store them securely using a secrets manager (HashiCorp Vault, AWS Secrets
      Manager, etc.) or offline (printed in a secure location). Never store them in
      plaintext in code, logs, or unsecured cloud storage. Each code can only be
      used once.
    </Callout>

    After storing the codes, confirm receipt to the platform. Pass the `verificationId` from the generate response so the server can reject confirmations against a rotated-away set:

    ```bash
    curl -X POST "https://your-platform.example.com/api/auth/wallet/secret-codes/confirm" \
      -H "Content-Type: application/json" \
      -H "X-Api-Key: YOUR_API_KEY" \
      -d '{
        "stored": true,
        "verificationId": "018f7b0a-1234-7890-abcd-ef0123456789"
      }'
    ```

    ```json
    {
      "success": true
    }
    ```
  </Step>

  <Step>
    ### Create on-chain identity [#create-on-chain-identity]

    Deploy the on-chain identity contract for the administrator account. The platform returns the identity contract address and current registration state:

    ```bash
    curl -X POST "https://your-platform.example.com/api/identity" \
      -H "X-Api-Key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "walletVerification": {
          "secretVerificationCode": "123456",
          "verificationType": "PINCODE"
        }
      }'
    ```

    ```json
    {
      "id": "0x1234567890abcdef1234567890abcdef12345678",
      "account": {
        "id": "0xabcdef1234567890abcdef1234567890abcdef12",
        "contractName": null
      },
      "isContract": false,
      "hasIdentity": false,
      "claims": []
    }
    ```

    The `id` is your identity contract address. The `hasIdentity` field will be `false` until the system is initialized and the identity is registered.
  </Step>

  <Step>
    ### Complete profile (optional) [#complete-profile-optional]

    Add your profile information for KYC purposes. The platform returns the profile version and current record:

    ```bash
    curl -X POST "https://your-platform.example.com/api/user/usr_abc123/kyc/upsert" \
      -H "X-Api-Key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "firstName": "Platform",
        "lastName": "Admin",
        "dob": "1990-01-15T00:00:00.000Z",
        "country": "BE",
        "residencyStatus": "resident",
        "nationalId": "123-45-6789"
      }'
    ```

    ```json
    {
      "changed": true,
      "currentVersion": {
        "id": "kyc_version_123",
        "number": 1,
        "contentHash": "0xabcdef...",
        "createdAt": "2024-01-15T10:00:00.000Z"
      },
      "profile": {
        "id": "kyc_profile_123",
        "userId": "usr_abc123",
        "firstName": "Platform",
        "lastName": "Admin",
        "dob": "1990-01-15T00:00:00.000Z",
        "country": "BE",
        "residencyStatus": "resident",
        "nationalId": "123-45-6789",
        "createdAt": "2024-01-15T10:00:00.000Z",
        "updatedAt": "2024-01-15T10:00:00.000Z"
      }
    }
    ```
  </Step>

  <Step>
    ### Initialize the system [#initialize-the-system]

    Deploy the core platform infrastructure. The platform returns a system address, then deploys the four core components. This step may take several minutes on public chains.

    ```bash
    curl -X POST "https://your-platform.example.com/api/systems" \
      -H "X-Api-Key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "walletVerification": {
          "secretVerificationCode": "123456",
          "verificationType": "PINCODE"
        }
      }'
    ```

    ```json
    {
      "systemAddress": "0x9876543210fedcba9876543210fedcba98765432"
    }
    ```

    The deployment creates:

    * Identity Registry: manages user identities
    * Access Manager: controls role-based permissions
    * System Registries: track deployed contracts
    * Compliance Framework: enforces regulatory rules

    #### Poll for deployment status [#poll-for-deployment-status]

    On public chains, poll the deployment status endpoint. The full response includes all deployed contract addresses, compliance modules, and access-control settings.

    ```bash
    curl -X GET "https://your-platform.example.com/api/system/default" \
      -H "X-Api-Key: YOUR_API_KEY"
    ```

    ```json
    {
      "id": "0x9876543210fedcba9876543210fedcba98765432",
      "status": "completed",
      "userIdentityRegistered": true,
      "canResume": false,
      "updatedAt": "2024-01-15T10:05:00.000Z",
      "deployedInTransaction": "0x00feaa...",
      "tokenFactoryRegistry": { "id": "0x...", "tokenFactories": [] }
      // ... additional contract registries and configuration
    }
    ```

    For polling, check these key fields:

    | Field                    | Value           | Meaning                                           |
    | ------------------------ | --------------- | ------------------------------------------------- |
    | `status`                 | `bootstrapping` | Deployment in progress                            |
    | `status`                 | `completed`     | Deployment finished                               |
    | `userIdentityRegistered` | `true`          | Your identity is registered in the system         |
    | `canResume`              | `true`          | Deployment can be resumed (if it was interrupted) |

    The system is ready when `status` is `completed` and `userIdentityRegistered` is `true`.

    <Callout type="info" title="Deployment time">
      System initialization may take several minutes on public blockchains. Poll the
      status endpoint every few seconds until `status` is `completed` and
      `userIdentityRegistered` is `true`.
    </Callout>
  </Step>

  <Step>
    ### Configure base currency [#configure-base-currency]

    Review the provider's current currency snapshot before selecting the platform base currency. The API returns the configured provider key and sorted ISO 4217 alpha-3 currency codes:

    ```bash
    curl -X GET "https://your-platform.example.com/api/v2/exchange-rates/supported-currencies" \
      -H "X-Api-Key: YOUR_API_KEY"
    ```

    ```json
    {
      "data": {
        "providerKey": "open-er-api",
        "currencies": ["AED", "EUR", "USD"]
      }
    }
    ```

    Set `BASE_CURRENCY` from an authenticated CLI session. Before running these commands, sign in to the same DALP instance in the web interface and set the workspace you created as the active organisation. Then run `dalp login` and confirm `dalp whoami` shows that workspace. Choose a base-currency code that the CLI accepts and that also appears in the provider snapshot. The CLI accepts `AED`, `AUD`, `CAD`, `CHF`, `EUR`, `GBP`, `JPY`, `MYR`, `SAR`, `SGD`, `USD`, and `ZAR`.

    ```bash
    dalp login --url https://your-platform.example.com
    dalp whoami
    dalp settings upsert --key BASE_CURRENCY --value USD
    ```

    After setting the currency, refresh exchange rates from the configured provider in the same authenticated session. The platform returns the count of updated rates:

    ```bash
    dalp exchange-rates sync
    ```

    ```json
    {
      "data": {
        "ratesUpdated": 132,
        "syncedAt": "2026-05-15T10:00:00.000Z"
      }
    }
    ```

    For the complete exchange-rate command list, see [CLI command reference](/docs/developers/cli/command-reference#exchange-rate-commands).
  </Step>

  <Step>
    ### Enable asset types [#enable-asset-types]

    Deploy token factories for each asset type you want to support. The new factory appears in `tokenFactoryRegistry.tokenFactories` of the complete system configuration the platform returns.

    ```bash
    curl -X POST "https://your-platform.example.com/api/system/token-factory" \
      -H "X-Api-Key: YOUR_API_KEY" \
      -H "Content-Type: application/json" \
      -d '{
        "factories": [
          { "type": "equity", "name": "Equity" }
        ],
        "walletVerification": {
          "secretVerificationCode": "123456",
          "verificationType": "PINCODE"
        }
      }'
    ```

    ```json
    {
      "id": "0x9876543210fedcba9876543210fedcba98765432",
      "tokenFactoryRegistry": {
        "id": "0xFDD42C4e9Bd27cCA35b38C2925f1ED7EAE1AAc5b",
        "tokenFactories": [
          {
            "id": "0xA93F2f761DB1B3F8dc0ed6EcC9d38Bf15C8f562B",
            "name": "Equity",
            "typeId": "equity"
          }
        ]
      }
      // ... full system configuration
    }
    ```

    The following asset types are available:

    * `equity`: Tokenized shares and stock.
    * `bond`: Fixed income securities.
    * `fund`: Investment fund units.
    * `stablecoin`: Stable value tokens.
    * `deposit`: Tokenized deposits.
    * `real-estate`: Property tokens.
    * `precious-metal`: Commodity-backed tokens.
  </Step>
</Steps>

## Request parameters [#request-parameters]

### Sign up endpoint [#sign-up-endpoint]

| Parameter  | Type   | Required | Description                                     |
| ---------- | ------ | -------- | ----------------------------------------------- |
| `name`     | string | Yes      | Display name for the administrator              |
| `email`    | string | Yes      | Email address (must be unique)                  |
| `password` | string | Yes      | Account password (min 8 characters recommended) |

### API key creation [#api-key-creation]

| Parameter   | Type   | Required | Description                                         |
| ----------- | ------ | -------- | --------------------------------------------------- |
| `name`      | string | Yes      | Descriptive name for the API key                    |
| `expiresIn` | number | No       | Expiration time in seconds (default: no expiration) |
| `metadata`  | object | No       | Custom metadata to attach to the key                |

### Wallet verification object [#wallet-verification-object]

| Field                    | Type   | Required | Description                         |
| ------------------------ | ------ | -------- | ----------------------------------- |
| `secretVerificationCode` | string | Yes      | The PIN code, OTP, or recovery code |
| `verificationType`       | string | Yes      | `PINCODE`, `OTP`, or `SECRET_CODES` |

### KYC profile [#kyc-profile]

| Parameter         | Type   | Required | Description                                               |
| ----------------- | ------ | -------- | --------------------------------------------------------- |
| `userId` (path)   | string | Yes      | User ID to update KYC for (in URL path)                   |
| `firstName`       | string | No       | First name (max 64 characters)                            |
| `lastName`        | string | No       | Last name (max 64 characters)                             |
| `dob`             | string | No       | Date of birth (ISO 8601, must be 18+ years old)           |
| `country`         | string | No       | ISO country code (e.g., `BE`, `DE`, `GB`)                 |
| `residencyStatus` | string | No       | `resident`, `non_resident`, `dual_resident`, or `unknown` |
| `nationalId`      | string | No       | National ID number (max 50 characters)                    |

### Factory creation [#factory-creation]

| Parameter                           | Type   | Required | Description                                       |
| ----------------------------------- | ------ | -------- | ------------------------------------------------- |
| `factories`                         | array  | Yes      | Array of factory objects (1-10 factories)         |
| `factories[].type`                  | string | Yes      | Asset type (see available types above)            |
| `factories[].name`                  | string | Yes      | Display name for the factory (max 100 characters) |
| `factories[].factoryImplementation` | string | No       | Custom factory implementation address             |
| `factories[].tokenImplementation`   | string | No       | Custom token implementation address               |
| `walletVerification`                | object | Yes      | Wallet verification (see above)                   |

## Best practices [#best-practices]

### Security considerations [#security-considerations]

* Store credentials in a secrets manager such as Vault or AWS Secrets Manager.
* Delete passwords from memory after use and keep sensitive values out of logs.
* Create short-lived API keys and rotate them regularly.
* Use a PIN that is not shared with other services.
* Store recovery codes in a secrets manager or offline. Keep them out of source control and unsecured storage.

### Transaction timing [#transaction-timing]

* On public chains, wait for sufficient block confirmations.
* Use `/api/system/default` to monitor deployment status.
* Allow 5 to 10 minutes for system initialization on congested networks before treating it as failed.

### Error handling [#error-handling]

* Use exponential backoff for transient failures.
* Retry idempotent setup calls instead of restarting the whole bootstrap.
* Check that the wallet has enough gas before transaction-bearing operations.

## Troubleshooting [#troubleshooting]

| Issue                         | Solution                                                                                   |
| ----------------------------- | ------------------------------------------------------------------------------------------ |
| `401 Unauthorized`            | Session expired or API key invalid. Re-authenticate or create a new API key.               |
| `403 Forbidden`               | Account lacks required permissions. First user should have admin privileges automatically. |
| `400 Email already exists`    | Email is taken. Use a different email or sign in to existing account.                      |
| `400 Invalid pincode`         | PIN must be exactly 6 digits.                                                              |
| Identity creation fails       | Ensure wallet has sufficient gas for contract deployment.                                  |
| System initialization timeout | Normal on public chains. Poll `/api/system/default` until status is `completed`.           |
| Factory creation fails        | System must be fully deployed first. Check system status before creating factories.        |
| Transaction reverted          | Verify PIN is correct. Check wallet has sufficient gas.                                    |
| API key not working           | Verify key hasn't expired. Check `X-Api-Key` header is set correctly.                      |

## Related guides [#related-guides]

* [Add Administrators](/docs/developers/platform-setup/add-admins): grant administrative permissions via API
* [Change Admin Roles](/docs/developers/platform-setup/change-admin-roles): modify or revoke role assignments via API
* [Getting Started](/docs/api-reference/reference/getting-started): API key setup and authentication
* [API Reference](/docs/api-reference/reference/openapi): full OpenAPI specification
* [First Administrator Setup (User Guide)](/docs/operators/platform-setup/first-admin-setup): web interface approach
