openapi: 3.0.0
info:
  title: Perplexity API
  description: |
    Get Perplexity's AI-generated answers in structured JSON. This API returns AI-synthesized
    text blocks, a generated markdown version of the answer, and the cited reference links.
  version: 1.0.0
servers:
  - url: https://www.searchapi.io/api/v1
paths:
  /search:
    get:
      summary: Perplexity Search
      security:
        - ApiKeyAuth: []
        - ApiKeyQuery: []
      parameters:
        - name: engine
          in: query
          required: true
          description: Search engine to use
          schema:
            type: string
            enum: ["perplexity"]
            default: "perplexity"
        - name: q
          in: query
          required: true
          description: The question or terms you want to ask Perplexity. The answer is returned in the same language as your query.
          schema:
            type: string
        - name: sources
          in: query
          required: false
          description: >
            Comma-separated list of source connectors Perplexity searches. Defaults to `web`.
            Supported values: `web` (general web results) and `scholar` (academic papers).
            Example: `sources=web,scholar`.
          schema:
            type: string
            default: "web"
      responses:
        '200':
          description: Successful response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SearchResponse'
        '400':
          description: Validation Error. There is an issue with query parameters, such as missing required parameters or invalid values.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Authentication Error. The API key is missing or invalid.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          description: Rate Limit Exceeded. The number of allowed requests has been exceeded. Consider upgrading your plan or waiting for the limit to reset.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '500':
          description: Server Error. Internal server error.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '503':
          description: Timeout. We could not retrieve results in 90 seconds.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: 'Use Bearer authentication. Format: "Bearer YOUR_API_KEY"'
    ApiKeyQuery:
      type: apiKey
      in: query
      name: api_key
      description: Pass API key as query parameter
  schemas:
    SearchResponse:
      type: object
      properties:
        search_metadata:
          $ref: '#/components/schemas/SearchMetadata'
        search_parameters:
          $ref: '#/components/schemas/SearchParameters'
        text_blocks:
          type: array
          description: AI-generated content blocks including paragraphs, headers, lists, tables, and code
          items:
            $ref: '#/components/schemas/TextBlock'
        response_metadata:
          $ref: '#/components/schemas/ResponseMetadata'
        search_queries:
          type: array
          description: >
            The search queries Perplexity grounded the answer on. In practice this echoes `q`.
            Dormant today: Perplexity currently grounds answers in `multi_step` mode, which omits
            this field, so live responses do not carry it. It returns only if Perplexity re-enables
            fast-mode search plans, reported as `response_metadata.search_implementation_mode:
            fast`. Treat the field as optional and read `search_implementation_mode` to tell a
            grounding mode that omits it from a genuinely missing value.
          items:
            type: string
        related_questions:
          type: array
          description: Suggested follow-up questions related to the answer
          items:
            $ref: '#/components/schemas/RelatedQuestion'
        markdown:
          type: string
          description: Markdown-formatted version of the AI-generated answer
        reference_links:
          type: array
          description: Reference links cited in the AI-generated answer
          items:
            $ref: '#/components/schemas/ReferenceLink'
        cards:
          type: array
          description: Typed structured cards (weather, finance, and others) rendered in the answer
          items:
            $ref: '#/components/schemas/AiAnswerCard'
      required:
        - search_metadata
        - search_parameters
        - markdown
        - text_blocks
    SearchMetadata:
      type: object
      required: [id, status, created_at]
      properties:
        id:
          type: string
          description: Unique identifier for the search request
        status:
          type: string
          description: Status of the search request
        created_at:
          type: string
          format: date-time
          description: Timestamp when the search was created
        request_time_taken:
          type: number
          description: Time taken to make the request in seconds
        parsing_time_taken:
          type: number
          description: Time taken to parse the results in seconds
        total_time_taken:
          type: number
          description: Total time taken for the search in seconds
        request_url:
          type: string
          description: Perplexity URL for this search
        html_url:
          type: string
          description: URL to view the raw response
        json_url:
          type: string
          description: URL to view JSON results
    SearchParameters:
      type: object
      properties:
        engine:
          type: string
          description: Search engine used
        q:
          type: string
          description: Search query
        sources:
          type: string
          description: >
            Comma-separated list of source connectors that were searched (`web`, `scholar`).
            Omitted when the request did not set it.
    TextBlock:
      type: object
      required: [type]
      properties:
        type:
          type: string
          enum: ["header", "paragraph", "unordered_list", "ordered_list", "table", "code_blocks"]
          description: Type of content block
        answer:
          type: string
          description: Text content for header, paragraph, or list introduction
        reference_indexes:
          type: array
          description: Zero-based indexes into `reference_links` cited by this block
          items:
            type: integer
        items:
          type: array
          description: List items (for ordered/unordered lists)
          items:
            $ref: '#/components/schemas/TextBlock'
        table:
          type: object
          description: Table data (for table type)
          properties:
            headers:
              type: array
              description: Table header row
              items:
                type: string
            rows:
              type: array
              description: Table data rows
              items:
                type: array
                items:
                  type: string
          required: [headers, rows]
        language:
          type: string
          description: Programming language (for code_blocks type)
        code:
          type: string
          description: Code content (for code_blocks type)
    ReferenceLink:
      type: object
      required: [index, link]
      properties:
        index:
          type: integer
          description: Zero-based position of this reference in `reference_links`
        title:
          type: string
          description: Page title
        link:
          type: string
          description: URL of the reference
        snippet:
          type: string
          description: Text snippet from the referenced page
        source:
          type: string
          description: Source website name
        date:
          type: string
          description: Publish date of the referenced page (ISO 8601)
        thumbnail:
          type: string
          description: Thumbnail image URL for the referenced page — the first entry of `images`
        images:
          type: array
          description: Thumbnail image URLs for the referenced page; usually one, occasionally more
          items:
            type: string
        trust:
          type: object
          description: >
            Perplexity's credibility grading for this source. Absent when the source is ungraded,
            and absent entirely on answers produced before the grading rolled out.
          properties:
            level:
              type: integer
              description: Numeric grade, higher is more trusted (e.g. `1` credible, `2` trusted)
            label:
              type: string
              description: Grade name (e.g. `credible`, `trusted`)
            description:
              type: string
              description: Why the source carries that grade, in Perplexity's words

    RelatedQuestion:
      type: object
      required: [question]
      properties:
        question:
          type: string
          description: Suggested follow-up question text

    ResponseMetadata:
      type: object
      description: Conversation-level metadata about the answer
      properties:
        model:
          type: string
          description: The Perplexity model that produced the answer
        search_implementation_mode:
          type: string
          description: >
            How Perplexity grounded the answer, e.g. `fast` or `multi_step`. Live answers report
            `multi_step` today, and that mode omits `search_queries`; the field is returned only
            under `fast`. Read this before treating an absent `search_queries` as a fault.
        backend_uuid:
          type: string
        context_uuid:
          type: string
        read_write_token:
          type: string

    # Cards union copied from public/openapi/shared/ai_answer_components.yaml (issue #5427):
    # branches are pairwise disjoint so strict oneOf validation passes; a permissive generic
    # Card branch would make a weather instance match two branches and FAIL. Keep in sync there.
    AiAnswerCard:
      description: >
        A structured card rendered alongside the AI answer. Known types are normalized to one
        canonical schema each; unknown provider types fall back to RawCard with the native
        payload preserved under `raw`.
      oneOf:
        - $ref: '#/components/schemas/WeatherCard'
        - $ref: '#/components/schemas/FinanceCard'
        - $ref: '#/components/schemas/RawCard'
      # No OAS `discriminator` on purpose: an unmapped value resolves as an implicit schema name,
      # so every type we degrade to RawCard would fail. Disjoint branches need no discriminator.
    WeatherCard:
      type: object
      description: A weather card summarising current conditions and a daily forecast.
      required: [type]
      additionalProperties: false
      properties:
        type:
          type: string
          enum: ["weather"]
          description: The card type (always `weather`)
        source:
          type: object
          description: Who supplied the card's data; the only attribution a widget-only answer carries
          properties:
            name:
              type: string
              description: Provider name (e.g. `AccuWeather`)
            link:
              type: string
              description: Provider page the data came from
            date:
              type: string
              description: Provider's as-of timestamp, when it ships one
        location:
          type: string
          description: Forecast location name
        country:
          type: string
          description: Forecast location country
        region:
          type: string
          description: Forecast location administrative region
        timezone:
          type: string
          description: IANA timezone of the location (e.g. `Europe/Paris`)
        local_time:
          type: string
          description: Local date and time at the location (`YYYY-MM-DD HH:MM`)
        current:
          type: object
          description: Current weather conditions
          properties:
            temperature:
              type: number
              description: Current temperature, in the unit named by `unit`
            unit:
              type: string
              description: >
                Temperature unit (`F` or `C`). Returned alongside `temperature`, and it governs that
                reading and the forecast `high`/`low` and nothing else — the wind, pressure,
                precipitation and visibility readings below are served in their own fixed units.
            condition:
              type: string
              description: Current condition text (e.g. `Sunny`)
            condition_icon:
              type: string
              description: Condition icon slug (e.g. `clear-day`)
            is_day:
              type: boolean
              description: Whether it is currently daytime at the location
            humidity:
              type: number
              description: Relative humidity in percent
            uv_index:
              type: number
              description: UV index
            wind_speed:
              type: number
              description: Wind speed in mph — the widget ships `wind_mph` only, never converted by `unit`
            wind_direction:
              type: string
              description: Wind origin direction (e.g. `west`, `NNE`)
            cloud_cover:
              type: number
              description: Cloud cover in percent
            pressure:
              type: number
              description: Barometric pressure in inches of mercury (inHg, never converted by `unit`)
            precipitation_inches:
              type: number
              description: Precipitation amount in inches (never converted by `unit`)
            visibility:
              type: number
              description: Visibility in miles (never converted by `unit`)
        forecast:
          type: array
          description: Daily forecast entries
          items:
            type: object
            properties:
              date:
                type: string
                description: Forecast day
              high:
                type: number
                description: High temperature, in the unit named by `current.unit`
              low:
                type: number
                description: Low temperature, in the unit named by `current.unit`
              condition:
                type: string
                description: Daytime forecast condition text
              precipitation_chance:
                type: number
                description: Chance of precipitation in percent
              day_of_week:
                type: string
                description: Day name for `date` (e.g. `Tuesday`)
              condition_icon:
                type: string
                description: Provider icon slug for the day's condition (e.g. `clear-day`)
              hourly:
                type: array
                description: Hour-by-hour readings — 12 entries for today, 24 thereafter
                items:
                  type: object
                  properties:
                    time:
                      type: string
                      description: Local time of the reading (e.g. `2026-07-21 12:00`)
                    temperature:
                      type: number
                      description: Temperature in the card's `current.unit`, to one decimal
                    condition:
                      type: string
                      description: Condition text for the hour
                    condition_icon:
                      type: string
                      description: Provider icon slug for the hour's condition
                    is_day:
                      type: boolean
                      description: Whether the hour falls in daylight
                    humidity:
                      type: number
                      description: Relative humidity percentage
                    wind_speed:
                      type: number
                      description: Wind speed in mph, as the provider ships it
                    precipitation_chance:
                      type: number
                      description: Chance of precipitation percentage
    FinanceCard:
      type: object
      description: A finance card summarising a stock/ticker quote.
      required: [type]
      additionalProperties: false
      properties:
        type:
          type: string
          enum: ["finance"]
          description: The card type (always `finance`)
        source:
          type: object
          description: Who supplied the card's data; the only attribution a widget-only answer carries
          properties:
            name:
              type: string
              description: Provider name (e.g. `AccuWeather`)
            link:
              type: string
              description: Provider page the data came from
            date:
              type: string
              description: Provider's as-of timestamp, when it ships one
        symbol:
          type: string
          description: Ticker symbol
        name:
          type: string
          description: Company or instrument name
        price:
          type: number
          description: Latest price
        currency:
          type: string
          description: Quote currency code
        change:
          type: number
          description: Absolute price change
        change_percent:
          type: number
          description: Percent price change
        previous_close:
          type: number
          description: Previous session's closing price
        market_cap:
          type: number
          description: Market capitalization
        pe_ratio:
          type: number
          description: Price/earnings ratio
        average_volume:
          type: number
          description: Average trading volume
        price_open:
          type: number
          description: Opening price
        price_high:
          type: number
          description: Intraday high price
        price_low:
          type: number
          description: Intraday low price
        price_52w_high:
          type: number
          description: 52-week high price
        price_52w_low:
          type: number
          description: 52-week low price
        security_type:
          type: string
          description: Instrument type (e.g. `equity`)
        exchange:
          type: string
          description: Listing exchange (e.g. `NASDAQ`)
        forward_pe_ratio:
          type: number
          description: Forward price/earnings ratio
        eps:
          type: number
          description: Earnings per share, trailing twelve months
        dividend_yield:
          type: number
          description: Trailing twelve-month (TTM) dividend yield
        volume:
          type: number
          description: Volume traded in the most recent regular session
        price_avg_50d:
          type: number
          description: 50-day average price
        price_avg_200d:
          type: number
          description: 200-day average price
        quote_time:
          type: string
          description: As-of time of the quote, ISO 8601 UTC (e.g. `2026-07-20T20:00:00Z`)
        logo:
          type: string
          description: Instrument logo image URL
        is_market_open:
          type: boolean
          description: Whether the listing exchange is inside its regular trading session
        dollar_volume_24h:
          type: number
          description: >
            24h traded value in the quote currency. Distinct from `volume` for equities (shares);
            for crypto the provider already denominates `volume` in dollars, so the two are equal —
            comparing them is how a client tells which unit `volume` is in.
        funding_rate:
          type: number
          description: >
            Perpetual-futures funding rate. Populated for `security_type: crypto`, absent otherwise.
        exchange_timezone:
          type: string
          description: IANA timezone of the listing exchange (e.g. `America/New_York`)
        exchange_country:
          type: string
          description: Country of the listing exchange (e.g. `US`); absent for crypto
        exchange_hours:
          type: object
          description: >
            The listing exchange's full trading day in `exchange_timezone`, which is the context
            `quote_time` and `extended_hours` are stated against. Absent for crypto, which never
            closes.
          properties:
            open:
              type: string
              description: Opening time, `HH:MM` in `exchange_timezone`
            close:
              type: string
              description: Closing time, `HH:MM` in `exchange_timezone`
        exchange_sessions:
          type: array
          description: >
            The annotated windows inside `exchange_hours` that bracket the regular session. `session`
            uses the same vocabulary as `extended_hours.session`.
          items:
            type: object
            properties:
              session:
                type: string
                description: Session name (`pre_market` or `after_hours`)
              open:
                type: string
                description: Session start, `HH:MM` in `exchange_timezone`
              close:
                type: string
                description: Session end, `HH:MM` in `exchange_timezone`
        extended_hours:
          type: object
          description: >
            Out-of-session quote, present when the exchange is outside its regular session. `session`
            names which extended session it belongs to (the provider ships both under `afterHours*`).
          properties:
            price:
              type: number
              description: Extended-hours price
            change:
              type: number
              description: Absolute change versus the regular-session close
            change_percent:
              type: number
              description: Percent change versus the regular-session close
            session:
              type: string
              description: Extended session the quote belongs to (`pre_market` or `after_hours`)
            quote_time:
              type: string
              description: As-of time of the extended-hours quote, ISO 8601 UTC
    RawCard:
      type: object
      description: >
        Structural fallback for provider card types without a canonical schema yet. The native
        payload is preserved unflattened under `raw`, which is REQUIRED: a card holding only its
        type tells a client nothing, so an emitter whose payload came out empty must drop the card
        rather than serve `{type}`. Emit-side trap: `{type:, raw:}.compact_blank` strips an empty
        `raw` and produces exactly that rejected shape.
      required: [type, raw]
      additionalProperties: false
      properties:
        type:
          type: string
          description: Provider card type (singular snake_case), never a known canonical type
          not:
            enum: ["weather", "finance", "knowledge_entity"]
        raw:
          type: object
          description: Provider-native card payload, preserved as-is; never empty
          # `required: [type, raw]` only catches a MISSING key — `raw: {}` satisfied it and shipped
          # a schema-valid but information-free card, which is exactly what the text above forbids.
          minProperties: 1
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: string
          description: Error message describing what went wrong
