> ## Documentation Index
> Fetch the complete documentation index at: https://docs.geoly.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Sync records

> Completed records ordered by changed_at then id, not by record date, so a late completion appears at its completion update. Uses a dedicated index and a safe watermark at least five minutes behind database time; a long-running database transaction that has already written holds the watermark at five minutes before its start until it ends, and polls in that time return nothing newer but are still billed. Scan work is bounded, so an empty page can have next_cursor. Follow next_cursor until it is null, then save page.sync_cursor and poll with it; every 5 to 15 minutes is enough, because nothing newer than the watermark is returned. Bound the first sync with updated_since rather than business_date_from, which filters inside each scanned batch and does not shorten the scan. With a cursor, updated_since is ignored; keep limit, business_date_from and include unchanged. Repeated records are updates: upsert by id. Analysis fields are read-time values: mention_rank backfills do not advance changed_at, so a rank change alone is not re-emitted. Deletions and records that leave completed are not emitted either. Reconcile by starting a separate pass without a cursor from an updated_since rewound at least 24 hours (a rewound updated_since sent together with a saved cursor is ignored and reconciles nothing), or by re-reading records by record date. Optional answer_text and citations expansions; citations cap at 30 with a flag. Every call, including an empty poll, is billed at the price in x-geoly-cost.



## OpenAPI

