nimimo Logonimimo

Developer Docs

The nimimo v1 API lets you resolve handles to blockchain addresses and create payment intents that users sign in their own wallet. Public, CORS-enabled, no authentication required for the read endpoints.

How it works

Every nimimo user has a human-readable handle like @neat-gecko. Behind that handle are receiving addresses for Bitcoin, Ethereum, and Solana. The resolve endpoint lets you look up those addresses programmatically. The intents endpoints let you create a payment record on the server that the user signs in their own wallet via a sign URL — useful for invoices, agents, and third-party checkout.

Public

No API key or auth required

Cross-origin

CORS enabled for browser use

Rate limited

Per-IP, headers on every response

Base URL

https://nimimo.com/api/v1

All endpoints are versioned. The current version is v1.

Endpoints

Handle Resolution
GET
/resolve?handle={handle}

Resolve a handle to all registered addresses

GET
/resolve?handle={handle}&chain={chain}

Resolve a handle to a specific chain address

Supported chains: bitcoin, ethereum, solana
Payment Intents
POST
/intents

Create a payment intent for a handle. Returns an intent_id and sign_url.

GET
/intents/{id}

Retrieve the current status and details of an intent.

PATCH
/intents/{id}

Update an intent: mark as signed, completed (with tx_hash), or cancelled.

State machine: awaiting_signature signedcompleted. Both awaiting_signature and signed can transition to cancelled. An intent past its expires_at auto-transitions to expired on the next read or write. Terminal states (completed, cancelled, expired) cannot be changed.

Try it

Test the API right here. The default handle neat-gecko is a real nimimo identity — hit send and see the live response.

GET/api/v1/resolve
https://nimimo.com/api/v1/resolve?handle=neat-gecko

Resolve all chains

Request
GET https://nimimo.com/api/v1/resolve?handle=neat-gecko
Response 200
{
  "handle": "neat-gecko",
  "addresses": {
    "bitcoin": "bc1qz3yaratxc9z6wz2pj2k97nzl00l4cucpvcquq9",
    "ethereum": "0x874a40B1857B006d46b80c9e6badCEF3BA3B705C",
    "solana": "9rhN3eug2LbqZKCtbkGRKjRq9BVa4Y5VE4Puf2p4HCRk"
  }
}

Resolve a single chain

Request
GET https://nimimo.com/api/v1/resolve?handle=neat-gecko&chain=bitcoin
Response 200
{
  "handle": "neat-gecko",
  "chain": "bitcoin",
  "address": "bc1qz3yaratxc9z6wz2pj2k97nzl00l4cucpvcquq9"
}

Create a payment intent

A payment intent is a server-side record of an intended transfer. The recipient handle is resolved server-side, the amount is preserved as a human-readable string (no unit conversion), and the response includes a sign_url the payer opens to sign in their wallet. Only to, chain, and amount are required.

Request
POST https://nimimo.com/api/v1/intents
Content-Type: application/json

{
  "from": "@agent",                       // optional
  "to": "@neat-gecko",                    // required
  "chain": "ethereum",                    // bitcoin | ethereum | solana
  "asset": "ETH",                         // optional, inferred from chain
  "amount": "0.05",                       // required, human-readable
  "memo": "Design payment",               // optional, max 500 chars
  "expires_at": "2026-04-15T12:00:00Z"   // optional ISO-8601, defaults to +1h
}
Response 201
{
  "intent_id": "int_9x21abc...",
  "status": "awaiting_signature",
  "to_handle": "@neat-gecko",
  "to_address": "0x874a40B1857B006d46b80c9e6badCEF3BA3B705C",
  "to_avatar": "https://nimimo.com/api/avatar?seed=neat-gecko",
  "chain": "ethereum",
  "asset": "ETH",
  "amount": "0.05",
  "memo": "Design payment",
  "sign_url": "https://nimimo.com/sign/int_9x21abc...",
  "expires_at": "2026-04-15T12:00:00.000Z",
  "created_at": "2026-04-14T12:00:00.000Z"
}

Check intent status

Poll this endpoint to track the lifecycle. If the intent is still awaiting_signature and past expires_at, the read auto-transitions it to expired.

