> For the complete documentation index, see [llms.txt](https://docs.luganodes.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.luganodes.com/apis/settlex-flow/settlex-flow-onboarding.md).

# Settlex Flow - Onboarding

> **Version:** 1.0.0\
> **Base URL:** `settlex-api.luganodes.com`\
> **Protocol:** HTTP/REST

***

## Authentication

All endpoints under **Party**, **API Key**, and **Balance** require a valid JWT token passed via the `Authorization` header using the **Bearer** scheme.

### Authorization Header Format

```http
Authorization: Bearer <jwt_token>
```

### Authentication Failure Responses

| Status Code | Response Body                    | Condition                                               |
| ----------- | -------------------------------- | ------------------------------------------------------- |
| `401`       | `{ "message": "Unauthorized" }`  | Missing or malformed `Authorization` header             |
| `401`       | `{ "message": "Invalid token" }` | Token is valid JWT but payload doesn't match schema     |
| `401`       | `{ "message": "Unauthorized" }`  | User not found in database or token verification failed |

***

## Error Handling

The API uses standard HTTP status codes and returns errors in a consistent JSON format:

```json
{
  "message": "Human-readable error description"
}
```

### Common Error Codes

| Status Code | Meaning               | Typical Cause                                          |
| ----------- | --------------------- | ------------------------------------------------------ |
| `400`       | Bad Request           | Request body validation failed (Zod schema errors)     |
| `401`       | Unauthorized          | Missing, invalid, or expired JWT token                 |
| `404`       | Not Found             | Requested resource does not exist                      |
| `409`       | Conflict              | Resource already exists (duplicate email, party, etc.) |
| `500`       | Internal Server Error | Unexpected server-side error or ledger failure         |
| `429`       | Rate Limit Exceeded   | A default rate limit of 300 requests every 15 minutes  |

### Validation Error Format

When request body validation fails, the error message contains field-level details joined by semicolons:

```json
{
  "message": "email: Invalid email; password: String must contain at least 8 character(s)"
}
```

***

## Endpoints

### Health

**`GET /api/health`**

A simple health check endpoint to verify the API server is running and responsive.

**Authentication:** None

**Request**

No request body or query parameters required.

**Response**

**200 OK**

The server is healthy and operational.

```json
{
  "message": "OK"
}
```

| Field     | Type   | Description                |
| --------- | ------ | -------------------------- |
| `message` | string | Health status confirmation |

**Example**

```bash
curl -X GET http://settlex.luganodes.com/api/health
```

```json
{
  "message": "OK"
}
```

***

### Onboarding

**`POST /api/onboarding/signup`**

Creates a new user account

**Authentication:** None

**Request Body**

| Field      | Type   | Required | Validation                    | Description                |
| ---------- | ------ | -------- | ----------------------------- | -------------------------- |
| `email`    | string | Yes      | Must be a valid email address | The user's email address   |
| `password` | string | Yes      | Minimum 8 characters          | The user's chosen password |

**Request Example**

```json
{
  "email": "user@example.com",
  "password": "securePassword123"
}
```

**Responses**

**201 Created**

User account was successfully created.

```json
{
  "message": "User created successfully",
  "email": "user@example.com"
}
```

| Field     | Type   | Description                                    |
| --------- | ------ | ---------------------------------------------- |
| `message` | string | Success confirmation message                   |
| `email`   | string | The email address of the newly created account |

**400 Bad Request**

The request body failed validation. The `message` field contains details about which fields failed and why.

```json
{
  "message": "email: Invalid email; password: String must contain at least 8 character(s)"
}
```

| Field     | Type   | Description                                   |
| --------- | ------ | --------------------------------------------- |
| `message` | string | Semicolon-separated list of validation errors |

**409 Conflict**

A user with the given email address already exists.

```json
{
  "message": "User with this email already exists"
}
```

*or*

```json
{
  "message": "Email already exists"
}
```

| Field     | Type   | Description          |
| --------- | ------ | -------------------- |
| `message` | string | Conflict description |

**500 Internal Server Error**

An unexpected error occurred during user creation.

```json
{
  "message": "Internal server error"
}
```

**Example**

```bash
curl -X POST http://settlex.luganodes.com/api/onboarding/signup \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alice@example.com",
    "password": "myStrongPass99"
  }'
```

**`POST /api/onboarding/login`**

Authenticates a user with email and password, returning a JWT token on success.

**Authentication:** None

**Request Body**

| Field      | Type   | Required | Validation                    | Description              |
| ---------- | ------ | -------- | ----------------------------- | ------------------------ |
| `email`    | string | Yes      | Must be a valid email address | The user's email address |
| `password` | string | Yes      | Minimum 8 characters          | The user's password      |

**Request Example**

```json
{
  "email": "user@example.com",
  "password": "securePassword123"
}
```

**Responses**

**200 OK**

Authentication successful. Returns a JWT token valid for 7 days, the user's email, and optionally their Canton party ID (if one has been created).

```json
{
  "message": "Login successful",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "email": "user@example.com",
  "partyId": "party-abc123::1220abcd..."
}
```

| Field     | Type                | Description                                                           |
| --------- | ------------------- | --------------------------------------------------------------------- |
| `message` | string              | Success confirmation message                                          |
| `token`   | string              | JWT token for authenticating subsequent API requests                  |
| `email`   | string              | The authenticated user's email address                                |
| `partyId` | string \| undefined | The user's Canton ledger party ID (undefined if no party created yet) |

**400 Bad Request**

The request body failed validation.

```json
{
  "message": "email: Invalid email"
}
```

| Field     | Type   | Description                                   |
| --------- | ------ | --------------------------------------------- |
| `message` | string | Semicolon-separated list of validation errors |

**401 Unauthorized**

The email does not match any registered user, or the password is incorrect. The same message is returned for both cases to prevent user enumeration.

```json
{
  "message": "Invalid email or password"
}
```

| Field     | Type   | Description                    |
| --------- | ------ | ------------------------------ |
| `message` | string | Generic authentication failure |

**500 Internal Server Error**

An unexpected error occurred during authentication.

```json
{
  "message": "Internal server error"
}
```

**Example**

```bash
curl -X POST http://settlex.luganodes.com/api/onboarding/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "alice@example.com",
    "password": "myStrongPass99"
  }'
```

**Example Response**

```json
{
  "message": "Login successful",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2ODI1YzRhNmE4OGU1NTAwMTIzNDU2NzgiLCJlbWFpbCI6ImFsaWNlQGV4YW1wbGUuY29tIiwiaWF0IjoxNzE3MzEyMDAwLCJleHAiOjE3MTc5MTY4MDB9.abc123",
  "email": "alice@example.com",
  "partyId": null
}
```

***

### Party

**`POST /api/party/create`**

#### **NOTE: This endpoint will take \~10 seconds to fulfill as it involves several ledger interactions sequentially.**

Allocates a new internal party on the Canton Network ledger for the authenticated user. Each user can only have one party.

After successful allocation, the endpoint:

{% stepper %}
{% step %}

#### Grant ledger rights (`readAs` and `actAs`) to Settlex

If rights granting fails, the party assignment is rolled back to maintain consistency between the database and the ledger.
{% endstep %}

{% step %}

#### Creates a transfer preapproval contract on the ledger for your party.

Builds a transfer preapproval command (referencing the configured provider party and DSO party), and submits it. If preapproval creation fails, the endpoint returns `500` with the error details.
{% endstep %}
{% endstepper %}

**Authentication:** Required (Bearer Token)

**Request Body**

No request body required. The user is identified from the JWT token.

**Responses**

**200 OK**

Party was successfully allocated on the ledger, assigned to the user, granted rights, and a transfer preapproval contract was created.

```json
{
    "message": "Party created successfully",
    "partyId": "9a923fe2-35a8-45f5-bccc-24848a393004::12207b916c3752acac9902d845ee0340d3b66e8ce0d1e2a00b187d97b99b10c44529",
    "rightsGranted": {
        "newlyGrantedRights": [
            {
                "kind": {
                    "CanReadAs": {
                        "value": {
                            "party": "9a923fe2-35a8-45f5-bccc-24848a393004::12207b916c3752acac9902d845ee0340d3b66e8ce0d1e2a00b187d97b99b10c44529"
                        }
                    }
                }
            },
            {
                "kind": {
                    "CanActAs": {
                        "value": {
                            "party": "9a923fe2-35a8-45f5-bccc-24848a393004::12207b916c3752acac9902d845ee0340d3b66e8ce0d1e2a00b187d97b99b10c44529"
                        }
                    }
                }
            }
        ]
    },
    "transferPreapproval": {
        "updateId": "122074aa25376a3d5e7d4e70cc93d603625ba46582eef0e081f3fa45620d8dc2a1ca",
        "completionOffset": 1117206
    }
}
```

| Field                 | Type   | Description                                                                                        |
| --------------------- | ------ | -------------------------------------------------------------------------------------------------- |
| `message`             | string | Success confirmation message                                                                       |
| `partyId`             | string | The Canton Network party identifier allocated to the user                                          |
| `rightsGranted`       | object | The ledger rights granted to the party (structure from Canton SDK)                                 |
| `transferPreapproval` | object | The result of submitting the transfer preapproval command (contains completionOffset and updateId) |

**400 Bad Request**

Validation error.

```json
{
  "message": "field: error description"
}
```

**401 Unauthorized**

Missing or invalid authentication token. See [Authentication Failure Responses](#authentication-failure-responses).

**409 Conflict**

The user already has a party assigned. This is checked both at the start and atomically during the update.

```json
{
  "message": "Party already exists"
}
```

| Field     | Type   | Description                                          |
| --------- | ------ | ---------------------------------------------------- |
| `message` | string | Indicates the user already has a party on the ledger |

**500 Internal Server Error**

Ledger allocation, rights granting, transfer preapproval creation, or an unexpected error occurred.

```json
{
  "message": "Failed to allocate party"
}
```

*or:*

```json
{
  "message": "Failed to grant ledger rights"
}
```

*or (transfer preapproval failures):*

```json
{
  "message": "Failed to build transfer preapproval contract command"
}
```

```json
{
  "message": "Failed to submit transfer preapproval contract command"
}
```

```json
{
  "message": "Failed to create transfer preapproval contract for party"
}
```

*or:*

```json
{
  "message": "Internal server error"
}
```

| Field     | Type   | Description                            |
| --------- | ------ | -------------------------------------- |
| `message` | string | Description of the server-side failure |

**Example**

```bash
curl -X POST http://settlex.luganodes.com/api/party/create \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
```

**Example Response**

```json
{
  "message": "Party created successfully",
  "partyId": "party-abc123::1220abcdef0123456789abcdef0123456789",
  "rightsGranted": {
    "readAs": ["party-abc123::1220abcdef0123456789abcdef0123456789"],
    "actAs": ["party-abc123::1220abcdef0123456789abcdef0123456789"]
  },
  "transferPreapproval": {}
}
```

***

### API Key

**`POST /api/api-key/create`**

Generates a new API key for the authenticated user. The raw key is generated using `crypto.randomBytes(32)` (64 hex characters) and is returned to the user **only once**. Each user can only have one active API key. If the user previously deactivated their key (via `DELETE /api/api-key`), calling this endpoint will reactivate with a freshly generated key.

**Authentication:** Required (Bearer Token)

**Request Body**

No request body required.

**Responses**

**200 OK**

API key was successfully generated and stored.

```json
{
  "message": "Api key created successfully",
  "apiKeyRaw": "a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890a1b2c3d4e5f67890"
}
```

| Field       | Type   | Description                                                                              |
| ----------- | ------ | ---------------------------------------------------------------------------------------- |
| `message`   | string | Success confirmation message                                                             |
| `apiKeyRaw` | string | The raw API key (64 hex characters). Store this securely — it cannot be retrieved again. |

> **Important:** The `apiKeyRaw` value is shown only once upon creation. We store only the key hash. If lost, a new key must be generated (after deleting the existing one, if applicable).

**401 Unauthorized**

Missing or invalid authentication token. See [Authentication Failure Responses](#authentication-failure-responses).

**409 Conflict**

The user already has an **active** API key. Only one active API key is allowed per user. Delete the existing key first before creating a new one.

```json
{
  "message": "Api key already exists"
}
```

| Field     | Type   | Description                                 |
| --------- | ------ | ------------------------------------------- |
| `message` | string | Indicates a duplicate API key for this user |

**500 Internal Server Error**

An unexpected error occurred during key generation or storage.

```json
{
  "message": "Internal server error"
}
```

**Example**

```bash
curl -X POST http://settlex.luganodes.com/api/api-key/create \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
```

**Example Response**

```json
{
  "message": "Api key created successfully",
  "apiKeyRaw": "f47ac10b58cc4372a5670e02b2c3d479f47ac10b58cc4372a5670e02b2c3d479"
}
```

**`DELETE /api/api-key`**

Deactivates the authenticated user's API key. After deactivation, the user can generate a new key via `POST /api/api-key/create`.

**Authentication:** Required (Bearer Token)

**Request Body**

No request body required. The user is identified from the JWT token.

**Responses**

**200 OK**

The API key was successfully deactivated.

```json
{
  "message": "Api key deleted successfully"
}
```

| Field     | Type   | Description                  |
| --------- | ------ | ---------------------------- |
| `message` | string | Success confirmation message |

**401 Unauthorized**

Missing or invalid authentication token. See [Authentication Failure Responses](#authentication-failure-responses).

**404 Not Found**

No active API key was found for the authenticated user. This occurs if the user never created a key or already deactivated it.

```json
{
  "message": "Api key not found"
}
```

| Field     | Type   | Description                                             |
| --------- | ------ | ------------------------------------------------------- |
| `message` | string | Indicates no active API key exists for the current user |

**500 Internal Server Error**

An unexpected error occurred during key deactivation.

```json
{
  "message": "Internal server error"
}
```

**Example**

```bash
curl -X DELETE http://settlex.luganodes.com/api/api-key \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
```

**Example Response**

```json
{
  "message": "Api key deleted successfully"
}
```

***

### Balance

**`GET /api/balance`**

Retrieves the available and reserved balance for the authenticated user's Canton party.

**Authentication:** Required (Bearer Token)

**Request**

No request body or query parameters required. The party is identified from the authenticated user's `partyId`.

**Responses**

**200 OK**

Balance successfully retrieved.

```json
{
  "balance": 1000.50,
  "reservedBalance": 250.00
}
```

| Field             | Type   | Description                                                                  |
| ----------------- | ------ | ---------------------------------------------------------------------------- |
| `balance`         | number | The available (spendable) balance for the user's party in Canton Coin (CC)   |
| `reservedBalance` | number | The amount currently reserved (locked for pending operations) in Canton Coin |

**401 Unauthorized**

Missing or invalid authentication token. See [Authentication Failure Responses](#authentication-failure-responses).

**404 Not Found**

The user's party was not found in the database. This can happen if the user has not yet created a party, or if the party record does not exist in the database.

```json
{
  "message": "Party not found"
}
```

| Field     | Type   | Description                                    |
| --------- | ------ | ---------------------------------------------- |
| `message` | string | Indicates no party record exists for this user |

**500 Internal Server Error**

An unexpected error occurred while querying the balance.

```json
{
  "message": "Internal server error"
}
```

**Example**

```bash
curl -X GET http://settlex.luganodes.com/api/balance \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."
```

**Example Response**

```json
{
  "balance": 5000,
  "reservedBalance": 150.75
}
```