````yaml api-reference/openapi.json GET /api/v1/records
openapi: 3.1.0
info:
  title: GEOly Data API
  version: '1'
  description: >-
    Typed endpoints of the GEOly Data API (`x-geoly-stability: stable`): the
    account behind the key (GET /api/v1/quota, /api/v1/brand), the brand catalog
    (GET /api/v1/topics, /api/v1/prompts), monitoring records (GET
    /api/v1/prompts/{prompt_id}/records, /api/v1/records/{record_id}) and the
    incremental record feed (GET /api/v1/records), writes (POST /api/v1/prompts,
    POST /api/v1/prompts/{prompt_id}/runs) and windowed reports (POST
    /api/v1/reports/*). Authenticate with an API key bound to one brand; the
    brand is implied by the key and never appears in paths.


    Stability: within /api/v1 the shape and meaning of every declared field is
    stable — fields are never removed, renamed or retyped, units never change,
    and enums only grow (response enums are open: ignore unknown values and
    unknown fields). Metric definitions follow the product and are recorded in
    the changelog. Breaking changes only ship under /api/v2.


    Envelope: `{ data, page?, meta }`. `meta.quota` is the billing outcome of
    the call; `meta.schema_conformant` is false only if the report output
    drifted from this document (the data is still returned).


    Pagination: send `limit` (and `cursor` from `page.next_cursor` for the next
    page, with exactly the same filters). `page.next_cursor === null` is the
    only end-of-list signal; `page.capped: true` means the position limit was
    reached before the end. GET /api/v1/prompts uses a true keyset cursor
    (ordered by created_at, id): a row whose sort key is unchanged and that
    still matches the filters is never skipped or repeated because other rows
    were added or archived meanwhile, and there is no position limit. It is not
    a snapshot: every page re-applies the filters, so a row archived before you
    reach it drops out of an active listing. `x-geoly-pagination` describes each
    list.


    Errors use one envelope `{error:{code,message,details?},meta}`; `error.code`
    is drawn from the closed list in `x-geoly-error-codes`. Branch on the HTTP
    status and `error.code`, never on `message`. Stable keys of `error.details`:
    errors[] ({param, reason, allowed?, message}), reason, required,
    retry_after_seconds, max_bytes, actual_bytes, _quota, and resource / id (404
    NOT_FOUND of a single-resource endpoint such as GET
    /api/v1/prompts/{prompt_id}).


    Billing: each call is billed at the price in `x-geoly-cost` ({tier, base,
    unit, long_window_multiplier, guard_rpm}: nominal price per call in credits,
    the same as the matching GEOly MCP tool (GET /api/v1/records has none: it is
    offered only on this API, so its price and guard are its own), against the
    MCP credits pool; tier "free" with base 0 = not charged — this includes
    operations whose matching MCP tool is registered as not charged
    (charged:false: POST /api/v1/prompts and POST
    /api/v1/prompts/{prompt_id}/runs), so no nominal price is declared for them;
    guard_rpm is the per-organization requests-per-minute guard of that endpoint
    — counted per organization together with the same organization's calls of
    the matching GEOly MCP tool on the other surfaces (MCP, Agent API)). The
    billing outcome of every call is in `meta.quota`. A monitoring run still
    consumes monitoring credits, a separate pool, as described on that
    operation.


    The HTTP tool wrappers under /api/v1/tools and /api/v1/tools/{tool} are
    retired: every method answers 410 TOOL_SURFACE_RETIRED, and for a known tool
    details.replacement names the resource endpoint. Use these resource
    endpoints. MCP and Agent API remain available.
servers:
  - url: https://app.geoly.ai
security:
  - bearerAuth: []
tags:
  - name: Records
    description: Monitoring records and optional full answer text.
  - name: Account
    description: The organization, credits and brand behind this API key.
  - name: Catalog
    description: >-
      Topics and prompts of the brand behind this API key (GET, no metrics
      computed; the price of each operation is in x-geoly-cost).
  - name: Reports
    description: >-
      Windowed brand-visibility reports (POST with a JSON body; nothing is
      created).
paths:
  /api/v1/records:
    get:
      tags:
        - Records
      summary: Incremental completed records and subsequent changes
      description: >-
        Completed records ordered by changed_at then id, not by record date, so
        a late completion appears at its completion update. Uses a dedicated
        index and a safe watermark at least five minutes behind database time; a
        long-running database transaction that has already written holds the
        watermark at five minutes before its start until it ends, and polls in
        that time return nothing newer but are still billed. Scan work is
        bounded, so an empty page can have next_cursor. Follow next_cursor until
        it is null, then save page.sync_cursor and poll with it; every 5 to 15
        minutes is enough, because nothing newer than the watermark is returned.
        Bound the first sync with updated_since rather than business_date_from,
        which filters inside each scanned batch and does not shorten the scan.
        With a cursor, updated_since is ignored; keep limit, business_date_from
        and include unchanged. Repeated records are updates: upsert by id.
        Analysis fields are read-time values: mention_rank backfills do not
        advance changed_at, so a rank change alone is not re-emitted. Deletions
        and records that leave completed are not emitted either. Reconcile by
        starting a separate pass without a cursor from an updated_since rewound
        at least 24 hours (a rewound updated_since sent together with a saved
        cursor is ignored and reconciles nothing), or by re-reading records by
        record date. Optional answer_text and citations expansions; citations
        cap at 30 with a flag. Every call, including an empty poll, is billed at
        the price in x-geoly-cost.
      operationId: listRecordsFeed
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            default: 25
            type: integer
            minimum: 1
            maximum: 100
        - name: cursor
          in: query
          required: false
          schema:
            type: string
            minLength: 1
            maxLength: 1024
        - name: updated_since
          in: query
          required: false
          description: >-
            Initial inclusive lower bound on changed_at for the first page (UTC
            with milliseconds). Ignored when cursor is present.
          schema:
            default: '1970-01-01T00:00:00.000Z'
            type: string
            format: date-time
            pattern: >-
              ^(?:(?:\d\d[2468][048]|\d\d[13579][26]|\d\d0[48]|[02468][048]00|[13579][26]00)-02-29|\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\d|30)|(?:02)-(?:0[1-9]|1\d|2[0-8])))T(?:(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d\.\d{3}(?:Z))$
        - name: business_date_from
          in: query
          required: false
          description: >-
            Optional earliest business day; filters inside each scanned batch
            and does not shorten the scan (UTC+8 business day, YYYY-MM-DD).
          schema:
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
        - name: include
          in: query
          required: false
          schema:
            type: string
            enum:
              - answer_text
              - citations
              - answer_text,citations
      responses:
        '200':
          description: >-
            Incremental completed records and subsequent changes. `data` follows
            RecordFeed when meta.schema_conformant is true.
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
            GEOly-API-Version:
              $ref: '#/components/headers/GEOly-API-Version'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-DB-Budget-Limit:
              $ref: '#/components/headers/X-DB-Budget-Limit'
            X-DB-Budget-Used:
              $ref: '#/components/headers/X-DB-Budget-Used'
            X-DB-Budget-Reset:
              $ref: '#/components/headers/X-DB-Budget-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RecordFeedEnvelope'
        '400':
          description: >-
            INVALID_ARGUMENT (unknown query parameter, non-integer or
            out-of-range number, invalid enum value — details.errors[] lists
            each {param, reason, allowed?}) / INVALID_CURSOR (the cursor is
            malformed, or limit, business_date_from or include differ from the
            request that issued it; resend them unchanged, or start a new pass
            from updated_since). Not charged.
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
            GEOly-API-Version:
              $ref: '#/components/headers/GEOly-API-Version'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-DB-Budget-Limit:
              $ref: '#/components/headers/X-DB-Budget-Limit'
            X-DB-Budget-Used:
              $ref: '#/components/headers/X-DB-Budget-Used'
            X-DB-Budget-Reset:
              $ref: '#/components/headers/X-DB-Budget-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          description: 'UNAUTHORIZED: missing or invalid API key.'
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
            GEOly-API-Version:
              $ref: '#/components/headers/GEOly-API-Version'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '402':
          description: >-
            QUOTA_EXCEEDED / SUBSCRIPTION_INACTIVE / ORG_NOT_ELIGIBLE. Not
            charged.
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
            GEOly-API-Version:
              $ref: '#/components/headers/GEOly-API-Version'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-DB-Budget-Limit:
              $ref: '#/components/headers/X-DB-Budget-Limit'
            X-DB-Budget-Used:
              $ref: '#/components/headers/X-DB-Budget-Used'
            X-DB-Budget-Reset:
              $ref: '#/components/headers/X-DB-Budget-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '403':
          description: >-
            API_KEY_BRAND_REQUIRED (the key is not bound to a brand) /
            PERMISSION_DENIED (the key lacks the read permission in
            x-geoly-required-permission; details.required names it). Not
            charged.
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
            GEOly-API-Version:
              $ref: '#/components/headers/GEOly-API-Version'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-DB-Budget-Limit:
              $ref: '#/components/headers/X-DB-Budget-Limit'
            X-DB-Budget-Used:
              $ref: '#/components/headers/X-DB-Budget-Used'
            X-DB-Budget-Reset:
              $ref: '#/components/headers/X-DB-Budget-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '422':
          description: >-
            RESPONSE_TOO_LARGE: response exceeds 4 MB; omit include=answer_text
            to retrieve metadata. Rejected before settlement.
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
            GEOly-API-Version:
              $ref: '#/components/headers/GEOly-API-Version'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-DB-Budget-Limit:
              $ref: '#/components/headers/X-DB-Budget-Limit'
            X-DB-Budget-Used:
              $ref: '#/components/headers/X-DB-Budget-Used'
            X-DB-Budget-Reset:
              $ref: '#/components/headers/X-DB-Budget-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '429':
          description: >-
            RATE_LIMITED / GUARDED_RATE_LIMITED / BUSY / BUDGET_EXCEEDED. Not
            charged; honor Retry-After and pace with X-DB-Budget-*.
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
            GEOly-API-Version:
              $ref: '#/components/headers/GEOly-API-Version'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-DB-Budget-Limit:
              $ref: '#/components/headers/X-DB-Budget-Limit'
            X-DB-Budget-Used:
              $ref: '#/components/headers/X-DB-Budget-Used'
            X-DB-Budget-Reset:
              $ref: '#/components/headers/X-DB-Budget-Reset'
            Retry-After:
              $ref: '#/components/headers/Retry-After'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '500':
          description: >-
            INTERNAL_ERROR: unexpected server error (not charged). Retry once
            with backoff; report the Request-Id if it persists.
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
            GEOly-API-Version:
              $ref: '#/components/headers/GEOly-API-Version'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-DB-Budget-Limit:
              $ref: '#/components/headers/X-DB-Budget-Limit'
            X-DB-Budget-Used:
              $ref: '#/components/headers/X-DB-Budget-Used'
            X-DB-Budget-Reset:
              $ref: '#/components/headers/X-DB-Budget-Reset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '503':
          description: >-
            TOOL_TIMEOUT / OVERLOADED / CIRCUIT_OPEN / TOOL_FAILED /
            FEED_NOT_READY (the dedicated feed index or the safe watermark is
            not available; no fallback scan was run). Not charged; retry later,
            honoring Retry-After when present. (502 / 504 are never used.)
          headers:
            Request-Id:
              $ref: '#/components/headers/Request-Id'
            GEOly-API-Version:
              $ref: '#/components/headers/GEOly-API-Version'
            X-RateLimit-Limit:
              $ref: '#/components/headers/X-RateLimit-Limit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/X-RateLimit-Remaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/X-RateLimit-Reset'
            X-DB-Budget-Limit:
              $ref: '#/components/headers/X-DB-Budget-Limit'
            X-DB-Budget-Used:
              $ref: '#/components/headers/X-DB-Budget-Used'
            X-DB-Budget-Reset:
              $ref: '#/components/headers/X-DB-Budget-Reset'
            Retry-After:
              $ref: '#/components/headers/Retry-After'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
      security:
        - bearerAuth: []
components:
  headers:
    Request-Id:
      description: >-
        Unique id of this request (equals meta.request_id). Include it when
        reporting a problem.
      schema:
        type: string
    GEOly-API-Version:
      description: API contract version. Always "1" on /api/v1.
      schema:
        type: string
    X-RateLimit-Limit:
      description: >-
        Requests allowed per credential in the current one-minute window. Sent
        on every response after the API key has been authenticated; absent on
        errors raised while authenticating (401) and on 405.
      schema:
        type: integer
    X-RateLimit-Remaining:
      description: Requests left in the current window.
      schema:
        type: integer
    X-RateLimit-Reset:
      description: Seconds until the current window resets (0–60).
      schema:
        type: integer
    X-DB-Budget-Limit:
      description: >-
        Organization database-seconds budget per rolling 60 minutes. Sent on
        every response after authentication.
      schema:
        type: integer
    X-DB-Budget-Used:
      description: Database seconds used by the organization in the last 60 minutes.
      schema:
        type: integer
    X-DB-Budget-Reset:
      description: Seconds until the oldest usage bucket expires.
      schema:
        type: integer
    Retry-After:
      description: Seconds to wait before retrying (429; 503 when retryable).
      schema:
        type: integer
  schemas:
    RecordFeedEnvelope:
      type: object
      properties:
        data:
          oneOf:
            - $ref: '#/components/schemas/RecordFeed'
            - type: 'null'
          description: >-
            Incremental completed records and subsequent changes. null only when
            meta.schema_conformant is false and the output could not be mapped.
        page:
          $ref: '#/components/schemas/FeedPage'
        meta:
          $ref: '#/components/schemas/CuratedMeta'
      required:
        - data
        - page
        - meta
    ErrorEnvelope:
      type: object
      properties:
        error:
          type: object
          properties:
            code:
              type: string
              description: '[Extensible enum] One of x-geoly-error-codes.'
              examples:
                - INVALID_BODY
                - INVALID_ARGUMENT
                - INVALID_CURSOR
                - IDEMPOTENCY_KEY_INVALID
                - FAILED_PRECONDITION
                - UNAUTHORIZED
                - SUBSCRIPTION_INACTIVE
                - QUOTA_EXCEEDED
                - ORG_NOT_ELIGIBLE
                - PLAN_LIMIT_REACHED
                - API_KEY_BRAND_REQUIRED
                - ORG_SELECTION_REQUIRED
                - ORGANIZATION_REQUIRED
                - PERMISSION_DENIED
                - REPORT_NOT_FOUND
                - NOT_FOUND
                - METHOD_NOT_ALLOWED
                - IDEMPOTENCY_KEY_REUSED
                - REQUEST_IN_PROGRESS
                - IDEMPOTENCY_OUTCOME_UNKNOWN
                - TOOL_SURFACE_RETIRED
                - PAYLOAD_TOO_LARGE
                - UNSUPPORTED_MEDIA_TYPE
                - QUERY_TOO_EXPENSIVE
                - RESPONSE_TOO_LARGE
                - RATE_LIMITED
                - GUARDED_RATE_LIMITED
                - BUSY
                - BUDGET_EXCEEDED
                - INTERNAL_ERROR
                - TOOL_TIMEOUT
                - OVERLOADED
                - CIRCUIT_OPEN
                - TOOL_FAILED
                - FEED_NOT_READY
                - IDEMPOTENCY_UNAVAILABLE
            message:
              type: string
              description: For humans; do not parse.
            details:
              type: object
              description: >-
                Stable keys: errors[] ({param, reason, allowed?, message}) on
                INVALID_ARGUMENT; reason; required (PERMISSION_DENIED);
                retry_after_seconds (429); max_bytes / actual_bytes (413 / 422);
                _quota (receipt); resource / id (404 NOT_FOUND of a
                single-resource endpoint). Other keys are informational and may
                change.
          required:
            - code
            - message
        meta:
          $ref: '#/components/schemas/CuratedErrorMeta'
      required:
        - error
        - meta
    RecordFeed:
      type: array
      items:
        type: object
        properties:
          id:
            type: string
          prompt_id:
            type: string
          prompt_text:
            type: string
          platform_code:
            type:
              - string
              - 'null'
          platform_name:
            type:
              - string
              - 'null'
          record_date_at:
            type: string
            format: date-time
          business_date:
            type: string
            pattern: ^\d{4}-\d{2}-\d{2}$
          changed_at:
            description: >-
              Update position of this record (prompt_record.updated_at,
              microseconds). The feed orders by changed_at then id; upsert by
              id.
            type: string
            format: date-time
          status:
            type: string
            const: completed
          mentions:
            type:
              - integer
              - 'null'
          mention_rank:
            type:
              - integer
              - 'null'
            description: >-
              Read-time value. Rank backfills do not advance changed_at, so this
              can change while changed_at stays the same and the feed will not
              re-emit the record for it. To reconcile, poll again from an
              updated_since rewound by an overlap of at least 24 hours, or
              re-read records by record date.
          sentiment:
            type:
              - string
              - 'null'
          sentiment_polarity:
            type:
              - number
              - 'null'
          answer_text:
            type:
              - string
              - 'null'
          citations:
            type:
              - array
              - 'null'
            items:
              type: object
              properties:
                id:
                  type: string
                source_type:
                  type: string
                url:
                  type:
                    - string
                    - 'null'
                domain:
                  type:
                    - string
                    - 'null'
                title:
                  type:
                    - string
                    - 'null'
                position:
                  type:
                    - integer
                    - 'null'
              required:
                - id
                - source_type
                - url
                - domain
                - title
                - position
              additionalProperties: {}
            description: >-
              Requested with include=citations: up to 30 per record, ordered by
              position (nulls last) then id — the same order and cut as GET
              /api/v1/records/{record_id}. citations_truncated flags a cut.
          citations_truncated:
            type:
              - boolean
              - 'null'
        required:
          - id
          - prompt_id
          - prompt_text
          - platform_code
          - platform_name
          - record_date_at
          - business_date
          - changed_at
          - status
          - mentions
          - mention_rank
          - sentiment
          - sentiment_polarity
          - answer_text
          - citations
          - citations_truncated
        additionalProperties: {}
    FeedPage:
      type: object
      description: >-
        Bounded incremental scan. Empty pages can have a continuation. Persist
        sync_cursor after processing data; do not advance on
        schema_conformant=false.
      properties:
        next_cursor:
          type:
            - string
            - 'null'
          description: >-
            Continue this scan: pass as `cursor` with the same limit,
            business_date_from and include (updated_since is ignored with a
            cursor). null = caught up to the watermark.
        sync_cursor:
          type:
            - string
            - 'null'
          description: >-
            Checkpoint to persist together with the processed rows. After
            next_cursor is null, poll later with it (every 5 to 15 minutes is
            enough). null only when schema_conformant is false: do not advance.
        limit:
          type: integer
        scanned:
          type: integer
        watermark_at:
          type: string
          format: date-time
          description: >-
            Exclusive upper bound of this scan: at least five minutes behind
            database time and never past the oldest in-flight transaction.
      required:
        - next_cursor
        - sync_cursor
        - limit
    CuratedMeta:
      type: object
      properties:
        request_id:
          type: string
        report:
          type: string
          description: >-
            Report name for POST /api/v1/reports/{report}; resource labels
            include quota, brand, topics, prompt-catalog, prompt,
            prompt-records, prompt-records-latest, record, record-feed,
            created-prompt and prompt-run.
        duration_ms:
          type: integer
        quota:
          $ref: '#/components/schemas/QuotaMeta'
        schema_conformant:
          type: boolean
          description: >-
            true = data matches the documented schema exactly (every declared
            key present). false = the report output drifted; data is still
            returned as mapped (or null) — treat it as best effort.
      required:
        - request_id
        - report
        - duration_ms
        - schema_conformant
    CuratedErrorMeta:
      type: object
      properties:
        request_id:
          type: string
        report:
          type: string
        duration_ms:
          type: integer
        quota:
          $ref: '#/components/schemas/QuotaMeta'
      required:
        - request_id
    QuotaMeta:
      type: object
      description: >-
        Billing outcome of this call. remaining / resets_at / unlimited are null
        on paths that never reserved credits (free reports, calls rejected
        before execution) — except QUOTA_EXCEEDED, which carries the live
        balance. Absent when the call was rejected before the report ran (schema
        errors, cursor errors, authentication).
      properties:
        cost:
          type: integer
          description: Credits actually charged for this call.
        unlimited:
          type:
            - boolean
            - 'null'
        remaining:
          type:
            - integer
            - 'null'
          description: >-
            Credits left this period; null when unlimited or unknown on this
            path.
        resets_at:
          type:
            - string
            - 'null'
          format: date-time
        charge:
          type: string
          examples:
            - charged
            - min_fee
            - free
            - refunded
            - refund_pending
            - unbilled
          description: >-
            [Extensible enum] charged = normal settlement (per row, or one call
            for a call-priced read — including a not-found from the prompt or
            record detail); min_fee = minimum call fee (no data / not found from
            a per-row report / platform not purchased / missing locale); free =
            free report or endpoint; refunded / refund_pending = failed and
            refunded; unbilled = rejected before execution, or not metered.
        min_fee_reason:
          type:
            - string
            - 'null'
          examples:
            - missing_locale
            - no_data
            - null
          description: '[Extensible enum]'
      required:
        - cost
        - unlimited
        - remaining
        - resets_at
        - charge
        - min_fee_reason
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        API key from Settings → Developer → API keys (prefix geom_). Each key is
        bound to one brand.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.