{"openapi":"3.1.0","info":{"title":"Asked Thrice public API","version":"1.0.0","summary":"Measure whether AI assistants recommend a brand, from live model answers.","description":"The public HTTP surface of askedthrice.com. Every operation listed here answers without an account, an API key or a session cookie: these are the same endpoints the site's own forms and the Asked Thrice MCP server call.\n\n**What this is not.** There is no paid, authenticated or SLA-backed API tier. Every measurement spends real money at three model providers, so anonymous callers share strict per-caller limits and one monthly provider budget across every operation below. At the cap, `checkBrandRecognition` and `runRepeatedBuyerQuestion` answer 503 and `startVisibilityAudit` degrades to a single run per provider. Plan for 429 and 503: both are ordinary states here, and both carry a `code` and a `hint` saying what to do.\n\n**If your client speaks MCP, use the MCP server instead of this API**: https://mcp.askedthrice.com/mcp (streamable HTTP, no signup, one free measurement per domain every 30 days). It wraps `startVisibilityAudit` and the buyer-question map into two tools with polling handled for you. Discovery manifest: https://askedthrice.com/.well-known/mcp.json\n\n**Errors are always JSON**, including 404s for paths that do not exist. Every error body carries `error` (human sentence), `code` (stable identifier to branch on), `hint` (what to do next) and `docs`.\n\nHuman documentation: https://askedthrice.com/developers · Methodology: https://askedthrice.com/methodology","contact":{"name":"Asked Thrice support","email":"jorgenahuelsoria@gmail.com","url":"https://askedthrice.com/contact"},"termsOfService":"https://askedthrice.com/terms","license":{"name":"Proprietary — free tier usable under the terms of service","url":"https://askedthrice.com/terms"}},"servers":[{"url":"https://askedthrice.com","description":"Production"}],"security":[],"externalDocs":{"description":"Asked Thrice developer and agent resources","url":"https://askedthrice.com/developers"},"tags":[{"name":"Measurement","description":"Ask real model APIs a buyer question and report what they answered. These operations cost provider money per call and are the ones under a budget ceiling."},{"name":"Embeds","description":"Read-only artifacts minted from a report, meant to be served on someone else's site."}],"paths":{"/api/tools/repeat":{"post":{"operationId":"runRepeatedBuyerQuestion","summary":"Ask one buyer question five times and report how much the answer moved","description":"Runs the same buyer question 5 times against one model, with temperature 0 and no web search, and returns which brands were named in every run, which appeared only once, and the Jaccard overlap between run pairs.\n\nThis is the cheapest way to see the core thesis: an AI recommendation is a distribution, not a fact. Free, no signup. Use it when you want evidence about a single question rather than a full brand audit.\n\nRate limit: 3 calls per hour per IP, plus a shared daily cap across all callers. Both answer 429 with `Retry-After`.","tags":["Measurement"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RepeatRequest"},"examples":{"passwordManagers":{"summary":"A category question with an explicit model","value":{"question":"What is the best password manager for a small team?","provider":"openai"}}}}}},"responses":{"200":{"description":"The five runs, the brands each one named, and the overlap metrics.","headers":{"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Calls left in the current window for this caller."},"X-RateLimit-Reset":{"schema":{"type":"string","format":"date-time"},"description":"When the current window resets."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RepeatResponse"}}}},"400":{"description":"The question is missing, too long, or the provider is not one of the accepted values. Codes: `bad_body`, `empty_question`, `question_too_long`, `bad_provider`, `provider_unavailable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Per-IP hourly limit or the shared daily cap. Codes: `rate_limited`, `daily_cap`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying. Always present on 429."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The model is not configured, or the monthly provider budget is used up. Codes: `budget`, `provider_unavailable`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/check":{"post":{"operationId":"checkBrandRecognition","summary":"Ask one model whether it knows a brand and whether it names it unprompted","description":"A single-model, single-pass verdict: does the model recognise the brand, does it name it in a blind list for the category (a list built from a prompt that never mentions the brand), and would it recommend it.\n\nCheaper and shallower than `startVisibilityAudit`: one provider, one run, no site crawl, no publishing plan. Use it for a fast yes/no, not as evidence.\n\nRate limit: 6 calls per hour and 20 per day per IP.","tags":["Measurement"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckRequest"},"examples":{"withCategory":{"summary":"Brand plus the category to build the blind list from","value":{"brand":"Asked Thrice","category":"AI visibility tools"}}}}}},"responses":{"200":{"description":"The verdict, the blind list, and where the brand landed in it.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CheckResponse"}}}},"400":{"description":"`brand` is missing or empty. Code: `missing_brand`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Per-IP hourly or daily limit. Code: `rate_limited`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying. Always present on 429."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The checker is paused: not configured, provider overloaded, or the monthly budget is used up. Codes: `not_configured`, `provider_paused`, `provider_overloaded`, `budget`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying. Always present on 429."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/audit":{"post":{"operationId":"startVisibilityAudit","summary":"Run a free AI visibility snapshot for one brand","description":"The full free snapshot, and the operation behind the form on the home page. Crawls the site for evidence, classifies the vertical, then asks several model APIs about the brand, repeating each prompt to report spread instead of one lucky run. Returns per-provider findings, a score, the competitors named instead, and concrete next steps.\n\nTakes 15 to 60 seconds: it is a synchronous call that waits for live model answers. Set a client timeout of at least 90 seconds.\n\nThe response carries an `id`. Pass it to `startBuyerQuestionMap` to run the buyer-question map, which is the part with the raw answers.\n\nRate limit: 6 calls per hour and 40 per day per caller. Past the monthly provider budget this operation does not stop, it degrades: each provider is queried once instead of several times, so the snapshot still works and reports less confidence. The other free endpoints answer 503 at that point.","tags":["Measurement"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditRequest"},"examples":{"minimal":{"summary":"The three required fields","value":{"brand":"Asked Thrice","website":"askedthrice.com","category":"AI visibility tools"}},"spanishMarket":{"summary":"Measured in Spanish, for an Argentine market","value":{"brand":"Asked Thrice","website":"askedthrice.com","category":"herramientas de visibilidad en IA","location":"Argentina","promptLanguage":"es"}}}}}},"responses":{"200":{"description":"The snapshot: score, per-provider findings, and the audit id to follow up with.","headers":{"X-RateLimit-Remaining":{"schema":{"type":"integer"},"description":"Calls left in the current window for this caller."},"X-RateLimit-Reset":{"schema":{"type":"string","format":"date-time"},"description":"When the current window resets."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditResponse"}}}},"400":{"description":"Validation failed. Codes: `invalid_body`, `missing_required`, `invalid_website`, `invalid_email`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Per-caller hourly or daily limit. Code: `rate_limited`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying. Always present on 429."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"The audit could not be completed. Code: `server_error`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"The audit ran but the database did not answer when saving it, even after one retry. Code: `store_unavailable`. Retry in a minute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/audit/{auditId}/fanout-map":{"parameters":[{"name":"auditId","in":"path","required":true,"description":"The `id` returned by `startVisibilityAudit`.","schema":{"type":"string","format":"uuid"}}],"post":{"operationId":"startBuyerQuestionMap","summary":"Ask the audit's buyer questions live and store the answers","description":"Step two of a measurement. Takes the buyer questions the audit generated and asks each one, live, to each configured provider, then stores the grid of answers.\n\nIdempotent: if the map already exists it comes back as-is with `cached: true`, without spending provider money again. The first run of each audit is free and needs no account; re-running an existing map costs one credit and requires a signed-in owner.\n\nTakes up to 60 seconds. Poll `getBuyerQuestionMap` if you would rather not hold the connection.\n\nRate limit: 6 calls per 10 minutes per caller.","tags":["Measurement"],"security":[{},{"sessionCookie":[]}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FanoutMapRequest"}}}},"responses":{"200":{"description":"The map, either freshly measured or the stored one.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FanoutMapResponse"}}}},"401":{"description":"Re-running an existing map needs a signed-in owner. Code: `unauthorized`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"No audit with that id. Code: `audit_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"That audit produced no buyer questions to ask. Code: `no_fanout`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Per-caller limit. Code: `rate_limited`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying. Always present on 429."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"operationId":"getBuyerQuestionMap","summary":"Read the buyer-question map of an audit","description":"Returns the stored map, or a running state if the measurement has not finished. Poll every few seconds while `map` is absent. Never spends provider money.","tags":["Measurement"],"responses":{"200":{"description":"The map if it is ready; otherwise a response with no `map`, meaning still running.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FanoutMapResponse"}}}},"404":{"description":"No audit with that id. Code: `audit_not_found`.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Per-caller limit. Code: `rate_limited`.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait before retrying. Always present on 429."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/badge/{token}":{"get":{"operationId":"getVisibilityBadge","summary":"Render the AI visibility badge as an SVG","description":"An SVG stating that the brand was measured, meant to be embedded on the brand's own site. The token is minted from a report; there is no way to mint one from this API.\n\nFalls back to a re-check state 60 days after the last run, so a stale badge never claims a fresh measurement. Cached for a day.","tags":["Embeds"],"parameters":[{"name":"token","in":"path","required":true,"description":"Badge token, minted from a report at /reports/{id}.","schema":{"type":"string"}}],"responses":{"200":{"description":"The badge.","content":{"image/svg+xml":{"schema":{"type":"string"}}}},"404":{"description":"Unknown token. Still an SVG, so an embed never renders a broken image.","content":{"image/svg+xml":{"schema":{"type":"string"}}}}}}}},"components":{"securitySchemes":{"sessionCookie":{"type":"apiKey","in":"cookie","name":"sb-access-token","description":"The session cookie set by signing in on the site. There is no way to obtain one from this API, and no operation other than re-running an existing map uses it."}},"schemas":{"ApiError":{"type":"object","title":"ApiError","description":"The shape of every error from this API, including 404s for paths that do not exist. Branch on `code`, show `error`, act on `hint`.","required":["error","code","hint","docs"],"properties":{"error":{"type":"string","description":"One sentence, safe to show to a person."},"code":{"type":"string","description":"Stable identifier for this failure. Branch on this one; the wording of `error` can change.","examples":["rate_limited","invalid_website","budget","endpoint_not_found"]},"hint":{"type":"string","description":"What to do next: wait, fix a field, or use another endpoint."},"docs":{"type":"string","format":"uri","description":"Human documentation for this API."}}},"RepeatRequest":{"type":"object","title":"RepeatRequest","required":["question"],"properties":{"question":{"type":"string","minLength":1,"maxLength":200,"description":"The buyer question to ask, exactly as a buyer would type it. Asked verbatim, with no system prompt."},"provider":{"type":"string","enum":["openai","gemini","anthropic"],"default":"openai","description":"Which model family answers. Not every provider is enabled at all times."}}},"RepeatResponse":{"type":"object","title":"RepeatResponse","required":["question","provider","model","runs","metrics","runsRequested"],"properties":{"question":{"type":"string","description":"The question as it was asked, whitespace normalised."},"provider":{"type":"string","enum":["openai","gemini","anthropic"]},"model":{"type":"string","description":"The exact model id that answered."},"runsRequested":{"type":"integer","description":"How many runs were attempted (5)."},"runs":{"type":"array","description":"One entry per run, in order, with the raw answer text.","items":{"$ref":"#/components/schemas/RepeatRun"}},"metrics":{"$ref":"#/components/schemas/RepeatMetrics"}}},"RepeatRun":{"type":"object","title":"RepeatRun","required":["ok","brands"],"properties":{"ok":{"type":"boolean","description":"False when that run failed; its brands are then empty."},"brands":{"type":"array","items":{"type":"string"},"description":"Brands named in this run, deduplicated."},"answer":{"type":["string","null"],"description":"The raw answer, or null when the run failed."},"error":{"type":"string","description":"Present only on a failed run."}}},"RepeatMetrics":{"type":"object","title":"RepeatMetrics","description":"The repeatability numbers, computed the same way as the published research.","required":["okRuns","comparable","pairs","sameSetPairs","union","inAll","once","perRun"],"properties":{"okRuns":{"type":"integer","description":"Runs that returned an answer."},"comparable":{"type":"boolean","description":"True when there are at least two runs to compare."},"pairs":{"type":"integer","description":"Run pairs compared."},"sameSetPairs":{"type":"integer","description":"Pairs that named exactly the same set of brands."},"jaccardMean":{"type":["number","null"],"description":"Mean Jaccard overlap across pairs, 0 to 1. Null when there are no pairs."},"union":{"type":"integer","description":"Distinct brands named across all runs."},"inAll":{"type":"array","items":{"type":"string"},"description":"Brands named in every successful run."},"once":{"type":"array","items":{"type":"string"},"description":"Brands named in exactly one run."},"perRun":{"type":"array","items":{"type":"array","items":{"type":"string"}},"description":"Brands per run, aligned with `runs`."}}},"CheckRequest":{"type":"object","title":"CheckRequest","required":["brand"],"properties":{"brand":{"type":"string","maxLength":80,"description":"The brand to ask about."},"category":{"type":"string","maxLength":120,"description":"The category for the blind list. Without it the blind-list half of the check is skipped and the score reflects recognition only."}}},"CheckResponse":{"type":"object","title":"CheckResponse","required":["score","verdict","brand","category","knows","description","recommendations","listPosition","recommendVerdict","recommendReason","model"],"properties":{"score":{"type":"integer","minimum":0,"maximum":100,"description":"Composite visibility score."},"verdict":{"type":"string","enum":["ai_approved","barely_visible","invisible"],"description":"Score bucket: 75+, 40 to 74, below 40."},"brand":{"type":"string"},"category":{"type":"string"},"knows":{"type":"boolean","description":"Whether the model says it recognises the brand."},"description":{"type":"string","description":"How the model describes the brand, verbatim. Empty when it does not know it."},"recommendations":{"type":"array","items":{"type":"string"},"maxItems":8,"description":"The blind list: what the model recommends for the category when the prompt never mentions the brand."},"listPosition":{"type":["integer","null"],"description":"1-based position of the brand in the blind list, or null when it was not named."},"recommendVerdict":{"type":"string","enum":["yes","no","unsure"]},"recommendReason":{"type":"string","description":"The model's stated reason, verbatim."},"model":{"type":"string","description":"The exact model id that answered."}}},"AuditRequest":{"type":"object","title":"AuditRequest","required":["brand","website","category"],"properties":{"brand":{"type":"string","maxLength":80,"description":"The brand as buyers write it."},"website":{"type":"string","maxLength":160,"description":"The brand's site. A bare domain is accepted and normalised to https. Must be a public host: localhost and private ranges are rejected.","examples":["askedthrice.com","https://askedthrice.com"]},"category":{"type":"string","maxLength":120,"description":"The category buyers would search, in their words. Merged with `location` when both are set."},"competitors":{"type":"string","maxLength":220,"description":"Comma-separated competitors to watch for. Optional: the audit finds its own."},"location":{"type":"string","maxLength":80,"description":"Market to measure for, when the category is local."},"promptLanguage":{"type":"string","enum":["en","es"],"description":"Language the prompts are asked in. Defaults to what the site crawl says the brand's market speaks."},"email":{"type":"string","format":"email","maxLength":120,"description":"Optional. Attaches the audit to that address so it can be claimed later, and sends the result there."}}},"AuditResponse":{"type":"object","title":"AuditResponse","description":"The free snapshot. Additional fields may be added over time; the ones listed here are the contract.","required":["id","createdAt","audit","providerFindings"],"properties":{"id":{"type":"string","format":"uuid","description":"Pass this to the buyer-question map operations."},"createdAt":{"type":"string","format":"date-time"},"mode":{"type":"string","description":"Which lineup measured this audit."},"measurementLocale":{"type":"string","enum":["en","es"],"description":"Language the prompts were asked in."},"categoryLocalized":{"type":"string","description":"The category as it was said to the models."},"audit":{"$ref":"#/components/schemas/AuditSummary"},"providerFindings":{"type":"array","description":"One entry per provider that answered.","items":{"$ref":"#/components/schemas/ProviderFinding"}},"providerErrors":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Providers that were asked and failed, with the reason."},"personalizedNextSteps":{"type":"array","items":{"type":"object","additionalProperties":true},"description":"Actions derived from the site crawl, not from a model's guess about the site."},"productFanout":{"type":["object","null"],"additionalProperties":true,"description":"The buyer questions generated for this brand, if any."},"fanoutMissingReason":{"type":"string","description":"Present only when there are no buyer questions, explaining why."},"categoryBenchmark":{"type":["object","null"],"additionalProperties":true,"description":"How this category scores on average, excluding this brand."},"rateLimit":{"type":"object","properties":{"remaining":{"type":"integer"},"resetAt":{"type":"string","format":"date-time"}},"description":"Same numbers as the X-RateLimit-* headers."}}},"AuditSummary":{"type":"object","title":"AuditSummary","description":"The headline of the snapshot: the score and what it means.","additionalProperties":true,"properties":{"score":{"type":"number","description":"Visibility score, 0 to 100."},"risk":{"type":"string","description":"Risk band derived from the score."},"brand":{"type":"string"},"website":{"type":"string"},"category":{"type":"string"}}},"ProviderFinding":{"type":"object","title":"ProviderFinding","description":"What one provider answered, and how much it moved across repeated runs.","additionalProperties":true,"properties":{"provider":{"type":"string","description":"Provider id, e.g. openai, anthropic, gemini."},"score":{"type":"number","description":"That provider's score for the brand."},"summary":{"type":"string","description":"What the provider said, condensed."}}},"FanoutMapRequest":{"type":"object","title":"FanoutMapRequest","properties":{"rerun":{"type":"boolean","default":false,"description":"Re-measure an existing map. Costs one credit and requires the signed-in owner. Ignored when there is no map yet, because that run is the free one."}}},"FanoutMapResponse":{"type":"object","title":"FanoutMapResponse","description":"The buyer-question map. An absent `map` means the measurement is still running: poll again.","properties":{"ok":{"type":"boolean"},"cached":{"type":"boolean","description":"True when the stored map was returned without re-measuring."},"status":{"type":"string","description":"Present while the map is being measured."},"map":{"type":"object","additionalProperties":true,"description":"The grid of buyer questions by provider, with the answers as measured."}}}}}}