{"openapi":"3.1.0","info":{"title":"Pirehub by ntanduy API","description":"Scoped developer APIs from ntanduy for domain and IP intelligence, secured media processing, and Vietnamese bank QR utilities.","version":"1.0.0"},"servers":[{"url":"https://www.ntanduy.com","description":"Production"}],"tags":[{"name":"Screenshot tools","description":"Capture public webpages as PNG images."},{"name":"Social embeds","description":"Resolve public social URLs into normalized oEmbed data."},{"name":"Domains","description":"Domain registration, WHOIS, and DNS resolution intelligence."},{"name":"IP information","description":"Normalized location, network, and timezone information for public IP addresses."},{"name":"Video tools","description":"Video metadata and secure download-option extraction."},{"name":"Payment utilities","description":"Scoped APIs for generating interoperable bank-transfer artifacts."},{"name":"Free subdomains","description":"First-party dashboard routes requiring a Clerk session and automatic website verification for namespace registration, desired DNS state, asynchronous provider operations, and DNS propagation verification."},{"name":"Web data","description":"Search, extract, map and crawl public web content."}],"paths":{"/api/v1/oembed/resolve":{"post":{"operationId":"resolveOEmbed","summary":"Resolve a social oEmbed","description":"Resolve public X, Facebook, Instagram, Threads, TikTok, or Reddit content with oembed:read. The body accepts only url. Results may be cached for one hour; cache hits still count toward API-key quotas. X returns metadata without embed HTML or scripts. Other providers may return third-party HTML and scripts; render them in an isolated, sandboxed iframe. Provider-specific metadata is omitted. Self-service API keys require a paid entitlement and shared subscription credits. Successful execution (including cache hits) consumes the catalog action cost; failures release credits. Owner and key rate limits use one-second windows. See the authentication guide for billing and idempotency semantics.","tags":["Social embeds"],"security":[{"pirehubOAuth":["oembed:read"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"additionalProperties":false,"properties":{"url":{"type":"string","format":"uri","maxLength":2048,"minLength":1,"description":"Public provider post URL. Trimmed, normalized to HTTPS, and checked against allowed hosts and public network destinations. Replace the example placeholders with a real post."}}},"example":{"url":"https://x.com/USERNAME/status/POST_ID"}}}},"responses":{"200":{"description":"Normalized oEmbed data in the standard success envelope; provider-specific metadata is omitted.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"Server-Timing":{"$ref":"#/components/headers/ServerTiming"}},"content":{"application/json":{"schema":{"type":"object","required":["success","data"],"properties":{"success":{"const":true},"data":{"type":"object","required":["provider","type","originalUrl","resolvedUrl","title","author","providerInfo","thumbnail","embed","capabilities"],"properties":{"provider":{"type":"string","enum":["x","facebook","instagram","threads","tiktok","reddit"]},"type":{"type":"string","enum":["rich","video","photo","link"]},"originalUrl":{"type":"string","format":"uri","description":"Input after initial URL normalization (HTTPS and fragment removal)."},"resolvedUrl":{"type":"string","format":"uri","description":"Provider URL after redirects and canonicalization."},"title":{"type":["string","null"]},"author":{"type":"object","required":["name","url"],"properties":{"name":{"type":["string","null"]},"url":{"type":["string","null"],"format":"uri"}}},"providerInfo":{"type":"object","required":["name","url"],"properties":{"name":{"type":"string"},"url":{"type":["string","null"],"format":"uri"}}},"thumbnail":{"type":["object","null"],"required":["url","width","height"],"properties":{"url":{"type":"string","format":"uri"},"width":{"type":["number","null"],"minimum":0},"height":{"type":["number","null"],"minimum":0}}},"embed":{"type":"object","required":["html","width","height","scripts"],"properties":{"html":{"type":["string","null"],"description":"Third-party markup. Render only in an isolated, sandboxed iframe. Null for X."},"width":{"type":["number","null"],"minimum":0},"height":{"type":["number","null"],"minimum":0},"scripts":{"type":"array","items":{"type":"object","required":["src"],"properties":{"src":{"type":"string","format":"uri"},"async":{"type":"boolean"},"defer":{"type":"boolean"}}}}}},"capabilities":{"type":"object","required":["html","thumbnail","nativeEmbed","requiresHydration"],"properties":{"html":{"type":"boolean","description":"Embed HTML is available."},"thumbnail":{"type":"boolean","description":"A thumbnail is available."},"nativeEmbed":{"type":"boolean","description":"Provider-native embedding is available."},"requiresHydration":{"type":"boolean","description":"Provider scripts must run to complete the embed."}}}}}}},"example":{"success":true,"data":{"provider":"x","type":"rich","originalUrl":"https://x.com/USERNAME/status/123456789","resolvedUrl":"https://x.com/i/status/123456789","title":"Example public post text.","author":{"name":"Example author","url":"https://x.com/USERNAME"},"providerInfo":{"name":"X","url":"https://x.com"},"thumbnail":null,"embed":{"html":null,"width":null,"height":null,"scripts":[]},"capabilities":{"html":false,"thumbnail":false,"nativeEmbed":false,"requiresHydration":false}}}}}},"400":{"description":"VALIDATION_ERROR for malformed body or extra fields; OEMBED_INVALID_URL or OEMBED_SECURITY_REJECTED for invalid or unsafe URLs.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"UNAUTHORIZED for invalid API credentials.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"FORBIDDEN for revoked, expired, or insufficiently scoped credentials; OEMBED_PRIVATE_CONTENT when the provider denies access.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"OEMBED_NOT_FOUND when the post is unavailable or removed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Self-service operation already handled/running (USAGE_RESERVATION_CONFLICT) or expired (USAGE_RESERVATION_EXPIRED). No response replay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"OEMBED_UNSUPPORTED_PROVIDER, OEMBED_UNSUPPORTED_URL, or OEMBED_URL_UNRESOLVED for unsupported content or unresolved short links.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"RATE_LIMITED or MONTHLY_QUOTA_EXCEEDED for API limits; OEMBED_RATE_LIMITED for provider throttling.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"OEMBED_UPSTREAM_ERROR or OEMBED_INVALID_RESPONSE for provider failures or unusable responses.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"504":{"description":"OEMBED_UPSTREAM_TIMEOUT when a provider request times out.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Self-service keys only. Owner-scoped operation identifier; reuse after admission returns 409 without another debit or execution. USAGE_CONCURRENCY_EXCEEDED retains no new reservation: wait and retry with the same identifier and payload. No response replay. Video jobs use requestId instead.","schema":{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-zA-Z0-9_-]+$"}}]}},"/api/v1/screenshot":{"post":{"operationId":"captureWebsiteScreenshot","summary":"Capture a website or social post","description":"Capture a public webpage or supported social post (X, Facebook, Instagram, Threads, TikTok, Reddit). Mode is selected from the URL. Share/short links must resolve to a supported public post; unsupported social paths use website capture. X uses a post card; other supported social posts use the provider widget and must be publicly embeddable. Native widget dimensions are provider-controlled, capped at 1920x8192. fullPage and delay only affect websites; X layout options only affect X. Send output=url for immutable PNG URLs with dimensions, byte size, MIME type, freshness and retention metadata. Omitting output or using data-url returns PNG data URIs; both modes require image storage. OAuth and API keys require screenshot:capture; cache hits count toward API-key quotas. Captures remain fresh for one hour; refreshCache bypasses application screenshot/oEmbed caches and requests fresh provider data. Provider caches may still affect freshness. Images are retained for approximately seven days. URL validation, rendering and storage share a 27-second production budget; rendering and provider resolution are limited to 18 seconds. Full-page website height is capped at 8192 pixels. Native widget HTTP errors and incomplete hydration return OEMBED_HYDRATION_FAILED, not a successful image of the error page. Video playback/frame selection is unsupported. Self-service API keys require a paid entitlement and shared subscription credits. Successful execution (including cache hits) consumes the catalog action cost; failures release credits. Owner and key rate limits use one-second windows. See the authentication guide for billing and idempotency semantics.","tags":["Screenshot tools"],"security":[{"pirehubOAuth":["screenshot:capture"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenshotCaptureRequest"},"example":{"url":"https://example.com","width":1280,"height":960,"fullPage":false,"device":"desktop","theme":"light","delay":0,"refreshCache":false,"output":"url"}}}},"responses":{"200":{"description":"URL mode returns PNG URLs and metadata; data-url mode returns PNG data URIs. Auto mode returns URLs on cache hits; fresh captures immediately return data URIs with cached=false while the main image and all variants upload to CDN storage in the background. postVariants is included for X posts when includePostVariants is true.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-ApiKey-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-ApiKey-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-ApiKey-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-Monthly-Limit":{"$ref":"#/components/headers/MonthlyLimit"},"X-ApiKey-Monthly-Remaining":{"$ref":"#/components/headers/MonthlyRemaining"},"X-ApiKey-Monthly-Reset":{"$ref":"#/components/headers/MonthlyReset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Cache-Control":{"schema":{"type":"string"},"description":"The API JSON response uses private, no-store. Image URLs have their own immutable CDN cache policy."},"X-Decoded-Content-Length":{"schema":{"type":"integer","minimum":1},"description":"JSON response byte length before HTTP compression; this is separate from data.size, which counts PNG bytes."},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"Server-Timing":{"$ref":"#/components/headers/ServerTiming"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ScreenshotSuccessResponse"},"examples":{"url":{"summary":"PNG URL and metadata (output=url)","value":{"success":true,"data":{"url":"https://media.ntanduy.com/screenshot/captures/v12/website/0000000000000000000000000000000000000000000000000000000000000000/497454-11111111-1111-4111-8111-111111111111/main.png","width":1280,"height":960,"size":1154736,"format":"png","mimeType":"image/png","freshUntil":"2026-10-01T07:31:24.034Z","expiresAt":"2026-10-08T06:31:32.496Z","cached":false}}},"urlPostVariants":{"summary":"X post URL layouts (output=url, includePostVariants=true)","value":{"success":true,"data":{"url":"https://media.ntanduy.com/screenshot/captures/v12/x/0000000000000000000000000000000000000000000000000000000000000000/497454-11111111-1111-4111-8111-111111111111/main.png","width":1920,"height":1600,"size":1154736,"format":"png","mimeType":"image/png","freshUntil":"2026-10-01T07:31:24.034Z","expiresAt":"2026-10-08T06:31:32.496Z","cached":false,"postVariants":{"s":{"url":"https://media.ntanduy.com/screenshot/captures/v12/x/0000000000000000000000000000000000000000000000000000000000000000/497454-11111111-1111-4111-8111-111111111111/s.png","width":1920,"height":1600,"size":1154736,"format":"png","mimeType":"image/png","freshUntil":"2026-10-01T07:31:24.034Z","expiresAt":"2026-10-08T06:31:32.496Z","parentHeight":240,"topPadding":108},"m":{"url":"https://media.ntanduy.com/screenshot/captures/v12/x/0000000000000000000000000000000000000000000000000000000000000000/497454-11111111-1111-4111-8111-111111111111/m.png","width":2400,"height":1480,"size":1154736,"format":"png","mimeType":"image/png","freshUntil":"2026-10-01T07:31:24.034Z","expiresAt":"2026-10-08T06:31:32.496Z","parentHeight":240,"topPadding":108},"l":{"url":"https://media.ntanduy.com/screenshot/captures/v12/x/0000000000000000000000000000000000000000000000000000000000000000/497454-11111111-1111-4111-8111-111111111111/l.png","width":2880,"height":1320,"size":1154736,"format":"png","mimeType":"image/png","freshUntil":"2026-10-01T07:31:24.034Z","expiresAt":"2026-10-08T06:31:32.496Z","parentHeight":240,"topPadding":108}}}}},"dataUri":{"summary":"Data URI compatibility mode (output=data-url or omitted)","value":{"success":true,"data":{"dataUrl":"data:image/png;base64,...","width":1280,"height":960,"cached":false}}}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"404":{"description":"OEMBED_NOT_FOUND: the social post is unavailable or has been removed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Self-service operation already handled/running (USAGE_RESERVATION_CONFLICT) or expired (USAGE_RESERVATION_EXPIRED). No response replay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Invalid or unsupported capture: page blocking/render deadline, repeated redirects, invalid TLS, interrupted navigation/download, empty target response or target HTTP 4xx (except 429), excessive X variant bytes, OEMBED_CAPTURE_UNSUPPORTED, or OEMBED_SECURITY_REJECTED. HTTP redirects are followed; document replacement during preparation is retried up to twice within the render deadline.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimitError"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"description":"UPSTREAM_BAD_RESPONSE for target website HTTP 5xx, connection failures or unusable responses. Target HTTP 429 returns 429 RATE_LIMITED. Social loading failures: OEMBED_HYDRATION_FAILED (SDK/frame/assets unavailable or widget HTTP error), OEMBED_URL_UNRESOLVED (share link did not resolve), OEMBED_PRIVATE_CONTENT, OEMBED_UNSUPPORTED_URL, OEMBED_INVALID_RESPONSE, or OEMBED_UPSTREAM_ERROR. delay does not extend social hydration.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"widgetFailed":{"summary":"Social widget could not load","value":{"success":false,"error":{"code":"OEMBED_HYDRATION_FAILED","message":"The social embed could not finish loading. It may be unavailable; please try again later."},"data":{"requestId":"1bfa5469-ad2d-4790-8ac7-9556ce85dce6"}}},"shareLinkUnresolved":{"summary":"Try the direct post permalink","value":{"success":false,"error":{"code":"OEMBED_URL_UNRESOLVED","message":"The social post could not be loaded. It may be private or unavailable."},"data":{"requestId":"c4464b21-fb11-446c-8402-bebadf7e5fd0"}}}}}}},"503":{"description":"SCREENSHOT_STORAGE_FAILED for storage configuration/upload failures; SERVICE_UNAVAILABLE for capacity or required dependencies.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"storageUnavailable":{"summary":"PNG storage failed","value":{"success":false,"error":{"code":"SCREENSHOT_STORAGE_FAILED","message":"Screenshot storage is unavailable. Please try again later."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}}}},"504":{"description":"UPSTREAM_TIMEOUT for website connection, URL classification, total request or storage timeout; OEMBED_UPSTREAM_TIMEOUT for provider resolution; OEMBED_RENDER_TIMEOUT during native image capture. An unfinished social hydration deadline returns 502 OEMBED_HYDRATION_FAILED.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Self-service keys only. Owner-scoped operation identifier; reuse after admission returns 409 without another debit or execution. USAGE_CONCURRENCY_EXCEEDED retains no new reservation: wait and retry with the same identifier and payload. No response replay. Video jobs use requestId instead.","schema":{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-zA-Z0-9_-]+$"}}]}},"/api/v1/domain/whois":{"post":{"operationId":"lookupDomainWhois","summary":"Lookup domain WHOIS","description":"Returns normalized, privacy-sanitized WHOIS data. Integrations use OAuth or a Pirehub API key with the `domain:whois` scope. The input may be a registrable domain, subdomain, or HTTP(S) URL. An unregistered domain is returned as a successful response with `registered: false`. Self-service API keys require a paid entitlement and shared subscription credits. Successful execution (including cache hits) consumes the catalog action cost; failures release credits. Owner and key rate limits use one-second windows. See the authentication guide for billing and idempotency semantics.","tags":["Domains"],"security":[{"pirehubOAuth":["domain:whois"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhoisRequest"},"examples":{"domain":{"summary":"Lookup a domain","value":{"domain":"example.com"}},"url":{"summary":"Normalize and lookup a URL","value":{"domain":"https://www.example.com/about"}},"forceRefresh":{"summary":"Bypass a cached result","value":{"domain":"example.com","forceRefresh":true}}}}}},"responses":{"200":{"description":"The WHOIS lookup completed. Check `data.registered` to determine whether the domain is registered.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-ApiKey-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-ApiKey-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-ApiKey-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-Monthly-Limit":{"$ref":"#/components/headers/MonthlyLimit"},"X-ApiKey-Monthly-Remaining":{"$ref":"#/components/headers/MonthlyRemaining"},"X-ApiKey-Monthly-Reset":{"$ref":"#/components/headers/MonthlyReset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"Server-Timing":{"$ref":"#/components/headers/ServerTiming"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WhoisSuccessResponse"},"examples":{"registered":{"summary":"Registered domain","value":{"success":true,"data":{"domain":"example.com","query_domain":"www.example.com","registered":true,"registrar":"Example Registrar, Inc.","organization":"Example Organization","creation_date":"2020-01-10T00:00:00.000Z","updated_date":"2026-01-10T00:00:00.000Z","expiration_date":"2027-01-10T00:00:00.000Z","status":["client transfer prohibited"],"dnssec":"unsigned","name_servers":["ns1.example.net","ns2.example.net"],"meta":{"cached":false}}}},"unregistered":{"summary":"Unregistered domain","value":{"success":true,"data":{"domain":"new-example-domain.com","query_domain":"new-example-domain.com","registered":false,"meta":{"cached":false}}}},"cached":{"summary":"Cached result","value":{"success":true,"data":{"domain":"example.com","query_domain":"example.com","registered":true,"meta":{"cached":true}}}}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"409":{"description":"Self-service operation already handled/running (USAGE_RESERVATION_CONFLICT) or expired (USAGE_RESERVATION_EXPIRED). No response replay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimitError"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"$ref":"#/components/responses/UpstreamBadResponse"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"description":"The WHOIS lookup exceeded its deadline.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UPSTREAM_TIMEOUT","message":"WHOIS lookup timed out. Please try again."},"data":{"retryAfter":2,"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Self-service keys only. Owner-scoped operation identifier; reuse after admission returns 409 without another debit or execution. USAGE_CONCURRENCY_EXCEEDED retains no new reservation: wait and retry with the same identifier and payload. No response replay. Video jobs use requestId instead.","schema":{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-zA-Z0-9_-]+$"}}]}},"/api/v1/domain/dns":{"post":{"operationId":"lookupDomainDns","summary":"Lookup domain DNS","description":"Resolves a hostname across all supported DNS record types and returns normalized records, sections, per-query status, and a combined status summary. Integrations use OAuth or a Pirehub API key with the `domain:dns` scope. Self-service API keys require a paid entitlement and shared subscription credits. Successful execution (including cache hits) consumes the catalog action cost; failures release credits. Owner and key rate limits use one-second windows. See the authentication guide for billing and idempotency semantics.","tags":["Domains"],"security":[{"pirehubOAuth":["domain:dns"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DnsLookupRequest"},"examples":{"google":{"summary":"Use the default Google resolver","value":{"domain":"example.com"}},"cloudflare":{"summary":"Use the Cloudflare resolver","value":{"domain":"example.com","provider":"cloudflare"}}}}}},"responses":{"200":{"description":"The DNS lookup completed. Inspect `data.statusSummary.domainExists` and individual query statuses for the resolver outcome.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-ApiKey-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-ApiKey-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-ApiKey-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-Monthly-Limit":{"$ref":"#/components/headers/MonthlyLimit"},"X-ApiKey-Monthly-Remaining":{"$ref":"#/components/headers/MonthlyRemaining"},"X-ApiKey-Monthly-Reset":{"$ref":"#/components/headers/MonthlyReset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"Server-Timing":{"$ref":"#/components/headers/ServerTiming"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DnsLookupSuccessResponse"},"example":{"success":true,"data":{"domain":"example.com","provider":"google","records":[{"section":"answer","name":"example.com","canonicalName":"example.com.","normalizedName":"example.com","typeCode":1,"typeName":"A","ttl":300,"TTL":300,"data":"93.184.216.34","normalizedData":"93.184.216.34"}],"sections":[{"section":"answer","title":"Answer","groups":[{"typeName":"A","typeCode":1,"records":[{"section":"answer","name":"example.com","canonicalName":"example.com.","normalizedName":"example.com","typeCode":1,"typeName":"A","ttl":300,"TTL":300,"data":"93.184.216.34","normalizedData":"93.184.216.34"}]}]}],"queries":[{"query":{"name":"example.com","normalizedName":"example.com","typeName":"A","typeCode":1},"status":{"code":0,"name":"NOERROR","message":"domain_info_page.dns.status_msg.ok"},"flags":{"tc":false,"rd":true,"ra":true,"ad":false,"cd":false},"records":[{"section":"answer","name":"example.com","canonicalName":"example.com.","normalizedName":"example.com","typeCode":1,"typeName":"A","ttl":300,"TTL":300,"data":"93.184.216.34","normalizedData":"93.184.216.34"}],"counts":{"answer":1,"authority":0,"additional":0}}],"statusSummary":{"status":"ok","message":"domain_info_page.dns.status_msg.ok","domainExists":true},"queried_at":"2026-08-11T12:00:00.000Z"}}}}},"400":{"description":"The DNS request domain or resolver selection is invalid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missingDomain":{"value":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Domain is required."},"data":{"field":"domain","requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}},"invalidDomain":{"value":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Invalid domain format."},"data":{"field":"domain","requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}},"domainTooLong":{"value":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Domain exceeds maximum length."},"data":{"field":"domain","max":253,"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}},"unsupportedResolver":{"value":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Unsupported DNS resolver selection."},"data":{"field":"provider","requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}}}},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"409":{"description":"Self-service operation already handled/running (USAGE_RESERVATION_CONFLICT) or expired (USAGE_RESERVATION_EXPIRED). No response replay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimitError"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"description":"The DNS lookup exceeded its deadline.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UPSTREAM_TIMEOUT","message":"DNS lookup timed out. Please try again."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Self-service keys only. Owner-scoped operation identifier; reuse after admission returns 409 without another debit or execution. USAGE_CONCURRENCY_EXCEEDED retains no new reservation: wait and retry with the same identifier and payload. No response replay. Video jobs use requestId instead.","schema":{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-zA-Z0-9_-]+$"}}]}},"/api/v1/ip-info/{ip}":{"post":{"operationId":"lookupIpInformation","summary":"Lookup IP information","description":"Returns normalized, best-effort location, network, and timezone information for a public IPv4 or IPv6 address. Integrations use OAuth or a Pirehub API key with the `ip:lookup` scope. Self-service API keys require a paid entitlement and shared subscription credits. Successful execution (including cache hits) consumes the catalog action cost; failures release credits. Owner and key rate limits use one-second windows. See the authentication guide for billing and idempotency semantics.","tags":["IP information"],"security":[{"pirehubOAuth":["ip:lookup"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"parameters":[{"name":"ip","in":"path","required":true,"description":"A valid public IPv4 or IPv6 address.","schema":{"type":"string","minLength":2},"examples":{"ipv4":{"summary":"IPv4 address","value":"8.8.8.8"},"ipv6":{"summary":"IPv6 address","value":"2001:4860:4860::8888"}}},{"name":"Idempotency-Key","in":"header","required":false,"description":"Self-service keys only. Owner-scoped operation identifier; reuse after admission returns 409 without another debit or execution. USAGE_CONCURRENCY_EXCEEDED retains no new reservation: wait and retry with the same identifier and payload. No response replay. Video jobs use requestId instead.","schema":{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-zA-Z0-9_-]+$"}}],"responses":{"200":{"description":"The IP information lookup completed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-ApiKey-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-ApiKey-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-Monthly-Limit":{"$ref":"#/components/headers/MonthlyLimit"},"X-ApiKey-Monthly-Remaining":{"$ref":"#/components/headers/MonthlyRemaining"},"X-ApiKey-Monthly-Reset":{"$ref":"#/components/headers/MonthlyReset"},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"Server-Timing":{"$ref":"#/components/headers/ServerTiming"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IpInfoSuccessResponse"},"example":{"success":true,"data":{"ip":"8.8.8.8","hostname":"dns.google","city":"Mountain View","region":"California","country":"US","loc":"37.4056,-122.0775","org":"AS15169 Google LLC","postal":"94043","timezone":"America/Los_Angeles"}}}}},"400":{"description":"The path does not contain a valid IP address.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"INVALID_REQUEST","message":"A valid IP address is required."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"409":{"description":"Self-service operation already handled/running (USAGE_RESERVATION_CONFLICT) or expired (USAGE_RESERVATION_EXPIRED). No response replay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimitError"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"$ref":"#/components/responses/UpstreamBadResponse"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/UpstreamTimeout"}}}},"/api/v1/video/extract/jobs":{"post":{"operationId":"startVideoExtraction","summary":"Start asynchronous video extraction","description":"Returns promptly with a short-lived job capability. Requires OAuth or a Pirehub API key with the video:extract scope. Reusing a requestId with the same caller and URL within five minutes does not repeat extraction. Administrator-issued keys retain normal start-request quota. For self-service keys, requestId prevents duplicate execution and debit; repeated starts return 409, and credits settle before the successful job result becomes available. Self-service API keys require a paid entitlement and shared subscription credits. Successful execution (including cache hits) consumes the catalog action cost; failures release credits. Owner and key rate limits use one-second windows. See the authentication guide for billing and idempotency semantics.","tags":["Video tools"],"security":[{"pirehubOAuth":["video:extract"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoExtractJobRequest"}}}},"responses":{"202":{"description":"Accepted. Poll the status route with the returned capability.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoExtractJobAcceptedResponse"}}},"headers":{"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"Server-Timing":{"$ref":"#/components/headers/ServerTiming"}}},"400":{"description":"Invalid URL or requestId.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Invalid credentials.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Missing scope or browser verification failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"The same caller and requestId already refer to a different URL.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimitError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Self-service keys only. Owner-scoped operation identifier; reuse after admission returns 409 without another debit or execution. USAGE_CONCURRENCY_EXCEEDED retains no new reservation: wait and retry with the same identifier and payload. No response replay. Video jobs use requestId instead.","schema":{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-zA-Z0-9_-]+$"}}]}},"/api/v1/video/extract/jobs/{jobId}":{"get":{"operationId":"getVideoExtraction","summary":"Read asynchronous extraction status","description":"Authenticate with the x-video-job-token capability issued when the job was started. Job IDs alone grant no access. Poll about every two seconds. Processing expires after two minutes; records expire after five minutes. Terminal provider errors retain their non-2xx HTTP status and public code.","tags":["Video tools"],"security":[{"videoExtractJobToken":[]}],"parameters":[{"name":"jobId","in":"path","required":true,"schema":{"type":"string","pattern":"^[a-f0-9]{64}$"}}],"responses":{"200":{"description":"Pending or completed with the same result DTO as synchronous extraction.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoExtractJobStatusResponse"}}}},"404":{"description":"Unknown or expired job, or invalid capability.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The media cannot be processed; inspect the public error code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimitError"},"502":{"description":"An upstream dependency returned an unusable response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/UpstreamTimeout"}}}},"/api/v1/video/extract":{"post":{"operationId":"extractVideoInformation","summary":"Extract video information","description":"Extracts normalized metadata and secure download options from a supported public media link. Integrations use OAuth or a Pirehub API key with the `video:extract` scope. Self-service API keys require a paid entitlement and shared subscription credits. Successful execution (including cache hits) consumes the catalog action cost; failures release credits. Owner and key rate limits use one-second windows. See the authentication guide for billing and idempotency semantics.","tags":["Video tools"],"security":[{"pirehubOAuth":["video:extract"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoExtractRequest"},"examples":{"youtube":{"summary":"YouTube video","value":{"url":"https://www.youtube.com/watch?v=VIDEO_ID"}},"tiktok":{"summary":"TikTok video","value":{"url":"https://www.tiktok.com/@creator/video/VIDEO_ID"}},"additionalSite":{"summary":"Additional supported site","value":{"url":"https://vimeo.com/VIDEO_ID"}},"reddit":{"summary":"Reddit post","value":{"url":"https://www.reddit.com/r/videos/comments/POST_ID/example/"}}}}}},"responses":{"200":{"description":"The public video metadata and available secure download options were extracted.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-ApiKey-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-ApiKey-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-Monthly-Limit":{"$ref":"#/components/headers/MonthlyLimit"},"X-ApiKey-Monthly-Remaining":{"$ref":"#/components/headers/MonthlyRemaining"},"X-ApiKey-Monthly-Reset":{"$ref":"#/components/headers/MonthlyReset"},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"Server-Timing":{"$ref":"#/components/headers/ServerTiming"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VideoExtractSuccessResponse"},"example":{"success":true,"data":{"platform":"youtube","sourceId":"VIDEO_ID","title":"Example video","previewUrl":"/api/v1/video/preview/opaque-preview-token","author":{"displayName":"Example creator","handle":"example"},"durationSeconds":95,"stats":{"views":12500,"likes":640,"comments":28},"variants":[{"id":"video_1080p","downloadToken":"opaque-short-lived-token","requiresPreparation":false,"label":"Video 1080p","kind":"video","extension":"mp4","mimeType":"video/mp4","sizeBytes":52428800,"qualityLabel":"1920x1080","recommended":true}]}}}}},"400":{"description":"The request body or media URL is invalid, or no configured extractor can accept the URL.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"invalidUrl":{"value":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Please enter a valid video link."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}},"unsupportedUrl":{"summary":"No configured extractor accepts the URL","value":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"This video platform is not supported by the configured extractor."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1","platform":"unknown","supportedPlatforms":["facebook","instagram","tiktok","youtube","twitter"]}}}}}}},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"409":{"description":"Self-service operation already handled/running (USAGE_RESERVATION_CONFLICT) or expired (USAGE_RESERVATION_EXPIRED). No response replay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"The platform is recognized but unavailable, or the public video cannot be processed.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"unsupportedPlatform":{"value":{"success":false,"error":{"code":"UNSUPPORTED_PLATFORM","message":"The request could not be processed."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}},"unprocessableContent":{"value":{"success":false,"error":{"code":"UNPROCESSABLE_ENTITY","message":"The request could not be processed."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}},"mediaUnavailable":{"value":{"success":false,"error":{"code":"MEDIA_UNAVAILABLE","message":"This media is currently unavailable. It may have been removed, made private, or restricted."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}}}},"429":{"$ref":"#/components/responses/RateLimitError"},"500":{"$ref":"#/components/responses/InternalError"},"502":{"$ref":"#/components/responses/UpstreamBadResponse"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"description":"A video extraction provider exceeded its deadline.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UPSTREAM_TIMEOUT","message":"The video extractor timed out."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Self-service keys only. Owner-scoped operation identifier; reuse after admission returns 409 without another debit or execution. USAGE_CONCURRENCY_EXCEEDED retains no new reservation: wait and retry with the same identifier and payload. No response replay. Video jobs use requestId instead.","schema":{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-zA-Z0-9_-]+$"}}]}},"/api/v1/video/preview/{token}":{"get":{"operationId":"getVideoPreview","summary":"Load a video preview","description":"Streams a validated image using the short-lived preview URL returned by video extraction. This operation is not wrapped in the JSON success envelope because the response body contains image bytes.","tags":["Video tools"],"security":[],"parameters":[{"name":"token","in":"path","required":true,"description":"Opaque encoded preview token from `data.previewUrl`. Use the returned URL directly instead of constructing this value.","schema":{"type":"string"}}],"responses":{"200":{"description":"The preview image bytes.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-Content-Type-Options":{"description":"Always `nosniff`.","schema":{"type":"string","const":"nosniff"}},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"image/jpeg":{"schema":{"type":"string","format":"binary"}},"image/png":{"schema":{"type":"string","format":"binary"}},"image/webp":{"schema":{"type":"string","format":"binary"}}}},"404":{"description":"The preview token is missing, invalid, or expired.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"NOT_FOUND","message":"Video preview is no longer available."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}},"429":{"description":"The per-IP video endpoint limit was exceeded.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"example":{"success":false,"error":{"code":"RATE_LIMITED","message":"Too many requests. Please try again later."},"data":{"retryAfter":12,"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}},"502":{"description":"None of the validated upstream preview images could be loaded.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UPSTREAM_BAD_RESPONSE","message":"Video preview could not be loaded."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/UpstreamTimeout"}}}},"/api/v1/tools/bank-qr":{"post":{"operationId":"generateBankTransferQr","summary":"Generate a bank transfer QR code","description":"Generates a Vietnamese bank account-transfer payload and an SVG data URI without contacting a QR image provider or storing submitted transfer details. Integrations use OAuth or a Pirehub API key with the `bank-qr:generate` scope. API-key requests use the key's rate and monthly quota policy. Self-service API keys require a paid entitlement and shared subscription credits. Successful execution (including cache hits) consumes the catalog action cost; failures release credits. Owner and key rate limits use one-second windows. See the authentication guide for billing and idempotency semantics.","tags":["Payment utilities"],"security":[{"pirehubOAuth":["bank-qr:generate"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankQrGenerateRequest"},"example":{"bank":"MB","accountNumber":"0123456789","amount":100000,"memo":"Thanh toán DH 1028","imageSize":512}}}},"responses":{"200":{"description":"The normalized transfer details, EMV payload, and locally rendered SVG data URI.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-ApiKey-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-ApiKey-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-Monthly-Limit":{"$ref":"#/components/headers/MonthlyLimit"},"X-ApiKey-Monthly-Remaining":{"$ref":"#/components/headers/MonthlyRemaining"},"X-ApiKey-Monthly-Reset":{"$ref":"#/components/headers/MonthlyReset"},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"Server-Timing":{"$ref":"#/components/headers/ServerTiming"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankQrGenerateSuccessResponse"},"example":{"success":true,"data":{"bank":{"bin":"970422","code":"MB","shortName":"MBBank","name":"Ngân hàng TMCP Quân đội","transferSupported":true},"transfer":{"accountNumber":"0123456789","amount":100000,"memo":"Thanh toan DH 1028"},"qr":{"type":"dynamic","payload":"00020101021238540010A00000072701240006970422011001234567890208QRIBFTTA530370454061000005802VN62220818Thanh toan DH 102863042442","image":{"format":"svg","mimeType":"image/svg+xml","dataUrl":"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iLi4u","size":512}},"warnings":[{"code":"MEMO_NORMALIZED","message":"The transfer memo was normalized for banking-app compatibility."}]}}}}},"400":{"description":"A bank identifier or transfer field is invalid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Account number must contain 6 to 19 digits."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}},"401":{"description":"The supplied API key is invalid, or browser request verification is missing or invalid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UNAUTHORIZED","message":"A valid API key is required."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}},"403":{"description":"The supplied API key is revoked, expired, or lacks the `bank-qr:generate` scope; or browser request verification was denied by policy.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"FORBIDDEN","message":"This API key does not have permission for this endpoint."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}},"409":{"description":"Self-service operation already handled/running (USAGE_RESERVATION_CONFLICT) or expired (USAGE_RESERVATION_EXPIRED). No response replay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"The JSON request body exceeds 4 KiB.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"PAYLOAD_TOO_LARGE","message":"Request payload is too large."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}},"429":{"$ref":"#/components/responses/RateLimitError"},"500":{"$ref":"#/components/responses/InternalError"},"503":{"$ref":"#/components/responses/ServiceUnavailable"}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Self-service keys only. Owner-scoped operation identifier; reuse after admission returns 409 without another debit or execution. USAGE_CONCURRENCY_EXCEEDED retains no new reservation: wait and retry with the same identifier and payload. No response replay. Video jobs use requestId instead.","schema":{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-zA-Z0-9_-]+$"}}]}},"/api/v1/tools/bank-qr/banks":{"get":{"operationId":"listBankQrInstitutions","summary":"List bank QR institutions","description":"Returns the allowlisted bank metadata accepted by the QR generator. Integrations use OAuth or a Pirehub API key with the `bank-qr:banks` scope. Metadata is refreshed from an upstream bank directory and falls back to a versioned local snapshot when the upstream directory is unavailable. The `transferSupported` flag is compatibility metadata, not an account-status guarantee. Self-service API keys require a paid entitlement and shared subscription credits. Successful execution (including cache hits) consumes the catalog action cost; failures release credits. Owner and key rate limits use one-second windows. See the authentication guide for billing and idempotency semantics.","tags":["Payment utilities"],"security":[{"pirehubOAuth":["bank-qr:banks"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"responses":{"200":{"description":"The current validated bank directory.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"Server-Timing":{"$ref":"#/components/headers/ServerTiming"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankQrDirectorySuccessResponse"},"example":{"success":true,"data":{"banks":[{"bin":"970422","code":"MB","shortName":"MBBank","name":"Ngân hàng TMCP Quân đội","transferSupported":true},{"bin":"970436","code":"VCB","shortName":"Vietcombank","name":"Ngân hàng TMCP Ngoại Thương Việt Nam","transferSupported":true}],"count":2,"updatedAt":"2026-08-21T00:00:00.000Z"}}}}},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"409":{"description":"Self-service operation already handled/running (USAGE_RESERVATION_CONFLICT) or expired (USAGE_RESERVATION_EXPIRED). No response replay.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimitError"}},"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Self-service keys only. Owner-scoped operation identifier; reuse after admission returns 409 without another debit or execution. USAGE_CONCURRENCY_EXCEEDED retains no new reservation: wait and retry with the same identifier and payload. No response replay. Video jobs use requestId instead.","schema":{"type":"string","minLength":1,"maxLength":80,"pattern":"^[a-zA-Z0-9_-]+$"}}]}},"/api/v1/subdomains/zones":{"get":{"operationId":"listManagedSubdomainZones","summary":"List managed zones and per-zone quota","tags":["Free subdomains"],"security":[{"clerkSession":[]}],"responses":{"200":{"description":"Managed zones visible to the user, including independent registration gates and per-zone quota."},"401":{"$ref":"#/components/responses/UnauthorizedError"}}}},"/api/v1/subdomains/availability":{"get":{"operationId":"checkSubdomainAvailability","summary":"Check canonical namespace availability","tags":["Free subdomains"],"security":[{"clerkSession":[]}],"parameters":[{"name":"zoneKey","in":"query","required":true,"schema":{"type":"string"}},{"name":"name","in":"query","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Availability only; no owner metadata is returned."},"401":{"$ref":"#/components/responses/UnauthorizedError"},"429":{"$ref":"#/components/responses/RateLimitError"}}}},"/api/v1/subdomains":{"get":{"operationId":"listOwnedSubdomains","summary":"List owned namespaces","tags":["Free subdomains"],"security":[{"clerkSession":[]}],"responses":{"200":{"description":"Cursor page of caller-owned namespaces."},"401":{"$ref":"#/components/responses/UnauthorizedError"}}},"post":{"operationId":"claimSubdomain","summary":"Claim a namespace without creating DNS records","tags":["Free subdomains"],"security":[{"clerkSession":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["zoneKey","name"],"properties":{"zoneKey":{"type":"string"},"name":{"type":"string"}},"additionalProperties":false}}}},"responses":{"201":{"description":"Registration created; records are empty until explicitly added."},"401":{"$ref":"#/components/responses/UnauthorizedError"},"409":{"description":"Quota, reservation, canonical uniqueness, registration gate, or idempotency conflict."},"429":{"$ref":"#/components/responses/RateLimitError"}}}},"/api/v1/subdomains/{subdomainId}":{"parameters":[{"$ref":"#/components/parameters/SubdomainId"}],"get":{"operationId":"getOwnedSubdomain","summary":"Get an owned namespace","tags":["Free subdomains"],"security":[{"clerkSession":[]}],"responses":{"200":{"description":"Namespace, desired records, and recent operations."},"404":{"description":"Not found, including non-owned resources."}}},"delete":{"operationId":"deleteOwnedSubdomain","summary":"Start safe namespace cleanup","description":"Transitions to DELETING. The namespace continues consuming quota until managed provider records are confirmed absent and registration reaches DELETED.","tags":["Free subdomains"],"security":[{"clerkSession":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"202":{"description":"Durable deletion operation accepted or returned idempotently."},"404":{"description":"Not found, including non-owned resources."}}}},"/api/v1/subdomains/{subdomainId}/records":{"parameters":[{"$ref":"#/components/parameters/SubdomainId"}],"get":{"operationId":"listOwnedDnsRecords","summary":"List desired DNS records","tags":["Free subdomains"],"security":[{"clerkSession":[]}],"responses":{"200":{"description":"Cursor page of caller-owned records with sync and propagation states."},"404":{"description":"Not found, including non-owned resources."}}},"post":{"operationId":"createOwnedDnsRecord","summary":"Create desired DNS state and an asynchronous operation","tags":["Free subdomains"],"security":[{"clerkSession":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DnsRecordMutation"}}}},"responses":{"202":{"description":"Desired record and durable provider operation accepted."},"409":{"description":"Lifecycle, quota, CNAME owner-mode, or idempotency conflict."},"422":{"description":"Invalid DNS name, record data, or non-globally-routable A/AAAA address."}}}},"/api/v1/subdomains/{subdomainId}/records/{recordId}":{"parameters":[{"$ref":"#/components/parameters/SubdomainId"},{"$ref":"#/components/parameters/DnsRecordId"}],"patch":{"operationId":"updateOwnedDnsRecord","summary":"Update desired DNS state","tags":["Free subdomains"],"security":[{"clerkSession":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/IfMatchVersion"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DnsRecordMutation"}}}},"responses":{"202":{"description":"Updated desired record and durable operation accepted."},"404":{"description":"Not found, including non-owned resources."},"409":{"description":"Version, owner-mode, lifecycle, or idempotency conflict."}}},"delete":{"operationId":"deleteOwnedDnsRecord","summary":"Set a DNS record desired state to absent","tags":["Free subdomains"],"security":[{"clerkSession":[]}],"parameters":[{"$ref":"#/components/parameters/IdempotencyKey"},{"$ref":"#/components/parameters/IfMatchVersion"}],"responses":{"202":{"description":"Provider cleanup accepted. Desired row is removed only after confirmed absence; audit survives."},"404":{"description":"Not found, including non-owned resources."},"409":{"description":"Version or idempotency conflict."}}}},"/api/v1/subdomains/{subdomainId}/records/{recordId}/verify":{"post":{"operationId":"verifyOwnedDnsRecordPropagation","summary":"Queue DNS-only propagation verification","tags":["Free subdomains"],"security":[{"clerkSession":[]}],"parameters":[{"$ref":"#/components/parameters/SubdomainId"},{"$ref":"#/components/parameters/DnsRecordId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"responses":{"202":{"description":"DNS resolver comparison operation accepted; no website fetch is performed."},"409":{"description":"Record is not present and provider-synced."}}}},"/api/v1/subdomains/{subdomainId}/records/{recordId}/retry":{"post":{"operationId":"retryOwnedDnsRecordOperation","summary":"Retry a retryable failed provider operation","tags":["Free subdomains"],"security":[{"clerkSession":[]}],"parameters":[{"$ref":"#/components/parameters/SubdomainId"},{"$ref":"#/components/parameters/DnsRecordId"},{"$ref":"#/components/parameters/IdempotencyKey"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["expectedStatus"],"properties":{"expectedStatus":{"type":"string","enum":["RETRY_WAIT","DEAD"]}},"additionalProperties":false}}}},"responses":{"202":{"description":"Prior retry-wait attempt is superseded and a new durable operation is accepted."},"409":{"description":"Failure is terminal, stale, or not retryable."}}}},"/api/v1/subdomains/{subdomainId}/operations/{operationId}":{"get":{"operationId":"getOwnedDnsOperation","summary":"Get asynchronous operation status","tags":["Free subdomains"],"security":[{"clerkSession":[]}],"parameters":[{"$ref":"#/components/parameters/SubdomainId"},{"name":"operationId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Operation lease/retry status without provider payloads."},"404":{"description":"Not found, including non-owned resources."}}}},"/api/v1/subdomains/{subdomainId}/audit":{"get":{"operationId":"listOwnedSubdomainAudit","summary":"List the last 90 days of owner-visible audit","tags":["Free subdomains"],"security":[{"clerkSession":[]}],"parameters":[{"$ref":"#/components/parameters/SubdomainId"}],"responses":{"200":{"description":"Redacted owner-visible audit page."},"404":{"description":"Not found, including non-owned resources."}}}},"/api/v1/web/search":{"post":{"operationId":"searchWeb","summary":"Search the web","description":"Requires web:search via an issued API key or OAuth token; browser sessions alone are not accepted. JSON bodies are limited to 16 KiB and reject unknown fields. Responses use the common envelope and are not cached. See the Web data guide for provider-independent semantics and limits.","tags":["Web data"],"security":[{"pirehubOAuth":["web:search"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"responses":{"200":{"description":"Successful operation.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Cache-Control":{"schema":{"type":"string","const":"private, no-store"}},"X-ApiKey-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-ApiKey-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-ApiKey-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"X-ApiKey-Monthly-Limit":{"$ref":"#/components/headers/MonthlyLimit"},"X-ApiKey-Monthly-Remaining":{"$ref":"#/components/headers/MonthlyRemaining"},"X-ApiKey-Monthly-Reset":{"$ref":"#/components/headers/MonthlyReset"},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/WebSearchResult"}}},"example":{"success":true,"data":{"query":"example","results":[{"url":"https://example.com/","title":"Example Domain","description":null,"content":null,"contentFormat":null,"contentTruncated":false,"httpStatus":null}]}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"409":{"description":"Duplicate crawl request or idempotency conflict.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"JSON body exceeds 16 KiB.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"Content-Type must be application/json.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimitError"},"502":{"$ref":"#/components/responses/UpstreamBadResponse"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/UpstreamTimeout"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebSearchRequest"},"example":{"query":"example","limit":5}}}}}},"/api/v1/web/scrape":{"post":{"operationId":"scrapeWeb","summary":"Extract a web page","description":"Requires web:scrape via an issued API key or OAuth token; browser sessions alone are not accepted. JSON bodies are limited to 16 KiB and reject unknown fields. Responses use the common envelope and are not cached. See the Web data guide for provider-independent semantics and limits.","tags":["Web data"],"security":[{"pirehubOAuth":["web:scrape"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"responses":{"200":{"description":"Successful operation.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Cache-Control":{"schema":{"type":"string","const":"private, no-store"}},"X-ApiKey-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-ApiKey-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-ApiKey-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"X-ApiKey-Monthly-Limit":{"$ref":"#/components/headers/MonthlyLimit"},"X-ApiKey-Monthly-Remaining":{"$ref":"#/components/headers/MonthlyRemaining"},"X-ApiKey-Monthly-Reset":{"$ref":"#/components/headers/MonthlyReset"},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/WebScrapeResult"}}},"example":{"success":true,"data":{"document":{"url":"https://example.com/","title":"Example Domain","description":null,"content":"# Example Domain","contentFormat":"markdown","contentTruncated":false,"httpStatus":200}}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"409":{"description":"Duplicate crawl request or idempotency conflict.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"JSON body exceeds 16 KiB.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"Content-Type must be application/json.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimitError"},"502":{"$ref":"#/components/responses/UpstreamBadResponse"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/UpstreamTimeout"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebScrapeRequest"},"example":{"url":"https://example.com/"}}}}}},"/api/v1/web/map":{"post":{"operationId":"mapWeb","summary":"Discover website URLs","description":"Requires web:map via an issued API key or OAuth token; browser sessions alone are not accepted. JSON bodies are limited to 16 KiB and reject unknown fields. Responses use the common envelope and are not cached. See the Web data guide for provider-independent semantics and limits.","tags":["Web data"],"security":[{"pirehubOAuth":["web:map"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"responses":{"200":{"description":"Successful operation.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Cache-Control":{"schema":{"type":"string","const":"private, no-store"}},"X-ApiKey-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-ApiKey-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-ApiKey-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"X-ApiKey-Monthly-Limit":{"$ref":"#/components/headers/MonthlyLimit"},"X-ApiKey-Monthly-Remaining":{"$ref":"#/components/headers/MonthlyRemaining"},"X-ApiKey-Monthly-Reset":{"$ref":"#/components/headers/MonthlyReset"},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/WebMapResult"}}},"example":{"success":true,"data":{"url":"https://example.com/","links":[{"url":"https://example.com/","title":"Example Domain","description":null}]}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"409":{"description":"Duplicate crawl request or idempotency conflict.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"JSON body exceeds 16 KiB.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"Content-Type must be application/json.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimitError"},"502":{"$ref":"#/components/responses/UpstreamBadResponse"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/UpstreamTimeout"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebMapRequest"},"example":{"url":"https://example.com/"}}}}}},"/api/v1/web/crawls":{"post":{"operationId":"startWebCrawl","summary":"Start a website crawl","description":"Requires web:crawl via an issued API key or OAuth token; browser sessions alone are not accepted. JSON bodies are limited to 16 KiB and reject unknown fields. Responses use the common envelope and are not cached. See the Web data guide for provider-independent semantics and limits. An asynchronous submission returns 202 and a job ID; a synchronous crawl returns 200 with id=null. For subscription-backed keys, submission usage is settled on successful admission, including 202; later job failure or cancellation does not automatically reverse it. See Authentication for execution and usage rules. requestId deduplicates admission for 24 hours.","tags":["Web data"],"security":[{"pirehubOAuth":["web:crawl"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"responses":{"200":{"description":"Successful operation.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Cache-Control":{"schema":{"type":"string","const":"private, no-store"}},"X-ApiKey-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-ApiKey-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-ApiKey-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"X-ApiKey-Monthly-Limit":{"$ref":"#/components/headers/MonthlyLimit"},"X-ApiKey-Monthly-Remaining":{"$ref":"#/components/headers/MonthlyRemaining"},"X-ApiKey-Monthly-Reset":{"$ref":"#/components/headers/MonthlyReset"},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/WebCrawlResult"}}},"example":{"success":true,"data":{"id":"a24abf21-8c7f-49c4-91d2-43c31e71fca2","status":"completed","total":1,"completed":1,"documents":[{"url":"https://example.com/","title":"Example Domain","description":null,"content":"# Example Domain","contentFormat":"markdown","contentTruncated":false,"httpStatus":200}],"nextOffset":null,"expiresAt":"2026-10-14T12:00:00Z"}}}}},"202":{"description":"Crawl accepted. Submission credits are consumed on admission.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Cache-Control":{"schema":{"type":"string","const":"private, no-store"}},"X-ApiKey-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-ApiKey-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-ApiKey-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"X-ApiKey-Monthly-Limit":{"$ref":"#/components/headers/MonthlyLimit"},"X-ApiKey-Monthly-Remaining":{"$ref":"#/components/headers/MonthlyRemaining"},"X-ApiKey-Monthly-Reset":{"$ref":"#/components/headers/MonthlyReset"},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/WebCrawlResult"}}},"example":{"success":true,"data":{"id":"a24abf21-8c7f-49c4-91d2-43c31e71fca2","status":"pending","total":null,"completed":0,"documents":[],"nextOffset":null,"expiresAt":"2026-10-14T12:00:00Z"}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"409":{"description":"Duplicate crawl request or idempotency conflict.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"413":{"description":"JSON body exceeds 16 KiB.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"415":{"description":"Content-Type must be application/json.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimitError"},"502":{"$ref":"#/components/responses/UpstreamBadResponse"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/UpstreamTimeout"}},"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebCrawlRequest"},"example":{"url":"https://example.com/","requestId":"01952140-e5b8-4e60-94fd-387c15f13257","limit":10,"maxDepth":1}}}}}},"/api/v1/web/crawls/{id}":{"get":{"operationId":"getWebCrawl","summary":"Read a website crawl","description":"Requires web:crawl via an issued API key or OAuth token; browser sessions alone are not accepted. JSON bodies are limited to 16 KiB and reject unknown fields. Responses use the common envelope and are not cached. See the Web data guide for provider-independent semantics and limits. Requires the original API key or original OAuth client and subject. Each successful read or cancellation is a separate operation. View current operation costs in the Developer API keys dashboard. Do not stop pagination solely because status is completed; follow nextOffset until null.","tags":["Web data"],"security":[{"pirehubOAuth":["web:crawl"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"responses":{"200":{"description":"Successful operation.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Cache-Control":{"schema":{"type":"string","const":"private, no-store"}},"X-ApiKey-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-ApiKey-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-ApiKey-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"X-ApiKey-Monthly-Limit":{"$ref":"#/components/headers/MonthlyLimit"},"X-ApiKey-Monthly-Remaining":{"$ref":"#/components/headers/MonthlyRemaining"},"X-ApiKey-Monthly-Reset":{"$ref":"#/components/headers/MonthlyReset"},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/WebCrawlResult"}}},"example":{"success":true,"data":{"id":"a24abf21-8c7f-49c4-91d2-43c31e71fca2","status":"completed","total":1,"completed":1,"documents":[{"url":"https://example.com/","title":"Example Domain","description":null,"content":"# Example Domain","contentFormat":"markdown","contentTruncated":false,"httpStatus":200}],"nextOffset":null,"expiresAt":"2026-10-14T12:00:00Z"}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"404":{"description":"Crawl not found, expired, or owned by another credential.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Duplicate crawl request or idempotency conflict.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimitError"},"502":{"$ref":"#/components/responses/UpstreamBadResponse"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/UpstreamTimeout"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},{"name":"offset","in":"query","schema":{"type":"integer","minimum":0,"maximum":10000,"default":0},"description":"Use the previous nextOffset; poll the same offset when no new documents are available."}]},"delete":{"operationId":"cancelWebCrawl","summary":"Cancel a website crawl","description":"Requires web:crawl via an issued API key or OAuth token; browser sessions alone are not accepted. JSON bodies are limited to 16 KiB and reject unknown fields. Responses use the common envelope and are not cached. See the Web data guide for provider-independent semantics and limits. Requires the original API key or original OAuth client and subject. Each successful read or cancellation is a separate operation. View current operation costs in the Developer API keys dashboard.","tags":["Web data"],"security":[{"pirehubOAuth":["web:crawl"]},{"pirehubApiKey":[]},{"bearerApiKey":[]}],"responses":{"200":{"description":"Successful operation.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Cache-Control":{"schema":{"type":"string","const":"private, no-store"}},"X-ApiKey-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-ApiKey-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-ApiKey-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-RateLimit-Window":{"$ref":"#/components/headers/ApiRateWindow"},"X-ApiKey-Monthly-Limit":{"$ref":"#/components/headers/MonthlyLimit"},"X-ApiKey-Monthly-Remaining":{"$ref":"#/components/headers/MonthlyRemaining"},"X-ApiKey-Monthly-Reset":{"$ref":"#/components/headers/MonthlyReset"},"X-ApiKey-Credit-Cost":{"$ref":"#/components/headers/ApiCreditCost"},"X-ApiKey-Credit-Limit":{"$ref":"#/components/headers/ApiCreditLimit"},"X-ApiKey-Credit-Reset":{"$ref":"#/components/headers/ApiCreditReset"}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"const":true},"data":{"$ref":"#/components/schemas/WebCrawlCancellation"}}},"example":{"success":true,"data":{"id":"a24abf21-8c7f-49c4-91d2-43c31e71fca2","status":"cancelled"}}}}},"400":{"$ref":"#/components/responses/ValidationError"},"401":{"$ref":"#/components/responses/UnauthorizedError"},"403":{"$ref":"#/components/responses/ForbiddenError"},"404":{"description":"Crawl not found, expired, or owned by another credential.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Duplicate crawl request or idempotency conflict.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"$ref":"#/components/responses/RateLimitError"},"502":{"$ref":"#/components/responses/UpstreamBadResponse"},"503":{"$ref":"#/components/responses/ServiceUnavailable"},"504":{"$ref":"#/components/responses/UpstreamTimeout"}},"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string","format":"uuid"}}]}}},"components":{"securitySchemes":{"videoExtractJobToken":{"type":"apiKey","in":"header","name":"x-video-job-token","description":"Short-lived sealed capability issued by the asynchronous extraction start endpoint."},"pirehubOAuth":{"type":"oauth2","description":"OAuth 2.0 Authorization Code with PKCE, issued by Pirehub's Clerk authorization server. Request only the scopes required by the operation.","flows":{"authorizationCode":{"authorizationUrl":"https://clerk.ntanduy.com/oauth/authorize","tokenUrl":"https://clerk.ntanduy.com/oauth/token","refreshUrl":"https://clerk.ntanduy.com/oauth/token","scopes":{"domain:whois":"Read normalized public WHOIS data.","domain:dns":"Read DNS records for a hostname.","ip:lookup":"Read allowlisted public IP information.","video:extract":"Read normalized metadata for a public video URL.","screenshot:capture":"Capture a public webpage as a PNG image.","oembed:read":"Resolve a public social URL to oEmbed data.","bank-qr:generate":"Generate a Vietnamese bank transfer QR payload and SVG.","bank-qr:banks":"Read the supported bank QR institution directory.","web:search":"Search the web.","web:scrape":"Extract public webpage content.","web:map":"Discover website URLs.","web:crawl":"Submit, read and cancel website crawls."}}}},"pirehubApiKey":{"type":"apiKey","in":"header","name":"x-pirehub-api-key","description":"A scoped Pirehub API key. Each operation documents its required scope."},"bearerApiKey":{"type":"http","scheme":"bearer","bearerFormat":"Pirehub API key","description":"A Pirehub API key beginning with `ph_live_`."},"clerkSession":{"type":"apiKey","in":"cookie","name":"__session","description":"Authenticated Clerk browser session resolved to an active AppUser."}},"parameters":{"SubdomainId":{"name":"subdomainId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"DnsRecordId":{"name":"recordId","in":"path","required":true,"schema":{"type":"string","format":"uuid"}},"IdempotencyKey":{"name":"Idempotency-Key","in":"header","required":true,"schema":{"type":"string","minLength":16,"maxLength":128}},"IfMatchVersion":{"name":"If-Match","in":"header","required":true,"description":"Current positive integer resource version.","schema":{"type":"string","pattern":"^[1-9][0-9]*$"}}},"schemas":{"ScreenshotCaptureRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","maxLength":2048,"description":"Public HTTP(S) URL without credentials; only ports 80 and 443 are allowed. Supported post URLs automatically select social capture; share/short links must resolve to a supported permalink."},"width":{"type":"integer","minimum":300,"maximum":1920,"default":1280,"description":"Browser viewport width in CSS pixels. Website output uses pixelRatio. Does not set X card width or guarantee native social widget width."},"height":{"type":"integer","minimum":168,"maximum":4096,"default":720,"description":"Browser viewport height in CSS pixels. Website output uses pixelRatio. Native social widget and full-page output heights can differ."},"pixelRatio":{"type":"integer","enum":[1,2,3],"default":1,"description":"Websites only: output pixels per CSS pixel, without changing responsive layout. A 360x640 mobile viewport at 3 produces 1080x1920. Output is proportionally reduced if a dimension exceeds 8192 pixels. Full-page CSS height is capped at floor(8192 / pixelRatio). Ignored for social cards and widgets."},"fullPage":{"type":"boolean","description":"Websites only: capture the page up to 8192 pixels high instead of only the viewport. Ignored for social posts.","default":false},"device":{"type":"string","enum":["desktop","mobile"],"default":"desktop","description":"Browser device emulation for websites and native social widgets. Ignored for X cards."},"theme":{"type":"string","enum":["light","dark"],"default":"light","description":"Browser color-scheme preference for websites and native social widgets; providers control widget appearance. Ignored for X cards."},"delay":{"type":"integer","enum":[0,2000,5000],"default":0,"description":"Websites only: additional capture delay in milliseconds. Does not extend social hydration."},"refreshCache":{"type":"boolean","default":false,"description":"Bypass application screenshot and oEmbed caches, request fresh provider data, and create new immutable image objects. Provider-controlled caches may still affect freshness."},"showParentPost":{"type":"boolean","default":true,"description":"X/Twitter post cards only: include the parent post when available."},"includePostVariants":{"type":"boolean","default":false,"description":"X/Twitter only: return S/M/L PNG layouts for local editing. URL mode returns image metadata and URLs; data-url mode returns data URIs. Variants include any available parent; the main image follows showParentPost. URL and auto modes limit each PNG variant to 12 MiB, including fresh auto responses. Data-url mode limits combined PNG variant bytes to 12 MiB, including cached captures; use URL or auto mode for larger sets."},"postWidth":{"type":"string","enum":["s","m","l"],"default":"s","description":"X/Twitter post card layout width: s=640, m=800, l=960 CSS pixels. Cards render at 3x density, scaled down if necessary to fit 8192 pixels. Website viewport, device, theme, delay and fullPage options do not affect post cards; supplied values must still be valid."},"output":{"type":"string","enum":["data-url","url","auto"],"default":"data-url","description":"Default preserves PNG dataUrl responses. URL mode returns immutable public CDN images retained for approximately seven days. URL and data-url modes require image storage. Auto mode returns URLs on cache hits and PNG data URIs immediately on fresh captures (cached=false, no CDN expiry metadata). The main image and all requested S/M/L variants upload in the background with a separate five-minute deadline; cache publication requires all uploads to succeed. Foreground storage checks time out after three seconds. Upload failure does not invalidate returned images; background work is not durable across server restarts. Auto allows 12 MiB per PNG (36 MiB of variants plus the main image); base64 adds about one third. Inspect url versus dataUrl; provider/render failures remain errors."}}},"ScreenshotSuccessResponse":{"type":"object","required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"oneOf":[{"title":"URL response (output=url or auto cache hit)","allOf":[{"$ref":"#/components/schemas/ScreenshotUrlImage"},{"type":"object","required":["cached"],"properties":{"cached":{"type":"boolean"},"postVariants":{"type":"object","required":["s","m","l"],"properties":{"s":{"$ref":"#/components/schemas/ScreenshotUrlPostVariant"},"m":{"$ref":"#/components/schemas/ScreenshotUrlPostVariant"},"l":{"$ref":"#/components/schemas/ScreenshotUrlPostVariant"}},"description":"Present only for X posts with includePostVariants=true; each layout has image URL metadata and parent-crop dimensions."}}}]},{"title":"Data URI response (output=data-url, omitted, or fresh auto capture)","type":"object","required":["cached","dataUrl","width","height"],"properties":{"cached":{"type":"boolean"},"dataUrl":{"type":"string","description":"PNG data URI: data:image/png;base64,..."},"width":{"type":"integer","description":"Actual output image width in pixels."},"height":{"type":"integer","description":"Actual output image height in pixels; may differ from the viewport height."},"postVariants":{"type":"object","description":"Present for X posts when includePostVariants is true.","required":["s","m","l"],"properties":{"s":{"$ref":"#/components/schemas/ScreenshotPostVariant"},"m":{"$ref":"#/components/schemas/ScreenshotPostVariant"},"l":{"$ref":"#/components/schemas/ScreenshotPostVariant"}}}}}]}}},"ScreenshotPostVariant":{"type":"object","required":["dataUrl","width","height","parentHeight","topPadding"],"properties":{"dataUrl":{"type":"string","description":"PNG data URI including any available parent post."},"width":{"type":"integer"},"height":{"type":"integer"},"parentHeight":{"type":"integer","minimum":0,"description":"Pixels removed when hiding the parent; zero if absent."},"topPadding":{"type":"integer","minimum":0,"description":"Top padding in pixels. After removing the parent, repaint this padding with #0b0d10 to remove the thread connector."}}},"ScreenshotUrlImage":{"type":"object","required":["url","width","height","format","mimeType","size","freshUntil","expiresAt"],"properties":{"url":{"type":"string","format":"uri","description":"Public HTTPS URL of an immutable PNG. Treat the object path as opaque; URLs in examples are illustrative."},"width":{"type":"integer","minimum":1,"maximum":8192,"description":"Actual output image width in pixels."},"height":{"type":"integer","minimum":1,"maximum":8192,"description":"Actual output image height in pixels."},"format":{"const":"png","type":"string"},"mimeType":{"const":"image/png","type":"string"},"size":{"type":"integer","minimum":1,"maximum":12582912,"description":"PNG file size in bytes; excludes JSON response and Base64 encoding."},"freshUntil":{"type":"string","format":"date-time","description":"UTC timestamp until which the captured image may be reused from cache. Freshness is one hour from capture start."},"expiresAt":{"type":"string","format":"date-time","description":"Expected R2 retention boundary; lifecycle deletion is asynchronous."}}},"ScreenshotUrlPostVariant":{"allOf":[{"$ref":"#/components/schemas/ScreenshotUrlImage"},{"type":"object","required":["parentHeight","topPadding"],"properties":{"parentHeight":{"type":"integer","minimum":0,"description":"Parent-post height in output pixels; zero when no parent is available."},"topPadding":{"type":"integer","minimum":0,"description":"Top-padding height in output pixels, used when cropping the parent locally."}}}]},"DnsRecordMutation":{"oneOf":[{"type":"object","required":["type","name","address","ttl","proxied"],"properties":{"type":{"const":"A"},"name":{"type":"string"},"address":{"type":"string"},"ttl":{"type":"integer"},"proxied":{"type":"boolean"}},"additionalProperties":false},{"type":"object","required":["type","name","address","ttl","proxied"],"properties":{"type":{"const":"AAAA"},"name":{"type":"string"},"address":{"type":"string"},"ttl":{"type":"integer"},"proxied":{"type":"boolean"}},"additionalProperties":false},{"type":"object","required":["type","name","target","ttl","proxied"],"properties":{"type":{"const":"CNAME"},"name":{"type":"string"},"target":{"type":"string"},"ttl":{"type":"integer"},"proxied":{"type":"boolean"}},"additionalProperties":false},{"type":"object","required":["type","name","text","ttl"],"properties":{"type":{"const":"TXT"},"name":{"type":"string"},"text":{"type":"string","maxLength":2048},"ttl":{"type":"integer"},"proxied":{"const":false}},"additionalProperties":false},{"type":"object","required":["type","name","target","priority","ttl"],"properties":{"type":{"const":"MX"},"name":{"type":"string"},"target":{"type":"string"},"priority":{"type":"integer","minimum":0,"maximum":65535},"ttl":{"type":"integer"},"proxied":{"const":false}},"additionalProperties":false},{"type":"object","required":["type","name","target","priority","weight","port","ttl"],"properties":{"type":{"const":"SRV"},"name":{"type":"string"},"target":{"type":"string"},"priority":{"type":"integer","minimum":0,"maximum":65535},"weight":{"type":"integer","minimum":0,"maximum":65535},"port":{"type":"integer","minimum":0,"maximum":65535},"ttl":{"type":"integer"},"proxied":{"const":false}},"additionalProperties":false}],"discriminator":{"propertyName":"type"},"description":"Strict desired-record mutation. Relative names may be @, nested labels, *, or a left-most nested wildcard such as *.preview. A/AAAA accept globally routable addresses only as a product/security restriction."},"BankQrGenerateRequest":{"type":"object","additionalProperties":false,"required":["bank","accountNumber"],"properties":{"bank":{"type":"string","minLength":2,"maxLength":64,"description":"A six-digit BIN, bank code, or short name returned by the bank-directory endpoint."},"accountNumber":{"type":"string","pattern":"^[0-9]{6,19}$","description":"Beneficiary account number. Keep this as a JSON string so leading zeroes are preserved."},"amount":{"type":"integer","minimum":1,"maximum":9999999999999,"description":"Optional amount in VND. Omit to create an open-amount QR."},"memo":{"type":"string","maxLength":200,"description":"Optional transfer memo. It is folded to ASCII, sanitized, and truncated to 25 characters for compatibility."},"imageSize":{"type":"integer","minimum":160,"maximum":1024,"default":512,"description":"SVG width and height in pixels."}}},"BankQrGenerateSuccessResponse":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":false,"required":["bank","transfer","qr","warnings"],"properties":{"bank":{"$ref":"#/components/schemas/BankQrBank"},"transfer":{"type":"object","additionalProperties":false,"required":["accountNumber"],"properties":{"accountNumber":{"type":"string","pattern":"^[0-9]{6,19}$"},"amount":{"type":"integer","minimum":1,"maximum":9999999999999},"memo":{"type":"string","maxLength":25}}},"qr":{"type":"object","additionalProperties":false,"required":["type","payload","image"],"properties":{"type":{"type":"string","enum":["static","dynamic"]},"payload":{"type":"string"},"image":{"type":"object","additionalProperties":false,"required":["format","mimeType","dataUrl","size"],"properties":{"format":{"type":"string","const":"svg"},"mimeType":{"type":"string","const":"image/svg+xml"},"dataUrl":{"type":"string","pattern":"^data:image/svg\\+xml;base64,"},"size":{"type":"integer","minimum":160,"maximum":1024}}}}},"warnings":{"type":"array","maxItems":2,"items":{"type":"object","additionalProperties":false,"required":["code","message"],"properties":{"code":{"type":"string","enum":["MEMO_NORMALIZED","LIMITED_TRANSFER_SUPPORT"]},"message":{"type":"string"}}}}}}}},"BankQrBank":{"type":"object","additionalProperties":false,"required":["bin","code","shortName","name","transferSupported"],"properties":{"bin":{"type":"string","pattern":"^[0-9]{6}$"},"code":{"type":"string","minLength":1,"maxLength":32},"shortName":{"type":"string","minLength":1,"maxLength":80},"name":{"type":"string","minLength":1,"maxLength":200},"transferSupported":{"type":"boolean"},"logo":{"type":"string","format":"uri","description":"Optional public bank logo URL from an allowlisted asset host. Clients should provide a local fallback when it is absent or cannot be loaded."}}},"BankQrDirectorySuccessResponse":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":false,"required":["banks","count","updatedAt"],"properties":{"banks":{"type":"array","minItems":1,"maxItems":100,"items":{"$ref":"#/components/schemas/BankQrBank"}},"count":{"type":"integer","minimum":1,"maximum":100},"updatedAt":{"type":"string","format":"date-time"}}}}},"WhoisRequest":{"type":"object","required":["domain"],"properties":{"domain":{"type":"string","minLength":1,"description":"A domain, subdomain, or HTTP(S) URL. The API strips the URL path and normalizes the hostname before lookup.","examples":["example.com","www.example.com","https://www.example.com/about"]},"forceRefresh":{"type":"boolean","default":false,"description":"Attempt a fresh upstream WHOIS lookup. If the provider fails and cached data exists, the API may return it with `meta.stale: true`."}}},"WhoisSuccessResponse":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/WhoisData"}}},"WhoisData":{"type":"object","additionalProperties":false,"required":["domain","query_domain","registered","meta"],"properties":{"domain":{"type":"string","description":"The normalized registrable root domain.","examples":["example.com"]},"query_domain":{"type":"string","description":"The normalized hostname submitted for lookup.","examples":["www.example.com"]},"registered":{"type":"boolean","description":"Whether the registry reports the domain as registered."},"registrar":{"type":["string","null"],"description":"Registrar name when supplied by the registry."},"organization":{"type":["string","null"],"description":"Public organization value when supplied by the registry."},"creation_date":{"type":["string","null"],"format":"date-time","description":"Domain creation time in ISO 8601 format when available."},"updated_date":{"type":["string","null"],"format":"date-time","description":"Last registry update time in ISO 8601 format when available."},"expiration_date":{"type":["string","null"],"format":"date-time","description":"Domain expiration time in ISO 8601 format when available."},"status":{"type":["array","null"],"description":"Normalized registry status values.","items":{"type":"string"}},"dnssec":{"type":["string","null"],"description":"DNSSEC status reported by the registry."},"name_servers":{"type":["array","null"],"description":"Authoritative nameservers reported by the registry.","items":{"type":"string"}},"meta":{"$ref":"#/components/schemas/WhoisMeta"}}},"WhoisMeta":{"type":"object","additionalProperties":false,"required":["cached"],"properties":{"cached":{"type":"boolean","description":"Whether the response came from cache."},"stale":{"type":"boolean","description":"Whether stale cached data was served because fresh upstream data was unavailable."}}},"DnsLookupRequest":{"type":"object","additionalProperties":false,"required":["domain"],"properties":{"domain":{"type":"string","minLength":1,"maxLength":253,"description":"A valid hostname without a URL scheme or path.","examples":["example.com","www.example.com"]},"provider":{"type":"string","enum":["google","cloudflare"],"default":"google","description":"The DNS-over-HTTPS resolver used for the lookup."}}},"DnsLookupSuccessResponse":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/DnsLookupData"}}},"DnsLookupData":{"type":"object","additionalProperties":false,"required":["domain","provider","records","sections","queries","statusSummary","queried_at"],"properties":{"domain":{"type":"string","description":"The validated lowercase hostname used for the lookup."},"provider":{"type":"string","enum":["google","cloudflare"]},"records":{"type":"array","description":"Records deduplicated across all supported query types.","items":{"$ref":"#/components/schemas/DnsRecord"}},"sections":{"type":"array","description":"Deduplicated records grouped by DNS section and record type.","items":{"$ref":"#/components/schemas/DnsUiSection"}},"queries":{"type":"array","description":"Normalized resolver result for each supported DNS record type.","items":{"$ref":"#/components/schemas/DnsQueryResult"}},"statusSummary":{"$ref":"#/components/schemas/DnsStatusSummary"},"queried_at":{"type":"string","format":"date-time","description":"Time the uncached resolver lookup completed. Cached responses retain the original value."}}},"DnsRecord":{"type":"object","additionalProperties":false,"required":["section","name","canonicalName","normalizedName","typeCode","typeName","ttl","TTL","data","normalizedData"],"properties":{"section":{"$ref":"#/components/schemas/DnsSection"},"name":{"type":"string","description":"Normalized display name without a trailing dot."},"canonicalName":{"type":"string","description":"Canonical DNS name with a trailing dot."},"normalizedName":{"type":"string","description":"Lowercase name used for comparison and deduplication."},"typeCode":{"type":"integer","description":"Numeric DNS record type code."},"typeName":{"type":"string","description":"Human-readable DNS record type such as `A`, `MX`, or `DNSKEY`."},"ttl":{"type":"integer","minimum":0,"description":"Record time-to-live in seconds."},"TTL":{"type":"integer","minimum":0,"description":"Compatibility alias of `ttl`."},"data":{"type":"string","description":"Normalized display value for the record."},"normalizedData":{"type":"string","description":"Value used for comparison and deduplication."},"parsed":{"$ref":"#/components/schemas/DnsParsedData"}}},"DnsSection":{"type":"string","enum":["answer","authority","additional"]},"DnsUiSection":{"type":"object","additionalProperties":false,"required":["section","title","groups"],"properties":{"section":{"$ref":"#/components/schemas/DnsSection"},"title":{"type":"string"},"groups":{"type":"array","items":{"$ref":"#/components/schemas/DnsUiTypeGroup"}}}},"DnsUiTypeGroup":{"type":"object","additionalProperties":false,"required":["typeName","typeCode","records"],"properties":{"typeName":{"type":"string"},"typeCode":{"type":"integer"},"records":{"type":"array","items":{"$ref":"#/components/schemas/DnsRecord"}}}},"DnsQueryResult":{"type":"object","additionalProperties":false,"required":["query","status","flags","records","counts"],"properties":{"query":{"$ref":"#/components/schemas/DnsQuery"},"status":{"$ref":"#/components/schemas/DnsStatus"},"flags":{"$ref":"#/components/schemas/DnsFlags"},"records":{"type":"array","items":{"$ref":"#/components/schemas/DnsRecord"}},"counts":{"$ref":"#/components/schemas/DnsCounts"}}},"DnsQuery":{"type":"object","additionalProperties":false,"required":["name","normalizedName","typeName"],"properties":{"name":{"type":"string"},"normalizedName":{"type":"string"},"typeName":{"type":"string"},"typeCode":{"type":"integer"}}},"DnsStatus":{"type":"object","additionalProperties":false,"required":["code","name","message"],"properties":{"code":{"type":"integer","description":"DNS response code, or `-1` when the individual query failed."},"name":{"type":"string","description":"Normalized resolver status such as `NOERROR`, `NXDOMAIN`, or `QUERY_ERROR`."},"message":{"type":"string","description":"Stable localization key describing the status."}}},"DnsFlags":{"type":"object","additionalProperties":false,"required":["tc","rd","ra","ad","cd"],"properties":{"tc":{"type":"boolean","description":"The response was truncated."},"rd":{"type":"boolean","description":"Recursion was desired."},"ra":{"type":"boolean","description":"Recursion is available."},"ad":{"type":"boolean","description":"DNSSEC data was authenticated."},"cd":{"type":"boolean","description":"DNSSEC checking was disabled."}}},"DnsCounts":{"type":"object","additionalProperties":false,"required":["answer","authority","additional"],"properties":{"answer":{"type":"integer","minimum":0},"authority":{"type":"integer","minimum":0},"additional":{"type":"integer","minimum":0}}},"DnsStatusSummary":{"type":"object","additionalProperties":false,"required":["status","message","domainExists"],"properties":{"status":{"type":"string","enum":["ok","nxdomain","servfail","refused","error"]},"message":{"type":"string","description":"Stable localization key for the combined lookup status."},"domainExists":{"type":"boolean","description":"Whether at least one supported query returned a successful DNS result."}}},"DnsParsedData":{"description":"Structured fields parsed from DS, DNSKEY, or CAA record data when available.","oneOf":[{"$ref":"#/components/schemas/DnsDsParsedData"},{"$ref":"#/components/schemas/DnskeyParsedData"},{"$ref":"#/components/schemas/DnsCaaParsedData"}]},"DnsDsParsedData":{"type":"object","additionalProperties":false,"required":["keyTag","algorithm","digestType","digest"],"properties":{"keyTag":{"type":"string"},"algorithm":{"type":"string"},"digestType":{"type":"string"},"digest":{"type":"string"}}},"DnskeyParsedData":{"type":"object","additionalProperties":false,"required":["flags","protocol","algorithm","publicKey"],"properties":{"flags":{"type":"string"},"protocol":{"type":"string"},"algorithm":{"type":"string"},"publicKey":{"type":"string"}}},"DnsCaaParsedData":{"type":"object","additionalProperties":false,"required":["flags","tag","value"],"properties":{"flags":{"type":"integer"},"tag":{"type":"string"},"value":{"type":"string"}}},"IpInfoSuccessResponse":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/IpInfoData"}}},"IpInfoData":{"type":"object","additionalProperties":false,"required":["ip"],"properties":{"ip":{"type":"string","description":"The normalized IP address returned by the lookup."},"hostname":{"type":"string","description":"Reverse hostname when available."},"city":{"type":"string","description":"Approximate city when available."},"region":{"type":"string","description":"Approximate region or administrative area when available."},"country":{"type":"string","description":"Country code when available."},"loc":{"type":"string","description":"Approximate latitude and longitude encoded as `latitude,longitude`."},"org":{"type":"string","description":"Public network or organization description when available."},"postal":{"type":"string","description":"Approximate postal code when available."},"timezone":{"type":"string","description":"IANA timezone name when available."}}},"VideoExtractJobRequest":{"type":"object","required":["url","requestId"],"properties":{"url":{"type":"string","minLength":1,"maxLength":2048,"description":"Public HTTP(S) media URL."},"requestId":{"type":"string","format":"uuid","description":"Caller-generated idempotency UUID, unrelated to the server-generated X-Request-Id correlation header."}}},"VideoExtractJobAcceptedResponse":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"type":"object","additionalProperties":false,"required":["jobId","token","pollAfterMs"],"properties":{"jobId":{"type":"string","pattern":"^[a-f0-9]{64}$"},"token":{"type":"string","description":"Five-minute sealed capability. Send only in x-video-job-token; do not log it."},"pollAfterMs":{"type":"integer","const":2000}}}}},"VideoExtractJobStatusResponse":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"oneOf":[{"type":"object","additionalProperties":false,"required":["status","pollAfterMs"],"properties":{"status":{"type":"string","const":"pending"},"pollAfterMs":{"type":"integer","const":2000}}},{"type":"object","additionalProperties":false,"required":["status","result"],"properties":{"status":{"type":"string","const":"completed"},"result":{"$ref":"#/components/schemas/VideoExtractResult"}}}]}}},"VideoExtractRequest":{"type":"object","required":["url"],"properties":{"url":{"type":"string","minLength":1,"description":"A public HTTP or HTTPS media URL from a supported site. A missing scheme is normalized to HTTPS.","examples":["https://www.youtube.com/watch?v=VIDEO_ID","https://www.tiktok.com/@creator/video/VIDEO_ID","https://vimeo.com/VIDEO_ID","https://www.reddit.com/r/videos/comments/POST_ID/example/"]}}},"VideoExtractSuccessResponse":{"type":"object","additionalProperties":false,"required":["success","data"],"properties":{"success":{"type":"boolean","const":true},"data":{"$ref":"#/components/schemas/VideoExtractResult"}}},"VideoExtractResult":{"type":"object","additionalProperties":false,"required":["platform","title","variants"],"properties":{"platform":{"type":"string","enum":["facebook","instagram","tiktok","youtube","twitter","unknown"],"description":"The normalized source family. `twitter` represents X; `unknown` is used for additional supported sites."},"sourceId":{"type":"string","description":"The source platform's public content identifier when available."},"title":{"type":"string","description":"The video title or a safe platform fallback title."},"previewUrl":{"type":"string","description":"A short-lived internal URL that streams a validated preview image."},"author":{"$ref":"#/components/schemas/VideoAuthor"},"durationSeconds":{"type":"number","minimum":0,"description":"Video duration in seconds when available."},"stats":{"$ref":"#/components/schemas/VideoStats"},"variants":{"type":"array","minItems":1,"description":"Deduplicated video and audio download options.","items":{"$ref":"#/components/schemas/VideoVariant"}}}},"VideoAuthor":{"type":"object","additionalProperties":false,"properties":{"displayName":{"type":"string","description":"Public creator or channel display name."},"handle":{"type":"string","description":"Public creator or channel handle without a leading `@`."}}},"VideoStats":{"type":"object","additionalProperties":false,"properties":{"views":{"type":"number","minimum":0},"likes":{"type":"number","minimum":0},"comments":{"type":"number","minimum":0},"shares":{"type":"number","minimum":0},"saves":{"type":"number","minimum":0}}},"VideoVariant":{"type":"object","additionalProperties":false,"required":["id","downloadToken","requiresPreparation","label","kind","extension","mimeType"],"properties":{"id":{"type":"string","description":"Stable identifier for this option within the extraction response."},"downloadToken":{"type":"string","description":"Opaque short-lived token for Pirehub's configured download flow. Do not decode or log it."},"requiresPreparation":{"type":"boolean","description":"Whether the download flow must prepare or render the media before delivery."},"label":{"type":"string","description":"Human-readable option label."},"kind":{"type":"string","enum":["video","audio"]},"extension":{"type":"string","description":"Expected file extension without a leading dot."},"mimeType":{"type":"string","description":"Expected media MIME type."},"sizeBytes":{"type":"number","minimum":0,"description":"Estimated or exact size in bytes when available."},"qualityLabel":{"type":"string","description":"Resolution, bitrate, or provider-neutral quality label when available."},"recommended":{"type":"boolean","description":"Whether Pirehub recommends this option as the best available choice."}}},"ErrorResponse":{"type":"object","additionalProperties":false,"required":["success","error","data"],"properties":{"success":{"type":"boolean","const":false},"error":{"$ref":"#/components/schemas/ErrorDetail"},"data":{"$ref":"#/components/schemas/ErrorData"}}},"ErrorData":{"type":"object","description":"Safe structured error metadata. Every JSON API error includes a correlation ID for support and log lookup.","additionalProperties":true,"required":["requestId"],"properties":{"requestId":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9._:-]+$","description":"Opaque correlation ID for this request. Include it when contacting support.","example":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}},"RateLimitErrorResponse":{"type":"object","additionalProperties":false,"required":["success","error","data"],"properties":{"success":{"type":"boolean","const":false},"error":{"type":"object","additionalProperties":false,"required":["code","message"],"properties":{"code":{"type":"string","enum":["RATE_LIMITED","MONTHLY_QUOTA_EXCEEDED"]},"message":{"type":"string"}}},"data":{"$ref":"#/components/schemas/RateLimitErrorData"}}},"RateLimitErrorData":{"type":"object","additionalProperties":false,"required":["requestId"],"properties":{"reason":{"type":"string","enum":["per_minute","monthly_quota"],"description":"Present for API-key limits. Browser/IP and upstream limits may omit it."},"limit":{"type":"integer","minimum":0,"description":"Configured API-key limit for the applicable window."},"remaining":{"type":"integer","minimum":0,"description":"Requests remaining in the applicable API-key window."},"resetAt":{"type":"string","format":"date-time","description":"ISO 8601 reset time for the applicable API-key window."},"retryAfter":{"type":"integer","minimum":1,"description":"Whole seconds to wait before retrying when supplied by the limiter."},"requestId":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9._:-]+$","description":"Opaque correlation ID for this request."}}},"ErrorDetail":{"type":"object","additionalProperties":false,"required":["code","message"],"properties":{"code":{"type":"string","description":"Stable machine-readable error code."},"message":{"type":"string","description":"Safe human-readable explanation."}}},"WebDocument":{"type":"object","additionalProperties":false,"required":["url","title","description","content","contentFormat","contentTruncated","httpStatus"],"properties":{"url":{"type":"string","format":"uri"},"title":{"type":["string","null"]},"description":{"type":["string","null"]},"content":{"type":["string","null"],"maxLength":200000},"contentFormat":{"enum":["markdown","text",null]},"contentTruncated":{"type":"boolean"},"httpStatus":{"type":["integer","null"],"description":"Target HTTP status when available; null when not reported."}}},"WebSearchResult":{"type":"object","additionalProperties":false,"required":["query","results"],"properties":{"query":{"type":"string"},"results":{"type":"array","items":{"$ref":"#/components/schemas/WebDocument"}}}},"WebScrapeResult":{"type":"object","additionalProperties":false,"required":["document"],"properties":{"document":{"$ref":"#/components/schemas/WebDocument"}}},"WebMapResult":{"type":"object","additionalProperties":false,"required":["url","links"],"properties":{"url":{"type":"string","format":"uri"},"links":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["url","title","description"],"properties":{"url":{"type":"string","format":"uri"},"title":{"type":["string","null"]},"description":{"type":["string","null"]}}}}}},"WebCrawlResult":{"type":"object","additionalProperties":false,"required":["id","status","total","completed","documents","nextOffset","expiresAt"],"properties":{"id":{"type":["string","null"],"format":"uuid","description":"Opaque job ID, or null for a synchronous crawl."},"status":{"enum":["pending","running","completed","failed","cancelled"]},"total":{"type":["integer","null"],"minimum":0,"description":"Reported total; null when unknown. Does not count every discovery or failed page."},"completed":{"type":"integer","minimum":0},"documents":{"type":"array","items":{"$ref":"#/components/schemas/WebDocument"}},"nextOffset":{"type":["integer","null"],"minimum":0,"maximum":10000},"expiresAt":{"type":["string","null"],"format":"date-time","description":"Job access expires at this UTC instant, at most 24 hours from submission. Null for synchronous results."}}},"WebCrawlCancellation":{"type":"object","additionalProperties":false,"required":["id","status"],"properties":{"id":{"type":"string","format":"uuid"},"status":{"const":"cancelled"}}},"WebSearchRequest":{"type":"object","additionalProperties":false,"required":["query"],"properties":{"query":{"type":"string","minLength":1,"maxLength":1000},"limit":{"type":"integer","minimum":1,"maximum":10,"default":5}}},"WebScrapeRequest":{"type":"object","additionalProperties":false,"required":["url"],"properties":{"url":{"type":"string","format":"uri","maxLength":2048,"description":"Public HTTP(S) URL without credentials, using port 80 or 443; checked against private addresses and DNS."}}},"WebMapRequest":{"type":"object","additionalProperties":false,"required":["url"],"properties":{"url":{"type":"string","format":"uri","maxLength":2048,"description":"Public HTTP(S) URL without credentials, using port 80 or 443; checked against private addresses and DNS."},"limit":{"type":"integer","minimum":1,"maximum":100,"default":20}}},"WebCrawlRequest":{"type":"object","additionalProperties":false,"required":["requestId","url"],"properties":{"requestId":{"type":"string","format":"uuid","description":"Unique UUID for an intentional crawl. Reuse by the same credential returns 409 for 24 hours, including after ambiguous upstream failures."},"url":{"type":"string","format":"uri","maxLength":2048,"description":"Public HTTP(S) URL without credentials, using port 80 or 443; checked against private addresses and DNS."},"limit":{"type":"integer","minimum":1,"maximum":20,"default":10},"maxDepth":{"type":"integer","minimum":1,"maximum":3,"default":1}}}},"headers":{"RequestId":{"description":"Opaque correlation ID for this request. It matches `data.requestId` on JSON error responses.","schema":{"type":"string","minLength":1,"maxLength":128,"pattern":"^[A-Za-z0-9._:-]+$"}},"RateLimitLimit":{"description":"Maximum requests in the configured window (admin keys: 60 seconds; self-service: 1 second).","schema":{"type":"integer"}},"RateLimitRemaining":{"description":"Estimated requests remaining in the configured rate window.","schema":{"type":"integer"}},"RateLimitReset":{"description":"Rate window reset time as Unix seconds.","schema":{"type":"integer"}},"MonthlyLimit":{"description":"Administrator-issued keys only. Monthly request limit, or `unlimited`.","schema":{"oneOf":[{"type":"integer"},{"type":"string","const":"unlimited"}]}},"MonthlyRemaining":{"description":"Administrator-issued keys only. Requests remaining this month, or `unlimited`.","schema":{"oneOf":[{"type":"integer"},{"type":"string","const":"unlimited"}]}},"MonthlyReset":{"description":"Administrator-issued keys only. Monthly quota reset time as Unix seconds.","schema":{"type":"integer"}},"RetryAfter":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","minimum":0}},"ApiCreditCost":{"description":"Self-service keys only: reserved action credits; consumption occurs on success.","schema":{"type":"integer","minimum":1}},"ApiCreditLimit":{"description":"Self-service keys only: owner subscription-period credit allowance.","schema":{"type":"integer","minimum":1}},"ApiCreditReset":{"description":"Self-service keys only: subscription period end as Unix seconds.","schema":{"type":"integer","minimum":1}},"ApiRateWindow":{"description":"Self-service keys only: 1 second. Administrator keys use a 60-second window.","schema":{"type":"integer","minimum":1}},"ServerTiming":{"description":"Optional server timing diagnostics in milliseconds. API-key responses may expose parse, lookup, verify, scope, runtime_snapshot, quota, credit_reserve, provider, credit_finalize, pre-provider and total durations using *_ms metric names. For paid keys, api_key_lookup_ms includes the fresh credential, owner, subscription, entitlement and capability read; runtime_snapshot_ms covers request parsing and snapshot validation. Admission stages overlap with api_key_pre_provider_ms; provider_ms includes endpoint validation and result-cache access. Inapplicable stages are omitted. Background logging and client network time are excluded. api_key_cache describes the lookup path.","schema":{"type":"string"}}},"responses":{"ValidationError":{"description":"The domain or request body is invalid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"VALIDATION_ERROR","message":"Invalid domain format or missing required fields"},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}},"UnauthorizedError":{"description":"The API key or OAuth access token is missing or invalid.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"invalidKey":{"value":{"success":false,"error":{"code":"UNAUTHORIZED","message":"A valid API key is required."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}}}},"ForbiddenError":{"description":"The API key is revoked, expired, or lacks the required scope; or browser request verification is required or failed. Self-service keys can also return API_PLAN_REQUIRED or API_CAPABILITY_DENIED.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missingScope":{"value":{"success":false,"error":{"code":"FORBIDDEN","message":"This API key does not have permission for this endpoint."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}},"revokedKey":{"value":{"success":false,"error":{"code":"FORBIDDEN","message":"This API key has been revoked."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}},"expiredKey":{"value":{"success":false,"error":{"code":"FORBIDDEN","message":"This API key has expired."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}},"captchaFailed":{"value":{"success":false,"error":{"code":"CAPTCHA_FAILED","message":"Request verification failed. Please try again."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}},"captchaRequired":{"value":{"success":false,"error":{"code":"CAPTCHA_REQUIRED","message":"Request verification is required."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}}}},"RateLimitError":{"description":"The API key exceeded its per-minute or monthly quota, the first-party browser request exceeded its per-IP limit, or an upstream provider throttled the request. Self-service keys can also return API_CREDITS_EXCEEDED or USAGE_CONCURRENCY_EXCEEDED; denied requests consume no credits.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"X-ApiKey-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-ApiKey-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-ApiKey-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"X-ApiKey-Monthly-Limit":{"$ref":"#/components/headers/MonthlyLimit"},"X-ApiKey-Monthly-Remaining":{"$ref":"#/components/headers/MonthlyRemaining"},"X-ApiKey-Monthly-Reset":{"$ref":"#/components/headers/MonthlyReset"},"X-RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"X-RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"X-RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Server-Timing":{"$ref":"#/components/headers/ServerTiming"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RateLimitErrorResponse"},"examples":{"perMinute":{"summary":"API-key per-minute limit exceeded","value":{"success":false,"error":{"code":"RATE_LIMITED","message":"Too many requests. Please try again later."},"data":{"reason":"per_minute","limit":60,"remaining":0,"resetAt":"2026-08-20T07:11:00.000Z","retryAfter":23,"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}},"monthlyQuota":{"summary":"API-key monthly quota exhausted","value":{"success":false,"error":{"code":"MONTHLY_QUOTA_EXCEEDED","message":"Monthly API request quota has been exhausted."},"data":{"reason":"monthly_quota","limit":10,"remaining":0,"resetAt":"2026-09-01T00:00:00.000Z","retryAfter":1010458,"requestId":"1f4f2b4a-da4f-47d2-be0e-f85ddaeab6d4"}}},"browserPerIp":{"summary":"First-party browser per-IP limit exceeded","value":{"success":false,"error":{"code":"RATE_LIMITED","message":"Too many requests. Please try again later."},"data":{"retryAfter":12,"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}},"upstreamRateLimit":{"summary":"Upstream provider rate limited the lookup","value":{"success":false,"error":{"code":"RATE_LIMITED","message":"Too many requests. Please try again later."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}}}},"InternalError":{"description":"An unexpected internal error occurred.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"INTERNAL_ERROR","message":"Something went wrong. Please try again later."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}},"UpstreamBadResponse":{"description":"An upstream provider returned an invalid or unusable response.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UPSTREAM_BAD_RESPONSE","message":"Bad gateway. The upstream service returned an invalid response."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}},"ServiceUnavailable":{"description":"The requested service or one of its dependencies is temporarily unavailable.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"dependencyUnavailable":{"value":{"success":false,"error":{"code":"SERVICE_UNAVAILABLE","message":"Service temporarily unavailable. Please try again later."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}},"quotaServiceUnavailable":{"value":{"success":false,"error":{"code":"SERVICE_UNAVAILABLE","message":"API key quota service is temporarily unavailable."},"data":{"retryAfter":2,"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}}}},"UpstreamTimeout":{"description":"The requested upstream dependency exceeded its deadline.","headers":{"X-Request-Id":{"$ref":"#/components/headers/RequestId"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"example":{"success":false,"error":{"code":"UPSTREAM_TIMEOUT","message":"Gateway timeout. The upstream service did not respond in time."},"data":{"requestId":"1704b073-5bf1-4d64-8f9a-3af95edb3bf1"}}}}}}}}