Request
GET https://nimimo.com/api/v1/intents/int_9x21abc...
Response 200
{
  "intent_id": "int_9x21abc...",
  "status": "signed",
  "from": "@agent",
  "to_handle": "@neat-gecko",
  "to_address": "0x874a40B1857B006d46b80c9e6badCEF3BA3B705C",
  "to_avatar": "https://nimimo.com/api/avatar?seed=neat-gecko",
  "chain": "ethereum",
  "asset": "ETH",
  "amount": "0.05",
  "memo": "Design payment",
  "tx_hash": null,
  "sign_url": "https://nimimo.com/sign/int_9x21abc...",
  "expires_at": "2026-04-15T12:00:00.000Z",
  "created_at": "2026-04-14T12:00:00.000Z",
  "updated_at": "2026-04-14T12:05:00.000Z"
}

Update or cancel an intent

Transition an intent through the state machine. The signing UI at sign_url handles signed and completed automatically when the user signs and broadcasts. You normally only call this endpoint directly to cancel an intent. When marking completed, tx_hash is required.

Cancel
PATCH https://nimimo.com/api/v1/intents/int_9x21abc...
Content-Type: application/json

{ "status": "cancelled" }
Mark completed
PATCH https://nimimo.com/api/v1/intents/int_9x21abc...
Content-Type: application/json

{
  "status": "completed",
  "tx_hash": "0xabc123..."
}

Returns the full intent object (same shape as GET /intents/{id}). Returns 409 invalid_transitionif the requested transition isn't allowed by the state machine, or 410 intent_expired if the intent has already expired.

Quick start

JavaScript / TypeScript
async function resolveHandle(handle) {
  const res = await fetch(
    `https://nimimo.com/api/v1/resolve?handle=${handle}`
  )
  if (!res.ok) throw new Error("Resolution failed")
  return res.json()
}

const { addresses } = await resolveHandle("neat-gecko")
console.log(addresses.bitcoin)
// "bc1qz3yaratxc9z6wz2pj2k97nzl00l4cucpvcquq9"
Python
import requests

def resolve_handle(handle, chain=None):
    params = {"handle": handle}
    if chain:
        params["chain"] = chain
    r = requests.get("https://nimimo.com/api/v1/resolve", params=params)
    r.raise_for_status()
    return r.json()

result = resolve_handle("neat-gecko")
print(result["addresses"]["ethereum"])
# "0x874a40B1857B006d46b80c9e6badCEF3BA3B705C"
cURL
curl "https://nimimo.com/api/v1/resolve?handle=neat-gecko"

# Single chain
curl "https://nimimo.com/api/v1/resolve?handle=neat-gecko&chain=solana"
JavaScript / TypeScript — intent flow
// 1. Create the intent
const create = await fetch("https://nimimo.com/api/v1/intents", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    to: "@neat-gecko",
    chain: "ethereum",
    amount: "0.05",
    memo: "Invoice #1042",
  }),
})
const intent = await create.json()

// 2. Send the user to intent.sign_url to sign in their wallet
window.open(intent.sign_url, "_blank")

// 3. Poll for completion
const poll = setInterval(async () => {
  const res = await fetch(`https://nimimo.com/api/v1/intents/${intent.intent_id}`)
  const status = await res.json()
  if (["completed", "cancelled", "expired"].includes(status.status)) {
    clearInterval(poll)
    if (status.status === "completed") console.log("Paid:", status.tx_hash)
  }
}, 3000)
cURL — intent flow
# Create
curl -X POST https://nimimo.com/api/v1/intents \
  -H "Content-Type: application/json" \
  -d '{ "to": "@neat-gecko", "chain": "ethereum", "amount": "0.05" }'

# Check status
curl https://nimimo.com/api/v1/intents/int_9x21abc...

# Cancel
curl -X PATCH https://nimimo.com/api/v1/intents/int_9x21abc... \
  -H "Content-Type: application/json" \
  -d '{ "status": "cancelled" }'

Error responses

All errors return a JSON object with error and message fields.

