Authentication

OpenSea separates application access from wallet identity.

CredentialHow it is sentUse
API keyX-API-KEYREST and MCP access and quota
SIWE sessionHTTP-only cookiesCreate, list, rotate, or revoke scoped personal access tokens (PATs)
Scoped PATBody of /api/v2/auth/tokens/exchangeMint replacement wallet JWTs without another signature
Wallet JWTAuthorization: Bearer <token>Short-lived wallet identity and scopes for REST or MCP

Public REST requests need an API key. Wallet-specific REST requests need both the API key and wallet JWT. MCP data tools follow the same rule; wallet-scoped tools need both credentials. The auth endpoints below and public scope discovery do not require an API key.

Never send a PAT in the Authorization header. Exchange it for a wallet JWT first.

Give an agent limited wallet access

Create a scoped token in Settings -> Developer. Choose only the scopes it needs and a short expiry. Put it in the agent's secret store, not prompts or logs.

CLI

Use OAuth for interactive work:

npm install -g @opensea/cli
opensea login --scopes read:favorites
opensea auth status
opensea whoami

Use SIWE when a server-side agent controls an EVM signing key:

export OPENSEA_API_KEY="..."
export OPENSEA_PRIVATE_KEY="..."
export DROP_SLUG="your-drop-slug"

opensea login --private-key --scopes read:eligibility
opensea whoami
opensea drops eligibility "$DROP_SLUG"
opensea auth revoke

The CLI keeps credentials in ~/.opensea/auth.json with file mode 0600. It does not store the private key. Run opensea auth refresh after the wallet JWT expires. See the CLI reference for commands.

SDK

OpenSeaAuth handles SIWE, PAT creation, JWT exchange, and session-authorized revocation:

import { ethers } from "ethers";
import { OpenSeaAPI, OpenSeaAuth } from "@opensea/sdk";

const privateKey = process.env.OPENSEA_PRIVATE_KEY;
if (!privateKey) throw new Error("OPENSEA_PRIVATE_KEY is required");

const signer = new ethers.Wallet(privateKey);
const auth = new OpenSeaAuth();
const token = await auth.authenticate(signer, {
  scopes: ["read:favorites"],
});

try {
  const api = new OpenSeaAPI({
    apiKey: process.env.OPENSEA_API_KEY,
    authToken: token.accessToken,
  });
  await api.walletAuth.getFavorites(await signer.getAddress(), { limit: 10 });
} finally {
  const current = await auth.getValidToken();
  await auth.revoke(current.accessToken);
}

See the @opensea/sdk reference for setup.

Manual REST flow

Use this flow if the CLI and SDK are not available:

All paths below are on https://api.opensea.io.

  1. POST /api/v2/auth/siwe/nonce to get a single-use nonce.

  2. Build and sign the SIWE message locally. The server rebuilds the text from the fields you send in step 3 and checks the signature against it, so sign exactly this layout:

    opensea.io wants you to sign in with your Ethereum account:
    0xYourWalletAddress
    
    Click to sign in and accept the OpenSea Terms of Service (https://opensea.io/tos) and Privacy Policy (https://opensea.io/privacy).
    
    URI: https://opensea.io
    Version: 1
    Chain ID: 1
    Nonce: <nonce from step 1>
    Issued At: 2026-09-30T12:00:00.000Z

    accountType is required: Ethereum for an EVM wallet, Solana for a Solana wallet, or Bitcoin for a Bitcoin wallet. It is the word before account: on the first line. The siwe library does not add it, and omitting it returns 400 Invalid signature even when the signature is correct. Use the statement exactly as shown; any other statement is rejected. Use a domain and uri on opensea.io and an Issued At close to the current time. Leave out the optional fields (Expiration Time, Not Before, Request ID, Resources), because the server does not rebuild them in EIP-4361 layout and the signature will not match.

  3. POST /api/v2/auth/siwe/verify with the same fields as a JSON object, the signature, and chainArch (EVM by default):

    {
      "message": {
        "domain": "opensea.io",
        "address": "0xYourWalletAddress",
        "statement": "Click to sign in and accept the OpenSea Terms of Service (https://opensea.io/tos) and Privacy Policy (https://opensea.io/privacy).",
        "uri": "https://opensea.io",
        "version": "1",
        "chainId": "1",
        "nonce": "<nonce from step 1>",
        "issuedAt": "2026-09-30T12:00:00.000Z",
        "accountType": "Ethereum"
      },
      "signature": "0x...",
      "chainArch": "EVM"
    }

    The response sets the session cookies. Keep all of them in a cookie jar: access_token authenticates the calls in step 4 and the PAT endpoints below, and refresh_token is what /api/v2/auth/session/refresh needs.

  4. POST /api/v2/auth/tokens with the cookies, a label, the smallest useful scope set, and expiresInDays. For example, profile edits (PATCH /api/v2/profile, POST /api/v2/profile/images) need write:profile.

  5. Save the returned PAT. The server returns it only once.

  6. POST /api/v2/auth/tokens/exchange with the PAT as subjectToken and ACCESS_TOKEN as subjectTokenType.

  7. Send the returned JWT with your API key:

curl "https://api.opensea.io/api/v2/drops/example/eligibility" \
  -H "X-API-KEY: $OPENSEA_API_KEY" \
  -H "Authorization: Bearer $OPENSEA_WALLET_JWT"

PAT management is session-only. Use the SIWE session cookies with these endpoints:

MethodPathAction
GET/api/v2/auth/tokensList PATs
POST/api/v2/auth/tokensCreate a PAT
POST/api/v2/auth/tokens/{id}/rotateRotate a PAT
DELETE/api/v2/auth/tokens/{id}Revoke a PAT

A wallet JWT cannot manage PATs. Revoking or rotating a PAT stops new exchanges, but JWTs already issued from it remain valid until they expire.

MCP

Connect to https://mcp.opensea.io/mcp. Compatible clients discover OAuth from the server automatically.

MCP data tools require X-API-KEY. Wallet-scoped tools also require the wallet JWT in Authorization: Bearer. The API key supplies access and quota; the JWT supplies wallet identity and scopes.

You can connect without credentials for the handshake, tools/list, and get_instant_api_key. Add the returned API key and reconnect before calling data tools. See the MCP setup guide.

Scopes

Discover the current scopes and their REST endpoints and MCP tools:

curl "https://api.opensea.io/api/v2/auth/scopes"
opensea auth scopes

Request only the scopes needed for the task. The live OpenAPI document contains the current request fields, response schemas, and scope requirements.

Token lifecycle

CredentialLifetimeRenewal
SIWE access cookie3.5 daysRefresh with /api/v2/auth/session/refresh
SIWE refresh cookie30 daysSign in again after expiry
Scoped PATSet at creationRotate or create another PAT using the SIWE session
Wallet JWTAbout 12 hoursExchange the PAT again

Session refresh rotates the refresh cookie. Store the newest cookie values.

Errors

StatusWhat to do
401Supply or replace the API key, or refresh an expired wallet JWT once
403Request a PAT or OAuth token with the missing scope; do not retry unchanged credentials
429Wait for Retry-After before retrying

Safety

  • Keep private keys, API keys, PATs, JWTs, cookies, signatures, and full authorization headers out of prompts, logs, client-side code, and version control.
  • Keep signing keys in environment variables or a secrets manager. Do not pass them as command arguments.
  • Use a separate, minimally funded wallet for testing and request the smallest useful scope set.
  • Revoke task-specific PATs when the task is complete.
  • Require human confirmation before signing or broadcasting a spend-bearing transaction.

For API-key options and limits, see API keys.