This document explains the API endpoints for wallet integrations with Zcash.me directory.
- Help wallet users find verified Zcash addresses by username or social handle.
- Provide a simple, stable API for wallet integrations without exposing database internals.
- Power search, autocomplete, and directory browsing features.
- Local:
http://localhost:3000 - Production:
https://zcash.me
Most endpoints require an API key:
X-API-Key: YOUR_KEY
If the key is missing or invalid, the API returns 401 unauthorized.
To obtain an API key for your wallet, contact the Zcash.me team.
Exception: The /api/lookup endpoint is public and requires no authentication.
| Endpoint | Cache TTL | Auth |
|---|---|---|
/api/lookup |
300 seconds | Public |
/api/directory |
30 seconds | API key |
/api/resolve |
60 seconds | API key |
/api/social |
300 seconds | API key |
GET /api/lookup/{username}
Resolve a ZcashMe username to a Zcash unified address. No authentication required. CORS enabled for browser-based clients.
This is the recommended endpoint for wallets that only need name-to-address resolution.
curl https://zcash.me/api/lookup/yoshiconst res = await fetch("https://zcash.me/api/lookup/yoshi");
const data = await res.json();
console.log(data.address); // "u1abc..."{
"username": "yoshi",
"display_name": "Yoshi",
"address": "u1abc...",
"address_verified": true
}| Field | Type | Description |
|---|---|---|
username |
string | The canonical username (case-preserved) |
display_name |
string | null | Optional display name set by the user |
address |
string | Zcash unified address |
address_verified |
boolean | Whether the user has verified ownership of this address |
| Status | Error Code | Meaning |
|---|---|---|
| 400 | invalid_username |
Empty or missing username |
| 404 | not_found |
Username does not exist |
| 404 | no_address |
Username exists but has no address set |
| 500 | lookup_failed |
Internal error — safe to retry |
| 503 | service_unavailable |
Service is temporarily down — safe to retry |
- Lookups are case-insensitive (
Yoshi,yoshi, andYOSHIall resolve the same) - An
address_verified: falseresponse means the address has not been cryptographically verified by the user — wallets may choose to warn before sending - Responses are cached at the edge — address updates may take a few minutes to propagate
GET /api/directory?q=<search>&limit=25&cursor=<token>&verified_only=true
Use this endpoint to power autocomplete or search lists.
| Parameter | Type | Default | Description |
|---|---|---|---|
q |
string | - | Search query (optional). Omit to browse all profiles. |
limit |
integer | 25 | Results per page (1-100) |
cursor |
string | - | Pagination cursor from previous response |
verified_only |
boolean | false | Only return profiles with verified addresses |
When q is provided:
- Matches usernames (case-insensitive)
- Matches social handles extracted from links (e.g.,
x.com/handle,linkedin.com/in/handle) - Matches non-social domains (e.g.,
example.commatcheswww.example.com)
Results are ranked in this order:
- Usernames that start with the query
- Usernames that contain the query
GET /api/directory?q=cobra&limit=25
{
"results": [
{
"username": "cobra",
"display_name": "Cobra",
"profile_image_url": "https://example.com/avatar.jpg",
"bio": "Zcash enthusiast and builder.",
"nearest_city_name": "Denver",
"address": "u1...",
"address_verified": true,
"verified_at": "2025-10-23T10:58:54.721199+00:00",
"authenticated_links": [
{ "id": 1, "label": "cobra.example.com", "url": "https://cobra.example.com", "is_verified": true }
],
"unauthenticated_links": [
{ "id": 2, "label": "cobracrypto", "url": "https://x.com/cobracrypto", "is_verified": false }
]
}
],
"next_cursor": "eyJuYW1lIjoiY29icmEiLCJpZCI6MX0"
}| Field | Type | Description |
|---|---|---|
results |
array | Array of profile objects |
next_cursor |
string | null | Cursor for next page, null if no more results |
| Field | Type | Description |
|---|---|---|
username |
string | Username (URL slug) |
display_name |
string | null | Display name |
profile_image_url |
string | null | Avatar URL |
bio |
string | null | Profile bio |
nearest_city_name |
string | null | Location |
address |
string | null | Zcash address |
address_verified |
boolean | True if address is verified on-chain |
verified_at |
string | null | ISO timestamp of last verification |
authenticated_links |
array | Links that have been verified |
unauthenticated_links |
array | Links that have not been verified |
| Field | Type | Description |
|---|---|---|
id |
integer | Link ID |
label |
string | Display label for the link |
url |
string | Full URL |
is_verified |
boolean | Whether link ownership is verified |
GET /api/resolve/{username}
Full profile details for a specific user, including all links. Use this when you need more than just the address (bio, links, avatar, etc.).
For address-only resolution, prefer /api/lookup/{username} instead.
GET /api/resolve/cobra
{
"username": "cobra",
"display_name": "Cobra",
"profile_image_url": "https://example.com/avatar.jpg",
"bio": "Zcash enthusiast and builder.",
"nearest_city_name": "Denver",
"address": "u1...",
"address_verified": true,
"verified_at": "2025-10-23T10:58:54.721199+00:00",
"authenticated_links": [
{ "id": 1, "label": "cobra.example.com", "url": "https://cobra.example.com", "is_verified": true }
],
"unauthenticated_links": [
{ "id": 2, "label": "cobracrypto", "url": "https://x.com/cobracrypto", "is_verified": false }
]
}GET /api/social?platform=<platform>&handle=<handle>
Find a Zcash address from a social media handle.
| Parameter | Type | Required | Description |
|---|---|---|---|
platform |
string | Yes | Social platform (see supported platforms) |
handle |
string | Yes | Username/handle on that platform |
x, twitter, github, instagram, reddit, linkedin, discord, tiktok, bluesky, mastodon, snapchat, telegram
GET /api/social?platform=x&handle=thefrankbraun
{
"link": {
"platform": "x",
"handle": "thefrankbraun",
"url": "https://x.com/thefrankbraun",
"is_verified": true
},
"address": "u1...",
"profile_name": "Frank Braun",
"address_verified": true
}- Only returns results for verified social links
- Handles messy input:
@handle,x.com/handle,https://twitter.com/handleall work - Prioritizes profiles with verified addresses
Use cursor-based pagination for browsing large result sets.
# First page
GET /api/directory?limit=100
# Response includes next_cursor
{ "results": [...], "next_cursor": "eyJuYW1lIjoiem9ybyIsImlkIjoxMDB9" }
# Next page
GET /api/directory?limit=100&cursor=eyJuYW1lIjoiem9ybyIsImlkIjoxMDB9
Important: Treat the cursor as an opaque token. Do not parse or modify it.
All errors return a JSON object with an error field:
{ "error": "not_found" }| Status | Error | Description |
|---|---|---|
| 400 | invalid_username, missing_parameters |
Bad request |
| 401 | unauthorized |
API key missing or invalid |
| 404 | not_found, no_address |
Username or handle not found |
| 500 | search_failed, lookup_failed, links_lookup_failed |
Server error |
| 503 | service_unavailable |
Service temporarily down |
For wallets that just need to resolve a username to an address:
async function resolveZcashMe(username) {
const res = await fetch(`https://zcash.me/api/lookup/${encodeURIComponent(username)}`);
if (!res.ok) return null;
const data = await res.json();
return data.address;
}
// Usage
const address = await resolveZcashMe("yoshi");
if (address) sendZec(address, amount);Allow users to type /username instead of full addresses:
function parseInput(input) {
const match = input.match(/^\/([A-Za-z0-9_-]+)$/);
if (match) {
return { type: 'zcashme', username: match[1] };
}
return { type: 'address', value: input };
}
async function resolveInput(input) {
const parsed = parseInput(input);
if (parsed.type === 'zcashme') {
const res = await fetch(`https://zcash.me/api/lookup/${parsed.username}`);
if (!res.ok) throw new Error('User not found');
const data = await res.json();
return data.address;
}
return parsed.value;
}// Debounce search input by 200-300ms
const response = await fetch(
`https://zcash.me/api/directory?q=${encodeURIComponent(query)}&limit=10`,
{ headers: { 'X-API-Key': 'YOUR_KEY' } }
);
const { results } = await response.json();
// Display results with verified badge
results.forEach(profile => {
const hasVerification = profile.address_verified || profile.authenticated_links.length > 0;
console.log(`${profile.display_name || profile.username} ${hasVerification ? '✓' : ''}`);
});- Debounce search input by 200-300ms to reduce API calls
- Start searching after 1-2 characters to reduce noise
- Show verified badge when
address_verifiedis true - Cache results client-side for responsive UX
- Handle errors gracefully with user-friendly messages
- 2026-03: Added public
/api/lookup/{username}endpoint- No authentication required
- CORS enabled for browser-based clients
- Returns only username, display_name, address, address_verified
- 2025-02: Updated
/api/directoryresponse format- Renamed
nametousername - Added
authenticated_linksandunauthenticated_linksarrays - Removed
id,exists,verified_links_countfields - Simplified ranking to 2 tiers (username-based only)
- Renamed
- 2025-02: Unified
/api/directoryendpoint replaces/api/search- Added
verified_onlyfilter - Added cursor-based pagination
- All endpoints require API key
- Added