StatusErrorMeaning
400invalid_handleHandle is malformed or empty (resolve)
400invalid_chainChain not supported (use bitcoin, ethereum, or solana)
400invalid_bodyRequest body is not valid JSON (intents)
400invalid_idIntent ID must start with int_ (intents)
400Validation failedBody parsed but failed schema validation; details in the response
404not_foundHandle or intent does not exist
404no_addressHandle exists but has no address for the requested chain
409invalid_transitionIllegal intent state transition (e.g. cancel an already-completed intent)
410intent_expiredIntent is past its expires_at and can no longer be updated
429Rate limit exceeded; honor the Retry-After header
500internal_errorServer error; safe to retry with backoff
Example error response
{
  "error": "not_found",
  "message": "Handle not found"
}

Handle format

Handles are lowercase alphanumeric strings with optional hyphens. They always start with a letter. When displayed to users, prefix with @.

Validneat-gecko
Validlucky-mountain-42
Invalid123-bad
InvalidCool-Water

Rate limiting

The v1 API is rate-limited per IP address. Limits are per-endpoint:

EndpointLimit
GET /resolve30 requests / 60 seconds
POST /intents30 requests / 60 seconds
GET /intents/{id}30 requests / 60 seconds
PATCH /intents/{id}30 requests / 60 seconds

Every API response carries the current limit state in headers:

X-RateLimit-Limit: 30
X-RateLimit-Remaining: 28
X-RateLimit-Reset: 1712073600000

When you exceed the limit, the API returns HTTP 429 with a Retry-After header (seconds). The official SDK retries 429s with exponential backoff for resolve calls; for intents calls you should handle 429 in your own retry loop.

If you need higher limits for a production integration, get in touch.

TypeScript SDK

The official SDK wraps the v1 API with typed responses, automatic retries, caching, and payment helpers. Zero runtime dependencies, ~5KB minified, dual ESM/CJS, full TypeScript types. Currently published as 0.1.0.

Install
npm install @nimimo/resolve

Initialize

Setup
import { NimimoClient } from "@nimimo/resolve"

const nimimo = new NimimoClient()

// Optional: custom config
const nimimo = new NimimoClient({
  baseUrl: "https://nimimo.com",  // default; SDK appends /api/v1 internally
  maxRetries: 2,                  // retries on 429 with exponential backoff
  cacheTtl: 30_000,               // 30s in-memory cache for resolve()
})

Resolve handles

Single handle — all chains
const { handle, addresses } = await nimimo.resolve("neat-gecko")

console.log(addresses.bitcoin)
// "bc1qz3yaratxc9z6wz2pj2k97nzl00l4cucpvcquq9"

console.log(addresses.ethereum)
// "0x874a40B1857B006d46b80c9e6badCEF3BA3B705C"

console.log(addresses.solana)
// "9rhN3eug2LbqZKCtbkGRKjRq9BVa4Y5VE4Puf2p4HCRk"
Single handle — specific chain
const { address } = await nimimo.resolve("neat-gecko", "bitcoin")
// "bc1qz3yaratxc9z6wz2pj2k97nzl00l4cucpvcquq9"
Batch resolve
const results = await nimimo.resolveMany(["neat-gecko", "lucky-mountain"])
// [
//   { handle: "neat-gecko", addresses: { bitcoin: "bc1q...", ... } },
//   { handle: "lucky-mountain", addresses: { bitcoin: "bc1q...", ... } }
// ]
Check if a handle exists
const exists = await nimimo.exists("neat-gecko")  // true
const nope = await nimimo.exists("nonexistent")   // false

Client-side payment helper

paymentIntent()is a local helper that resolves the recipient and converts the amount to the chain's smallest unit (wei, satoshis, lamports). It does not hit the /intents endpoints — nothing is stored server-side. Use it when your app already owns the signing flow.

Build a wallet-ready transaction
const intent = await nimimo.paymentIntent("neat-gecko", {
  chain: "ethereum",
  amount: "0.05",  // ETH
})

// intent = {
//   to: "0x874a40B1857B006d46b80c9e6badCEF3BA3B705C",
//   value: "50000000000000000",  // wei
//   chain: "ethereum",
//   amount: "0.05",
//   handle: "neat-gecko",
// }

