Prompt Universe API
Build a representative, versioned query universe for a category and market: the set of questions people ask AI. You start an asynchronous build, poll it, lock an immutable version, then read its manifest and queries by intent. Prompt Universe decides what to ask; running those queries and measuring visibility is a separate step.
Overview
The API is small and workspace-scoped. Every response is wrapped in a { data, meta } envelope; errors return a top level error object (see Errors). Builds run asynchronously on a worker, so creation returns immediately and you poll for progress.
Base URL
https://api.universe.searchestra.com
Conventions
- All request and response bodies are
application/json. - Successful responses carry a top level
datafield, optionally withmeta. Errors carry a top levelerrorfield. - Every
/v1/*endpoint requires theX-API-Keyheader./healthzand/metricsdo not. - Identifiers are UUIDs; versions are semantic (
MAJOR.MINOR.PATCH, for example1.0.0). - Timestamps are UTC, ISO 8601 (for example
2026-08-19T13:47:09Z).
Lifecycle
A universe moves through a fixed lifecycle. Each build produces candidate queries, selects a representative subset with full intent coverage, and, once you are satisfied, gets locked into an immutable version.
create universe ──▶ build (async pipeline) ──▶ status: ready
│ │
│ lock version ──▶ 1.0.0 (immutable)
│ │
refresh ◀─────────── new build ◀── scheduled_review │ structural_market_change
▼
read manifest + queries (by intent)A locked version never changes. To evolve a universe you trigger a refresh, which starts a new build that locks a new version, preserving the old one for historical comparison.
Quickstart
Create a universe, wait for the build, lock it, then read the queries. Replace un_your_key with the key issued to your workspace (see Authentication).
1 · Start a build
curl -X POST https://api.universe.searchestra.com/v1/universes \
-H "X-API-Key: un_your_key" \
-H "Content-Type: application/json" \
-d '{
"category": "project management software",
"market": "TR",
"language": "tr",
"requested_query_count": 120
}'{
"data": {
"id": "b3f1c2a4-...",
"universe_id": "9a71e0d2-...",
"status": "queued",
"requested_query_count": 120,
"created_at": "2026-08-19T13:47:09Z"
}
}2 · Poll the build
curl https://api.universe.searchestra.com/v1/universes/9a71e0d2-.../builds/b3f1c2a4-.../status \ -H "X-API-Key: un_your_key"
{
"data": {
"status": "running",
"progress": { "stage": 4, "total_stages": 7, "percentage": 57 },
"queries_target": 120,
"queries_selected": 0,
"candidates_generated": 210
}
}3 · Lock the version
curl -X POST https://api.universe.searchestra.com/v1/universes/9a71e0d2-.../builds/b3f1c2a4-.../lock \ -H "X-API-Key: un_your_key"
{
"data": {
"version": "1.0.0",
"build_id": "b3f1c2a4-...",
"universe_id": "9a71e0d2-...",
"query_count": 120,
"taxonomy_version": "1.0.0",
"baseline": { "reset_required": false, "compatible_with_previous": null },
"locked_at": "2026-08-19T13:59:41Z"
}
}4 · Read the queries
curl "https://api.universe.searchestra.com/v1/universes/9a71e0d2-.../versions/1.0.0/queries?intent=recommendation" \ -H "X-API-Key: un_your_key"
Authentication
Every /v1/* request must carry an API key in the X-API-Key header. Keys begin with the un_ prefix and identify a workspace, not a user.
X-API-Key: un_your_key
Keys are provisioned per workspace by the workspaceinit command. The raw value is shown once, at creation, and cannot be recovered; the server stores only a hash. A workspace can hold several keys, and revoking one does not affect the others.
GET /v1/me with your key to confirm which workspace it resolves to. A missing or revoked key returns 401 unauthorized.Create a universe
Creates a new universe and starts its first build asynchronously. Returns 201 with the build; the pipeline continues on a worker. Poll build status for progress.
Request body
| Field | Type | Notes |
|---|---|---|
| category required | string | The category to sample, for example project management software. |
| market required | string | Market / geography code, for example TR. |
| language required | string | Language the queries are written in, for example tr. |
| requested_query_count required | integer | Target size of the locked universe. Typically in the normal range (100–150). |
| control_plane optional | object | Sampling controls: intent_policy (balanced or observed_weighted), generation_budget, auto_lock, evidence_dataset_ids. |
| validation_context optional | object | Brand context used only to exclude first-party bias, never to steer generation. Carries brand_name, aliases, domains. |
validation_context.brand_name is used to keep the universe brand-neutral, not to generate flattering queries. A target brand is never fed into generation.Build status
Returns the build's progress. Poll this until status reaches a terminal state (for example ready or failed).
{
"data": {
"status": "ready",
"progress": { "stage": 7, "total_stages": 7, "percentage": 100 },
"queries_target": 120,
"queries_selected": 120,
"candidates_generated": 240
}
}To inspect the universe and its builds together, call GET /v1/universes/{id}. A per-build health summary is available at .../builds/{build_id}/health.
Lock a version
Freezes a ready build into an immutable, semantically versioned universe. After locking, the version's manifest and queries can never change.
If a universe has exactly one active build, the alias POST /v1/universes/{id}/lock locks it without a build id. Locking a build that is not ready, or a universe with multiple active builds via the alias, returns 409 conflict.
baseline block. When reset_required is true, downstream measurement must start a fresh baseline from this version and must not merge it with earlier trends. See Versioning & baseline.Get the manifest
Returns the version's Universe Manifest: the immutable description of how this universe was sampled — taxonomy, intent distribution, coverage and provenance. This is the reference a measurement run pins to.
The alias GET /v1/universes/{id}/manifest returns the manifest of the latest locked version. Intent coverage is also summarised at GET /v1/universes/{id}/coverage.
List queries
Returns the queries of a locked version. Every query carries complete, stable intent metadata, so you can segment measurement per intent.
Query parameters
| Name | Type | Notes |
|---|---|---|
| intent optional | string | Filter by one of informational, comparison, recommendation, transactional. See Intents. |
The alias GET /v1/universes/{id}/queries lists queries from the latest locked version.
Refresh a universe
Triggers a new build to evolve the universe. The old version stays locked and comparable; the new build locks a new version.
| Field | Type | Notes |
|---|---|---|
| refresh_trigger required | string | scheduled_review (a periodic review) or structural_market_change (the category itself shifted). |
Evidence datasets
Optional. Create a dataset of observed demand (real questions seen in the wild), then ingest items into it. When a build uses intent_policy: observed_weighted, these datasets ground the sampling so proportions reflect what the market actually asks.
curl -X POST https://api.universe.searchestra.com/v1/evidence/datasets \
-H "X-API-Key: un_your_key" \
-H "Content-Type: application/json" \
-d '{ "name": "TR PM search logs Q3", "source_summary": "anonymised site search" }'Ingest items with POST /v1/evidence/datasets/{id}/items, then reference the dataset id in a universe's control_plane.evidence_dataset_ids.
Intents
Every query is classified into exactly one intent. Coverage across all four is planned on purpose, not left to chance.
| Intent | What the asker wants |
|---|---|
informational | To understand the category or a concept ("how does a kanban board work?"). |
comparison | To weigh options against each other ("X vs Y for a small team"). |
recommendation | To be pointed to a good choice ("best tool for a 10-person startup"). |
transactional | To act: pricing, plans, limits ("cheapest plan with SSO"). |
Versioning & baseline
Versions are immutable and semantic. A measurement run references a specific version, which guarantees historical comparability: the exact question set behind last quarter's numbers still exists, unchanged.
- Immutable: once locked, a version's manifest and queries never change.
- Baseline disclosure: a lock reports whether it is
compatible_with_previous. Whenreset_requiredistrue, measurement must begin a new baseline and not merge across the boundary. - Refresh: evolving a universe means a new version, never an edit to an old one.
Errors
Errors return an error object with a code, a message and optional details.
{ "error": { "code": "unauthorized", "message": "X-API-Key required." } }| Status | code | When |
|---|---|---|
| 400 | validation_failed | Missing or malformed field (for example no category, or an unknown refresh_trigger). |
| 401 | unauthorized | Missing, invalid or revoked API key. |
| 403 | forbidden | The key's workspace may not perform this action. |
| 404 | not_found | Unknown universe, build or version. |
| 409 | conflict / duplicate | Locking a build that is not lockable, or a conflicting concurrent change. |
| 422 | quota_exceeded | Generation budget (LLM calls or cost) for this workspace is spent. |
| 429 | rate_limited | Too many requests in a short window. Retry shortly. |
| 503 | unavailable | A provider is not configured or temporarily unavailable. |
| 500 | internal | Unexpected internal failure. The real cause is only in server logs. |
Budgets & limits
Because a build calls LLMs, each build runs under explicit guards, so a universe cannot cost or run without bound.
| Guard | Scope | On limit |
|---|---|---|
| Max LLM calls per build | per build | build stops; 422 quota_exceeded on create |
| Max cost (USD) per build | per build | cost guard halts the pipeline |
| Max duration | per build | build is reaped and marked failed |
| Requests per minute | per API key | 429 rate_limited |
| First-party contribution cap | per build | own-domain evidence is capped, to prevent self-bias |
Budgets scale with your plan. Exact defaults (query count, cost, duration) are set per deployment.