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

# Verify signature and authenticate

> Verify signature and authenticate.

 Verify the signed challenge message and create an authenticated session.
 Binds the caller-provided ed25519 session public key to the new session;
 subsequent requests are signed with the matching private key.

 How the signature is checked depends on the address. Almost every address
 is verified by recovering the signing key, exactly as before. EIP-1271
 verification applies only to an address that is BOTH eligible for
 contract-signature login AND has contract code deployed on chain: an
 eligible payout wallet that is an ordinary EOA is still verified by
 recovery. The two are mutually exclusive, and the request is identical
 either way — `signature` carries whatever the wallet produced, and no
 field changes. The one bound is length: a contract signature over 8192
 decoded bytes is refused as invalid rather than forwarded to the chain,
 which is far above any realistic owner set.

 Because deciding that requires reading the chain, this adds one
 client-visible outcome the recovery path does not have: UNAVAILABLE (REST
 503). It is retryable and is NOT a verdict on the signature. It covers
 three cases, and an eligible address can see any of them even when it turns
 out to be an EOA, because the code lookup happens first: the chain did not
 answer, it did not answer within the deadline, or the server declined to
 ask because its own concurrent-verification capacity was full. That last
 one is local saturation rather than a chain fault, so an operator
 diagnosing it should check this service before the RPC endpoint.

 A rejected signature is 401, and so is a contract that rejects by
 REVERTING: that is the wallet answering "no", not a failure to ask.



## OpenAPI

````yaml /proto-openapi/api/openapi.yaml post /api/v1/auth/verify
openapi: 3.0.3
info:
  title: Monaco Protocol API
  description: REST API for the Monaco Protocol hybrid CLOB exchange.
  version: 1.0.0
servers:
  - url: https://staging.apimonaco.xyz
    description: Staging server (Testnet)
security: []
tags:
  - name: AccountsService
  - name: ApplicationsService
  - name: AuthService
  - name: BuildercodeRewardsService
  - name: CopyTradingService
  - name: DelegatedAgentsService
  - name: FaucetService
  - name: FeesService
  - name: HealthService
  - name: ManagedMarketsService
    description: |-
      Wallet-authenticated maker assignments. The signed session must match both
       configured maker user and application. Owner IDs are process fences, never auth.
       Only wallet sessions are supported; delegated-agent sessions receive 403.
  - name: MarginAccountsService
    description: |-
      Current public isolated-margin semantics:
       - a user has one parent margin account per application scope
       - opening orders create or reuse isolated position buckets under that parent
       - parent account creation is handled internally by margin workflows
  - name: MarketService
  - name: OrderbookService
  - name: OrdersService
  - name: PositionsService
    description: |-
      Current public isolated-margin semantics:
       - positions link to a parent margin account and, when applicable, a bucket id
       - opening orders create or reuse isolated position buckets under the parent
       - users can open another isolated position by reusing the parent account with a different market bucket
  - name: PulseService
  - name: SweeperService
  - name: TraderCodeService
  - name: TradesService
  - name: WhitelistService
  - name: WithdrawalsService
paths:
  /api/v1/auth/verify:
    post:
      tags:
        - AuthService
        - Auth
      summary: Verify signature and authenticate
      description: |-
        Verify signature and authenticate.

         Verify the signed challenge message and create an authenticated session.
         Binds the caller-provided ed25519 session public key to the new session;
         subsequent requests are signed with the matching private key.

         How the signature is checked depends on the address. Almost every address
         is verified by recovering the signing key, exactly as before. EIP-1271
         verification applies only to an address that is BOTH eligible for
         contract-signature login AND has contract code deployed on chain: an
         eligible payout wallet that is an ordinary EOA is still verified by
         recovery. The two are mutually exclusive, and the request is identical
         either way — `signature` carries whatever the wallet produced, and no
         field changes. The one bound is length: a contract signature over 8192
         decoded bytes is refused as invalid rather than forwarded to the chain,
         which is far above any realistic owner set.

         Because deciding that requires reading the chain, this adds one
         client-visible outcome the recovery path does not have: UNAVAILABLE (REST
         503). It is retryable and is NOT a verdict on the signature. It covers
         three cases, and an eligible address can see any of them even when it turns
         out to be an EOA, because the code lookup happens first: the chain did not
         answer, it did not answer within the deadline, or the server declined to
         ask because its own concurrent-verification capacity was full. That last
         one is local saturation rather than a chain fault, so an operator
         diagnosing it should check this service before the RPC endpoint.

         A rejected signature is 401, and so is a contract that rejects by
         REVERTING: that is the wallet answering "no", not a failure to ask.
      operationId: verify_signature
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/VerifyRequest'
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VerifyResponse'
        '400':
          description: Invalid request parameters, nonce expired, or already used
        '401':
          description: Invalid signature
        '403':
          description: >-
            Origin not allowed for this application; the account or wallet is
            deactivated; or Mainnet Invite Mode is on and the wallet has no
            access grant and supplied no valid invite code. Auth refusals carry
            a stable code (REST error envelope code; gRPC google.rpc.ErrorInfo
            reason, domain auth.0xmonaco.com): ACCOUNT_DEACTIVATED for a
            deactivated signing account, INVITE_CODE_REQUIRED when no code was
            supplied, and INVITE_CODE_INVALID for a wrong, already redeemed or
            revoked code, or an unused code whose issuer is deactivated. No
            account identity or administrative deactivation reason is disclosed.
            Unused invites become usable again after issuer reactivation
        '404':
          description: Invalid nonce or clientId
        '429':
          description: >-
            Authentication rate limit exceeded — challenge and verify share one
            per-client-address budget (60 requests/min sustained, burst 120),
            checked before the signature; or, while Mainnet Invite Mode is on,
            invite-code attempts from this wallet or client address are in their
            cooldown. Retry after the interval in details.retryAfter
        '500':
          description: Internal server error
        '503':
          description: >-
            Contract-wallet (EIP-1271) verification could not be completed for
            an eligible address: the chain did not answer, did not answer within
            the deadline, or the server was at its concurrent-verification
            capacity and declined to ask; or an invite code could not be
            validated now (validation at capacity, or no trusted client
            address). Retryable, and not a verdict on the signature or the code.
