FOR DEVELOPERS
REST API
Everything your agent can do, over plain HTTP. Put its answers in your own app, pull the questions it couldn't answer into your CRM, or re-sync your site from your deploy pipeline — in any language, with the API key you already have.
Working inside Claude or ChatGPT instead? Use the MCP connector → Same capabilities, no code.
1. Get an API key
Generate one in your dashboard. Keys start with awg_ and carry the same access as your account — treat one like a password, keep it server-side, and revoke it in the dashboard if it leaks.
2. Check it works
Every request needs an Authorization header. This call touches nothing else, so it is the quickest way to confirm a key is good:
curl https://mcp-server-kpgyjzyfka-uc.a.run.app/v1/me \ -H "Authorization: Bearer awg_your_key_here"
It returns the site the key controls. Every other endpoint works on that site and no other — the key decides which data you see, so there is no account or domain parameter to pass anywhere in this API.
3. Ask your agent something
curl https://mcp-server-kpgyjzyfka-uc.a.run.app/v1/ask \
-H "Authorization: Bearer awg_your_key_here" \
-H "Content-Type: application/json" \
-d '{"question": "Do you ship to Japan?"}'Check the answered field rather than reading the answer text. It is false when the agent had nothing to go on — that is your cue to escalate to a human, or to add the missing knowledge.
Endpoints
Base URL https://mcp-server-kpgyjzyfka-uc.a.run.app/v1. All responses are JSON.
/meConfirm a key works and see which site it controls.
The cheapest call to make first — no upstream services are touched.
Response
{
"domain": "example.com",
"url": "https://example.com",
"plan": "premium",
"subscriptionStatus": "active",
"apiVersion": "v1"
}/askAsk your agent a question, exactly as a visitor would.
Returns the answer, the pages it came from, and whether the agent could actually help — branch on "answered" rather than parsing the text.
Request body
{ "question": "Do you ship to Japan?" }Response
{
"question": "Do you ship to Japan?",
"answer": "Yes — delivery takes 3–5 working days.",
"answered": true,
"sources": [
{ "title": "Shipping", "uri": "https://example.com/shipping" }
],
"domain": "example.com"
}/questionsWhat visitors recently asked, newest first.
Query: days (1–30, default 7), limit (1–100, default 30).
Response
{
"domain": "example.com",
"days": 7,
"total": 42,
"questions": [
{
"question": "Do you ship to Japan?",
"answer": "Yes — delivery takes 3–5 working days.",
"answered": true,
"askedAt": "2026-07-31T04:37:03+00:00",
"channel": "whatsapp"
}
]
}/questions/unansweredOnly the questions your agent could not answer.
Your content gaps. Same shape and query parameters as /questions. This is the one worth putting on a schedule.
Response
{
"domain": "example.com",
"days": 7,
"total": 3,
"questions": [
{
"question": "Do you integrate with Zendesk?",
"answer": "I don't have enough information to answer that yet.",
"answered": false,
"askedAt": "2026-07-31T04:37:03+00:00",
"channel": "web"
}
]
}/knowledgeEverything your agent knows.
Crawled pages and uploaded documents, listed separately.
Response
{
"domain": "example.com",
"pageCount": 48,
"uploadCount": 2,
"pages": ["about.md", "index.md"],
"uploads": ["kb-refund-policy.md"]
}/knowledgePaid planTeach your agent something new, effective immediately.
No website change or re-crawl needed. Requires a paid plan.
Request body
{ "title": "Refund policy", "content": "We refund within 30 days…" }Response
{
"ok": true,
"title": "Refund policy",
"filename": "kb-refund-policy.md",
"domain": "example.com"
}/recrawlRe-read your website after you publish changes.
Returns 202 — the crawl runs in the background, usually a few minutes.
Response
{
"started": true,
"url": "https://example.com",
"message": "Re-crawl started. It runs in the background and usually takes a few minutes."
}/statusHow much of your site is indexed, and when it last synced.
Response
{
"domain": "example.com",
"pagesIndexed": 48,
"lastUpdated": "2026-07-31T04:37:03+00:00",
"samplePages": ["index.md", "about.md"]
}A worked example: close your content gaps nightly
The endpoint most people automate first. Pull what your agent couldn't answer, and send it wherever your team already works:
const key = process.env.AIWEBGPT_API_KEY;
const res = await fetch('https://mcp-server-kpgyjzyfka-uc.a.run.app/v1/questions/unanswered?days=1', {
headers: { Authorization: `Bearer ${key}` },
});
const { questions } = await res.json();
for (const q of questions) {
await postToSlack(`Nobody could answer: "${q.question}" (via ${q.channel})`);
}Answer one of them once, and every future visitor gets it — POST /v1/knowledge takes effect immediately, with no re-crawl.
Errors
Failures return {"error": {"code", "message"}} with a matching status. The message is written to be shown to a developer, not a visitor.
| Status | Code | Means |
|---|---|---|
| 400 | invalid_request | Missing or malformed input — the message says what. |
| 401 | unauthorized | No key, or a revoked one. Check the Authorization header. |
| 402 | payment_required | The endpoint needs a paid plan. |
| 403 | forbidden | No website is set up on this account yet. |
| 429 | rate_limited | Over 60 requests/minute. Retry-After says when to retry. |
| 500 | internal_error | Something broke on our side. Safe to retry. |
| 502 | upstream_error | A service behind the API failed. Safe to retry. |
Credentials are checked before the path is matched, so a mistyped path without a valid key returns 401 rather than 404. With a valid key, an unknown path returns a plain 404 without the JSON error envelope.
Rate limits and versioning
60 requests per minute per key. Going over returns 429 with a Retry-After header. It is there to stop runaway loops — if you have a legitimate reason to need more, tell us and we will raise it.
The path carries the version. Anything that would break your code — a removed field, a renamed endpoint — arrives as /v2, and /v1 keeps working. New fields can appear in existing responses, so parse leniently and ignore what you don't recognise.