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

# Create User

> Registers an end-user under the authenticated partner. KYC is flexible JSON — core fields are required, provider-specific data lives in kyc_data.

## Overview

Registers an end-user under the authenticated partner and stores their KYC record. The user must have `kyc_level: "full"` before they can quote trades or redemptions.

Core KYC fields (`country`, `full_name`, `kyc_provider`, `kyc_level`, `kyc_verified_at`) are structured columns; provider-specific data goes in the free-form `kyc_data` object. If `kyc_data` includes `id_type` and `id_number`, GRAIL validates the `id_number` against the regex registered for that country/ID-type pair (advisory — absent regex means no validation is enforced).

## Headers

<ParamField header="x-api-key" type="string" required>
  A valid `PARTNER` scope key.
</ParamField>

## Request Body

<ParamField body="user_id" type="string" required>
  Your internal identifier for this user. Unique within your partner (two users cannot share the same `user_id`). Stored as `partner_user_id` in GRAIL.
</ParamField>

<ParamField body="wallet_address" type="string" required>
  The user's Solana wallet address. Must be a valid pubkey, and globally unique across all GRAIL users.
</ParamField>

<ParamField body="kyc" type="object" required>
  The user's KYC record.

  <Expandable title="properties">
    <ParamField body="country" type="string" required>
      ISO-3166-1 alpha-2 country code. Must match an active country in GRAIL's country list (e.g., `PK`, `AE`, `SA`).
    </ParamField>

    <ParamField body="full_name" type="string" required>
      Full legal name. 1–255 characters.
    </ParamField>

    <ParamField body="kyc_provider" type="string" required>
      Label identifying the KYC provider you used (e.g., `"sumsub"`, `"manual"`). Free-form string — stored verbatim.
    </ParamField>

    <ParamField body="kyc_level" type="string" required>
      Must be `"full"`. Partial or basic KYC is rejected.
    </ParamField>

    <ParamField body="kyc_verified_at" type="string" required>
      ISO-8601 timestamp of when KYC was completed on your side.
    </ParamField>

    <ParamField body="kyc_data" type="object">
      Optional free-form JSON for provider-specific fields. If `id_type` and `id_number` are included, the server performs advisory validation of `id_number` against the country's registered regex.
    </ParamField>
  </Expandable>
</ParamField>

## Response

<ResponseField name="grail_user_id" type="string">
  GRAIL's identifier for the user, prefixed `gu_`. Use this in all subsequent trade and redemption calls.
</ResponseField>

<ResponseField name="user_id" type="string">
  Echo of your `user_id` (the `partner_user_id`).
</ResponseField>

<ResponseField name="wallet_address" type="string">
  Echo of `wallet_address`.
</ResponseField>

<ResponseField name="created_at" type="string">
  ISO-8601 timestamp of user creation.
</ResponseField>

## Errors

| HTTP | `error`                  | When                                                                                                  |
| ---- | ------------------------ | ----------------------------------------------------------------------------------------------------- |
| 400  | `invalid_request`        | Missing required fields, bad timestamp, bad `full_name`, `kyc_data.id_number` doesn't match the regex |
| 400  | `invalid_wallet`         | `wallet_address` is not a valid Solana pubkey                                                         |
| 400  | `kyc_level_insufficient` | `kyc.kyc_level` is not `"full"`                                                                       |
| 409  | `user_already_exists`    | `wallet_address` is already registered, or `user_id` is already registered for this partner           |

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://grail-stack-dev.onrender.com/v1/users \
    -H "x-api-key: grail_partner_<hex>" \
    -H "Content-Type: application/json" \
    -d '{
      "user_id": "partner_internal_user_001",
      "wallet_address": "2u7vVGJCTtsijCqWJNCEknVkuLjeU7Rd4PMg4dXuTZGx",
      "kyc": {
        "country": "PK",
        "full_name": "Alice Example",
        "kyc_provider": "manual",
        "kyc_level": "full",
        "kyc_verified_at": "2026-04-17T00:00:00Z",
        "kyc_data": {
          "id_type": "CNIC",
          "id_number": "12345-1234567-1"
        }
      }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 201 Created theme={null}
  {
    "grail_user_id": "gu_6b60956e-a8ee-4de2-8128-04c7fdf633c3",
    "user_id": "partner_internal_user_001",
    "wallet_address": "2u7vVGJCTtsijCqWJNCEknVkuLjeU7Rd4PMg4dXuTZGx",
    "created_at": "2026-04-17T10:15:30.000Z"
  }
  ```
</ResponseExample>
