Partner API · Analysis preview

Integrate wallet analysis

Your backend sends a wallet address. The API returns supported recovery opportunities, protected accounts and fee estimates.

  • Server-side only
  • HTTPS or optional SDK
  • Execution disabled

01 / Connect

Make your first request

You need an activated API key and a wallet address. Request access if you have not been onboarded. Choose either integration below.

Keep the key on your server. Your frontend calls your own authenticated backend. Never put the key in browser code or expose an unrestricted API proxy.
Direct HTTPS Go, Python, Rust or any backend. No npm package needed.

Send JSON to https://unclaimedsol.com/api/v1/check-wallet. Replace the placeholders with your server-side key, wallet address and a saved idempotency key.

POST /api/v1/check-wallet HTTP/1.1
Host: unclaimedsol.com
Authorization: Bearer <server-only-api-key>
Content-Type: application/json
Accept: application/json
Idempotency-Key: <saved-key-for-this-request>

{
  "wallet": "<wallet-base58-address>",
  "mode": "safe",
  "limit": 20,
  "includeAirdrops": false
}

Save the exact body and key before sending. Validate responses against the pinned contract. The SDK version header is optional.

JavaScript / TypeScript SDK Node.js 22–24 · @unclaimedsol/wallet-sdk · preview.5
npm install --save-exact @unclaimedsol/wallet-sdk@0.1.0-preview.5

Load UNCLAIMED_API_ORIGIN=https://unclaimedsol.com and UNCLAIMED_API_KEY through your normal backend secret loader. Do not add /api/v1 to the SDK origin. The SDK does not load .env automatically.

Save this as analysis.ts. Your application persists the request body and key before calling it.

import {
  createUnclaimedClient,
  nextWalletPage,
  summarizeOpportunities,
} from "@unclaimedsol/wallet-sdk";

const baseUrl = process.env.UNCLAIMED_API_ORIGIN;
const apiKey = process.env.UNCLAIMED_API_KEY;
if (!baseUrl || !apiKey) throw new Error("Server API configuration is missing.");
const client = createUnclaimedClient({ baseUrl, apiKey });

// Your app persists the exact input and key BEFORE calling this function.
export async function analyzeWallet(wallet: string, idempotencyKey: string) {
  const input = { wallet, mode: "safe", limit: 20, includeAirdrops: false } as const;
  const page = await client.checkWallet(input, { idempotencyKey });

  return {
    items: page.data.items,
    summary: summarizeOpportunities(page.data.items),
    nextInput: nextWalletPage(input, page),
  };
}

Preview.5 accepts protected stale_price results. Earlier SDKs that reject this state must be upgraded. Full SDK reference ↗

02 / Display

Show opportunities and protections

A successful 200 response contains one page in data.items. It can include protected accounts alongside recoverable opportunities.

Recovery opportunity

Display the returned action and fee/net estimate. An estimate is not recovered funds. Keep execution controls off while your access is analysis-only.

Protected account

Show its protection reason. Never enable selection or count recovery value for action: "none" or protected items.

  • Keep amounts as exact decimal strings. null means unavailable, not zero.
  • Deduplicate overlapping opportunities across loaded pages. With the SDK, use summarizeOpportunities(allLoadedItems); do not add page summaries together.
  • Totals cover only loaded pages. Fee/net estimates exclude network and setup costs.
What does stale_price mean?

A positive token account has a reference price that is too old to use safely. It stays protected, with stale_price in both classification reason and protection arrays, action none, and execution unsupported. Accept this valid response state; do not treat it as zero value or “no price.”

Independently verified empty accounts may still appear. Stale SOL references leave USD estimates null. Failed required account or protection lookups still return 503 incomplete. Direct clients must enforce the full contract, including these constraints.

Load another page only when the user asks

When data.pagination.hasMore is true, copy data.pagination.nextCursor to the next request's cursor. Keep the wallet and settings unchanged, and save a new key for that page. Do not paginate automatically.

For SDK users, nextWalletPage(input, page) prepares the next input or returns null when done:

// Use the saved first request and key.
const first = await analyzeWallet(wallet, savedFirstKey);
const allLoadedItems = [...first.items];
if (first.nextInput) {
  // Persist first.nextInput and a NEW page key before calling.
  const nextPage = await client.checkWallet(first.nextInput, {
    idempotencyKey: savedPageKey,
  });
  allLoadedItems.push(...nextPage.data.items);
}
const totals = summarizeOpportunities(allLoadedItems);

03 / Retry safely

Reuse a key for uncertainty. Use a new key for fresh work.

Persist each exact request body and its key before sending. The same body and key replay the stored result; they do not refresh the wallet.

What happened?What to do
Timeout, lost/malformed response, request_in_progress or platform_unavailableRetry the same body and key. The outcome may already exist.
Parsed, retryable terminal failure such as 503 incomplete or platform_failure_recordedUse a new saved key for a deliberate new attempt. Honor retryAfterMs.
snapshot_expired, or a deliberate fresh wallet scanOmit the old cursor and use a new saved key.
Nonretryable refusal or invalid inputCorrect the cause before trying again.

After a confirmed failed analysis and a server correction, start fresh without the old cursor. After an unknown outcome, reconcile with the original request first. Never add automatic retries or pagination.