components:
  schemas:
    VerifyRequest:
      required:
        - address
        - signature
        - nonce
        - sessionPublicKey
      type: object
      properties:
        address:
          example: '0x742d35Cc6634C0532925a3b8D4060f31E2C3d8B5'
          maxLength: 42
          minLength: 42
          pattern: ^0x[0-9a-fA-F]{40}$
          type: string
          description: Ethereum wallet address
        signature:
          example: >-
            0x1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0
          maxLength: 16386
          minLength: 1
          type: string
          description: >-
            Wallet authorization of the challenge message, hex with an optional
            0x prefix. An EOA sends a 65-byte EIP-191 signature; a contract
            wallet verified under EIP-1271 sends whatever its own contract
            accepts, which is usually longer and need not be 65 bytes. Capped at
            8192 decoded bytes (16386 characters with the prefix) — enough for
            well over a hundred owner slots — and a longer value is rejected as
            an invalid signature rather than forwarded to the chain.
        nonce:
          example: abc123def456
          minLength: 1
          type: string
          description: Challenge nonce
        clientId:
          example: monaco-frontend
          type: string
          description: Optional application identifier
          nullable: true
        chainId:
          example: '1328'
          type: string
          description: Optional chain ID supplied by SDK clients
          nullable: true
        sessionPublicKey:
          example: 3b6a27bcceb6a42d62a3a8d02a6f0d73653215771de243a63ac048a18b59da29
          maxLength: 64
          minLength: 64
          pattern: ^[0-9a-f]{64}$
          type: string
          description: >-
            Lowercase hex (64 chars) ed25519 public key generated locally by the
            SDK. Subsequent authenticated requests are signed with the matching
            private key. The wallet's signature on the challenge message proves
            the user authorized this specific public key.
        referralCode:
          example: '0x1234567890abcdef1234567890abcdef12345678'
          type: string
          description: >-
            Optional TraderCode captured at signup (e.g. from a `?ref=CODE`
            link). When a user verifies for the very first time with a valid
            code, a referral relationship is recorded atomically. Ignored for
            users who already exist, and silently ignored if the code is unknown
            — a bad code never blocks sign-in.
          nullable: true
        inviteCode:
          example: aB3dE5gH7J
          maxLength: 10
          minLength: 10
          pattern: ^[A-Za-z0-9]{10}$
          type: string
          description: >-
            Optional Mainnet invite code. Exactly 10 case-sensitive ASCII
            letters or digits. Required while Mainnet Invite Mode is enabled for
            any wallet without a Mainnet access grant, including existing
            accounts that never received one; ignored while Invite Mode is off
            and unnecessary once the wallet has access. A missing code is
            refused with 403 and code INVITE_CODE_REQUIRED; a wrong, redeemed or
            revoked code, or an unused code whose issuer is deactivated, with
            403 and code INVITE_CODE_INVALID. A deactivated signing account is
            refused with 403 and code ACCOUNT_DEACTIVATED.
          nullable: true
      additionalProperties: false
    VerifyResponse:
      type: object
      properties:
        expiresAt:
          example: 1699876543
          type: integer
          description: Unix timestamp when the session expires
          format: int32
          nullable: true
        user:
          $ref: '#/components/schemas/UserInfo'
        isNewUser:
          example: true
          type: boolean
          description: >-
            True when this verify created the user row — the wallet's first
            sign-in to THIS application. Identity is per `(wallet,
            application)`, so a wallet already registered on another application
            is still `is_new_user` on its first sign-in to a new one. Use it to
            gate first-time UX (e.g. showing the optional "have a referral
            code?" input only to new users) and to confirm a fresh sign-up.
            False for every returning verify on this application.
          nullable: true
        referralApplied:
          example: true
          type: boolean
          description: >-
            True when a TraderCodes referral relationship was recorded on this
            verify: a valid, non-self TraderCode presented on the wallet's first
            sign-in to THIS application resolved to a known referrer. Scoped
            like `is_new_user` (per `(wallet, application)`), so a wallet
            already registered on another application can still see this on its
            first sign-in to a new one. False for a returning user of this
            application, an absent or unknown code, or a self-referral. Confirms
            the referral landed for the signup success screen.
          nullable: true
    UserInfo:
      type: object
      properties:
        id:
          example: 123e4567-e89b-12d3-a456-426614174000
          type: string
          description: User UUID
          format: uuid
          nullable: true
        address:
          example: '0x742d35Cc6634C0532925a3b8D4060f31E2C3d8B5'
          type: string
          description: Wallet address
          nullable: true
          pattern: ^0x[0-9a-fA-F]{40}$
          minLength: 42
          maxLength: 42
        username:
          example: trader123
          type: string
          description: Display username
          nullable: true

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.