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

# Verify and link wallet

> Verifies a signed EIP-4361 message and links the wallet to the authenticated Shodai account. OAuth bearer tokens require agreements.write; API keys require the equivalent account entitlement. Verification proves control only: it does not transfer custody or authorize Shodai to sign agreement inputs.

For the complete documentation index, see [llms.txt](https://docs.shodai.network/llms.txt).

Use an OAuth bearer access token with the `agreements.write` scope, or provide `X-API-Key` as the alternative credential. Verification proves control and links the wallet to the authenticated Shodai account; it does not transfer custody or authorize Shodai to sign agreement inputs. See [Authentication](/authentication) for delegated OAuth access and [Link a wallet and access agreements](/workflow/link-a-wallet-and-access-agreements) for the creator and participant access model.

### TypeScript with viem

```typescript
import { createSiweMessage } from 'viem/siwe';

const baseUrl = process.env.SHODAI_API_BASE_URL!.replace(/\/+$/, '');
const apiUrl = new URL(baseUrl);
const accessToken = process.env.SHODAI_ACCESS_TOKEN!;
const headers = {
  Authorization: `Bearer ${accessToken}`,
  'Content-Type': 'application/json',
};
const address = walletClient.account.address;
const nonceResponse = await fetch(`${baseUrl}/v0/siwe/nonce`, {
  method: 'POST',
  headers,
  body: JSON.stringify({ address }),
});
const { data: challenge } = await nonceResponse.json();
const message = createSiweMessage({
  domain: apiUrl.host,
  address,
  statement: 'Prove control of this wallet to Shodai.',
  uri: apiUrl.origin,
  version: '1',
  chainId: 59141,
  nonce: challenge.nonce,
  issuedAt: new Date(challenge.issuedAt),
});
const signature = await walletClient.signMessage({
  account: walletClient.account,
  message,
});
const verification = await fetch(`${baseUrl}/v0/siwe/verify`, {
  method: 'POST',
  headers,
  body: JSON.stringify({
    address,
    message,
    signature,
    domain: apiUrl.host,
    chainId: 59141,
  }),
});
console.log(await verification.json());

// X-API-Key: YOUR_API_KEY is the alternative to Authorization.
```


## OpenAPI

````yaml /openapi.json post /v0/siwe/verify
openapi: 3.1.0
info:
  title: Agreements API
  version: v0
  description: Author, deploy, read, and advance agreements through the Agreements API.
servers:
  - url: https://test-api.shodai.network
    description: Public base URL for the Agreements API testnet environment.
  - url: https://api.shodai.network
    description: Public base URL for the Agreements API production environment.
security: []
tags:
  - name: Agreement Records
    description: List and read agreement records.
  - name: Agreement Documents
    description: Resolve hosted agreement prose documents.
  - name: Authoring
    description: Check authored agreement JSON before deployment.
  - name: Deployment
    description: Preflight and deploy agreements.
  - name: Using Agreements
    description: Read state, inspect input history, and submit signed inputs.
  - name: Webhooks
    description: Register signed push callbacks for agreement events.
  - name: Wallet Access
    description: Prove control of a wallet and link it to the authenticated Shodai account.
  - name: System
    description: Health and OpenAPI discovery endpoints.
paths:
  /v0/siwe/verify:
    post:
      tags:
        - Wallet Access
      summary: Verify and link wallet
      description: >-
        Verifies a signed EIP-4361 message and links the wallet to the
        authenticated Shodai account. OAuth bearer tokens require
        agreements.write; API keys require the equivalent account entitlement.
        Verification proves control only: it does not transfer custody or
        authorize Shodai to sign agreement inputs.
      operationId: verifySiweWallet
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SiweVerifyRequest'
            examples:
              proof:
                summary: Signed EIP-4361 proof
                value:
                  address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
                  message: >-
                    api.example.com wants you to sign in with your Ethereum
                    account:

                    0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266


                    Prove control of this wallet to Shodai.


                    URI: https://api.example.com

                    Version: 1

                    Chain ID: 59141

                    Nonce: 0123456789abcdef0123456789abcdef

                    Issued At: 2026-08-07T20:00:00.000Z
                  signature: >-
                    0x1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
                  domain: api.example.com
                  chainId: 59141
      responses:
        '201':
          description: Verified and linked wallet.
          content:
            application/json:
              schema:
                type: object
                required:
                  - data
                  - meta
                properties:
                  data:
                    $ref: '#/components/schemas/SiweVerifyResponse'
                  meta:
                    $ref: '#/components/schemas/ResponseMeta'
              examples:
                verifiedWallet:
                  summary: Verified wallet linked to the authenticated account
                  value:
                    data:
                      wallet: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
                      walletBinding: verified_via_siwe
                      verifiedAt: '2026-08-07T20:01:00.000Z'
                    meta:
                      apiVersion: v0
                      requestId: req_123
        '400':
          description: Malformed wallet verification input.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: >-
            Missing or invalid API credential, or invalid, expired, replaced,
            consumed, or mismatched SIWE proof.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '402':
          description: >-
            The authenticated Shodai account has paid_required entitlement mode
            for the requested scope. Per-call x402 settlement is not
            implemented. Treat this as an entitlement/operator issue.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '403':
          description: >-
            The credential lacks the required OAuth scope, the account lacks
            entitlement, or the account cannot access the resource.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: The wallet belongs to another Shodai account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
      security:
        - ApiKeyAuth: []
        - OAuthBearerAuth: []
      x-codeSamples:
        - lang: typescript
          label: viem with OAuth bearer
          source: >-
            import { createSiweMessage } from 'viem/siwe';


            const baseUrl = process.env.SHODAI_API_BASE_URL!.replace(/\/+$/,
            '');

            const apiUrl = new URL(baseUrl);

            const accessToken = process.env.SHODAI_ACCESS_TOKEN!;

            const headers = {
              Authorization: `Bearer ${accessToken}`,
              'Content-Type': 'application/json',
            };

            const address = walletClient.account.address;

            const nonceResponse = await fetch(`${baseUrl}/v0/siwe/nonce`, {
              method: 'POST',
              headers,
              body: JSON.stringify({ address }),
            });

            const { data: challenge } = await nonceResponse.json();

            const message = createSiweMessage({
              domain: apiUrl.host,
              address,
              statement: 'Prove control of this wallet to Shodai.',
              uri: apiUrl.origin,
              version: '1',
              chainId: 59141,
              nonce: challenge.nonce,
              issuedAt: new Date(challenge.issuedAt),
            });

            const signature = await walletClient.signMessage({
              account: walletClient.account,
              message,
            });

            const verification = await fetch(`${baseUrl}/v0/siwe/verify`, {
              method: 'POST',
              headers,
              body: JSON.stringify({
                address,
                message,
                signature,
                domain: apiUrl.host,
                chainId: 59141,
              }),
            });

            console.log(await verification.json());


            // X-API-Key: YOUR_API_KEY is the alternative to Authorization.
components:
  schemas:
    SiweVerifyRequest:
      type: object
      required:
        - address
        - message
        - signature
      properties:
        address:
          type: string
          pattern: ^0x[0-9a-fA-F]{40}$
          description: Wallet address claimed by the EIP-4361 message.
        message:
          type: string
          description: >-
            Complete EIP-4361 message containing the issued nonce, wallet,
            domain, URI, chain ID, and issued-at timestamp.
        signature:
          type: string
          pattern: ^0x[0-9a-fA-F]+$
          description: Hexadecimal signature over the complete EIP-4361 message.
        domain:
          type: string
          description: >-
            Optional assertion that must exactly match the domain in the signed
            message.
        chainId:
          type: integer
          description: >-
            Optional assertion that must match the chain ID in the signed
            message.
      example:
        address: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
        message: |-
          api.example.com wants you to sign in with your Ethereum account:
          0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266

          Prove control of this wallet to Shodai.

          URI: https://api.example.com
          Version: 1
          Chain ID: 59141
          Nonce: 0123456789abcdef0123456789abcdef
          Issued At: 2026-08-07T20:00:00.000Z
        signature: >-
          0x1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
        domain: api.example.com
        chainId: 59141
    SiweVerifyResponse:
      type: object
      required:
        - wallet
        - walletBinding
        - verifiedAt
      properties:
        wallet:
          type: string
          pattern: ^0x[0-9a-f]{40}$
          description: Verified lowercase wallet address.
        walletBinding:
          type: string
          enum:
            - verified_via_siwe
          description: Proof-backed wallet binding method.
        verifiedAt:
          type: string
          format: date-time
          description: Time the proof was accepted and consumed.
      example:
        wallet: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
        walletBinding: verified_via_siwe
        verifiedAt: '2026-08-07T20:01:00.000Z'
    ResponseMeta:
      type: object
      required:
        - apiVersion
        - requestId
      properties:
        apiVersion:
          type: string
          example: v0
        requestId:
          type: string
          description: Correlation ID for support and debugging.
    ErrorResponse:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - requestId
          properties:
            code:
              type: string
              example: unauthorized
              description: Stable machine-readable error code.
            message:
              type: string
              example: Missing API credential
              description: Safe human-readable error summary.
            details:
              description: Optional field errors or upstream-safe context.
            requestId:
              type: string
              description: Correlation ID for support and debugging.
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: >-
        Canonical API-key credential. Send X-API-Key: cns_pk_..., or
        Authorization: Bearer cns_pk_... only as an API-key compatibility alias.
      x-default: YOUR_API_KEY
    OAuthBearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: OAuth access token issued by Shodai.
      x-default: Bearer YOUR_OAUTH_ACCESS_TOKEN

````