Skip to main content
POST
/
v1
/
users
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"
      }
    }
  }'
{
  "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"
}

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

x-api-key
string
required
A valid PARTNER scope key.

Request Body

user_id
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.
wallet_address
string
required
The user’s Solana wallet address. Must be a valid pubkey, and globally unique across all GRAIL users.
kyc
object
required
The user’s KYC record.

Response

grail_user_id
string
GRAIL’s identifier for the user, prefixed gu_. Use this in all subsequent trade and redemption calls.
user_id
string
Echo of your user_id (the partner_user_id).
wallet_address
string
Echo of wallet_address.
created_at
string
ISO-8601 timestamp of user creation.

Errors

HTTPerrorWhen
400invalid_requestMissing required fields, bad timestamp, bad full_name, kyc_data.id_number doesn’t match the regex
400invalid_walletwallet_address is not a valid Solana pubkey
400kyc_level_insufficientkyc.kyc_level is not "full"
409user_already_existswallet_address is already registered, or user_id is already registered for this partner
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"
      }
    }
  }'
{
  "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"
}