Pace requests at least 31 seconds apart for the current invited rollout, with one request in flight. Honor retryAfterMs and your assigned limits.

Reference & next steps

Limits and provider allowances

Confirm your assigned limits before live use. The current invited rollout allows 2 requests/minute, one in flight, 100 provider stages per customer per UTC day, and a shared 300-stage pool.

A new first page reserves 3 stages; each subsequent page reserves 1. Failed or uncertain reservations remain spent. When exhausted, stop fresh attempts and wait for the next UTC day or contact us. Stored replays use no new provider stages while your key is active, but request-rate limits still apply.

The preview supports up to 400 token accounts per wallet and 200 per page. Start with 20 per page. Unsupported or unresolved required lookups can still produce incomplete.

Try the example without credentials

This local fixture demonstrates analysis and pagination with synthetic responses. No API key, wallet connection or provider calls are needed.

npm install --save-exact --ignore-scripts @unclaimedsol/wallet-sdk@0.1.0-preview.5
node node_modules/@unclaimedsol/wallet-sdk/examples/reference/server.mjs

Open http://127.0.0.1:4317; stop with Ctrl + C. The example uses in-memory analysis sessions and a private journal for optional execution. Public hosting needs authentication, durable shared state and concurrency controls.

Request access and agree pilot terms

Request partner access or use your existing partner channel. Share your application, intended analysis flow and expected volume. Receive your activated key privately; never include it in an agent prompt.

Free or invoiced pilot terms are agreed per partner. Metering does not prove payment collection. Recovery service fees and Solana network costs are separate when execution is enabled.

Start with fixture checks, then agree a bounded live test of analysis, user-requested pagination, exact replay and error handling. Previous owner test allowances cannot be reused. Independent partner acceptance remains pending.

Later: claim & earn Separate activation required. Not part of the initial analysis integration.

Execution is separately admitted. The pilot builds one direct transaction for 1–10 explicitly selected empty, non-native classic SPL accounts. Token-2022, collectibles, positive-balance burns, Excess SOL, swaps and gasless are outside this execution scope. Broader analysis does not make those items executable.

Before a live test: the reference checks transaction expiry before opening the wallet and again after signing, prevents duplicate signing clicks, and retains exact work after interruption. Include both height reads in the agreed test allowance. A wallet prompt can still outlast the transaction; expired or uncertain work requires reconciliation without automatic replacement.
  1. Retain selection and consent. Save the build key and ordered selected item IDs/actions. If attribution is chosen, use createPartnerConsent on your server; the wallet signs its exact UTF-8 message. Attach the canonical base64 signature to the helper’s proof as partnerAttribution. Declining message signing omits that field and preserves ordinary recovery without partner credit or website cashback.
  2. Build and review. Call client.build(input, session.token, { idempotencyKey }). Retain the accepted receipt before showing fresh amounts, service fee, network estimate and funding. A null receipt means nothing was built. Explain that the existing instruction can permanently burn tokens deposited after validation.
  3. Sign and submit through your app. Check confirmed block height before the wallet prompt and again after signing against the exact lastValidBlockHeight. Validate the unchanged message, payer and all required signatures. Save exact signed bytes, signature and transaction ID before your backend submits. A failed read, expiry or unknown send requires reconciliation, with no automatic rebuild or resend.
  4. Record and resume. Call client.recordExecution({ wallet, transactions }, receipt.token), using the original transaction ID and known signature. Record retries retain the same receipt and data; they take no idempotency key. Reload saved work after interruption. Never change signed work to not_signed.

The SDK never signs or submits. Keep API, receipt/session and provider credentials on the server. The loopback reference is single-process; public hosting needs application authentication, durable shared state and concurrency controls. Do not expose its local /pilot route as a hosted service.

Recovery and earnings are separate
StateDisplay and action
Recovery pending or unknownRetain exact work and reconcile. No recovered-value claim.
Recovery verified; credit pending or heldShow verified recovery separately. Do not label the credit as applied or paid.
Credit appliedCredited earnings are 20% of verified eligible service fees, floored once per immutable batch. This is not payout or a share of principal.
Attribution not requested or not eligibleNo partner earnings; retain the recovery outcome separately.

The existing manual payout minimum is 0.1 SOL. Build estimates, metering and credited earnings do not establish payment collection, payout or profitable partner economics.

Historical owner receipt: one October 1 receipt retains a public item held field although separately bound canonical audit evidence proves published credit applied once. The later engine correction fixes new first responses; saved terminal responses were not rewritten. Do not interpret arbitrary held receipts as applied. Inconsistent historical metadata needs evidence-based reconciliation.

Independent integration review

Begin with the fixture and record setup time and friction. Before live work, agree the tester, exact application revision, key/enrollment, supported wallet/account scope, attribution choice, finite UTC window, provider/API/submission/signing limits, fee ceiling and shutdown owner. Prior owner-test allowances cannot be reused. Preserve receipt recording when execution is switched off.

Record who actually operated the integration and distinguish completed, failed and unresolved attempts. Verified recovery, credit, actual network cost and provider cost need separate evidence. One independent test does not establish recurring adoption or a paying customer. Public naming, quotes and results require separate permission.

Already using another Unclaimed integration?

Wallet API credentials are separate from existing integrations:

Contract: preview.5 analysis.yaml · SDK reference · Agent integration brief