openapi: 3.1.0
info:
  title: Journely Markets API
  version: "0.1.0"
  description: |
    Real-time markets data for engineer-investors building bots, dashboards, and
    AI integrations. All endpoints return JSON; authenticate with a personal
    access token issued from your Journely account.

    ## Quickstart

    Three steps to your first call:

    1. **Get a token** at
       [journely.me/settings/api-keys](https://journely.me/settings/api-keys).
       Copy the `jrn_live_…` secret immediately — it's shown once.
    2. **Set the auth header** on every request:
       `Authorization: Bearer jrn_live_…`
    3. **Try a call** — start with the catalog so you know what we cover:
       ```bash
       curl https://api.journely.me/api/v1/journely/data/markets \
         -H "Authorization: Bearer jrn_live_YOUR_TOKEN"
       ```

    For AI agents, prefer the
    [MCP server](/api-docs/mcp) — same data, same auth, but spoken over Model
    Context Protocol so Claude Desktop / Cursor / Cline can call it natively.

    ## Top-down workflow

    Endpoints are designed for the classic top-down investor funnel. Call them
    in this order to go from "what's the macro?" to "should I buy this stock?":

    | Step | Endpoint | Question it answers |
    |---|---|---|
    | 1 | `GET /data/markets` | What countries + asset types do you cover? |
    | 2 | `GET /data/macro/indicators` | What's the macro regime in country X? |
    | 3 | `GET /data/macro/forecast` | Where are GDP / inflation / rates heading? |
    | 4 | `GET /data/macro/calendar` | When's the next FOMC / CPI / NFP? |
    | 5 | `GET /data/sectors` | Which sectors are cheap relative to history? |
    | 6 | `GET /data/stocks/{symbol}/overview` | Snapshot of a ticker — start here |
    | 7 | `GET /data/stocks/{symbol}/statements` | Revenue, earnings, balance health |
    | 8 | `GET /data/stocks/{symbol}/technicals` | RSI, MACD, MA — entry timing |

    ## Authentication

    Every request must include `Authorization: Bearer jrn_live_…`. The token is
    a high-entropy (~190-bit) secret; we store only its SHA-256 hash and verify
    with constant-time comparison. The plaintext token leaves our service exactly
    once, at creation. **Lost a token? Revoke it and issue a new one** — there
    is no recovery path by design.

    ## Rate limits

    Each token has a daily call ceiling and an hourly burst ceiling that scale
    with your subscription plan. Counters are tracked atomically in Redis and
    reset at midnight UTC (daily) and at the top of each hour (hourly).

    | Plan | Daily | Hourly | Max keys per user |
    |---|---:|---:|---:|
    | Free | 100 | 30 | 1 |
    | Pro | 5,000 | 500 | 5 |
    | Max | 50,000 | 5,000 | 20 |

    Hitting your ceiling? [Upgrade plan](https://journely.me/pricing) for more
    headroom — limits apply immediately on plan change.

    Every successful response carries these headers — read them to self-throttle
    instead of trial-and-erroring into a `429`:

    | Header | Meaning |
    |---|---|
    | `X-RateLimit-Limit` | Daily call ceiling for your plan |
    | `X-RateLimit-Remaining` | Calls left in today's window |
    | `X-RateLimit-Reset` | Unix-seconds timestamp of the next daily reset |
    | `Retry-After` | (429 only) Seconds to wait before retrying |

    When you exceed the limit, the API responds `429 Too Many Requests`. Your
    counter is **rolled back** for the denied call — the denied request does
    not consume budget.

    There's also a per-IP gate at the gateway (`120/minute`) that fires even
    before token-level limits, defending the auth path against brute-force or
    cache-pollution attempts.

    ## Errors

    All errors share one envelope across every endpoint:

    ```json
    {
      "error": {
        "type": "rate_limit_error",
        "code": "RATE_LIMIT_EXCEEDED",
        "message": "wait for the hourly/daily window to reset"
      }
    }
    ```

    - `type` — machine-readable family. Match on this in your client code.
    - `code` — stable specific case. See the
      [Error schema](#tag/Stocks/operation/get_api_v1_data_stocks__symbol__technicals)
      below for the full type→code table.
    - `message` — human copy. Subject to change — **do not parse**.

    ### HTTP status codes

    | Status | Meaning | Example trigger |
    |---|---|---|
    | `200` | OK | A successful call |
    | `202` | Accepted | A JSON-RPC notification with no response body (MCP only) |
    | `400` | Bad Request | Missing required param, invalid country code |
    | `401` | Unauthorized | Missing / malformed / revoked / unknown token |
    | `404` | Not Found | Unknown symbol, category, or country slice |
    | `405` | Method Not Allowed | Wrong HTTP method (e.g., POST on a GET route) |
    | `413` | Payload Too Large | Request body exceeded 1 MiB (MCP) or 10 MiB (REST) |
    | `429` | Too Many Requests | Daily / hourly cap exceeded, or per-IP cap hit |
    | `500` | Internal Server Error | Unhandled server-side bug. Retry with backoff |
    | `503` | Service Unavailable | Upstream data source temporarily down. Retry |

    ## Versioning

    The current API version is `v1`. We use a single major-version path prefix
    rather than per-endpoint versioning so clients only have to know one
    namespace.

    **Breaking-change policy:** any change that could break a well-behaved client
    — removing a field, renaming a field, changing a field's type, tightening
    a validation rule, changing an error code — ships under a new major version
    (`v2`, `v3`). The old version stays online for at least 12 months after
    deprecation, with deprecation notices in response headers (`Deprecation:`,
    `Sunset:`) and on this page.

    **Non-breaking changes** (adding fields, adding endpoints, loosening
    validation) ship in-place under `v1` without warning. Your client should be
    tolerant of additive changes — don't fail on unknown fields.

    ## CORS

    Every endpoint on the public surface accepts cross-origin requests with
    `Access-Control-Allow-Origin: *` and no credentials. You can call this API
    directly from a browser — no proxy needed. Rate-limit headers are exposed
    so JavaScript clients can self-throttle.

    ## Idempotency

    All endpoints in this v1 spec are `GET` and idempotent by HTTP definition —
    you can safely retry on transient failures. The token-management endpoints
    (create / revoke) live behind the user's Supabase session, not the API
    token, and aren't documented here.

    ## Stability + data freshness

    - **Macro indicators / forecasts:** refreshed daily.
    - **Sectors + sector stats:** refreshed daily.
    - **Stocks (overview / statements / technicals):** refreshed daily.
    - **Economic calendar:** refreshed every few hours during the trading week.
    - **Markets catalog:** rebuilt on each symbol-fetcher run (~daily).

    Each response carries an `as_of` field — read it to know how fresh the
    underlying snapshot is. Inside Journely's servers there is also a 30-second
    in-process slice cache on each endpoint, so two calls within 30s may return
    byte-identical responses with the same `as_of` — that's a feature, not a bug.

    ## Support

    Found a bug, need higher limits, or want to discuss a use case?
    Open an issue at [github.com/journelyme](https://github.com/journelyme) or
    reach out via [journely.me](https://journely.me).
  contact:
    name: Journely
    url: https://journely.me
  license:
    name: Proprietary — Journely Terms of Service
    url: https://journely.me/legal/terms

servers:
  - url: https://api.journely.me
    description: Production

# ─── Security ─────────────────────────────────────────────────────────

security:
  - bearerAuth: []

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: jrn_live_xxxx
      description: Personal access token from /settings/api-keys

  parameters:
    Country:
      name: country
      in: query
      required: true
      schema:
        type: string
        enum: [US, JP, VN]
      description: ISO country code for the market data.

    Symbol:
      name: symbol
      in: path
      required: true
      schema:
        type: string
        minLength: 1
        maxLength: 20
      description: Ticker symbol (uppercase, e.g. `AAPL`, `FPT`, `7203`).

  schemas:
    DataResponse:
      type: object
      description: |
        Common envelope returned by every data endpoint. The `data` field's
        shape varies per endpoint — see the per-route response example for the
        exact structure. The envelope itself (`country` / `data` / `as_of`) is
        stable and won't change in v1.
      required: [data, as_of]
      properties:
        country:
          type: string
          enum: [US, JP, VN]
          description: |
            ISO country code the response applies to. Omitted on endpoints that
            return data for multiple countries (e.g., `/markets`, global calendar).
          example: US
        data:
          description: |
            Endpoint-specific payload. See the per-route example for the exact
            shape — every endpoint documents it.
          oneOf:
            - type: object
            - type: array
            - type: "null"
        as_of:
          type: string
          format: date-time
          description: |
            Server-side timestamp when this response was produced. Note: with
            our 30-second slice cache, two calls within the same window will
            share the same `as_of` — that's expected.
          example: "2026-05-12T11:46:11.795171Z"

    Error:
      type: object
      required: [error]
      properties:
        error:
          type: object
          required: [type, code, message]
          properties:
            type:
              type: string
              description: |
                Error family. One of the values listed below. Match on
                this in your client code rather than parsing `message`,
                which is human copy and may change.
              enum:
                - invalid_request
                - authentication_error
                - rate_limit_error
                - not_found
                - server_error
              example: rate_limit_error
            code:
              type: string
              description: |
                Specific error code. Stable, machine-readable. Each
                `type` has a small set of codes:

                | type | possible codes |
                |---|---|
                | `invalid_request` | `INVALID_REQUEST` |
                | `authentication_error` | `MISSING_AUTH`, `INVALID_TOKEN` |
                | `rate_limit_error` | `RATE_LIMIT_EXCEEDED` |
                | `not_found` | `NOT_FOUND` |
                | `server_error` | `INTERNAL` |
              example: RATE_LIMIT_EXCEEDED
            message:
              type: string
              description: Human-readable explanation. Subject to change — do not parse.

  headers:
    XRateLimitLimit:
      schema: { type: integer }
      description: Daily call ceiling for this plan.
    XRateLimitRemaining:
      schema: { type: integer }
      description: Calls remaining in today's window.
    XRateLimitReset:
      schema: { type: integer }
      description: Unix seconds at the next daily reset (midnight UTC).
    RetryAfter:
      schema: { type: integer }
      description: Seconds the client should wait before retrying.

  responses:
    Unauthorized:
      description: Missing, malformed, expired, or revoked API token.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          examples:
            missing:
              summary: No Authorization header
              value:
                error:
                  type: "authentication_error"
                  code: "MISSING_AUTH"
                  message: "missing Authorization: Bearer token"
            invalid:
              summary: Token doesn't exist or has been revoked
              value:
                error:
                  type: "authentication_error"
                  code: "INVALID_TOKEN"
                  message: "invalid or revoked api token"
    NotFound:
      description: The requested country / symbol / category / statement isn't available.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example:
            error:
              type: "not_found"
              code: "NOT_FOUND"
              message: 'symbol "ZZZNOEXIST" not found in US'
    BadRequest:
      description: Invalid or missing query parameter.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          examples:
            missing_country:
              summary: Missing required country
              value:
                error:
                  type: "invalid_request"
                  code: "INVALID_REQUEST"
                  message: "country must be one of: US, JP, VN"
            bad_statement_type:
              summary: Unknown statement type
              value:
                error:
                  type: "invalid_request"
                  code: "INVALID_REQUEST"
                  message: 'statementType must be one of: "income", "balance", "cashflow"'
    RateLimited:
      description: |
        Rate limit exceeded — either the daily / hourly cap on the token
        or the per-IP gate at the gateway. The denied call was rolled back
        and does **not** count against future budget. Wait `Retry-After`
        seconds, then retry.
      headers:
        Retry-After: { $ref: "#/components/headers/RetryAfter" }
        X-RateLimit-Limit: { $ref: "#/components/headers/XRateLimitLimit" }
        X-RateLimit-Remaining: { $ref: "#/components/headers/XRateLimitRemaining" }
        X-RateLimit-Reset: { $ref: "#/components/headers/XRateLimitReset" }
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example:
            error:
              type: "rate_limit_error"
              code: "RATE_LIMIT_EXCEEDED"
              message: "api rate limit exceeded — upgrade to Pro or Max for higher caps"

# ─── Tags ─────────────────────────────────────────────────────────────

tags:
  - name: Markets
    description: Discovery — countries, asset types, trading holidays.
  - name: Macro
    description: Country-level macroeconomic indicators, forecasts, and economic calendar.
  - name: Sectors
    description: Sector roll-ups with valuation + financial-stat medians.
  - name: Stocks
    description: Per-ticker fundamentals, statements, and technicals.

# ─── Paths ────────────────────────────────────────────────────────────

paths:
  /api/v1/journely/data/markets:
    get:
      tags: [Markets]
      summary: Market catalog
      description: |
        Returns the table-of-contents for the API: every supported country with its
        ISO currency, primary index name, asset-type breakdown (stocks / etfs / indices /
        etc.) with per-type symbol counts, and the next upcoming trading-holiday closures.
        Call this first when you need to enumerate the universe or check whether a
        market is closed today.
      responses:
        "200":
          description: Market catalog.
          headers:
            X-RateLimit-Limit: { $ref: "#/components/headers/XRateLimitLimit" }
            X-RateLimit-Remaining: { $ref: "#/components/headers/XRateLimitRemaining" }
            X-RateLimit-Reset: { $ref: "#/components/headers/XRateLimitReset" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DataResponse" }
              example:
                data:
                  countries:
                    - code: US
                      name: United States
                      currency: USD
                      index_name: S&P 500
                      asset_types: [commodity, cryptocurrency, currency, etf, index, stock]
                      asset_counts:
                        stock: 504
                        etf: 36
                        index: 11
                        commodity: 14
                        currency: 14
                        cryptocurrency: 7
                      total_symbols: 586
                      upcoming_holidays:
                        - { date: "2026-05-25", name: "Memorial Day" }
                        - { date: "2026-06-19", name: "Juneteenth National Independence Day" }
                    - code: JP
                      name: Japan
                      currency: JPY
                      index_name: Nikkei 225
                      asset_types: [currency, etf, index, reit, stock]
                      asset_counts: { stock: 200, etf: 25, index: 8, reit: 5, currency: 2 }
                      total_symbols: 240
                      upcoming_holidays: []
                    - code: VN
                      name: Vietnam
                      currency: VND
                      index_name: VN-Index
                      asset_types: [etf, index, stock]
                      asset_counts: { stock: 380, etf: 12, index: 6 }
                      total_symbols: 398
                      upcoming_holidays: []
                  supported_countries: [US, JP, VN]
                  total_symbols: 1224
                as_of: "2026-05-12T11:46:11.795171Z"
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/v1/journely/data/macro/indicators:
    get:
      tags: [Macro]
      summary: Macro indicators
      description: |
        Returns the macro indicator panel for a country. Optional `category`
        filter narrows the response to a single domain — useful when you only
        care about inflation, employment, etc.
      parameters:
        - $ref: "#/components/parameters/Country"
        - name: category
          in: query
          required: false
          schema:
            type: string
            enum: [gdp, labor, prices, money, consumer, housing, business, trade, government, energy]
          description: Optional category filter.
      responses:
        "200":
          description: Indicator panel.
          headers:
            X-RateLimit-Limit: { $ref: "#/components/headers/XRateLimitLimit" }
            X-RateLimit-Remaining: { $ref: "#/components/headers/XRateLimitRemaining" }
            X-RateLimit-Reset: { $ref: "#/components/headers/XRateLimitReset" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DataResponse" }
              example:
                country: US
                data:
                  prices:
                    - { name: "Inflation Rate", latest: 3.3, previous: 2.4, latest_raw: "3.3", previous_raw: "2.4" }
                    - { name: "Inflation Rate MoM", latest: 0.9, previous: 0.3, latest_raw: "0.9", previous_raw: "0.3" }
                    - { name: "Consumer Price Index CPI", latest: 330, previous: 327, latest_raw: "330", previous_raw: "327" }
                    - { name: "Core Inflation Rate", latest: 2.6, previous: 2.5, latest_raw: "2.6", previous_raw: "2.5" }
                    - { name: "Producer Prices Change", latest: 4, previous: 3.4, latest_raw: "4", previous_raw: "3.4" }
                as_of: "2026-05-12T11:46:11.795171Z"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/v1/journely/data/macro/forecast:
    get:
      tags: [Macro]
      summary: Macro forecasts
      description: |
        Forward-looking macro forecasts (next 4-8 quarters where available) for
        the requested country.
      parameters:
        - $ref: "#/components/parameters/Country"
      responses:
        "200":
          description: Forecast panel grouped by category, with horizons (4-8 quarters out).
          headers:
            X-RateLimit-Limit: { $ref: "#/components/headers/XRateLimitLimit" }
            X-RateLimit-Remaining: { $ref: "#/components/headers/XRateLimitRemaining" }
            X-RateLimit-Reset: { $ref: "#/components/headers/XRateLimitReset" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DataResponse" }
              example:
                country: US
                data:
                  country: united-states
                  timestamp: "2026-05-12T11:46:11Z"
                  forecast_horizons: ["Q2/26", "Q3/26", "Q4/26", "Q1/27"]
                  categories:
                    gdp:
                      - name: "GDP Growth Rate"
                        unit: "%"
                        "Q2/26": "2.1"
                        "Q3/26": "2.0"
                        "Q4/26": "1.8"
                        "Q1/27": "1.7"
                    consumer:
                      - name: "Consumer Confidence"
                        unit: "Index"
                        "Q2/26": "108.5"
                        "Q3/26": "109.0"
                  summary:
                    indicators_total: 155
                    categories: 10
                  metadata:
                    source: "TradingEconomics"
                as_of: "2026-05-12T11:46:11.795171Z"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/v1/journely/data/macro/calendar:
    get:
      tags: [Macro]
      summary: Economic calendar
      description: |
        Returns the upcoming economic-calendar events — FOMC decisions, CPI / NFP /
        GDP releases, central-bank statements, etc. Events are filtered to "today and
        forward" (past events excluded) and sorted chronologically. Each event carries
        previous / forecast / consensus / actual values where available.

        Filter by `country` (optional — omit for global) and `importance` (single
        value or CSV: `high` / `medium` / `low` / `all`). `limit` caps the result
        size (default 20, hard cap 200).

        A `summary` block in the response carries counts (total / high / medium / low)
        before the limit was applied, so clients see the real shape of the filtered
        window even when they asked for the top 10.
      parameters:
        - name: country
          in: query
          required: false
          schema:
            type: string
            enum: [US, JP, VN]
          description: Optional country filter. Omit for global events.
        - name: importance
          in: query
          required: false
          schema: { type: string }
          description: Importance filter — `high`, `medium`, `low`, CSV like `high,medium`, or `all`.
        - name: limit
          in: query
          required: false
          schema: { type: integer, minimum: 1, maximum: 200, default: 20 }
          description: Max events to return.
      responses:
        "200":
          description: Calendar events.
          headers:
            X-RateLimit-Limit: { $ref: "#/components/headers/XRateLimitLimit" }
            X-RateLimit-Remaining: { $ref: "#/components/headers/XRateLimitRemaining" }
            X-RateLimit-Reset: { $ref: "#/components/headers/XRateLimitReset" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DataResponse" }
              example:
                data:
                  events:
                    - id: "ff_9"
                      country: "japan"
                      event: "Leading Indicators"
                      date: "2026-05-12"
                      time: "01:00"
                      importance: "low"
                      previous: "112.4%"
                      forecast: "114.4%"
                      consensus: null
                      actual: null
                      currency: "JPY"
                    - id: "ff_36"
                      country: "united states"
                      event: "ISM Services PMI"
                      date: "2026-05-12"
                      time: "10:00"
                      importance: "high"
                      previous: "54.0"
                      forecast: "53.7"
                      consensus: null
                      actual: null
                      currency: "USD"
                  summary:
                    total: 84
                    high: 9
                    medium: 3
                    low: 72
                as_of: "2026-05-12T11:46:11.825536Z"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/v1/journely/data/sectors:
    get:
      tags: [Sectors]
      summary: Sector roll-up with valuation
      description: |
        Returns the sector hierarchy for a country with everything investors
        need for rotation calls:

          - Country-level index P/E (S&P 500 / Nikkei 225 / VN-Index) and
            its 5-year average, with a `valuation_status` flag.
          - Per-sector overview + performance metrics.
          - Per-sector `stats` object: P/E median + p25/p75, P/B median,
            P/S median, EV/EBITDA, ROE median, ROIC, revenue + EPS growth
            medians, margin medians (gross / operating / net / FCF),
            `sample_size`, and `reliable` flag.

        The `stats` block is sourced from a daily Damodaran/VNDirect refresh
        and is what investors actually use to decide cheap-vs-expensive at the
        sector level.
      parameters:
        - $ref: "#/components/parameters/Country"
      responses:
        "200":
          description: Sector data.
          headers:
            X-RateLimit-Limit: { $ref: "#/components/headers/XRateLimitLimit" }
            X-RateLimit-Remaining: { $ref: "#/components/headers/XRateLimitRemaining" }
            X-RateLimit-Reset: { $ref: "#/components/headers/XRateLimitReset" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DataResponse" }
              example:
                country: US
                data:
                  country_pe: 25.98
                  country_pe_5y_avg: 20.16
                  country_index_name: "S&P 500"
                  country_valuation_status: "Fair"
                  sectors:
                    commercial-services:
                      name: "Commercial Services"
                      slug: "commercial-services"
                      overview:
                        Sector: "Commercial Services"
                        "Mkt cap": "1.09 TUSD"
                        Industries: "5"
                        Stocks: "251"
                        "Chg %": "−1.95%"
                      performance:
                        "Chg %": "−1.95%"
                        "Perf %1W": "−3.54%"
                        "Perf %1M": "+0.90%"
                        "Perf %YTD": "−12.31%"
                        "Perf %1Y": "−6.67%"
                      stats:
                        pe_median: 31.06
                        pe_p25: 11.93
                        pe_p75: 43.39
                        pb_median: 4.18
                        ps_median: 4.59
                        ev_ebitda_median: 14.84
                        roe_median: 24.36
                        roic_median: 12.06
                        revenue_growth_median: 8.77
                        eps_growth_median: 19.09
                        gross_margin_median: 49.50
                        operating_margin_median: 19.59
                        net_margin_median: 16.07
                        fcf_margin_median: null
                        sample_size: 11
                        reliable: true
                        updated_at: "2026-01-16T22:30:01.630006Z"
                as_of: "2026-05-12T11:46:11.836Z"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/v1/journely/data/stocks/{symbol}/overview:
    get:
      tags: [Stocks]
      summary: Stock overview
      description: |
        Returns the high-level snapshot for a single ticker: overview,
        valuation, business model, performance.
      parameters:
        - $ref: "#/components/parameters/Symbol"
        - $ref: "#/components/parameters/Country"
      responses:
        "200":
          description: Overview panel.
          headers:
            X-RateLimit-Limit: { $ref: "#/components/headers/XRateLimitLimit" }
            X-RateLimit-Remaining: { $ref: "#/components/headers/XRateLimitRemaining" }
            X-RateLimit-Reset: { $ref: "#/components/headers/XRateLimitReset" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DataResponse" }
              example:
                country: US
                data:
                  overview:
                    Name: "Apple Inc."
                    "Mkt cap": "4.3 TUSD"
                    "P/E": "39.07"
                    "EPS dilTTM": "8.27USD"
                    "EPS dil growthTTM YoY": "+29.00%"
                    "Div yield %TTM": "0.35%"
                    "Analyst rating": "Buy"
                    "Chg %": "−0.22%"
                  valuation:
                    "Mkt cap": "4.3 TUSD"
                    EV: "4.31 TUSD"
                    "P/E": "39.07"
                    "P/B": "40.31"
                    "EV/EBITDATTM": "26.97"
                    "EV/revenueTTM": "9.56"
                  business_model:
                    business_summary: "Apple Inc. designs, manufactures, and markets smartphones, personal computers, tablets, wearables, and accessories worldwide."
                    employees: 150000
                    sector: "Technology"
                    industry: "Consumer Electronics"
                    fetch_timestamp: "2026-03-15T16:30:13.069994Z"
                  performance:
                    "Perf %1W": "+4.66%"
                    "Perf %1M": "+12.58%"
                    "Perf %1Y": "+47.08%"
                    "Perf %5Y": "+137.18%"
                    "Perf %10Y": "+1,152.37%"
                as_of: "2026-05-12T11:46:11.864Z"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/v1/journely/data/stocks/{symbol}/statements:
    get:
      tags: [Stocks]
      summary: Financial statements
      description: |
        Returns income statement, balance sheet, or cash flow for a single
        ticker. Pick the statement via the `type` query parameter.
      parameters:
        - $ref: "#/components/parameters/Symbol"
        - $ref: "#/components/parameters/Country"
        - name: type
          in: query
          required: true
          schema:
            type: string
            enum: [income, balance, cashflow]
          description: Which statement to return.
      responses:
        "200":
          description: Statement panel.
          headers:
            X-RateLimit-Limit: { $ref: "#/components/headers/XRateLimitLimit" }
            X-RateLimit-Remaining: { $ref: "#/components/headers/XRateLimitRemaining" }
            X-RateLimit-Reset: { $ref: "#/components/headers/XRateLimitReset" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DataResponse" }
              example:
                country: US
                data:
                  Name: "Apple Inc."
                  "Fiscal periodCurrent": "2026-Q2"
                  "Fiscal period endCurrent": "2026-03-31"
                  "RevenueTTM": "451.44 BUSD"
                  "Revenue growthTTM YoY": "+12.76%"
                  "Gross profitTTM": "216.07 BUSD"
                  "Op incomeTTM": "147.37 BUSD"
                  "EBITDATTM": "159.98 BUSD"
                  "Net incomeTTM": "122.58 BUSD"
                  "EPS dilTTM": "8.27USD"
                  "EPS dil growthTTM YoY": "+29.00%"
                as_of: "2026-05-12T11:46:11.865Z"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }

  /api/v1/journely/data/stocks/{symbol}/technicals:
    get:
      tags: [Stocks]
      summary: Technical indicators
      description: |
        Returns the technical-indicator panel for a single ticker
        (moving averages, RSI, MACD, oscillators, etc.).
      parameters:
        - $ref: "#/components/parameters/Symbol"
        - $ref: "#/components/parameters/Country"
      responses:
        "200":
          description: Technicals panel.
          headers:
            X-RateLimit-Limit: { $ref: "#/components/headers/XRateLimitLimit" }
            X-RateLimit-Remaining: { $ref: "#/components/headers/XRateLimitRemaining" }
            X-RateLimit-Reset: { $ref: "#/components/headers/XRateLimitReset" }
          content:
            application/json:
              schema: { $ref: "#/components/schemas/DataResponse" }
              example:
                country: US
                data:
                  Name: "Apple Inc."
                  "Tech rating": "Buy"
                  "MA rating": "Strong buy"
                  "Os rating": "Sell"
                  "RSI (14)": "71.96"
                  "MACD (12,26)Level": "7.79"
                  "MACD (12,26)Signal": "5.81"
                  "Stoch (14,3,3)%K": "90.27"
                  "Stoch (14,3,3)%D": "90.56"
                  "CCI (20)": "151.42"
                  "Mom (10)": "25.07"
                  "AO": "21.38"
                as_of: "2026-05-12T11:46:11.877Z"
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "404": { $ref: "#/components/responses/NotFound" }
        "429": { $ref: "#/components/responses/RateLimited" }
