# AUTO-GENERATED by scripts/gen-openapi.ts — DO NOT EDIT BY HAND. # Source of truth: serverless.yml routes + handler middleware + types/schema.json. # Regenerate: `npm run openapi:gen` 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_records`, `intelligence_signals`, `campaign_brief`, `llm_context`). Authentication: send your secret key in the `x-api-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. security: - ApiKeyAuth: [] paths: /health: get: operationId: health summary: Health + sync-freshness probe description: Liveness probe reporting connectivity and database sync freshness. Returns `503` when the database is unreachable. security: - ApiKeyAuth: [] responses: "200": description: Successful response content: application/json: schema: type: object headers: X-Kinetk-Credits-Used: $ref: "#/components/headers/CreditsUsed" X-Kinetk-Credits-Remaining: $ref: "#/components/headers/CreditsRemaining" default: $ref: "#/components/responses/Error" /intelligence/jobs: post: operationId: intelligenceJobsSubmit summary: Submit an async intelligence job description: > Submit a job for async execution. Request body is `{ kind, input }` discriminated by `kind`. 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. For `intelligence_records` / `campaign_brief` / `llm_context`, `window` (in `input`) is REQUIRED — it drives the credit price (fresher windows cost more). `estimated_cost` is an upper bound; the final charge is computed from the records actually returned. Input validation is strict: any undeclared field, type mismatch, or out-of-enum value is rejected with `400` (nothing is silently stripped or coerced). security: - ApiKeyAuth: [] requestBody: required: true content: application/json: schema: oneOf: - $ref: "#/components/schemas/IQueryJobSubmitBody" - $ref: "#/components/schemas/ISignalsJobSubmitBody" - $ref: "#/components/schemas/ICampaignJobSubmitBody" discriminator: propertyName: kind mapping: intelligence_records: "#/components/schemas/IQueryJobSubmitBody" intelligence_signals: "#/components/schemas/ISignalsJobSubmitBody" campaign_brief: "#/components/schemas/ICampaignJobSubmitBody" llm_context: "#/components/schemas/ICampaignJobSubmitBody" responses: "200": description: Successful response content: application/json: schema: $ref: "#/components/schemas/IJobSubmitResponse" headers: X-Kinetk-Credits-Used: $ref: "#/components/headers/CreditsUsed" X-Kinetk-Credits-Remaining: $ref: "#/components/headers/CreditsRemaining" "402": description: Insufficient credits. The body reports the credits required vs available plus budget recommendations (max affordable limit, cheaper windows, top-up URL). content: application/json: schema: $ref: "#/components/schemas/IInsufficientCreditsResponse" default: $ref: "#/components/responses/Error" "/intelligence/jobs/{id}": get: operationId: intelligenceJobsStatus 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 an unknown `jobId`, `410` once 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. security: - ApiKeyAuth: [] parameters: - name: id in: path required: true schema: minLength: 1 type: string responses: "200": description: Successful response content: application/json: schema: $ref: "#/components/schemas/IJobStatusResponse" headers: X-Kinetk-Credits-Used: $ref: "#/components/headers/CreditsUsed" X-Kinetk-Credits-Remaining: $ref: "#/components/headers/CreditsRemaining" default: $ref: "#/components/responses/Error" "/narratives/{id}": get: operationId: narrativeDetail summary: Drill into one precomputed narrative description: Full evidence bundle for one narrative cluster — header, content rows, top creators, per-platform breakdown, duplicate groups. security: - ApiKeyAuth: [] parameters: - name: id in: path required: true schema: minLength: 1 type: string responses: "200": description: Successful response content: application/json: schema: $ref: "#/components/schemas/INarrativeDetailResponse" headers: X-Kinetk-Credits-Used: $ref: "#/components/headers/CreditsUsed" X-Kinetk-Credits-Remaining: $ref: "#/components/headers/CreditsRemaining" default: $ref: "#/components/responses/Error" /narratives/search: get: operationId: narrativesSearch summary: Filtered search over precomputed narratives description: "Free-text search over precomputed narrative clusters, ranked by relevance. For live retrieval, use `POST /intelligence/jobs` with `kind: intelligence_signals`." security: - ApiKeyAuth: [] parameters: - name: limit in: query required: false schema: type: string - name: q in: query required: true schema: minLength: 1 type: string - name: window in: query required: false schema: enum: - 30d - 7d type: string responses: "200": description: Successful response content: application/json: schema: $ref: "#/components/schemas/INarrativeSearchResponse" headers: X-Kinetk-Credits-Used: $ref: "#/components/headers/CreditsUsed" X-Kinetk-Credits-Remaining: $ref: "#/components/headers/CreditsRemaining" default: $ref: "#/components/responses/Error" /narratives/trending: get: operationId: narrativesTrending summary: Top precomputed narratives for a window description: "Reads precomputed narrative clusters for a bounded window. For live retrieval, submit `POST /intelligence/jobs` with `kind: intelligence_signals`." security: - ApiKeyAuth: [] parameters: - name: limit in: query required: false schema: type: string - name: window in: query required: false schema: enum: - 30d - 7d type: string responses: "200": description: Successful response content: application/json: schema: $ref: "#/components/schemas/ITrendingResponse" headers: X-Kinetk-Credits-Used: $ref: "#/components/headers/CreditsUsed" X-Kinetk-Credits-Remaining: $ref: "#/components/headers/CreditsRemaining" default: $ref: "#/components/responses/Error" /usage: get: operationId: usage 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`. security: - ApiKeyAuth: [] responses: "200": description: Successful response content: application/json: schema: type: object headers: X-Kinetk-Credits-Used: $ref: "#/components/headers/CreditsUsed" X-Kinetk-Credits-Remaining: $ref: "#/components/headers/CreditsRemaining" default: $ref: "#/components/responses/Error" components: securitySchemes: ApiKeyAuth: type: apiKey in: header name: x-api-key description: Customer secret key, required on every request. Validated natively by API Gateway; missing / invalid / revoked → 403. responses: Error: description: Error response content: application/json: schema: $ref: "#/components/schemas/ErrorResponse" schemas: ErrorResponse: type: object required: - error properties: error: type: string IBudgetRecommendations: additionalProperties: false properties: alternativeWindows: items: $ref: "#/components/schemas/IBudgetWindowOption" type: array maxLimitForWindow: type: number topUpUrl: type: string required: - maxLimitForWindow - topUpUrl type: object IBudgetWindowOption: additionalProperties: false properties: maxLimit: type: number window: $ref: "#/components/schemas/PublicJobWindow" required: - maxLimit - window type: object ICampaignBriefInput: additionalProperties: false properties: audience: type: string campaign: type: string limit: type: number platforms: items: type: string type: array tone: type: string window: $ref: "#/components/schemas/PublicJobWindow" required: - campaign - limit - window type: object ICampaignJobSubmitBody: additionalProperties: false properties: input: $ref: "#/components/schemas/ICampaignBriefInput" kind: enum: - campaign_brief - llm_context type: string required: - input - kind type: object IInsufficientCreditsResponse: additionalProperties: false properties: credits_available: type: number credits_required: type: number error: enum: - insufficient_credits type: string recommendations: $ref: "#/components/schemas/IBudgetRecommendations" tier: description: "Pricing tier of the requested kind: 1 = per-record, 2 = per-signal." enum: - 1 - 2 type: number window: $ref: "#/components/schemas/PublicJobWindow" required: - credits_available - credits_required - error - recommendations - tier - window type: object IJobStatusResponse: additionalProperties: false properties: completedAt: type: number error: type: string jobId: type: string kind: $ref: "#/components/schemas/JobKind" result: {} startedAt: type: number status: $ref: "#/components/schemas/JobStatus" submittedAt: type: number required: - jobId - kind - status - submittedAt type: object IJobSubmitResponse: additionalProperties: false properties: dedup: type: boolean fromCache: type: boolean jobId: type: string result: {} status: $ref: "#/components/schemas/JobStatus" statusUrl: type: string required: - jobId - status type: object INarrativeContentItem: additionalProperties: false properties: comment_count: type: number nullable: true description: type: string nullable: true is_representative: type: boolean like_count: type: number nullable: true platform: type: string published_at: type: string nullable: true relevance_score: type: number share_count: type: number nullable: true tags: items: type: string type: array title: type: string nullable: true uuid: type: string view_count: type: number nullable: true required: - is_representative - platform - relevance_score - tags - uuid type: object INarrativeCreatorMetric: additionalProperties: false properties: amplifier_score: type: number content_count: type: number first_seen_at: type: string follower_count: type: number nullable: true id: type: string platform: type: string total_engagement: type: string required: - amplifier_score - content_count - first_seen_at - id - platform - total_engagement type: object INarrativeDetailResponse: additionalProperties: false properties: content: items: $ref: "#/components/schemas/INarrativeContentItem" type: array creators: items: $ref: "#/components/schemas/INarrativeCreatorMetric" type: array duplicateGroups: items: $ref: "#/components/schemas/INarrativeDuplicateGroup" type: array narrative: $ref: "#/components/schemas/INarrativeSummary" platformBreakdown: items: $ref: "#/components/schemas/INarrativePlatformBreakdown" type: array required: - content - creators - duplicateGroups - narrative - platformBreakdown type: object INarrativeDuplicateGroup: additionalProperties: false properties: canonical_content_uuid: type: string confidence: type: number id: type: string members: items: type: string type: array required: - canonical_content_uuid - confidence - id - members type: object INarrativePlatformBreakdown: additionalProperties: false properties: content_count: type: number platform: type: string total_engagement: type: string required: - content_count - platform - total_engagement type: object INarrativeSearchResponse: additionalProperties: false properties: narratives: items: $ref: "#/components/schemas/INarrativeSummary" type: array query: type: string window: enum: - 30d - 7d type: string required: - narratives - query - window type: object INarrativeSummary: additionalProperties: false properties: computed_at: type: string content_count: type: number creator_count: type: number emerging_score: type: number id: type: string label: type: string momentum_score: type: number overlap_count: type: number platform_count: type: number representative_content_uuid: type: string nullable: true score_breakdown: additionalProperties: true properties: {} type: object summary: type: string text_rank: type: number top_tags: items: type: string type: array total_engagement: type: string window_end: type: string window_key: enum: - 30d - 7d type: string window_start: type: string required: - computed_at - content_count - creator_count - emerging_score - id - label - momentum_score - platform_count - score_breakdown - summary - top_tags - total_engagement - window_end - window_key - window_start type: object IQueryIntelligenceInput: additionalProperties: false properties: clusterCount: type: number limit: type: number platforms: items: type: string type: array query: type: string window: $ref: "#/components/schemas/PublicJobWindow" required: - limit - query - window type: object IQueryJobSubmitBody: additionalProperties: false properties: input: $ref: "#/components/schemas/IQueryIntelligenceInput" kind: enum: - intelligence_records type: string required: - input - kind type: object ISignalsJobInput: additionalProperties: false properties: query: type: string required: - query type: object ISignalsJobSubmitBody: additionalProperties: false properties: input: $ref: "#/components/schemas/ISignalsJobInput" kind: enum: - intelligence_signals type: string required: - input - kind type: object ITrendingResponse: additionalProperties: false properties: narratives: items: $ref: "#/components/schemas/INarrativeSummary" type: array window: enum: - 30d - 7d type: string required: - narratives - window type: object JobKind: enum: - campaign_brief - intelligence_records - intelligence_signals - llm_context type: string JobStatus: enum: - failed - queued - running - succeeded type: string PublicJobWindow: enum: - 30d - 7d - all type: string 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