// Use with ethers.js
await signer.sendTransaction({ to: intent.to, value: intent.value })

// Use with wagmi/viem
await sendTransaction({ to: intent.to, value: intent.value })
Bitcoin
const intent = await nimimo.paymentIntent("neat-gecko", {
  chain: "bitcoin",
  amount: "0.001",  // BTC
})
// intent.to    → "bc1qz3yaratxc9z6wz2pj2k97nzl00l4cucpvcquq9"
// intent.value → "100000" (satoshis)

Server-side intents (with sign URL)

These methods talk to the /intents endpoints. Use them when the signer is a different person than the caller — invoices, agent payments, third-party checkout. The payer signs at sign_url in their own wallet.

Create, poll, cancel
import { NimimoClient, IntentNotFound, IntentExpired } from "@nimimo/resolve"

const nimimo = new NimimoClient()

// Create
const created = await nimimo.createIntent({
  to: "@neat-gecko",
  chain: "ethereum",
  amount: "0.05",
  memo: "Invoice #1042",
  // from, asset, expires_at are optional
})

console.log(created.intent_id)   // "int_9x21abc..."
console.log(created.sign_url)    // "https://nimimo.com/sign/int_9x21abc..."
console.log(created.status)      // "awaiting_signature"

// Poll
const intent = await nimimo.getIntent(created.intent_id)
if (intent.status === "completed") {
  console.log("Paid:", intent.tx_hash)
}

// Cancel
try {
  await nimimo.cancelIntent(created.intent_id)
} catch (err) {
  if (err instanceof IntentExpired) {
    console.log("Already expired")
  } else if (err instanceof IntentNotFound) {
    console.log("No such intent")
  }
}

Pay URLs

Generate shareable payment links. No SDK needed on the receiving end — the link opens the nimimo profile with a pre-filled payment flow.

Generate a pay URL
const url = nimimo.payUrl("neat-gecko", {
  chain: "ethereum",
  amount: "0.05",
})
// "https://nimimo.com/@neat-gecko?pay=0.05&chain=ethereum"

// Drop it in an invoice, a bio, or a chat message.
// Anyone who clicks it can pay from any wallet.

Error handling

Typed errors
import { NimimoClient, HandleNotFound, NoAddress, RateLimited } from "@nimimo/resolve"

try {
  const result = await nimimo.resolve("nonexistent-handle")
} catch (err) {
  if (err instanceof HandleNotFound) {
    console.log("Handle doesn't exist")
  } else if (err instanceof NoAddress) {
    console.log("No address for this chain")
  } else if (err instanceof RateLimited) {
    console.log("Too many requests — SDK auto-retries with backoff")
  }
}

SDK features

  • Full TypeScript types for all v1 responses and errors
  • Automatic retry with exponential backoff on 429 (resolve only)
  • In-memory cache for resolve with configurable TTL (default 30s)
  • Batch resolution for multiple handles in one call
  • Client-side payment helper with unit conversion (ETH to wei, BTC to sat, SOL to lamports)
  • Server-side intents: createIntent, getIntent, cancelIntent
  • Pay URL generation for zero-code payment links
  • Typed error classes: HandleNotFound, NoAddress, InvalidHandle, InvalidChain, IntentNotFound, IntentExpired, InvalidTransition, RateLimited
  • ~5KB minified, zero runtime dependencies, dual ESM/CJS

Use cases

Payment integrations

Let users pay to a @handle instead of copying a raw address. Resolve the handle, show the address, and initiate the transaction.

Invoices & checkout

Create a payment intent server-side and email or display the sign URL. The payer signs in their own wallet; you poll the intent for completion and tx_hash.

Agent payments

Let an automated agent (bot, service, or AI) propose a payment without ever holding keys. Create the intent, hand the sign URL to the human in the loop, and wait for them to sign.

Contact books

Store nimimo handles as contacts and resolve addresses on demand. Addresses update automatically when users rotate keys.

Multi-chain apps

Resolve a single handle to Bitcoin, Ethereum, and Solana addresses simultaneously. No chain selection needed from the user.

This API is provided as-is. nimimo reserves the right to adjust rate limits or deprecate endpoints with reasonable notice.