docs · v1

Namedesk API

Programmatic access to the same research, pricing, and signals you see in the dashboard. Same wallet, same data — REST + JSON.

Quick start

Generate a key in Settings → API keys. The full key is shown once when you create it — copy it into your secrets manager right then; we can't show it again afterward.

# FULL READS - the deep multi-model appraisal on every name.
# 10 credits per uncached name. Optional 'label' (max 80 chars) is
# shown in the dashboard, completion email, and activity feed.
curl -X POST https://api.namedesk.app/v1/research-jobs \
  -H "x-api-key: $ND_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["acme.ai", "beta.ai"], "label": "weekly portfolio scan"}'

# QUICK READS - a fast routing pass decides whether each name is worth
# the full appraisal. Names judged worthy run the full appraisal at
# 10 credits; the rest get a first-pass estimate at 1 credit.
curl -X POST https://api.namedesk.app/v1/research-jobs/quick \
  -H "x-api-key: $ND_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"domains": ["acme.ai", "buyusedlaptopsonline.com"]}'

# RERUN ONE NAME - force a fresh appraisal on a single domain. Useful
# when you want to upgrade a quick read to the full appraisal. Default
# (empty body) runs the full appraisal at 10 credits.
curl -X POST https://api.namedesk.app/v1/domains/acme.ai/rerun \
  -H "x-api-key: $ND_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

# Poll job status (per-domain outcomes only; no report data)
curl https://api.namedesk.app/v1/research-jobs/job_xyz \
  -H "x-api-key: $ND_KEY"

# Pull every report for the job in ONE call. Pending and running names
# carry only their status; paid and cached names carry the full report.
# Safe to poll while the job runs.
curl https://api.namedesk.app/v1/research-jobs/job_xyz/report \
  -H "x-api-key: $ND_KEY"

# Or just the pricing slice for every name in the job (lighter; price
# bands + reasoning + key factors + risks).
curl https://api.namedesk.app/v1/research-jobs/job_xyz/report/pricing \
  -H "x-api-key: $ND_KEY"

# Single-domain report (most recent in your org)
curl https://api.namedesk.app/v1/report/acme.ai \
  -H "x-api-key: $ND_KEY"

# Single-domain pricing slice
curl https://api.namedesk.app/v1/domains/acme.ai/pricing \
  -H "x-api-key: $ND_KEY"

Authentication

Pass your key in either the x-api-key header or Authorization: Bearer <key> — both work, pick whichever your client prefers. Keys are tied to one organization, and that org's wallet pays for every call. The credit balance is the only throttle; there are no separate API quotas.

Quick reads vs full reads

Two tiers, two endpoints. Both submit the same domain list and return the same job shape — they differ in what each name costs and how deep the appraisal goes.

Full read (POST /v1/research-jobs). 10 credits per uncached name. The deep multi-model appraisal: three pricing bands (wholesale / aftermarket / brokered), full reasoning, key factors, risks, plus every signal we publish (frontier recall, attractor, inference, coherence, hallucination, spellability, verdict). What the dashboard search box runs by default.

Quick read (POST /v1/research-jobs/quick). Each name goes through a fast routing pass that decides whether the full appraisal is worth it. Names judged worthy run the full appraisal at 10 credits; the rest get a first-pass pricing estimate plus a one-sentence rationale at 1 credit. Useful for scanning a long candidate list cheaply — pay 10 credits only for the names that actually warrant it.

Per-domain rows on the response carry read_type: "quick" | "full" and an optional read_reason. The credits_charged field on each row is authoritative — 1 for a quick read, 10 for a full read. The job-level credits_charged sums these and tracks what your wallet actually paid.

Upgrading a quick read. If a name came back as a quick read and you want the full appraisal, call POST /v1/domains/{domain}/rerun with an empty body. That charges 10 credits, runs the full appraisal, and overwrites the quick read for future calls to /v1/domains/{domain}/pricing and the report endpoints.

Endpoints

