The .pump resolver API
Turn a .pump domain into the Solana address behind it with a single HTTP request. Every read endpoint is public, CORS-open, cacheable, and requires no authentication.
Overview
How the .pump registry records ownership.
This is an off-chain registry with on-chain payment. Ownership records live in a Postgres database that this service controls. Solana is used for one thing: collecting and verifying a flat 0.1 SOL payment before a domain is recorded.
Practical implications
Quickstart
Resolve your first domain in one command.
curl https://your-domain.com/api/resolve/alice.pumpA registered domain returns 200 with the resolution payload. An unregistered one returns 404. That is the whole integration surface for most applications.
async function resolveDomain(domain) {
const res = await fetch(`https://your-domain.com/api/resolve/${domain}`);
if (res.status === 404) return null; // not registered
if (!res.ok) throw new Error(`resolver failed: ${res.status}`);
const { target } = await res.json();
return target; // base58 address to pay
}
// Accept either form in your send flow.
const recipient = input.endsWith(".pump")
? await resolveDomain(input)
: input;Conventions
Rules that hold across every endpoint.
Base URL- All paths are relative to your deployment, for example
https://your-domain.com. Format- Requests and responses are JSON. Write endpoints expect a JSON body with Content-Type: application/json.
Authentication- Read endpoints require none. The two write endpoints authenticate with an Ed25519 wallet signature over a single-use nonce, never a session or API key.
CORS- Read endpoints send
Access-Control-Allow-Origin: *and answerOPTIONS, so they are callable directly from a browser. Errors- Non-2xx responses carry a stable machine-readable
error.codeplus a human-readableerror.message. Branch on the code, display the message. Casing- Domains are normalized to lowercase everywhere. Sending ALICE and alice hits the same record.
{
"error": {
"code": "INVALID_NAME",
"message": "Only a-z, 0-9 and hyphens are allowed."
}
}Resolve a domain
The endpoint most integrations only ever need.
/api/resolve/{domain}Returns the address a domain points at, plus its owner and resolver records.
Path parameter
domainstring, required- With or without the TLD.
aliceandalice.pumpare equivalent. URL-encode anything you did not construct yourself.
Response fields
namestring- The fully qualified domain, for example alice.pump.
tldstring- Always pump.
ownerbase58- The wallet that controls the record and can repoint or transfer it.
targetbase58- The address to pay. Defaults to the owner and only differs if the owner deliberately repointed the domain.
recordsobject- Optional profile metadata. Keys are limited to
url,twitter,githubanddescription. Absent keys are omitted rather than null. registeredAtISO 8601- When the payment was verified and ownership recorded.
Example
{
"name": "alice.pump",
"tld": "pump",
"owner": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"target": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"records": {
"url": "https://alice.example",
"twitter": "@alice",
"github": "alice",
"description": "builder"
},
"registeredAt": "2026-08-16T11:02:44.180Z"
}{
"error": {
"code": "NOT_FOUND",
"message": "alice.pump is not registered."
}
}Pay the target, not the owner
owner is who controls the record; target is where value should go. They are identical until the owner repoints the domain, and any integration that pays owner will silently send funds to the wrong place the first time somebody does.Typed client
import { PublicKey } from "@solana/web3.js";
type Resolution = {
name: string;
owner: string;
target: string;
records: Partial<Record<"url" | "twitter" | "github" | "description", string>>;
registeredAt: string | null;
};
export async function resolve(domain: string): Promise<PublicKey | null> {
const res = await fetch(`https://your-domain.com/api/resolve/${encodeURIComponent(domain)}`, {
// Resolutions are stable; let the platform cache do the work.
next: { revalidate: 30 },
});
if (!res.ok) return null;
const data = (await res.json()) as Resolution;
return new PublicKey(data.target);
}Check availability
Whether a domain can be registered right now, and why not if it cannot.
/api/check?name={name}Validates the name and reports availability. Always 200; read the available flag.
namestring, required- The candidate domain, without the TLD.
availableboolean- True only if the domain is free and not held by a live reservation.
normalizedstring- The lowercase form that would actually be registered. Empty when the input is unusable.
reasonstring, optional- Present when unavailable: a validation failure, "Already registered.", or "Someone is registering this right now."
priceSol / priceLamportsnumber- The current flat price, so clients never hardcode it.
{
"available": true,
"normalized": "alice",
"tld": "pump",
"priceSol": 0.1,
"priceLamports": 100000000
}Availability is advisory
available: true in the same second; the database decides the winner at reservation time and the loser gets a 409. Never treat this endpoint as a lock.List the registry
Every confirmed domain, newest first.
/api/domainsPaginated list of all confirmed registrations, with an optional substring filter.
limitnumber, default 50- Page size, capped at 100.
offsetnumber, default 0- Rows to skip. Combine with total for pagination.
qstring, optional- Case-insensitive substring filter on the domain name.
{
"tld": "pump",
"total": 128,
"limit": 50,
"offset": 0,
"domains": [
{
"name": "alice",
"fqdn": "alice.pump",
"owner": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"target": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"registeredAt": "2026-08-16T11:02:44.180Z"
}
]
}Domains by wallet
Reverse lookup: everything a given address owns.
/api/wallet/{address}Confirmed domains owned by a base58 address, newest first.
addressbase58, required- A valid Solana public key. Anything else returns 400 INVALID_WALLET.
{
"owner": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"tld": "pump",
"count": 2,
"names": [
{
"name": "alice",
"fqdn": "alice.pump",
"target": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
"registeredAt": "2026-08-16T11:02:44.180Z",
"signature": "4Nd1mBQ...",
"records": {}
}
]
}Owner, not target
owner_wallet. A domain repointed at another address still belongs to, and is listed under, its owner.Domain rules
Validation is identical on the availability check and at registration, so a name that passes one always passes the other.
Character seta-z 0-9 -- Lowercase ASCII letters, digits and hyphens only.
Unicoderejected- All non-ASCII input is refused outright rather than normalized. Accented characters, emoji and Cyrillic lookalikes cannot be registered, so there are no homograph domains to defend against.
Length3 to 63- Measured on the normalized name, excluding the TLD.
Hyphensconstrained- No leading or trailing hyphen, and no consecutive hyphens.
Reservedblocked- admin, www, api, support, help, mail, root, system, official, and pump itself.
Why the ASCII check runs first
k), so normalizing first would let exactly the lookalikes this rule exists to stop through the gate.Error codes
Stable identifiers you can branch on. Messages may be reworded; codes will not.
INVALID_NAME400- The domain failed validation. The message says which rule.
INVALID_WALLET400- The supplied address is not a valid base58 public key.
NOT_FOUND404- The domain is not registered.
RATE_LIMITED429- Too many requests from your IP. Honour the Retry-After header.
NOT_CONFIGURED503- The deployment is missing its database credentials.
DB_ERROR500- The registry database could not be reached.
NAME_UNAVAILABLE409- Taken, or held by a live reservation. Pick another domain.
NO_RESERVATION404- The reservation id does not exist.
RESERVATION_EXPIRED409- The ten-minute window closed before confirmation.
TX_NOT_FOUND425- The transaction is not visible at confirmed commitment yet. Retryable.
TX_FAILED400- The transaction landed but failed on-chain, so no payment was made.
UNDERPAID400- The treasury's balance change was below the required lamports.
MEMO_MISMATCH400- The transaction does not carry this reservation's memo reference.
PAYER_MISMATCH400- A different wallet signed the payment than made the reservation.
SIGNATURE_ALREADY_USED409- That transaction has already been credited to a domain.
BAD_SIGNATURE401- The Ed25519 signature does not verify against the owner wallet.
BAD_NONCE401- The nonce is unknown, already used, or expired. Request a new one.
Rate limits
Applied per IP, per endpoint, on a sliding one-minute window.
/api/check60 / min- Sized for as-you-type search.
/api/domains60 / min- Registry listing.
/api/wallet/{address}60 / min- Reverse lookup.
/api/register/reserve10 / min- Reservations are cheap to make and costly to squat.
/api/register/confirm30 / min- Deliberately generous: someone who has already paid must always be able to retry.
/api/domains/{name}20 / min- Record updates.
/api/domains/{name}/transfer10 / min- Transfers.
Exceeding a limit returns 429 with RATE_LIMITED and a Retry-After header in seconds. /api/resolve is not rate limited, because it is meant to be called at volume and is cached.
Caching
Resolutions are stable, so cache them aggressively.
/api/resolve/{domain}200- public, max-age=15, s-maxage=30, stale-while-revalidate=300
/api/resolve/{domain}404- public, max-age=5. Short, because an unregistered domain can be claimed at any moment.
/api/domains200- public, max-age=10, s-maxage=30, stale-while-revalidate=120
/api/wallet/{address}200- no-store. Owners expect their own list to update immediately.
Because registration never expires, a cached resolution can only go stale when the owner repoints or transfers the domain. If you cache for longer than the headers suggest, invalidate on those two events rather than on a timer.
How registration works
Three steps. Payment is verified server-side against the chain, not reported by the client.
- 01
Reserve
POST /api/register/reserve with a name and wallet. A pending row is inserted with a fresh memo reference and a ten-minute expiry. If a unique index rejects the insert, you get a 409 and the domain is not yours.
- 02
Pay
One transaction: a priority fee, a transfer of 100000000 lamports to the treasury, and a Memo instruction carrying the reservation reference. Your wallet signs and sends it.
- 03
Confirm
POST /api/register/confirm with the signature. The server fetches the transaction itself and verifies it before writing anything.
What confirmation actually checks
Transaction landed- It exists at confirmed commitment and meta.err is null.
Amount- The treasury's balance delta (
postBalances - preBalancesat its index) is at least 100000000 lamports. The amount is never read from a transfer instruction: a transaction can contain any number of decoy transfers, but the settled balance change cannot be forged. Memo- A memo instruction's data exactly equals this reservation's reference, tying the payment to this domain and no other.
Payer- The fee payer, which is always account index 0 and always a signer, matches the wallet that made the reservation.
Freshness- The reservation is still pending and inside its ten-minute window.
Single use- The signature has not already been credited. A UNIQUE constraint is the backstop; the server writes and catches rather than checking first.
If confirmation fails after you have paid
Ownership and transfers
Two authenticated endpoints, both gated by a wallet signature rather than a session.
/api/domains/{name}Update the resolution target and resolver records.
/api/domains/{name}/transferHand the domain to a different wallet.
Both follow the same handshake: request a nonce from GET /api/auth/nonce?wallet=, sign a message containing the domain, the values being written and that nonce, then send the signature. The server rebuilds the identical message from your request body and verifies it against the owner wallet.
Replay protection- The signed bytes cover every value being written, so a captured signature cannot be reused with different values.
Single-use nonces- Nonces expire after five minutes and are burned with a conditional update, so two concurrent requests race on the row and exactly one wins.
Order of operations- The signature is verified before the nonce is consumed, so a bad signature never burns a good nonce.
Transfers repoint- A transfer also resets the target to the new owner. Leaving it on the previous owner's wallet would quietly keep routing value to them.
FAQ
Questions that come up when integrating.
Do I need a Solana RPC?- No. Resolution is plain HTTP. You only need an RPC if you are building your own registration flow.
Can a domain be revoked?- Not through the API. Only a signature from the owner wallet can change ownership or the target.
What happens if I lose my wallet?- The domain stays pointed where it is and cannot be moved. There is no recovery path: ownership changes require a signature from the owner wallet, and nothing overrides that.
Is the price ever different?- Every domain costs the same flat 0.1 SOL regardless of length or demand. Read priceLamports from /api/check rather than hardcoding it.
Which cluster is this?- Mainnet-beta only. There is no devnet deployment and no cluster switch, so every payment is real SOL.
Can I list every domain?- Yes, through
/api/domains, or visually on the registry page.