{
  "openapi": "3.1.0",
  "info": {
    "title": "Sociable AI Search API",
    "version": "1.0.0",
    "description": "Search indexed short-form social content with natural language and retrieve structured post detail. Use this API for social-content research, trend discovery, and brand engagement workflows. All operations require a Sociable AI Search API key. The versioning and deprecation policy is published at https://www.sociable.how/api-versioning.md.",
    "license": {
      "name": "Proprietary",
      "url": "https://www.sociable.how/legal/terms"
    },
    "x-versioning-policy": "https://www.sociable.how/api-versioning.md"
  },
  "externalDocs": {
    "description": "Sociable AI Search API documentation",
    "url": "https://docs.sociable.how/search-api/overview/"
  },
  "servers": [
    {
      "url": "https://search-backend-s4c56s44ia-uk.a.run.app/v1",
      "description": "Production Search API"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Search",
      "description": "Natural-language social-content discovery"
    },
    {
      "name": "Posts",
      "description": "Structured detail for posts returned by search"
    }
  ],
  "paths": {
    "/search": {
      "post": {
        "operationId": "searchSocialContent",
        "tags": ["Search"],
        "summary": "Search social content",
        "description": "Run a synchronous natural-language search over the indexed Instagram corpus. A search commonly takes 30–60 seconds and can take longer for thin-result diagnosis. Send a unique Idempotency-Key for each intended search and reuse it with an identical body after a network timeout.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "A natural-language query, a reference video, or both.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchRequest"
              },
              "examples": {
                "topicSearch": {
                  "summary": "Search by topic and time range",
                  "value": {
                    "query": "funny cooking videos from the last 30 days"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Completed search, including valid zero-result searches.",
            "headers": {
              "X-Request-Id": {
                "description": "Identifier for the logical search.",
                "schema": { "type": "string" }
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SearchResponse" }
              }
            }
          },
          "400": {
            "description": "Malformed JSON request.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "401": {
            "description": "Missing, invalid, or revoked Search API key.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "409": {
            "description": "An Idempotency-Key was reused with a different request body.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "422": {
            "description": "Request validation failed.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "429": {
            "description": "Hourly, concurrency, or total search quota exceeded.",
            "headers": {
              "Retry-After": { "description": "Seconds before a timed retry is appropriate.", "schema": { "type": "integer", "minimum": 0 } },
              "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/ErrorResponse" } } }
          },
          "500": {
            "description": "Unexpected search failure.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "503": {
            "description": "Temporary capacity or database availability failure.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "504": {
            "description": "Work exceeded its execution deadline.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          }
        }
      }
    },
    "/reference-uploads": {
      "post": {
        "operationId": "createReferenceUpload",
        "tags": ["Search"],
        "summary": "Create a reference-video upload target",
        "description": "Create a short-lived signed upload target for an MP4 reference video. Upload the file with the returned method, URL, and headers, then pass the returned uploadId in a search request.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ReferenceUploadRequest" }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Upload target created.",
            "headers": {
              "Deprecation": { "$ref": "#/components/headers/Deprecation" },
              "Sunset": { "$ref": "#/components/headers/Sunset" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ReferenceUploadResponse" }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, or revoked Search API key.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "422": {
            "description": "Request validation failed.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "429": {
            "description": "Hourly, concurrency, or total quota exceeded.",
            "headers": {
              "Retry-After": { "description": "Seconds before a timed retry is appropriate.", "schema": { "type": "integer", "minimum": 0 } },
              "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/ErrorResponse" } } }
          }
        }
      }
    },
    "/posts/{id}": {
      "get": {
        "operationId": "getSocialPost",
        "tags": ["Posts"],
        "summary": "Get structured post detail",
        "description": "Retrieve post, creator, content analysis, and transcript data for a post ID returned by search.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Post identifier returned by a Search API result.",
            "schema": { "type": "string", "minLength": 1 }
          }
        ],
        "responses": {
          "200": {
            "description": "Structured post detail.",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PostDetailResponse" }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, or revoked Search API key.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "404": {
            "description": "The requested post does not exist.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "429": {
            "description": "Hourly, concurrency, or total quota exceeded.",
            "headers": {
              "Retry-After": { "description": "Seconds before a timed retry is appropriate.", "schema": { "type": "integer", "minimum": 0 } },
              "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/ErrorResponse" } } }
          },
          "503": {
            "description": "Temporary capacity or database availability failure.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "504": {
            "description": "Work exceeded its execution deadline.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Sociable Search API key",
        "description": "Use an active Sociable AI Search API key: Authorization: Bearer sak_live_<KEY_PREFIX>.<SECRET>."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Strongly recommended. A unique 1–255 character key for the intended search. Reuse the same key and identical body when retrying after a timeout.",
        "schema": { "type": "string", "minLength": 1, "maxLength": 255 }
      }
    },
    "headers": {
      "RateLimitLimit": {
        "description": "Hourly request allowance.",
        "schema": { "type": "integer", "minimum": 0 }
      },
      "RateLimitRemaining": {
        "description": "Accepted operations remaining in the rolling window.",
        "schema": { "type": "integer", "minimum": 0 }
      },
      "RateLimitReset": {
        "description": "Unix timestamp when the oldest accepted request in the window expires.",
        "schema": { "type": "integer", "format": "int64" }
      },
      "Deprecation": {
        "description": "Present with value true when the operation or version is deprecated. See the versioning policy for migration guidance.",
        "schema": { "type": "boolean" }
      },
      "Sunset": {
        "description": "HTTP-date after which a deprecated operation or version may stop serving requests.",
        "schema": { "type": "string" }
      }
    },
    "schemas": {
      "SearchRequest": {
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "query": {
            "type": "string",
            "minLength": 2,
            "maxLength": 500,
            "description": "Natural-language search request with any topic, recency, language, region, format, audience, or exclusion constraints."
          },
          "reference": {
            "$ref": "#/components/schemas/SearchReference"
          }
        },
        "anyOf": [
          { "required": ["query"] },
          { "required": ["reference"] }
        ]
      },
      "SearchReference": {
        "type": "object",
        "additionalProperties": false,
        "description": "Exactly one reference source.",
        "properties": {
          "uploadId": { "type": "string", "description": "Opaque ID from POST /reference-uploads." },
          "mediaUrl": { "type": "string", "format": "uri", "description": "Supported internal GCS media URL." },
          "permalink": { "type": "string", "format": "uri", "description": "Public Instagram post permalink." }
        },
        "oneOf": [
          { "required": ["uploadId"] },
          { "required": ["mediaUrl"] },
          { "required": ["permalink"] }
        ]
      },
      "SearchResponse": {
        "type": "object",
        "required": ["requestId", "query", "message", "suggestions", "totalCount", "results"],
        "properties": {
          "requestId": { "type": "string", "description": "Stable identifier for this logical search." },
          "query": { "type": "string" },
          "message": { "type": "string", "description": "Search summary and optional thin-result diagnosis." },
          "suggestions": { "type": "array", "items": { "type": "string" }, "maxItems": 3 },
          "totalCount": { "type": "integer", "minimum": 0 },
          "results": { "type": "array", "items": { "$ref": "#/components/schemas/SearchResult" } }
        }
      },
      "SearchResult": {
        "type": "object",
        "required": ["id", "platform", "url", "caption", "mediaUrl", "creatorHandle", "postedAt", "metrics"],
        "properties": {
          "id": { "type": "string" },
          "platform": { "type": "string", "enum": ["Instagram"] },
          "url": { "type": "string", "format": "uri" },
          "caption": { "type": "string" },
          "mediaUrl": { "type": "string", "format": "uri" },
          "creatorHandle": { "type": "string" },
          "postedAt": { "type": "string", "format": "date-time" },
          "metrics": { "$ref": "#/components/schemas/Metrics" },
          "subject": { "type": ["string", "null"] }
        }
      },
      "Metrics": {
        "type": "object",
        "required": ["likes", "comments"],
        "properties": {
          "likes": { "type": "integer", "minimum": 0 },
          "comments": { "type": "integer", "minimum": 0 }
        }
      },
      "ReferenceUploadRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": ["fileName", "contentType", "sizeBytes"],
        "properties": {
          "fileName": { "type": "string", "minLength": 1 },
          "contentType": { "type": "string", "const": "video/mp4" },
          "sizeBytes": { "type": "integer", "minimum": 1, "maximum": 104857600 }
        }
      },
      "ReferenceUploadResponse": {
        "type": "object",
        "required": ["uploadId", "upload", "reference"],
        "properties": {
          "uploadId": { "type": "string" },
          "upload": {
            "type": "object",
            "required": ["method", "url", "headers", "expiresAt"],
            "properties": {
              "method": { "type": "string", "const": "PUT" },
              "url": { "type": "string", "format": "uri" },
              "headers": { "type": "object", "additionalProperties": { "type": "string" } },
              "expiresAt": { "type": "string", "format": "date-time" }
            }
          },
          "reference": {
            "type": "object",
            "required": ["uploadId"],
            "properties": { "uploadId": { "type": "string" } }
          }
        }
      },
      "PostDetailResponse": {
        "type": "object",
        "required": ["post", "creator", "analysis", "transcript"],
        "properties": {
          "post": { "$ref": "#/components/schemas/SearchResult" },
          "creator": { "type": "object", "additionalProperties": true, "description": "Creator identity and audience context." },
          "analysis": { "type": "object", "additionalProperties": true, "description": "Structured subject, summary, language, region, format, topic, brand, and product analysis." },
          "transcript": { "type": ["string", "null"], "description": "Timestamped play-by-play transcript when available." }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": ["error"],
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": ["type", "code", "message"],
            "properties": {
              "type": { "type": "string", "description": "Broad error category." },
              "code": { "type": "string", "description": "Stable machine-readable code. Branch on this value." },
              "message": { "type": "string", "description": "Human-readable explanation and resolution hint when available." }
            }
          }
        }
      }
    }
  }
}