POST/v1/research-jobs
Submit 1–100 domain names for a full read on every name. Charges 10 credits per uncached name. Returns 202 immediately; poll GET /v1/research-jobs/:id until every name is terminal. The default behaviour for the API — every name gets the deep multi-model appraisal.
Sample response
{
  "id": "9f8e7d6c5b4a39281706f5e4d3c2b1a0",
  "status": "running",
  "source": "api",
  "mode": "full",
  "submitted_at": "2026-05-06T17:00:00.000Z",
  "completed_at": null,
  "credits_charged": 0,
  "credits_balance_after": 450,
  "counts": { "total": 2, "pending": 2, "running": 0, "paid": 0, "cached": 0, "failed": 0 },
  "domains": [
    { "domain": "acme.ai", "outcome": "pending", "read_type": null, "read_reason": null, "credits_charged": 0, "report_url": null },
    { "domain": "beta.io", "outcome": "pending", "read_type": null, "read_reason": null, "credits_charged": 0, "report_url": null }
  ],
  "status_url": "https://api.namedesk.app/v1/research-jobs/9f8e7d6c5b4a39281706f5e4d3c2b1a0"
}
POST/v1/research-jobs/quick
Submit 1–100 domain names in quick mode. Each name goes through a fast routing pass: names worth the full appraisal run the full appraisal (10 credits); the rest get a quick-read pricing estimate (1 credit) with a one-sentence rationale. Returns 202 immediately with every name still pending; poll GET /v1/research-jobs/:id until terminal to see each name's read_type andread_reason. Credits are reserved at the worst case (10 per name) and the total settles down as the quick reads come back. credits_charged on the job payload is always what your wallet has actually paid.
Sample response
{
  "id": "a1b2c3d4e5f6789012345678901234ab",
  "status": "running",
  "source": "api",
  "mode": "quick",
  "submitted_at": "2026-05-12T12:00:00.000Z",
  "completed_at": null,
  "credits_charged": 0,
  "credits_balance_after": 480,
  "counts": { "total": 2, "pending": 2, "running": 0, "paid": 0, "cached": 0, "failed": 0 },
  "domains": [
    { "domain": "buyusedlaptopsonline.com", "outcome": "pending", "read_type": null, "read_reason": null, "credits_charged": 0, "report_url": null },
    { "domain": "acme.ai", "outcome": "pending", "read_type": null, "read_reason": null, "credits_charged": 0, "report_url": null }
  ],
  "status_url": "https://api.namedesk.app/v1/research-jobs/a1b2c3d4e5f6789012345678901234ab"
}
POST/v1/domains/:domain/rerun
Force a fresh appraisal on one name. Body is optional —{"quick": true} uses the same per-name decision as the quick endpoint (1 credit for a quick read, 10 credits if the routing pass judges the name worth the full appraisal); {} or empty body runs the full appraisal at 10 credits. The new pricing supersedes any prior result for the name. This is the API equivalent of the dashboard's "Run full read" upgrade button — call it to upgrade a quick read to the full appraisal.
Sample response
{
  "run_id": "f1e2d3c4b5a6978869788896f5e4d3c2",
  "domain": "buyusedlaptopsonline.com",
  "status": "succeeded",
  "mode": "quick",
  "read_type": "quick",
  "read_reason": "Long descriptive phrase with no aftermarket appeal",
  "credits_charged": 1,
  "credits_balance_after": 488
}
GET/v1/research-jobs
List your org's recent jobs, paginated. Pass?cursor=… from a previous response to fetch the next page; ?limit= defaults to 20, with a maximum of 100 (higher values are clamped to 100).
Sample response
{
  "items": [
    {
      "id": "9f8e7d6c5b4a39281706f5e4d3c2b1a0",
      "status": "completed",
      "source": "api",
      "submitted_at": "2026-05-06T17:00:00.000Z",
      "completed_at": "2026-05-06T17:01:42.000Z",
      "domain_count": 2,
      "status_url": "https://api.namedesk.app/v1/research-jobs/9f8e7d6c5b4a39281706f5e4d3c2b1a0"
    }
  ],
  "next_cursor": null
}
GET/v1/research-jobs/:id
Job status with per-domain outcomes. Safe to poll every 2–5 seconds — reads are free and uncapped. Sample below shows a full-mode job's terminal payload; a quick-mode job has the same shape with mode: "quick" and per-row read_type populated to "quick" or "full" per the routing decision.
Sample response
{
  "id": "9f8e7d6c5b4a39281706f5e4d3c2b1a0",
  "status": "completed",
  "source": "api",
  "mode": "full",
  "submitted_at": "2026-05-06T17:00:00.000Z",
  "completed_at": "2026-05-06T17:01:42.000Z",
  "credits_charged": 20,
  "credits_balance_after": 450,
  "counts": { "total": 2, "pending": 0, "running": 0, "paid": 2, "cached": 0, "failed": 0 },
  "domains": [
    {
      "domain": "acme.ai",
      "outcome": "paid",
      "read_type": "full",
      "read_reason": null,
      "credits_charged": 10,
      "report_url": "https://api.namedesk.app/v1/research-jobs/9f8e7d6c5b4a39281706f5e4d3c2b1a0/report/acme.ai"
    },
    {
      "domain": "beta.io",
      "outcome": "paid",
      "read_type": "full",
      "read_reason": null,
      "credits_charged": 10,
      "report_url": "https://api.namedesk.app/v1/research-jobs/9f8e7d6c5b4a39281706f5e4d3c2b1a0/report/beta.io"
    }
  ],
  "status_url": "https://api.namedesk.app/v1/research-jobs/9f8e7d6c5b4a39281706f5e4d3c2b1a0"
}
DELETE/v1/research-jobs/:id
Cancel a running job. Returns whatever credits were still reserved for names that hadn't finished yet — names that already came back (full reads at 10 credits, quick reads at 1) keep their charge. The response is the full job payload with credits_refunded at the top so you can see exactly how much landed back in your wallet and which names finished before the cancel. 409 if the job is already terminal.
GET/v1/research-jobs/:id/report
Every domain in the job + each one's full DomainReport (when computed). Returns { job, reports: [{ domain, job_status, report?, error? }] }. Domains in pending or running status carry only job_status; paid and cached domains carry the full report; failed domains carry a stable error code (see Error codes section in the schema doc). Customers poll this during a running job to watch reports fill in.
GET/v1/research-jobs/:id/report/pricing
Pricing-only variant of /report. Each entry carries the price band (wholesale / aftermarket / brokered), reasoning, key factors, and risks — capped at 4 KB per text field. Faster and lighter than the full report wrapper; built for the "give me a CSV of just the dollars" workflow without pulling the probes / signals / brand signals you don't need.
GET/v1/research-jobs/:id/report/:domain
The report for a single domain in the context of a specific job. Same payload as GET /v1/report/:domain.
GET/v1/report/:domain
Most-recent report for a domain your org has researched. Returns the canonical DomainReport shape — see the result schema for every field.
Sample response
{
  "schema_version": "1.0",
  "exported_at": "2026-05-07T18:34:21Z",
  "domain": {
    "name": "acme.ai",
    "sld": "acme",
    "tld": ".ai",
    "report_url": "https://namedesk.app/research/acme.ai"
  },
  "status": "complete",
  "history": {
    "first_researched_at": "2026-05-04T22:00:00.000Z",
    "last_refreshed_at": "2026-05-07T18:33:55Z"
  },
  "name_shape": {
    "char_length": 4,
    "char_length_class": "premium",
    "tld_tier": "premium",
    "pattern_class": "dictionary",
    "syllable_count": 2,
    "is_single_syllable": false,
    "is_alpha_only": true,
    "has_numbers": false,
    "has_hyphens": false,
    "is_dictionary_likely": true,
    "is_short_premium": true,
    "brandability_score": 87,
    "commercial_intent_score": 76
  },
  "verdict": {
    "call": "yes",
    "intent": "build",
    "paragraph": "Short, brand-friendly .ai with strong AI-tooling synergy...",
    "computed_at": "2026-05-07T18:33:55Z"
  },
  "pricing": {
    "wholesale":   { "low": 200,   "mid": 500,    "high": 1500 },
    "aftermarket": { "low": 5000,  "mid": 25000,  "high": 65000 },
    "brokered":    { "low": 50000, "mid": 165000, "high": 350000 },
    "confidence": "high",
    "reasoning": "acme.ai sits in the premium 4-char .ai cohort...",
    "key_factors": ["Exact 4-char dictionary on the hottest premium TLD", "..."],
    "risks": ["Buyer pool narrower than horizontal AI words"],
    "model": "claude-opus-4.7",
    "engine_version": "v5",
    "computed_at": "2026-05-07T18:33:42Z"
  },
  "ai_signals": {
    "frontier_recall": { "models_total": 4, "models_recognizing": 2, "per_model": [/* ... */] },
    "attractor": { "topics": ["..."], "samples_per_topic": 3, "target_hits_per_topic": {/* ... */} },
    "inference": { "top_industry": "automation", "industry_agreement": 4, "samples_count": 5 },
    "coherence": { "top_intent": "build", "intent_agreement": 3, "samples_count": 5 }
  },
  "brand_signals": {
    "spellability": { "target_match_type": "exact", "target_rank": 1 },
    "commercial_intent": { "score": 76, "bucket": "high" }
  }
}
GET/v1/domains/:domain/pricing
Just the pricing block for a domain you've already researched. Free, fast, and uncapped — built for scanning a portfolio.
Sample response
{
  "wholesale":   { "low": 100, "mid": 250, "high": 500 },
  "aftermarket": { "low": 500, "mid": 1500, "high": 4000 },
  "brokered":    { "low": 4000, "mid": 12000, "high": 35000 },
  "confidence": "medium",
  "reasoning": "...",
  "model": "claude-opus-4.7",
  "engine_version": "v5",
  "computed_at": "2026-05-06T17:01:42.000Z"
}
GET/v1/wallet
Current credit balance for the bound organization.
Sample response
{ "credits": 47 }

