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
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"
}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"
}{"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
}?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
}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"
}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.{ 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./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/report/:domain.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" }
}
}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"
}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.
| code | http | when |
|---|---|---|
| unauthorized | 401 | Key missing, malformed, revoked, expired, or not bound to an org. |
| cookie_session_required | 403 | API key used on a key-management endpoint (use the dashboard). |
| invalid_json | 400 | Request body is not valid JSON. |
| invalid_body | 400 | Body fails validation (missing domains, wrong types). |
| invalid_domain | 400 | A domain in the array (or path) fails normalization. |
| invalid_cursor | 400 | Pagination cursor is malformed. |
| too_many_domains | 413 | More than 100 domains per submission. |
| payload_too_large | 413 | Request body exceeds the 256KB cap. |
| insufficient_credits | 402 | Wallet balance < domains needed. Top up or use cache. |
| idempotency_key_reused | 409 | Same key replayed with a different request body within 24h. |
| idempotency_request_in_progress | 409 | Same key is currently in flight; poll the original job. |
| invalid_idempotency_key | 400 | Idempotency-Key header is too long or has invalid characters. |
| job_not_found | 404 | Job id is unknown or belongs to a different org. |
| already_terminal | 409 | DELETE on a job that already finished or was cancelled. |
| not_yet_researched | 404 | Domain has never been researched by your org. |
| pricing_unavailable | 404 | Domain was researched but the pricing step has no row. |
| domain_not_in_job | 404 | Domain was not part of the referenced job. |
| report_unavailable | 404 | Job 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.