You need to verify whether a wallet holds an NFT before granting access, applying a discount, or making a trust decision. Most approaches require running your own node, indexing contract events, or calling chain-specific APIs. InsumerAPI does it with one POST request across 32 blockchains. Here is the working code.

The simplest NFT ownership check

The attestation endpoint is POST /v1/attest. You send a wallet address and an array of conditions. For NFT ownership, use type: "nft_ownership" with the collection contract address and chain ID. No threshold is needed. The API verifies whether the wallet owns at least one NFT from the collection.

curl:

curl -X POST https://api.insumermodel.com/v1/attest \
  -H "X-API-Key: insr_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "wallet": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    "conditions": [
      {
        "type": "nft_ownership",
        "contractAddress": "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
        "chainId": 1,
        "label": "Bored Ape Yacht Club"
      }
    ]
  }'

The response returns met: true or met: false. It never reveals how many NFTs the wallet holds or which token IDs it owns.

Python example

import requests

API_KEY = "insr_live_YOUR_KEY_HERE"
BASE    = "https://api.insumermodel.com"

resp = requests.post(
    f"{BASE}/v1/attest",
    headers={
        "X-API-Key": API_KEY,
        "Content-Type": "application/json",
    },
    json={
        "wallet": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
        "conditions": [
            {
                "type": "nft_ownership",
                "contractAddress": "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
                "chainId": 1,
                "label": "Bored Ape Yacht Club",
            }
        ],
    },
)

data = resp.json()
owns_nft = data["data"]["attestation"]["results"][0]["met"]
print(f"Owns a Bored Ape: {owns_nft}")

The boolean result is at data["data"]["attestation"]["results"][0]["met"]. The overall pass field at data["data"]["attestation"]["pass"] is true only when all conditions are met.

JavaScript example

const API_KEY = "insr_live_YOUR_KEY_HERE";
const BASE    = "https://api.insumermodel.com";

const resp = await fetch(`${BASE}/v1/attest`, {
  method: "POST",
  headers: {
    "X-API-Key": API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    wallet: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    conditions: [
      {
        type: "nft_ownership",
        contractAddress: "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
        chainId: 1,
        label: "Bored Ape Yacht Club",
      },
    ],
  }),
});

const data = await resp.json();
const ownsNft = data.data.attestation.results[0].met;
console.log("Owns a Bored Ape:", ownsNft);

What comes back

The response looks like this:

{
  "ok": true,
  "data": {
    "attestation": {
      "id": "ATST-B8D4F6A7E9C01234",
      "pass": true,
      "results": [
        {
          "condition": 0,
          "label": "Bored Ape Yacht Club",
          "type": "nft_ownership",
          "chainId": 1,
          "met": true,
          "evaluatedCondition": {
            "type": "nft_ownership",
            "chainId": 1,
            "contractAddress": "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
            "operator": "gt",
            "threshold": 0
          },
          "conditionHash": "0x7e2f...",
          "blockNumber": "0x13a1b40",
          "blockTimestamp": "2026-03-06T10:00:00.000Z"
        }
      ],
      "passCount": 1,
      "failCount": 0,
      "attestedAt": "2026-03-06T10:00:01.000Z",
      "expiresAt": "2026-03-06T10:30:01.000Z"
    },
    "sig": "...",
    "kid": "insumer-attest-v1"
  },
  "meta": { "creditsRemaining": 97, "creditsCharged": 1, "version": "1.0", "timestamp": "2026-03-06T10:00:01.000Z" }
}

Key fields:

Multiple NFT collections in one call

You can verify up to 10 conditions in a single call, each on a different chain. This costs 1 credit total regardless of how many conditions you include.

curl -X POST https://api.insumermodel.com/v1/attest \
  -H "X-API-Key: insr_live_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "wallet": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
    "solanaWallet": "YOUR_SOLANA_WALLET_ADDRESS",
    "conditions": [
      {
        "type": "nft_ownership",
        "contractAddress": "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
        "chainId": 1,
        "label": "Bored Ape Yacht Club"
      },
      {
        "type": "nft_ownership",
        "contractAddress": "0xBd3531dA5CF5857e7CfAA92426877b022e612cf8",
        "chainId": 1,
        "label": "Pudgy Penguins"
      },
      {
        "type": "nft_ownership",
        "contractAddress": "YOUR_SOLANA_NFT_COLLECTION_ADDRESS",
        "chainId": "solana",
        "label": "Solana NFT Collection"
      }
    ]
  }'

Each result comes back independently. If the wallet holds a Bored Ape and a Pudgy Penguin but not the Solana NFT, you get met: true for the first two and met: false for the third. The overall pass will be false because not all conditions were met.

For Solana NFTs, pass the Solana wallet address in the solanaWallet parameter and use "solana" as the chain ID. EVM and Solana conditions can be mixed in the same request.

NFT check without exposing what they hold

This is the key privacy property of the API. A standard NFT ownership check on a block explorer or indexer reveals the wallet's entire collection: every token ID, every contract, every transfer history. The InsumerAPI attestation returns only a boolean.

The question the API answers is: "Does this wallet own at least one NFT from this collection?" The answer is yes or no, following the same privacy-by-design principle as every other endpoint. Nothing else leaks. Not which specific token IDs the wallet holds. Not how many. Not when they were acquired.

This matters for privacy-preserving verification. A token-gated community can confirm membership eligibility without learning the member's full portfolio. A checkout system can verify discount eligibility without cataloging assets.

Verifying the signature

Every response includes an ECDSA P-256 signature in the sig field. To verify it independently, use the insumer-verify npm package:

npm install insumer-verify
import { verifyAttestation } from "insumer-verify";

// Pass the full API response envelope, not apiResponse.data
const result = await verifyAttestation(apiResponse);
console.log("Signature valid:", result.valid);
console.log("Signer:", result.kid);

The package fetches the public key from /.well-known/jwks.json and verifies the signature locally. No API call needed for verification. You can also pass a custom jwksUrl if you want to pin the key.

What to build with NFT ownership checks

The full API reference covers additional endpoints including wallet trust profiles (POST /v1/trust), merchant discovery (GET /v1/merchants), and compliance templates (GET /v1/compliance/templates).

Get a free API key in 30 seconds

10 free credits. 32 blockchains. No credit card. Start verifying NFT ownership with one API call.

View Developer Portal