{
  "openapi": "3.1.0",
  "info": {
    "title": "askingahuman API",
    "version": "2026-10",
    "summary": "AI agents reach real, reviewed people: free questions and paid live chat or video.",
    "description": "Every request names a person (there is no open pool). Find people with /humans or /api/live-now, then send a free question or quote and book a live session. Live sessions are sold in blocks (an hour of chat or 30 minutes of video) at the price each person sets; the first block is held when you request and charged in full once the person accepts, at the block price or the session minimum (`sessionMinimumCents`, see the Terms), whichever is higher; extensions are at the block price. A key can carry a monthly spend cap set by its owner; past it, connect and extend return 402 `SPEND_CAP_REACHED`. Errors are JSON `{ \"error\": \"message\" }`; some live refusals return 409 `{ \"status\", \"message\" }`. Most agents use the MCP server instead: npx -y @askingahuman/mcp-server.",
    "contact": { "name": "askingahuman support", "email": "support@askingahuman.com", "url": "https://askingahuman.com/developers" },
    "termsOfService": "https://askingahuman.com/terms"
  },
  "servers": [{ "url": "https://api.askingahuman.com" }],
  "security": [{ "apiKey": [] }],
  "components": {
    "securitySchemes": {
      "apiKey": { "type": "http", "scheme": "bearer", "bearerFormat": "ahk_...", "description": "Create a key at https://askingahuman.com/developers" },
      "apiKeyHeader": { "type": "apiKey", "in": "header", "name": "X-API-Key", "description": "The same key; use this header when Authorization carries an MPP payment credential" }
    },
    "schemas": {
      "Error": { "type": "object", "properties": { "error": { "type": "string" } }, "required": ["error"] },
      "Receipt": {
        "type": ["string", "null"],
        "description": "Compact JWS (EdDSA, Ed25519) signed by askingahuman: who answered or met you, when, and SHA-256 hashes of the text (sessions: metadata and amount). Verify with the keys at /.well-known/askingahuman-receipt-keys.json. Null while receipts are off."
      },
      "Redactions": {
        "type": "array",
        "description": "Kinds of personal details removed from your text before the person saw it (the original is never stored); empty when none",
        "items": { "type": "string", "enum": ["secret", "card", "iban", "email", "ssn", "phone", "ip", "address", "dob", "id_number"] }
      },
      "Refusal": { "type": "object", "properties": { "status": { "type": "string", "examples": ["HUMAN_OFFLINE", "OVER_SPEND_LIMIT"] }, "message": { "type": "string" } } },
      "SpendCapReached": {
        "type": "object",
        "description": "402: this key is at the monthly spend cap its owner set. Nothing was held; only the owner can raise the cap.",
        "properties": {
          "status": { "type": "string", "const": "SPEND_CAP_REACHED" },
          "error": { "type": "string", "const": "SPEND_CAP_REACHED" },
          "monthlySpendCapCents": { "type": "integer" },
          "message": { "type": "string" }
        }
      },
      "Human": {
        "type": "object",
        "properties": {
          "humanId": { "type": "string", "examples": ["exp_0123456789abcdef"] },
          "name": { "type": "string" },
          "title": { "type": "string" },
          "bio": { "type": "string" },
          "specializations": { "type": "array", "items": { "type": "string" } },
          "languages": { "type": "array", "items": { "type": "string" } },
          "rating": { "type": "number" },
          "reviewCount": { "type": "integer" },
          "credentials": {
            "type": "array",
            "description": "Credentials we have checked",
            "items": {
              "type": "object",
              "properties": {
                "type": { "type": "string", "enum": ["license", "degree", "certification", "employment", "portfolio"] },
                "title": { "type": "string" },
                "issuer": { "type": "string" }
              }
            }
          }
        }
      },
      "Pricing": {
        "type": "object",
        "properties": {
          "blockMinutes": { "type": "integer", "description": "60 for chat, 30 for video" },
          "blockPriceCents": { "type": "integer", "description": "Price of each extension block" },
          "firstChargeCents": { "type": "integer", "description": "Price of the first block: blockPriceCents or the session minimum, whichever is higher" },
          "currency": { "type": "string", "examples": ["usd"] }
        }
      },
      "Question": {
        "type": "object",
        "properties": {
          "questionId": { "type": "string" },
          "text": { "type": "string" },
          "status": { "type": "string", "enum": ["PENDING_ANSWER", "ANSWERED", "DECLINED"] },
          "answer": { "type": ["string", "null"] },
          "answeredAt": { "type": ["string", "null"], "format": "date-time" },
          "answeredBy": { "type": ["string", "null"] },
          "receipt": { "$ref": "#/components/schemas/Receipt" },
          "replyCount": { "type": "integer" }
        }
      },
      "Session": {
        "type": "object",
        "properties": {
          "sessionId": { "type": "string" },
          "status": { "type": "string", "enum": ["REQUESTED", "ACCEPTED", "ACTIVE", "ENDED", "DECLINED", "EXPIRED"] },
          "mode": { "type": "string", "enum": ["message", "video"] },
          "pricing": { "$ref": "#/components/schemas/Pricing" },
          "blockMinutes": { "type": "integer" },
          "blocksPurchased": { "type": "integer" },
          "maxBlocks": { "type": "integer" },
          "endsAt": { "type": ["string", "null"], "format": "date-time" },
          "canExtend": { "type": "boolean" },
          "room": { "type": "string", "description": "Once ACCEPTED: LiveKit room name" },
          "url": { "type": "string", "description": "Once ACCEPTED: LiveKit server URL" },
          "token": { "type": "string", "description": "Once ACCEPTED: LiveKit join token. Chat is plain UTF-8 text on the data channel." },
          "billedSeconds": { "type": "integer", "description": "Once ENDED" },
          "amountCents": { "type": "integer", "description": "Once ENDED" },
          "receipt": { "$ref": "#/components/schemas/Receipt" }
        }
      }
    },
    "responses": {
      "Unauthorized": { "description": "Missing or invalid API key", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "No such person or record (also for unlisted people and people who blocked your key)", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "RateLimited": { "description": "Too many requests", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  },
  "paths": {
    "/humans": {
      "get": {
        "operationId": "listHumans",
        "summary": "List reviewed people",
        "security": [],
        "parameters": [
          { "name": "audience", "in": "query", "required": true, "schema": { "type": "string", "enum": ["bot"] } },
          { "name": "specialty", "in": "query", "schema": { "type": "string" } },
          { "name": "credential", "in": "query", "description": "Only people with a verified credential of this type", "schema": { "type": "string", "enum": ["license", "degree", "certification", "employment", "portfolio"] } },
          { "name": "verifiedOnly", "in": "query", "description": "Only people with any verified credential", "schema": { "type": "boolean" } },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50 } },
          { "name": "offset", "in": "query", "schema": { "type": "integer", "default": 0 } }
        ],
        "responses": {
          "200": { "description": "People", "content": { "application/json": { "schema": { "type": "object", "properties": { "humans": { "type": "array", "items": { "$ref": "#/components/schemas/Human" } }, "count": { "type": "integer" } } } } } }
        }
      }
    },
    "/api/live-now": {
      "get": {
        "operationId": "whoIsLive",
        "summary": "People live right now",
        "parameters": [{ "name": "specialization", "in": "query", "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "Live people", "content": { "application/json": { "schema": { "type": "object", "properties": { "humans": { "type": "array", "items": { "type": "object", "properties": { "humanId": { "type": "string" }, "name": { "type": "string" }, "title": { "type": "string" }, "specializations": { "type": "array", "items": { "type": "string" } }, "acceptsMessage": { "type": "boolean" }, "acceptsVideo": { "type": "boolean" } } } }, "count": { "type": "integer" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/api/questions": {
      "post": {
        "operationId": "askHuman",
        "summary": "Send a free question to a person",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["questionText", "targetExpertId"], "properties": { "questionText": { "type": "string", "minLength": 1, "maxLength": 500, "description": "Personal details (emails, phone numbers...) are scrubbed before the person sees it" }, "targetExpertId": { "type": "string" }, "callbackUrl": { "type": "string", "format": "uri", "description": "POSTed when answered" }, "screenshotKey": { "type": "string" } } } } }
        },
        "responses": {
          "201": { "description": "Sent", "content": { "application/json": { "schema": { "type": "object", "properties": { "questionId": { "type": "string" }, "status": { "type": "string", "const": "PENDING_ANSWER" }, "redactions": { "$ref": "#/components/schemas/Redactions" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "description": "The person doesn't take free questions" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/questions/{questionId}": {
      "get": {
        "operationId": "checkAnswer",
        "summary": "Read a question and its answer",
        "parameters": [{ "name": "questionId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": {
          "200": { "description": "The question", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Question" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/questions/{questionId}/replies": {
      "get": {
        "operationId": "listReplies",
        "summary": "The conversation after an answer",
        "parameters": [{ "name": "questionId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Replies" }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      },
      "post": {
        "operationId": "replyToHuman",
        "summary": "Reply to the person (only after ANSWERED)",
        "parameters": [{ "name": "questionId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["text"], "properties": { "text": { "type": "string", "maxLength": 2000 } } } } } },
        "responses": { "201": { "description": "Sent" }, "403": { "description": "This conversation is closed" }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      }
    },
    "/api/questions/{questionId}/feedback": {
      "post": {
        "operationId": "rateAnswer",
        "summary": "Thumbs up or down on an answer",
        "parameters": [{ "name": "questionId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["rating"], "properties": { "rating": { "type": "string", "enum": ["up", "down"] }, "comment": { "type": "string" } } } } } },
        "responses": { "200": { "description": "Saved" } }
      }
    },
    "/api/connect/quote": {
      "get": {
        "operationId": "getQuote",
        "summary": "Price and bookability for a live session (free; nobody is notified)",
        "parameters": [
          { "name": "humanId", "in": "query", "required": true, "schema": { "type": "string" } },
          { "name": "mode", "in": "query", "required": true, "schema": { "type": "string", "enum": ["message", "video"] } },
          { "name": "maxSpendCents", "in": "query", "schema": { "type": "integer" } }
        ],
        "responses": {
          "200": { "description": "Quote", "content": { "application/json": { "schema": { "type": "object", "properties": { "bookable": { "type": "boolean" }, "mode": { "type": "string" }, "human": { "type": "object", "properties": { "humanId": { "type": "string" }, "name": { "type": "string" }, "live": { "type": "boolean" } } }, "pricing": { "$ref": "#/components/schemas/Pricing" }, "blockPriceCents": { "type": "integer" }, "firstChargeCents": { "type": "integer", "description": "What the first block costs (held at request)" }, "sessionMinimumCents": { "type": "integer" }, "holdAmountCents": { "type": "integer" }, "maxBlocks": { "type": "integer" }, "maxSessionMinutes": { "type": "integer" }, "spendLimitCents": { "type": "integer" }, "spendCapRemainingCents": { "type": "integer", "description": "Only when the key has a monthly spend cap: what is left of it this month" }, "paymentReady": { "type": "boolean", "description": "false: no card or balance on this key, or the first charge would pass the key's spend cap (error: SPEND_CAP_REACHED)" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" }
        }
      }
    },
    "/api/connect": {
      "post": {
        "operationId": "connectHuman",
        "summary": "Request a live session (holds the first block and notifies the person)",
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["mode", "humanId"], "properties": { "mode": { "type": "string", "enum": ["message", "video"] }, "humanId": { "type": "string" }, "message": { "type": "string", "description": "What you need help with" }, "maxSpendCents": { "type": "integer" } } } } } },
        "responses": {
          "201": { "description": "Requested", "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string", "const": "REQUESTED" }, "sessionId": { "type": "string" }, "pricing": { "$ref": "#/components/schemas/Pricing" }, "firstChargeCents": { "type": "integer" }, "sessionMinimumCents": { "type": "integer" }, "holdAmountCents": { "type": "integer" }, "blockMinutes": { "type": "integer" }, "maxBlocks": { "type": "integer" }, "spendLimitCents": { "type": "integer" }, "redactions": { "$ref": "#/components/schemas/Redactions" }, "waitsUntil": { "type": "string", "format": "date-time" } } } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "description": "No usable card or balance on this key, or SPEND_CAP_REACHED", "content": { "application/json": { "schema": { "oneOf": [{ "$ref": "#/components/schemas/Error" }, { "$ref": "#/components/schemas/SpendCapReached" }] } } } },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "description": "HUMAN_OFFLINE (video), OVER_SPEND_LIMIT, or the person doesn't take this mode", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Refusal" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/api/connect/{sessionId}": {
      "get": {
        "operationId": "checkConnection",
        "summary": "Session status; LiveKit credentials once accepted",
        "parameters": [{ "name": "sessionId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Session", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Session" } } } }, "404": { "$ref": "#/components/responses/NotFound" } }
      }
    },
    "/api/connect/{sessionId}/extend": {
      "post": {
        "operationId": "extendSession",
        "summary": "Buy one more block",
        "requestBody": { "required": false, "content": { "application/json": { "schema": { "type": "object", "description": "No fields; send {} or no body" } } } },
        "parameters": [{ "name": "sessionId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Extended (one more block at the block price)", "content": { "application/json": { "schema": { "type": "object", "properties": { "blocksPurchased": { "type": "integer" }, "maxBlocks": { "type": "integer" }, "endsAt": { "type": ["string", "null"], "format": "date-time" }, "totalCents": { "type": "integer", "description": "The first charge plus the block price for each extension" } } } } } }, "402": { "description": "Couldn't authorize the payment, or SPEND_CAP_REACHED", "content": { "application/json": { "schema": { "oneOf": [{ "$ref": "#/components/schemas/Error" }, { "$ref": "#/components/schemas/SpendCapReached" }] } } } }, "409": { "description": "At the session's limit" } }
      }
    },
    "/api/credits": {
      "get": {
        "operationId": "getCredits",
        "summary": "This key's prepaid credit",
        "security": [{ "apiKey": [] }, { "apiKeyHeader": [] }],
        "responses": { "200": { "description": "Balance", "content": { "application/json": { "schema": { "type": "object", "properties": { "balanceCents": { "type": "integer" }, "currency": { "type": "string" } } } } } }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      },
      "post": {
        "operationId": "buyCredits",
        "summary": "Buy prepaid credit with x402 or MPP (see Paying a 402 in docs/API.md)",
        "description": "Without a payment, answers 402 with an x402 PAYMENT-REQUIRED header and MPP WWW-Authenticate: Payment challenges for amountCents. Retry with the payment (x402: PAYMENT-SIGNATURE; MPP: Authorization: Payment plus the key in X-API-Key) and the key is credited once. Credit pays for live sessions when the key has no card. Test keys can't buy credit.",
        "security": [{ "apiKey": [] }, { "apiKeyHeader": [] }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "properties": { "amountCents": { "type": "integer", "minimum": 500, "maximum": 50000 } }, "required": ["amountCents"] } } } },
        "responses": { "200": { "description": "Credited", "content": { "application/json": { "schema": { "type": "object", "properties": { "creditedCents": { "type": "integer" }, "alreadyCredited": { "type": "boolean" }, "reference": { "type": ["string", "null"] }, "balanceCents": { "type": "integer" }, "currency": { "type": "string" } } } } } }, "400": { "description": "amountCents must be a whole number from 500 to 50000", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }, "401": { "$ref": "#/components/responses/Unauthorized" }, "402": { "description": "Payment required: x402 and MPP challenges in the headers, the same in the body", "content": { "application/json": { "schema": { "type": "object", "properties": { "error": { "type": "string" }, "amountCents": { "type": "integer" }, "x402": { "type": "object" }, "mpp": { "type": "object" }, "payWith": { "type": "string" } } } } } }, "403": { "description": "Test keys never pay" }, "429": { "$ref": "#/components/responses/RateLimited" }, "503": { "description": "Buying credit isn't available on this API right now" } }
      }
    },
    "/api/keys": {
      "post": {
        "operationId": "createKey",
        "summary": "Create an API key (accepts the Terms)",
        "description": "The request must accept the Terms of Service and Privacy Policy; the key records when (termsAcceptedAt, termsVersion). Any bot or business may be a Requester; businessUse is optional and only recorded (businessUseAt). The raw key is shown once. Stripe emails receipts for card charges to the card's billing email, or else to the optional email.",
        "security": [],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["acceptTerms"], "properties": { "acceptTerms": { "type": "boolean", "const": true, "description": "You accept the Terms of Service (https://askingahuman.com/terms) and Privacy Policy, and are 18 or older" }, "businessUse": { "type": "boolean", "description": "Optional: you use askingahuman for business" }, "label": { "type": "string" }, "email": { "type": "string", "format": "email", "description": "Optional: where receipts are emailed" } } } } } },
        "responses": { "201": { "description": "Created", "content": { "application/json": { "schema": { "type": "object", "properties": { "keyId": { "type": "string" }, "key": { "type": "string", "description": "Shown once" }, "message": { "type": "string" } } } } } }, "400": { "description": "acceptTerms must be true, or the email is invalid" }, "429": { "description": "Too many keys from this address; try later" } }
      }
    },
    "/api/keys/{keyId}/spend-cap": {
      "put": {
        "operationId": "setSpendCap",
        "summary": "Set or remove this key's monthly spend cap on live sessions (resets on the 1st, UTC)",
        "parameters": [{ "name": "keyId", "in": "path", "required": true, "description": "The key's id, or `me`", "schema": { "type": "string" } }],
        "requestBody": { "required": true, "content": { "application/json": { "schema": { "type": "object", "required": ["monthlySpendCapCents"], "properties": { "monthlySpendCapCents": { "type": ["integer", "null"], "minimum": 0, "description": "null removes the cap; 0 pauses paid sessions" } } } } } },
        "responses": { "200": { "description": "Saved", "content": { "application/json": { "schema": { "type": "object", "properties": { "keyId": { "type": "string" }, "monthlySpendCapCents": { "type": ["integer", "null"] }, "spentThisMonthCents": { "type": "integer" } } } } } }, "400": { "description": "Invalid cap" }, "401": { "$ref": "#/components/responses/Unauthorized" } }
      }
    },
    "/.well-known/askingahuman-receipt-keys.json": {
      "get": {
        "operationId": "getReceiptKeys",
        "summary": "Public keys (JWKS) that sign receipts",
        "security": [],
        "responses": { "200": { "description": "JWKS", "content": { "application/json": { "schema": { "type": "object", "properties": { "keys": { "type": "array", "items": { "type": "object", "properties": { "kty": { "const": "OKP" }, "crv": { "const": "Ed25519" }, "x": { "type": "string" }, "kid": { "type": "string" }, "alg": { "const": "EdDSA" }, "use": { "const": "sig" } } } } } } } } } }
      }
    },
    "/api/connect/{sessionId}/end": {
      "post": {
        "operationId": "endSession",
        "summary": "End the session (before acceptance: cancel and release the hold)",
        "parameters": [{ "name": "sessionId", "in": "path", "required": true, "schema": { "type": "string" } }],
        "responses": { "200": { "description": "Ended" } }
      }
    }
  }
}
