체인지로그새 기능

AI 답변 엔진을 하나로 묶은 API가 나왔어요

API
POST /v1/async/task 요청, 돌아온 대기 태스크, webhook으로 전달된 완료 태스크

요청 하나로 지원하는 모든 AI 엔진에 질문을 보내고, 답변을 구조화된 JSON으로 webhook이나 폴링으로 받아요.

받는 결과

  • 모든 엔진이 POST /v1/async/task 엔드포인트 하나를 씁니다. taskType으로 엔진을, country로 시장을 고릅니다.
  • 엔진마다 다른 response를 같은 형식이 감쌉니다. 태스크의 id, status, 시각 정보가 항상 같은 자리에 옵니다.
  • 결과는 원하는 방식으로 받습니다. 태스크가 끝나는 즉시 서버로 webhook을 받거나, 필요할 때 GET /v1/async/task/{id}로 조회하세요.
  • idempotencyKey로 안전하게 재시도합니다. 같은 키로 다시 보내면 이미 만든 태스크를 그대로 돌려줍니다.

동작 방식

AI 엔진은 답하는 데 몇 초에서 몇 분이 걸리므로, 모든 요청은 안전하게 저장되는 태스크가 됩니다. 태스크는 QUEUED에서 PROCESSING으로 넘어가고, 답변과 함께 COMPLETED로 또는 사유와 함께 FAILED로 끝납니다. 엔진에서 일시적인 오류가 나면 태스크가 큐로 돌아가 다시 시도되므로, 짧은 장애는 사용자에게 닿기 전에 재시도로 처리됩니다.

webhook.url을 넣으면 끝난 태스크를 서버로 보내 드리고, 약 30분 동안 최대 다섯 번까지 전송합니다. 끝난 태스크는 24시간 동안 폴링으로도 조회되므로, webhook과 폴링을 함께 쓰면 모든 결과를 확실하게 받습니다.

이렇게 활용하세요

  • 자체 백엔드에서 원하는 주기로 브랜드 노출을 점검합니다.
  • 같은 질문을 여러 엔진에 보내 답변을 나란히 비교합니다.
  • 오래 걸리는 작업도 간단합니다. 제출해 두고 다른 일을 하면 webhook이 결과를 가져옵니다.

요청 보내기

API 키를 Authorization 헤더에 넣어 요청을 보냅니다.

curl -X POST https://api.querying.ai/v1/async/task \
  -H "Authorization: Bearer $QUERYING_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "taskType": "CHATGPT",
    "payload": {
      "prompt": "Best coffee shops near me for working on a laptop",
      "country": "US"
    },
    "webhook": { "url": "https://your-server.example.com/hooks/querying" },
    "idempotencyKey": "laptop-cafes-us-0001"
  }'

요청 필드

taskTypestring필수
실행할 엔진입니다. CHATGPT, GOOGLE 같은 값을 넣습니다.
payloadobject필수
엔진 입력입니다. prompt 또는 query와 country, 엔진별 옵션을 넣습니다.
webhook.urlstring
서버의 HTTPS 주소입니다. 태스크가 끝나는 즉시 결과를 이 주소로 보냅니다.
idempotencyKeystring
요청에 붙이는 고유 ID입니다. 키 하나는 계정에서 태스크 하나를 만들므로, 다시 보내도 태스크는 하나로 유지됩니다.

응답

호출하면 QUEUED 상태의 태스크가 바로 돌아옵니다. credits는 태스크가 끝날 때까지 0이고, idempotencyKey는 보낸 값 그대로 돌아옵니다.

JSON
{
  "success": true,
  "task": {
    "id": "…",
    "taskType": "CHATGPT",
    "status": "QUEUED",
    "priority": 1,
    "createdAt": "…",
    "idempotencyKey": "laptop-cafes-us-0001"
  },
  "credits": { "creditsToCharge": 0, "creditsCharged": 0 }
}

결과 받기

status가 COMPLETED나 FAILED가 될 때까지 몇 초마다 GET /v1/async/task/{id}로 태스크를 조회하세요. 결과는 태스크가 끝난 뒤 24시간 동안 조회됩니다.

curl https://api.querying.ai/v1/async/task/$TASK_ID \
  -H "Authorization: Bearer $QUERYING_API_KEY"

끝난 태스크에는 차감된 credits와 엔진의 response가 담깁니다. 실제 태스크를 길이만 줄여 보여 드립니다.