Idempotency

Send Idempotency-Key: <your-uuid> onPOST /v1/research-jobs to make safe retries. Same key + same body within 24 hours replays the original response. Same key with a different body returns409 idempotency_key_reused.

Errors

Every error response is JSON with error (a stable machine-readable code), message (a human-friendly sentence), and request_id for support correlation.

codehttpwhen
unauthorized401Key missing, malformed, revoked, expired, or not bound to an org.
cookie_session_required403API key used on a key-management endpoint (use the dashboard).
invalid_json400Request body is not valid JSON.
invalid_body400Body fails validation (missing domains, wrong types).
invalid_domain400A domain in the array (or path) fails normalization.
invalid_cursor400Pagination cursor is malformed.
too_many_domains413More than 100 domains per submission.
payload_too_large413Request body exceeds the 256KB cap.
insufficient_credits402Wallet balance < domains needed. Top up or use cache.
idempotency_key_reused409Same key replayed with a different request body within 24h.
idempotency_request_in_progress409Same key is currently in flight; poll the original job.
invalid_idempotency_key400Idempotency-Key header is too long or has invalid characters.
job_not_found404Job id is unknown or belongs to a different org.
already_terminal409DELETE on a job that already finished or was cancelled.
not_yet_researched404Domain has never been researched by your org.
pricing_unavailable404Domain was researched but the pricing step has no row.
domain_not_in_job404Domain was not part of the referenced job.
report_unavailable404Job referenced a domain whose research record is no longer available.

Result schema

Every field on a domain report — verdict,pricing, marketplace,signals — is documented in the result schema reference.

Versioning

Your client should ignore unknown fields rather than fail on them — we add fields without bumping the version. If we ever need to remove or rename a field, that lands as/v2/ with advance notice; /v1/keeps working.

Rate limits

None in v1 beyond the credit gate. Read endpoints (status polls, pricing reads, wallet) are uncapped — call them as often as you need. We'll add per-key limits if a customer's traffic forces our hand; advance notice via email if so.