prompterdocs

prompter API

Ask one of nine AI platforms a single question and get the raw answer back, with native citations where the platform provides them. prompter is stateless: every call is a real, live upstream request.

Overview

prompter exposes a small REST API. You send a prompt and a platform, prompter calls that platform, and returns the unmodified answer. It never summarizes or invents data: if a platform cannot answer, the response says so.

Base URL

base url
https://api.prompter.searchestra.com

Conventions

  • All request and response bodies are application/json.
  • Successful reads return a top level data field. Errors return a top level error field (see Errors).
  • Every /v1/* endpoint requires the X-API-Key header. /healthz does not.
  • Timestamps are UTC, ISO 8601 (for example 2026-08-16T13:47:09Z).
Honesty principle. On 4xx the error message is real and specific. On 5xx (500 and 503) the message is always masked to a fixed string; the real cause is only in prompter's server logs.

Quickstart

Send your first query. Replace prsk_your_key with a key that carries the run permission (see Authentication).

requestcurl
curl -X POST https://api.prompter.searchestra.com/v1/collect \
  -H "X-API-Key: prsk_your_key" \
  -H "Content-Type: application/json" \
  -d '{
    "platform": "perplexity",
    "promptText": "which dishwasher detergent is best, briefly",
    "countryCode": "tr",
    "language": "tr"
  }'
response200
{
  "data": {
    "rawText": "Finish and Fairy are among the most preferred...",
    "rawHtml": "",
    "citations": [
      { "url": "https://example.com/review", "title": "Best detergent", "position": 1 }
    ],
    "textLinks": [],
    "collectedAt": "2026-08-16T13:47:09Z"
  }
}

Authentication

Every /v1/* request must carry an API key in the X-API-Key header. There are two kinds of key, each with its own prefix.

PrefixTypeBound toUse for
prsk_servicea resource (tenant, workspace or project)machine-to-machine access. Survives even if the person who created it leaves.
pruk_usera person's membershipscripts a person runs as themselves. Stops working the moment that person loses access.
header
X-API-Key: prsk_your_key

Scope

Every key is scoped to exactly one resource: a tenant, a workspace or a project. The key can only act within that resource. A project key cannot touch another project; a workspace key cannot reach outside its workspace.

Permissions

Each key carries an explicit permission set. Three permissions exist today; each endpoint states which one it needs.

PermissionGrants
runExecute a live query (POST /v1/collect). A key without it gets 403 forbidden.
readRead stored resources, such as listing projects.
manageCreate, edit and delete projects within the key's scope.

For a user key, permissions are additionally capped by that person's role in the scope. Granting manage to a viewer still yields a key that cannot manage, and run to a viewer yields a key that cannot run.

The server stores only a SHA-256 hash of the key. The raw value is shown once, at creation, and cannot be recovered. If a key is lost, issue a new one. A resource can hold several keys; revoking one does not affect the others. Keys may also carry an optional expiry.

Getting a key. You create keys yourself from the panel: service keys from a project's or workspace's settings, personal keys from your account. A missing, revoked or expired key returns 401 unauthorized; a key without the needed permission returns 403 forbidden.

Query a platform

POST/v1/collect

The one-call shortcut: ask a single platform one question, under a project, and get the stored run back. Internally it saves the prompt (reusing an existing one with the same text in the project) and runs it, so the answer is persisted with its real cost, attributed to the project. It is the convenience wrapper over save a prompt then run it.

Project and permission. Every call resolves a project: a project-scoped key takes it from its scope (omit projectId); a tenant/workspace key or a panel session must send projectId, and the key's scope must cover it (out of scope → 404). Needs the run permission. Country and language come from the project, not the request.

Request body

FieldTypeNotes
platform requiredstringOne of the nine codes (see Platforms).
promptText requiredstringThe question to ask. Trimmed; must not be empty.
projectId conditionalstringRequired unless the key is project-scoped. The project the run is stored under; the key's scope must cover it.
requestcurl
curl -X POST https://api.prompter.searchestra.com/v1/collect \
  -H "X-API-Key: prsk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "platform": "perplexity", "promptText": "which dishwasher detergent is best" }'

Response data (a single run)

Returns one stored run: id, promptId, projectId, platform, status (ok/error), answerText, citations, textLinks, and the real cost fields (costEstimate, model, inputTokens, outputTokens).

response201
{
  "data": {
    "id": "run_…", "promptId": "prm_…", "projectId": "prj_…", "platform": "perplexity",
    "status": "ok", "answerText": "Finish and Fairy are among the most preferred…",
    "citations": [ { "url": "https://example.com/review", "title": "Best detergent", "position": 1 } ],
    "textLinks": [], "costEstimate": 0.0041, "model": "sonar", "createdAt": "2026-08-31T13:47:09Z"
  }
}
Citations vs textLinks. citations are the provider's native sources. textLinks are links the model wrote into its prose. They use different ordering systems, so relate them by URL, not by position.

List platforms

GET/v1/platforms

Returns the fixed list of registered platforms. A platform whose credentials are not configured still appears here; it only fails on a real /v1/collect call. Fetch this once and cache it on your side.

response200
{
  "data": [
    { "code": "chatgpt", "unsupportedCountries": [] },
    { "code": "perplexity", "unsupportedCountries": null }
  ]
}

unsupportedCountries is either an empty array or null; both mean "no restriction". Treat them the same way.

Health check

GET/healthz

Liveness probe for container orchestration. No authentication. Returns 200 when the service is up.

Projects

A project pairs your brand with a market (country and language); prompts and runs live under it. These endpoints let a machine key manage projects, so you can provision brands from your own systems. They accept the same panel session (JWT) or an API key.

Scope decides reach. A tenant key manages any project in the tenant. A workspace key is confined to its workspace. A project key can only edit or delete its own project; it cannot list or create. Acting outside the scope returns 404 not_found (the resource is hidden, not merely refused).

List projects

GET/v1/workspaces/{workspaceId}/projects

Lists the projects in a workspace. Needs the read permission and a key whose scope covers the workspace (tenant or that workspace).

response200
{
  "data": [
    { "id": "prj_…", "workspaceId": "wsp_…", "name": "Acme", "slug": "acme", "countryCode": "tr", "language": "tr" }
  ]
}

Create a project

POST/v1/workspaces/{workspaceId}/projects

Creates a project under a workspace. Needs the manage permission and a tenant- or workspace-scoped key (a project-scoped key cannot create, and gets 403 forbidden).

Request body

FieldTypeNotes
name requiredstringDisplay name. The slug is derived from it and made unique in the workspace.
countryCode requiredstringThe market the brand is measured in. One of tr, us, gb, de, fr, es, in (uk is accepted as gb). There is no default country.
language requiredstringLanguage code, for example tr, en or pt-br. aimode and aio query Google in this language.
requestcurl
curl -X POST https://api.prompter.searchestra.com/v1/workspaces/wsp_123/projects \
  -H "X-API-Key: prsk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Acme", "countryCode": "tr", "language": "tr" }'
response201
{
  "data": { "id": "prj_…", "workspaceId": "wsp_123", "name": "Acme", "slug": "acme", "countryCode": "tr", "language": "tr" }
}

Update a project

PATCH/v1/projects/{projectId}

Renames a project or changes its market. Needs the manage permission and a key whose scope covers the project (tenant, its workspace, or that project). Same body fields as create, all optional: send only what changes, omitted fields keep their value. A field you send is validated like on create, so an empty string is rejected rather than clearing the field.

The market (countryCode and language) is locked once the project has its first run, so earlier results stay tied to one market. Changing it after that returns 409 conflict; to measure another market, create a new project.

Delete a project

DELETE/v1/projects/{projectId}

Deletes a project and everything under it (prompts, runs and scoped keys cascade). Needs the manage permission and a key whose scope covers the project. This cannot be undone.

Prompts and runs

These endpoints are the persisted run channel: you save a prompt under a project, run it across platforms, and the answer is stored with its real cost, attributed to the project. This is the same surface the panel uses, so a machine key does exactly what a person does in the app. It is the counterpart to /v1/collect, which is stateless and records nothing.

Scope and permission. Every call resolves a target project (from the path, or from the prompt) and checks the key's scope covers it (tenant → any; workspace → its projects; project → itself). Reads need read, running needs run, creating or deleting a prompt needs manage. Out of scope → 404; missing permission → 403.

List prompts

GET/v1/projects/{projectId}/prompts

Lists the saved prompts in a project. Needs read.

Create a prompt

POST/v1/projects/{projectId}/prompts

Saves a prompt under a project. Needs manage.

FieldTypeNotes
promptText requiredstringThe question to store. Trimmed; must not be empty.
response201
{ "data": { "id": "prm_…", "projectId": "prj_…", "text": "best crm for startups", "createdAt": "2026-08-31T12:00:00Z" } }

Run a prompt

POST/v1/prompts/{promptId}/run

Runs a saved prompt across the chosen platforms as live upstream requests. The project (and its country/language) is taken from the prompt. Each platform is independent: one failing does not stop the others. Every result is stored as a run with its real cost. Needs run.

FieldTypeNotes
platforms requiredstring[]One or more platform codes (see Platforms). Deduplicated; at least one.
requestcurl
curl -X POST https://api.prompter.searchestra.com/v1/prompts/prm_123/run \
  -H "X-API-Key: prsk_your_key" \
  -H "Content-Type: application/json" \
  -d '{ "platforms": ["chatgpt", "perplexity"] }'
response201
{
  "data": [
    { "id": "run_…", "promptId": "prm_123", "platform": "chatgpt", "status": "ok",
      "answerText": "…", "citations": [], "textLinks": [],
      "costEstimate": 0.0037, "model": "gpt-4o", "inputTokens": 812, "outputTokens": 143,
      "createdAt": "2026-08-31T12:00:05Z" }
  ]
}

status is ok or error per platform (partial success). A failed platform carries errorMessage and zero cost.

List a prompt's runs

GET/v1/prompts/{promptId}/runs

Returns the run history for a prompt, newest first. Needs read.

Project usage

GET/v1/projects/{projectId}/usage

Aggregates the project's real cost from stored runs, total and per platform. Needs read. (GET /v1/projects/{projectId}/overview returns the same usage plus the prompt count and recent runs.)

response200
{
  "data": { "totalRuns": 42, "totalCostUsd": 0.1873,
    "byPlatform": [ { "platform": "chatgpt", "runs": 20, "costUsd": 0.09 } ] }
}

Platforms

Nine platforms have a real integration. citations are native only on the three sourced platforms; the six LLM platforms surface links through textLinks instead.

CodeProviderCitations
chatgptOpenAI Chat CompletionstextLinks only
claudeAnthropic MessagestextLinks only
geminiGoogle Generative LanguagetextLinks only
deepseekDeepSeek Chat CompletionstextLinks only
grokxAI Chat CompletionstextLinks only
metaaiMeta Model APItextLinks only
perplexityPerplexity Sonarnative
aimodeGoogle AI Mode (DataForSEO)native
aioGoogle AI Overviews (DataForSEO)native

Errors

Errors return an error object with a code and a message.

error envelope
{ "error": { "code": "unauthorized", "message": "X-API-Key required." } }
StatuscodeWhen
400validation_failedEmpty promptText, or the country is not supported.
401unauthorizedMissing, invalid, revoked or expired API key.
403forbiddenThe key is valid but lacks the permission this call needs (run, read or manage), or is a project-scoped key attempting a workspace-level action.
404not_foundUnknown platform code, or a workspace/project outside the key's scope (hidden rather than refused).
422quota_exceededDaily request quota or cost budget for this tenant and platform is spent. Try again tomorrow.
429rate_limitedPer-key requests per minute or concurrency limit exceeded. Retry shortly.
500internalUpstream or internal failure. Message is masked.
503unavailablePlatform not configured, or its circuit breaker is open. Message is masked.
422 vs 429. 422 means "try again tomorrow" (a daily budget is spent). 429 means "try again shortly" (a short window limit). Do not confuse them.

Rate limits and quotas

prompter applies five independent guards. The rate and concurrency guards apply only to the run endpoints (POST /v1/collect and POST /v1/prompts/{promptId}/run) that spend real money; listing, creating and reading are exempt.

GuardScopeOn limit
Requests per minuteper identity (API key, or panel user)429 rate_limited
Concurrent requestsper identity (API key, or panel user)429 rate_limited
Daily request quotaper tenant and platform422 quota_exceeded
Daily cost budgetper tenant and platform422 quota_exceeded
Circuit breakerper platform, all tenants503 unavailable

The rate and concurrency guards are keyed by identity: a machine call by its API key, a panel call by its user. One key being throttled does not affect the tenant's other keys or users. Limits scale with your plan. The circuit breaker opens after a platform fails repeatedly and closes automatically after a cooldown.