JSON
{
  "success": true,
  "task": {
    "id": "e5aba119-a26e-4f82-a965-35c9234a428b",
    "taskType": "CHATGPT",
    "status": "COMPLETED",
    "createdAt": "2026-10-02T15:41:03.858Z"
  },
  "credits": { "creditsToCharge": 2, "creditsCharged": 2 },
  "response": {
    "text": "Here are several **nearby cafés that look particularly suitable for laptop work**, based on current local listings and work-friendly details:\n…",
    "sources": [
      {
        "position": 1,
        "url": "https://awifiplace.com/cities/new-york",
        "label": "Best Coffee Shops to Work in New York (2026) | 103 Cafes with WiFi"
      }
    ],
    "searchQueries": [
      "best coffee shops for laptop work near me wifi outlets"
    ]
  }
}
task.statusstring
QUEUED, PROCESSING을 거쳐 답변과 함께 COMPLETED로, 또는 error와 함께 FAILED로 끝납니다.
credits.creditsChargedinteger
끝난 태스크에 청구된 크레딧입니다. 실패한 태스크는 0입니다.

웹훅

webhook.url을 넣으면 끝난 태스크가 조회 결과와 같은 task, credits, response를 담은 POST로 서버에 도착합니다. 실패한 태스크도 같은 방식으로 response.error와 차감 0크레딧을 담아 도착합니다. 모든 전달에는 서명 헤더 세 개가 붙습니다.

querying-webhook-idstring
태스크 ID입니다. 전달은 한 번 이상 도착하므로 이 값으로 중복을 걸러 내세요.
querying-webhook-timestampstring
전달에 서명한 시각(Unix 초)입니다. 최근 5분 안에 서명된 전달을 받으세요.
querying-webhook-signaturestring
v1, 뒤에 {id}.{timestamp}.{body}의 base64 HMAC-SHA256이 붙습니다. 키는 API 키의 SHA-256 다이제스트입니다.

본문을 파싱하기 전에 받은 그대로의 본문으로 서명을 확인하고, 일치하면 전달을 받아들이세요.

import base64
import hashlib
import hmac
import os
import time


def verify_webhook(headers, body: bytes) -> bool:
    """body is the raw request body, before json.loads."""
    task_id = headers["querying-webhook-id"]
    timestamp = headers["querying-webhook-timestamp"]
    key = hashlib.sha256(os.environ["QUERYING_API_KEY"].encode()).digest()
    signed = f"{task_id}.{timestamp}.".encode() + body
    digest = hmac.new(key, signed, hashlib.sha256).digest()
    expected = "v1," + base64.b64encode(digest).decode()
    given = headers.get("querying-webhook-signature", "")
    fresh = abs(time.time() - int(timestamp)) <= 300
    return fresh and hmac.compare_digest(expected, given)

30초 안에 2xx 상태로 응답하세요. 2xx가 아니거나 30초를 넘기면 2분, 4분, 8분, 16분 뒤에 다시 보내 모두 다섯 번 시도하고, 시도마다 같은 querying-webhook-id가 붙습니다.

오류

거절된 요청은 success가 false이고 code, message, 필드 오류일 때 details를 담은 error로 바로 응답합니다. 대기열 밖에서 끝나므로 크레딧은 0입니다.

VALIDATION_ERRORHTTP 400
필드가 빠졌거나 허용 범위를 벗어났습니다. details에 해당 필드가 나옵니다.
MISSING_API_KEYHTTP 401
Authorization 헤더가 빠졌거나, 키가 잘못됐거나 만료 또는 폐기됐습니다.
INSUFFICIENT_CREDITSHTTP 402
잔액이 태스크 가격보다 적습니다. 크레딧을 충전하거나 플랜을 올린 뒤 다시 보내세요.
KEY_SCOPE_DENIEDHTTP 403
키의 허용 엔진 밖의 엔진입니다. 다른 키를 쓰거나 키 관리에서 엔진을 추가하세요.
RESOURCE_ALREADY_EXISTSHTTP 409
이 idempotencyKey로 만든 태스크가 이미 계정에 있어 태스크 하나로 유지됩니다. 배치에서는 그 항목을 성공으로 처리합니다.
PAYLOAD_TOO_LARGEHTTP 413
본문이 1 MiB를 넘었습니다. 배치는 8 MiB까지 받습니다.
REGION_UNSUPPORTEDHTTP 422
country가 엔진이 지원하는 국가 목록 밖에 있습니다. 다시 보내도 결과가 같으므로 details.retryable이 false입니다.
PLAN_CONCURRENCY_LIMITHTTP 429
대기 중이거나 실행 중인 태스크가 플랜 한도에 닿았습니다. 무료 플랜 2개, Basic 10개, Pro 25개, Scale 50개, Max 100개입니다. Retry-After초 뒤에 다시 보내세요.
KEY_DAILY_CREDIT_LIMITHTTP 429
키가 일일 크레딧 한도를 다 썼습니다. Retry-After는 다음 UTC 자정까지입니다.
QUEUE_BACKPRESSUREHTTP 429
엔진 대기열이 잠시 가득 찼습니다. Retry-After초 뒤에 다시 보내세요.

요금

엔진마다 크레딧 가격이 정해져 있고, 태스크가 완료될 때 차감됩니다. 실패한 태스크는 예약했던 크레딧을 돌려줍니다.

다른 업데이트

새 소식을 받아보세요

제품 업데이트를 이메일로 보내드립니다.

RSS 구독