openapi: 3.0.3 info: title: KINETK Graph Service API version: 1.0.0 description: | Public HTTP API for the KINETK graph-service. Health/sync probe, precomputed narrative reads, and an async job queue for heavy intelligence work (`intelligence_search`, `intelligence_discover`, `campaign_brief`, `llm_context`). Authentication: send your secret key in the `x-kinetk-key` header on every request. Keys are issued from the billing dashboard and shown only once. Billing: async intelligence jobs are metered in credits (1 credit = $1.00). `POST /intelligence/jobs` requires an explicit `window` and returns an `estimated_cost` — an UPPER BOUND. Pricing is dynamic: you are charged the ACTUAL cost once the job completes (a query that returns fewer records than `limit` costs less), and the unused remainder of the reservation is released. If your balance can't cover a call you get `402` with a recommendation for how to fit your remaining budget. Every successful response carries `X-Kinetk-Credits-Used` and `X-Kinetk-Credits-Remaining` headers. Precomputed narrative/creator reads are free in V1. Cached job answers are charged at the full rate (no cache discount). contact: name: KINETK Engineering license: name: Proprietary servers: - url: https://api.kinetk.ai/graph description: Production. - url: https://{apiId}.execute-api.us-east-1.amazonaws.com/{stage} description: Direct API Gateway invoke URL (bypass custom domain). variables: apiId: default: example stage: default: prod enum: [dev, prod] security: - KinetkKeyAuth: [] paths: /health: get: summary: Health + sync-freshness probe description: Liveness probe with DB connectivity and ingestion freshness. Returns `503` when the DB is unreachable. operationId: getHealth security: - AdminKeyAuth: [] responses: "200": description: Service is healthy content: application/json: schema: $ref: "#/components/schemas/HealthResponse" "503": description: Service is degraded content: application/json: schema: $ref: "#/components/schemas/HealthResponse" /narratives/trending: get: summary: Top precomputed narratives for a window description: >- Reads precomputed narrative clusters for a bounded window (`24h | 7d | 30d`; `all` is coerced to `7d`). For live retrieval, submit `POST /intelligence/jobs` with `kind: intelligence_discover`. operationId: listTrendingNarratives parameters: - $ref: "#/components/parameters/Window" - $ref: "#/components/parameters/Limit" responses: "200": description: Trending narratives for the requested window content: application/json: schema: type: object required: [window, narratives] properties: window: $ref: "#/components/schemas/NarrativeWindow" narratives: type: array items: $ref: "#/components/schemas/NarrativeCluster" default: $ref: "#/components/responses/Error" /narratives/search: get: summary: Filtered search over precomputed narratives description: >- Free-text search over precomputed narrative clusters, ranked by relevance. Same `24h | 7d | 30d` window constraint as `/narratives/trending`. For live retrieval, use `POST /intelligence/jobs` with `kind: intelligence_discover`. operationId: searchNarratives parameters: - name: q in: query required: true schema: type: string minLength: 1 - $ref: "#/components/parameters/Window" - $ref: "#/components/parameters/Limit" responses: "200": description: Narratives matching the search query content: application/json: schema: type: object required: [query, window, narratives] properties: query: type: string window: $ref: "#/components/schemas/NarrativeWindow" narratives: type: array items: $ref: "#/components/schemas/NarrativeCluster" "400": $ref: "#/components/responses/Error" default: $ref: "#/components/responses/Error" /narratives/{id}: get: summary: Drill into one precomputed narrative description: Full evidence bundle for one narrative cluster — header, content rows, top creators, per-platform breakdown, duplicate groups. operationId: getNarrativeDetail parameters: - name: id in: path required: true schema: type: integer minimum: 1 responses: "200": description: Narrative detail with supporting evidence content: application/json: schema: $ref: "#/components/schemas/NarrativeDetailResponse" "400": $ref: "#/components/responses/Error" "404": $ref: "#/components/responses/Error" default: $ref: "#/components/responses/Error" /intelligence/jobs: post: summary: Submit an async intelligence job description: | Submit a job for async execution. Request body is `{ kind, input }` discriminated by `kind` (see `JobSubmitRequest`). Response shapes: - `200 + { fromCache: true, result, charged }` — identical recent job within the per-kind freshness window; cached result returned synchronously and charged at full rate (no cache discount). - `202 + { dedup: true }` — identical job currently queued/running; reuses the in-flight `jobId`. - `202 + { estimated_cost }` — new job accepted; the credits were reserved at the estimate. Poll `GET /intelligence/jobs/{id}`. - `402` — insufficient credits; body recommends how to fit your budget. `window` (in `input`) is REQUIRED — it drives the credit price (up to a 10x premium for `24h`). `estimated_cost` is an upper bound; the final charge is computed from records actually returned. Freshness windows: `intelligence_search` 15 min, `intelligence_discover` 1 h, `campaign_brief` / `llm_context` 6 h. operationId: submitIntelligenceJob requestBody: required: true content: application/json: schema: $ref: "#/components/schemas/JobSubmitRequest" responses: "200": description: Cached result returned synchronously (charged at full rate) headers: X-Kinetk-Credits-Used: $ref: "#/components/headers/CreditsUsed" X-Kinetk-Credits-Remaining: $ref: "#/components/headers/CreditsRemaining" content: application/json: schema: $ref: "#/components/schemas/JobSubmitCacheHitResponse" "202": description: Job accepted (queued, or deduplicated against a running job) headers: X-Kinetk-Credits-Used: $ref: "#/components/headers/CreditsUsed" X-Kinetk-Credits-Remaining: $ref: "#/components/headers/CreditsRemaining" content: application/json: schema: $ref: "#/components/schemas/JobSubmitAcceptedResponse" "400": $ref: "#/components/responses/Error" "402": description: Insufficient credits — body includes a budget recommendation headers: X-Kinetk-Credits-Used: $ref: "#/components/headers/CreditsUsed" X-Kinetk-Credits-Remaining: $ref: "#/components/headers/CreditsRemaining" content: application/json: schema: $ref: "#/components/schemas/InsufficientCreditsResponse" default: $ref: "#/components/responses/Error" /intelligence/jobs/{id}: get: summary: Poll an async job — status + final result description: | Returns the job's current state. All states (`queued`, `running`, `succeeded`, `failed`) return `200`; `404` for unknown `jobId`, `410` when the result has expired (~24h after completion). On `succeeded`, `result` carries the per-kind payload. Large results (>300 KB) are rehydrated transparently from object storage. Recommended polling cadence: every 2–5 seconds. Typical end-to-end run: 5–20 s. operationId: getIntelligenceJob parameters: - name: id in: path required: true schema: type: string responses: "200": description: Job state. Once settled, `charged` reflects the actual cost. headers: X-Kinetk-Credits-Used: $ref: "#/components/headers/CreditsUsed" X-Kinetk-Credits-Remaining: $ref: "#/components/headers/CreditsRemaining" content: application/json: schema: $ref: "#/components/schemas/Job" "400": $ref: "#/components/responses/Error" "404": $ref: "#/components/responses/Error" "410": $ref: "#/components/responses/Error" default: $ref: "#/components/responses/Error" /usage: get: summary: Credit usage history for your account description: >- Newest-first ledger entries (top-ups, settlements, refunds) for the account the key belongs to. Paginate with `limit` + `cursor`; scope to a single key with `keyId`. operationId: getUsage parameters: - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 100 default: 25 - name: cursor in: query required: false description: Opaque pagination cursor from a previous response's `nextCursor`. schema: type: string - name: keyId in: query required: false description: Restrict to usage charged against a single API key. schema: type: string responses: "200": description: Usage history page content: application/json: schema: $ref: "#/components/schemas/UsageResponse" default: $ref: "#/components/responses/Error" components: securitySchemes: KinetkKeyAuth: type: apiKey in: header name: x-kinetk-key description: >- Customer secret key (format `ktk_live_…`). Required on all data endpoints. Validated by a custom authorizer that resolves your account and applies usage-plan throttling. Missing / invalid / revoked / expired → `401`; suspended account → `403`. AdminKeyAuth: type: apiKey in: header name: x-api-key description: >- Native API Gateway key for the `/health` probe. Not a customer credential. headers: CreditsUsed: description: Credits consumed by this call (reserved estimate on a 202; actual once settled). schema: type: number example: 4 CreditsRemaining: description: Account credit balance remaining after this call. schema: type: number example: 996 parameters: Window: name: window in: query required: false schema: $ref: "#/components/schemas/NarrativeWindow" Limit: name: limit in: query required: false schema: type: integer minimum: 1 maximum: 50 default: 12 responses: Error: description: Error response content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" schemas: ErrorResponse: type: object required: [error] properties: error: type: string NarrativeWindow: type: string enum: [24h, 7d, 30d] default: 7d description: Bounded window for precomputed narrative endpoints. `all` is coerced to `7d`. QueryWindow: type: string enum: [24h, 7d, 30d, all] default: all description: >- Time window for live-retrieval jobs. `all` disables the published-at filter (Historical tier). REQUIRED on `POST /intelligence/jobs` — it sets the credit price (yield/cost varies up to 10x by freshness). HealthResponse: type: object required: [status, stage, db, sync, durationMs, timestamp] properties: status: type: string enum: [ok, error] stage: type: string db: type: string enum: [connected, unreachable] dbError: type: string sync: type: object required: [lastRunAt, minutesSinceLastRun, isStale, lastCycle, today] properties: lastRunAt: type: string nullable: true format: date-time minutesSinceLastRun: type: integer nullable: true isStale: type: boolean lastCycle: # OpenAPI 3.0: an explicit `type: object` is required next to # `nullable: true` for the validator to accept it. The `allOf` # pulls in the SyncStats fields. type: object nullable: true allOf: - $ref: "#/components/schemas/SyncStats" today: nullable: true type: object properties: processedCount: type: integer insertedCount: type: integer durationMs: type: integer timestamp: type: string format: date-time SyncStats: type: object required: [processedCount, insertedCount, durationMs] properties: processedCount: type: integer insertedCount: type: integer durationMs: type: integer NarrativeCluster: type: object required: [id, window_key, label, summary, momentum_score, emerging_score, content_count, creator_count, platform_count, total_engagement, top_tags] properties: id: type: integer window_key: $ref: "#/components/schemas/NarrativeWindow" window_start: type: string format: date-time window_end: type: string format: date-time label: type: string summary: type: string momentum_score: type: number emerging_score: type: number content_count: type: integer creator_count: type: integer platform_count: type: integer total_engagement: type: integer top_tags: type: array items: type: string representative_content_uuid: type: string nullable: true score_breakdown: type: object additionalProperties: true computed_at: type: string format: date-time NarrativeDetailResponse: type: object required: [narrative, content, creators, platformBreakdown, duplicateGroups] properties: narrative: $ref: "#/components/schemas/NarrativeCluster" content: type: array items: $ref: "#/components/schemas/NarrativeContentEvidence" creators: type: array items: $ref: "#/components/schemas/NarrativeCreator" platformBreakdown: type: array items: $ref: "#/components/schemas/NarrativePlatformBreakdown" duplicateGroups: type: array items: type: object additionalProperties: true description: A group of near-duplicate content items within the cluster. NarrativeCreator: type: object description: >- A creator active in this narrative cluster, with their amplifier metrics. Creator identity (handle / display name) is not exposed — use the numeric `id` to correlate the same creator across narratives or to look them up. required: [id, platform, content_count, total_engagement, amplifier_score] properties: id: type: integer description: Stable `creators.id` PK. platform: type: string follower_count: type: integer nullable: true content_count: type: integer description: Number of posts by this creator in the cluster. total_engagement: type: integer format: int64 description: Summed views + likes + shares + comments across the creator's posts in the cluster. first_seen_at: type: string format: date-time nullable: true amplifier_score: type: number description: 0–1 score of the creator's amplifying influence within the cluster. NarrativePlatformBreakdown: type: object description: Per-platform content + engagement totals for the cluster. required: [platform, content_count, total_engagement] properties: platform: type: string content_count: type: integer total_engagement: type: integer format: int64 NarrativeContentEvidence: type: object required: [uuid, platform, tags] properties: uuid: type: string platform: type: string nullable: true content_id: type: string nullable: true title: type: string nullable: true description: type: string nullable: true view_count: type: integer nullable: true like_count: type: integer nullable: true share_count: type: integer nullable: true comment_count: type: integer nullable: true published_at: type: string nullable: true format: date-time tags: type: array items: type: string relevance_score: type: number is_representative: type: boolean QueryIntelligenceRequest: type: object required: [query, window] properties: query: type: string description: Free-text query. Hashtags in the query are auto-extracted and used as a tag-overlap widening filter when vector retrieval under-fills `limit`. platforms: type: array items: type: string description: Optional uppercase platform whitelist (e.g. `["TIKTOK", "INSTAGRAM"]`). vectors: oneOf: - type: string - type: array items: type: string description: Use `all_media` (default) or a list/comma-separated value such as `image_vector,video_vector`. default: all_media limit: type: integer minimum: 100 maximum: 50000 default: 1000 description: Max ranked content rows to return. Clamped to [100, 50000]. maxDistance: type: number nullable: true expandQuery: type: boolean default: true description: When true, generate 3 LLM-expanded query variants and fan out retrieval across them. clusterCount: type: integer minimum: 2 maximum: 8 description: Optional query-time semantic cluster count. window: $ref: "#/components/schemas/QueryWindow" IntelligenceSearchResponse: type: object required: [type, generatedAt, query, window, content, graph] properties: type: type: string enum: [kinetk.query_intelligence.search.v1] generatedAt: type: string format: date-time query: type: string window: $ref: "#/components/schemas/QueryWindow" content: type: array items: $ref: "#/components/schemas/RankedContent" graph: allOf: - $ref: "#/components/schemas/QueryGraph" description: >- A relationship graph over the result set — content, tag, creator and narrative nodes linked by similarity, tag co-occurrence and authorship edges. Use it to visualize "the shape of the conversation" as a network diagram, or to run graph analysis (hubs, bridges, communities) over the returned set. Skip it if you only need the ranked `content` list. QueryNarrativeDiscoveryResponse: type: object description: >- Intelligence-signals payload. Returns the LLM-generated insight signals only — combined, tag-focused and narrative-focused — plus a light metadata envelope. The underlying analytics (narratives, tags, creators, graphs) are computed internally to derive the insights but are not exposed. For the raw enriched records use `intelligence_search`. required: [type, generatedAt, query, window, insights, tagInsights, narrativeInsights] properties: type: type: string enum: [kinetk.query_intelligence.narrative_discovery.v1] generatedAt: type: string format: date-time query: type: string window: $ref: "#/components/schemas/QueryWindow" insights: type: array description: 4–6 LLM-generated arbitrage prose lines. Empty `[]` on LLM failure or empty inputs. items: type: string tagInsights: type: array description: 4–6 LLM-generated tag-only arbitrage prose lines. Empty `[]` on LLM failure or empty tag inputs. items: type: string narrativeInsights: type: array description: 4–6 LLM-generated narrative-only arbitrage prose lines. Empty `[]` on LLM failure or empty narrative inputs. items: type: string RankedContent: type: object required: [uuid, tags, targetVectors, rrfScore, rawSimilarity, similarity, engagementScore, recencyScore, authorReach, engagementDepth, exampleRankingScore] properties: uuid: type: string platform: type: string nullable: true contentType: type: string nullable: true title: type: string nullable: true description: type: string nullable: true creatorId: type: integer nullable: true followerCount: type: integer nullable: true publishedAt: type: string nullable: true format: date-time tags: type: array items: type: string viewCount: type: integer likeCount: type: integer shareCount: type: integer commentCount: type: integer targetVectors: type: array description: Which media vectors this item matched on (the per-vector queries it surfaced in). items: type: string rrfScore: type: number description: > Reciprocal Rank Fusion score combining the item's rank across every vector it matched: Σ 1/(rank + 60). A pre-scoring retrieval signal. rawSimilarity: type: number description: > Raw vector store certainty for the best-matching vector (text→visual). Lands in a narrow ~0.55–0.62 band; 0 means no multimodal evidence (e.g. a tag-widened row). similarity: type: number description: > Multimodal relevance SUB-score only: rawSimilarity rescaled against fixed anchors to [0,1]. This is NOT the overall ranking signal — use `relevance`. (Previously this was batch-normalized; it no longer spans the full [0,1] range or guarantees a 1.0 top result.) relevance: type: number description: > Combined relevance actually used to rank: the multimodal `similarity` blended with the text→text rerank (`textRerankScore`), after the soft certainty floor. Prefer this over `similarity` for display/sorting. textRerankScore: type: number nullable: true description: > Text→text relevance of the query against the row's title/description/ tags in [0,1]; null when the row has no usable text (then `relevance` falls back to the multimodal signal). engagementScore: type: number description: > Log-scaled, weighted engagement normalized to the top item in the set: log1p(views + 2·likes + 3·comments + 4·shares) / max. recencyScore: type: number description: > How recent the item is vs the oldest/newest in the set, in [0,1]: (ts − oldest) / (newest − oldest). 0.5 when the item has no published date. authorReach: type: number description: > Log-scaled follower count normalized to the set: log1p(followers) / max(log1p(followers)). engagementDepth: type: number description: > Like-per-view ratio — engagement quality, not volume: views>0 ? min(1, likes/(views+1)) : 0. exampleRankingScore: type: number description: > The single score the content list is sorted by (descending). Blends `relevance` with engagement, recency, author reach and depth; on a set with little engagement data it leans almost entirely on relevance + recency. See the "Content object" guide for the exact formula. QueryGraph: type: object description: >- Relationship graph over a result set: `nodes` are content / tag / creator / narrative entities and `edges` are their relationships (semantic similarity, tag co-occurrence, creator authorship, cluster membership). Built for network visualization and graph analysis; creator nodes carry a non-identifying `creatorId` for correlation. required: [nodes, edges] properties: nodes: type: array items: $ref: "#/components/schemas/QueryGraphNode" edges: type: array items: $ref: "#/components/schemas/QueryGraphEdge" QueryGraphNode: type: object required: [id, type, label] properties: id: type: string type: type: string enum: [content, tag, creator, narrative] label: type: string score: type: number creatorId: type: integer description: >- Stable `creators.id` PK. Populated only on `type: creator` nodes. A non-identifying reference for correlating the same creator across narratives. QueryGraphEdge: type: object required: [source, target, type, weight] properties: source: type: string target: type: string type: type: string enum: [semantic_similarity, tag_overlap, creator_posted, tagged_with, contains] weight: type: number CampaignBriefRequest: type: object required: [campaign, window] properties: campaign: type: string description: Free-text campaign description. audience: type: string description: Optional audience descriptor. Carried into the response context; not used as a retrieval filter. platforms: type: array items: type: string tone: type: string description: Optional tone hint. Carried into the response context; not used as a retrieval filter. limit: type: integer minimum: 100 maximum: 50000 default: 1000 window: $ref: "#/components/schemas/QueryWindow" CampaignBriefResponse: type: object required: [id, createdAt, brief, context] properties: id: type: integer createdAt: type: string format: date-time brief: $ref: "#/components/schemas/CampaignBrief" context: $ref: "#/components/schemas/CampaignContext" CampaignBrief: type: object additionalProperties: true required: [campaign, positioning, narrativesToRide, emergingNarrativesToTest, topTags, creatorArchetypes, recommendedCreators, platformStrategy, contentAngles, evidence] properties: campaign: type: string positioning: type: array items: type: string narrativesToRide: type: array items: type: object additionalProperties: true emergingNarrativesToTest: type: array items: type: string topTags: type: array items: type: string creatorArchetypes: type: array items: type: string recommendedCreators: type: array items: type: object additionalProperties: true platformStrategy: type: array items: type: object additionalProperties: true contentAngles: type: array items: type: string evidence: type: array items: type: object additionalProperties: true CampaignContext: type: object additionalProperties: true required: [campaign, input, narratives, topTags, creators, representativeContent, sourceNarrativeIds, sourceContentUuids] properties: campaign: type: string input: type: object additionalProperties: true narratives: type: array items: type: object additionalProperties: true topTags: type: array items: type: string creators: type: array items: type: object additionalProperties: true representativeContent: type: array items: type: object additionalProperties: true sourceNarrativeIds: type: array items: oneOf: - type: integer - type: string sourceContentUuids: type: array items: type: string LlmContextResponse: type: object description: >- `llm_context` payload — the assembled campaign context wrapped for direct injection into an LLM prompt. Same `context` block as `campaign_brief`, minus the generated brief. required: [type, generatedAt, context] properties: type: type: string enum: [kinetk.campaign_context.v1] description: Versioned envelope tag so consumers can pin to a context schema version. generatedAt: type: string format: date-time description: When the context was assembled. context: $ref: "#/components/schemas/CampaignContext" JobKind: type: string description: Discriminator for which pipeline runs the job. enum: - intelligence_search - intelligence_discover - campaign_brief - llm_context JobStatus: type: string enum: - queued - running - succeeded - failed JobSubmitRequest: description: Discriminated union by `kind`. oneOf: - $ref: "#/components/schemas/IntelligenceSearchJobRequest" - $ref: "#/components/schemas/IntelligenceDiscoverJobRequest" - $ref: "#/components/schemas/CampaignBriefJobRequest" - $ref: "#/components/schemas/LlmContextJobRequest" discriminator: propertyName: kind mapping: intelligence_search: "#/components/schemas/IntelligenceSearchJobRequest" intelligence_discover: "#/components/schemas/IntelligenceDiscoverJobRequest" campaign_brief: "#/components/schemas/CampaignBriefJobRequest" llm_context: "#/components/schemas/LlmContextJobRequest" IntelligenceSearchJobRequest: type: object required: [kind, input] description: Live retrieval — ranked content only. properties: kind: type: string enum: [intelligence_search] input: $ref: "#/components/schemas/QueryIntelligenceRequest" IntelligenceDiscoverJobRequest: type: object required: [kind, input] description: >- Intelligence-signals job: returns LLM-generated insight signals (see QueryNarrativeDiscoveryResponse). Currently limited to `window: all`; the 24h, 7d and 30d time windows are coming soon. properties: kind: type: string enum: [intelligence_discover] input: allOf: - $ref: "#/components/schemas/QueryIntelligenceRequest" - type: object properties: window: type: string enum: [all] description: >- Currently limited to `all`. The 24h, 7d and 30d time windows are coming soon for intelligence_discover. CampaignBriefJobRequest: type: object required: [kind, input] description: Live discovery + persisted campaign brief. properties: kind: type: string enum: [campaign_brief] input: $ref: "#/components/schemas/CampaignBriefRequest" LlmContextJobRequest: type: object required: [kind, input] description: Same evidence bundle as `campaign_brief`, no prose, no save. properties: kind: type: string enum: [llm_context] input: $ref: "#/components/schemas/CampaignBriefRequest" JobSubmitAcceptedResponse: type: object required: [jobId, status, statusUrl] properties: jobId: type: string status: $ref: "#/components/schemas/JobStatus" estimated_cost: type: number description: Upper-bound cost in credits, reserved on submit. Final charge (≤ this) is computed when the job completes. example: 3.333 statusUrl: type: string example: /intelligence/jobs/01931f7e-... dedup: type: boolean description: True when this `jobId` was reused for an in-flight identical request. JobSubmitCacheHitResponse: type: object required: [jobId, status, fromCache, result] properties: jobId: type: string status: type: string enum: [succeeded] fromCache: type: boolean charged: type: number description: Credits charged for this cached answer (full rate — no cache discount). example: 2 result: description: | Per-kind result payload: `intelligence_search` → `IntelligenceSearchResponse`, `intelligence_discover` → `QueryNarrativeDiscoveryResponse`, `campaign_brief` → `CampaignBriefResponse`, `llm_context` → `LlmContextResponse`. oneOf: - $ref: "#/components/schemas/IntelligenceSearchResponse" - $ref: "#/components/schemas/QueryNarrativeDiscoveryResponse" - $ref: "#/components/schemas/CampaignBriefResponse" - $ref: "#/components/schemas/LlmContextResponse" Job: type: object required: [jobId, kind, status, submittedAt] properties: jobId: type: string kind: $ref: "#/components/schemas/JobKind" status: $ref: "#/components/schemas/JobStatus" submittedAt: type: integer format: int64 description: Unix epoch ms — when the job was accepted. startedAt: type: integer format: int64 description: Unix epoch ms — when the worker began processing. completedAt: type: integer format: int64 description: Unix epoch ms — when the terminal result was written. result: description: | Present once `status: succeeded`. Per-kind payload: `intelligence_search` → `IntelligenceSearchResponse`, `intelligence_discover` → `QueryNarrativeDiscoveryResponse`, `campaign_brief` → `CampaignBriefResponse`, `llm_context` → `LlmContextResponse`. Large results are rehydrated transparently from object storage. oneOf: - $ref: "#/components/schemas/IntelligenceSearchResponse" - $ref: "#/components/schemas/QueryNarrativeDiscoveryResponse" - $ref: "#/components/schemas/CampaignBriefResponse" - $ref: "#/components/schemas/LlmContextResponse" error: type: string description: >- Present once `status: failed`. Human-readable failure reason. charged: type: number description: Credits actually charged. Present once a successful job has settled; a failed job is refunded in full. example: 2 actualRecordsReturned: type: integer description: Records the job returned, used to compute the dynamic final cost (Tier-1 only). InsufficientCreditsResponse: type: object description: >- Returned with `402` when the account balance can't cover the call. The `recommendations` block tells the caller how to fit their remaining budget — either a smaller `limit` (Tier 1) or a cheaper `window`. required: [error, required, available, tier, window, recommendations] properties: error: type: string enum: [insufficient_credits] required: type: number description: Credits the call would cost (upper-bound estimate). example: 4 available: type: number description: Credits currently available. example: 1.5 tier: type: integer enum: [1, 2] description: "1 = Enriched Records (priced by volume); 2 = Intelligence Signals (priced per signal)." window: $ref: "#/components/schemas/QueryWindow" recommendations: type: object properties: maxLimitForWindow: type: integer description: "Tier 1: largest `limit` affordable for the requested window with the current balance." example: 450 alternativeWindows: type: array description: "Tier 1: per-window max affordable `limit`, so freshness can be traded for volume." items: type: object required: [window, maxLimit] properties: window: $ref: "#/components/schemas/QueryWindow" maxLimit: type: integer affordableWindows: type: array description: "Tier 2: windows whose per-signal cost the balance can cover, cheapest first." items: type: object required: [window, cost] properties: window: $ref: "#/components/schemas/QueryWindow" cost: type: number topUpUrl: type: string format: uri description: Where to purchase more credits. UsageEntry: type: object required: [txnId, type, credits, occurredAt] properties: txnId: type: string type: type: string enum: [topup, settle, refund] credits: type: number description: Signed credits — positive for top-ups/settles, negative for refunds. occurredAt: type: string format: date-time balanceAfter: type: number jobId: type: string keyId: type: string kind: $ref: "#/components/schemas/JobKind" window: $ref: "#/components/schemas/QueryWindow" stripePaymentIntentId: type: string UsageResponse: type: object required: [accountId, usage] properties: accountId: type: string keyId: type: string description: Present when the query was scoped to a single key. usage: type: array items: $ref: "#/components/schemas/UsageEntry" nextCursor: type: string nullable: true description: Pass as `cursor` to fetch the next page; absent/null on the last page.