N Namebyes

NameByes API v1

JSON over HTTPS. Base URL: https://dn.majhe.com/api/v1

Authentication

Create a key on the Developers page and send it on every request. Keys act as you, limited to their scopes. Keep them on your server: the API offers no CORS, so browser code cannot use them.

curl https://dn.majhe.com/api/v1/listings?tld=com \
  -H "Authorization: Bearer nbk_xxxxxxxxxxxx_..."
Scopes
ScopeAllows
readRead public marketplace data: listings, listing details, sold comps
account:readRead your own listings, orders, offers and account summary
listings:writeChange price, offer settings and listed/unlisted state of your own listings
offers:writeMake offers and respond to offers (accept, decline, counter, withdraw)

Errors, limits, pagination

Endpoints

Endpoints
MethodPathScopeWhat it does
GET/listingsreadDomains for sale. Filters: q, tld, category, min_price, max_price, min_len, max_len, sort, view=all|featured|auctions.
GET/listings/{id}readOne listing, with buy-now quote (fee included) and comparable sales.
GET/compsreadCompleted sales + summary stats. Filters: q, tld, min_price, max_price, min_len, max_len, period=30|90|365|all, sort.
GET/feesreadCurrent fee and who pays it. ?price=1000 adds a quote.
GET/meaccount:readYour account summary and this key's scopes.
GET/my/listingsaccount:readYour listings with 30-day views. status, q, sort, page.
POST/my/listings/bulklistings:writeChange up to 200 of your listings at once (see below).
GET/my/ordersaccount:readOrders where you are buyer or seller. Authorization codes are never returned.
GET/my/orders/{id}account:readOne order with its timeline.
GET/my/offersaccount:readYour offers (as buyer and seller).
GET/my/offers/{id}account:readOne offer: history, whose turn it is, and the version to respond to.
POST/offersoffers:writeMake an offer.
POST/offers/{id}/respondoffers:writeAccept, decline, counter or withdraw.
GET/webhooks/event-typesnoneAll webhook event types.

Bulk listing changes

POST /api/v1/my/listings/bulk
{"ids": [12, 15], "action": "price_pct", "value": -10}

-> {"data": {"updated": 2, "skipped": []}}

Actions: price_set (value = new price), price_pct (-90..300), offers_on, offers_off, min_offer_pct (10-95, 0 clears), lease_on (months, down_payment_percent), lease_off, unlist, relist, broker_set (value = seller-paid broker commission, 2-15 %), broker_off. Listings with a running order or lease are skipped and reported.

Sold comps

GET /api/v1/comps?tld=com&max_len=5&period=365

{"data": [{"domain": "brand.com", "price": 12000, "date": "2026-10-02", "sale_type": "sale", ...}],
 "stats": {"count": 41, "median": 3200, "average": 5110, "highest": 48000, "total": 209510}, ...}

Offers

POST /api/v1/offers
{"domain_id": 42, "amount_usdc": 800, "message": "Optional, up to 300 characters"}

POST /api/v1/offers/7/respond
{"action": "counter", "amount_usdc": 950, "version": 3}

Always send the version you read from GET /my/offers/{id}. If the offer changed in the meantime you get 409 stale instead of acting on old terms. Accepting reserves the domain and the buyer pays on the website.

Webhooks

Add an https endpoint on the Developers page. We POST JSON like this:

POST https://your-server/hook
NameByes-Event: order.funded
NameByes-Delivery: 5f0c...
NameByes-Signature: t=1767225600,v1=3b1f...

{"id": "evt_tl_1832", "type": "order.funded", "created": "2026-01-01T00:00:00.000Z",
 "data": {"order_id": "...", "domain": "brand.com", "amount_usdc": 12000, "your_role": "seller", "actor": "buyer"}}
Event types
TypeWhen
order.fundedThe buyer paid into escrow
order.transfer_startedThe seller started the domain transfer
order.buyer_confirmedThe buyer confirmed they received the domain
order.auto_confirmedThe registry showed the domain at the buyer's registrar and the order was confirmed automatically
order.releasedPayment was released to the seller
order.disputedA dispute was opened
order.dispute_resolvedAn admin resolved the dispute
order.refund_requestedThe buyer requested a refund after a missed deadline
order.refund_contestedThe seller contested a refund request
order.refund_withdrawnThe buyer withdrew a refund request
order.refundedThe buyer was refunded
order.cancelled_by_sellerThe seller cancelled and refunded the order
order.deadline_extendedThe seller used the one-time deadline extension
order.registrar_changedThe registry shows the domain at a different registrar
order.evidence_addedA party uploaded dispute evidence
sale.completedA sale or lease-to-own completed (you were the buyer or seller)
offer.createdA new offer was made
offer.counteredA counter-offer was made
offer.acceptedAn offer was accepted
offer.declinedAn offer was declined
offer.withdrawnAn offer was withdrawn
offer.expiredAn offer expired without an answer
bid.placedSomeone bid on your auction
message.receivedYou received a message about a domain
pingTest event (sent when you press Send test)

Verifying signatures

Compute HMAC-SHA256 of <t>.<raw body> with your signing secret, compare it to v1 in constant time, and reject timestamps older than 5 minutes.

// Node.js / Workers
import { createHmac, timingSafeEqual } from "node:crypto"

function verify(secret, header, rawBody) {
  const parts = Object.fromEntries(header.split(",").map(p => p.split("=")))
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false
  const expected = createHmac("sha256", secret).update(parts.t + "." + rawBody).digest("hex")
  return expected.length === parts.v1.length && timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1))
}
# Python
import hmac, hashlib, time

def verify(secret: str, header: str, raw_body: bytes) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    expected = hmac.new(secret.encode(), parts["t"].encode() + b"." + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])