openapi: 3.1.0
info:
  title: DataDriven.ro Public API
  version: 1.0.0
  description: |
    Programmatic access to Romanian public-procurement data from DataDriven.ro.

    The endpoints below mirror the app's own menus 1-to-1:

    - **Monitorizare Licitații** → `POST /v1/tenders/monitored`
    - **Salvate** (bookmarks)    → `POST /v1/tenders/saved`
    - **Toate Licitațiile**      → `POST /v1/tenders/all`
    - **Atribuiri**              → `POST /v1/awards/fullSearch`
    - **Detalii** (tender page)  → `GET  /v1/tenders/byId/{id}` / `GET /v1/tenders/byNotice/{noticeNo}`
    - **Istoric**                → `GET  /v1/tenders/byId/{id}/history`
    - Company stats              → `GET  /v1/companies/{cui}`

    Plus `GET /v1/health`, `GET /v1/me`, `GET /v1/usage`.

    ## Authentication
    All endpoints require an `Ocp-Apim-Subscription-Key` header obtained from the
    Premium plan dashboard. Calls go through `api.datadriven.ro` (or
    `ddroapi.azure-api.net`) which adds APIM headers consumed by the backend.

    ## Quotas
    Premium includes 100 **full retrievals** per Stripe billing cycle. Full
    retrievals are charged by `GET /v1/tenders/byId/{id}` and
    `GET /v1/tenders/byNotice/{noticeNo}`. Repeat calls for the same tender
    within the same cycle are free. Once the cap is exhausted, additional
    retrievals consume **credit packs** (buy from the dashboard, 100 credits each).

    All other endpoints count as ordinary **operations**. There is a per-cycle
    operations cap of `max(50000, creditsUsedThisCycle × 500)` — a 50,000 floor
    that rises by 500 calls for every credit used this cycle. Doing full
    retrievals (the thing that consumes credits) is exactly what raises the
    ceiling, so the cap is self-balancing. `GET /v1/usage` reports the live
    `callsCap`, `callsCapFloor`, and `creditsUsedThisCycle`.

    ## Period boundaries
    The cap rolls on the **subscription anniversary day**, not the calendar 1st.
    `GET /v1/usage` reports the exact `periodStart` / `periodEnd`.
servers:
  - url: https://api.datadriven.ro
  - url: https://ddroapi.azure-api.net
tags:
  - name: Account
    description: Caller's project identity, quota, and usage counters.
  - name: Tenders
    description: Look up, list, and search tenders.
  - name: Awards
    description: Search awarded contracts and ongoing acquisitions.
  - name: Companies
    description: Company identity + precomputed statistics.
security:
  - SubscriptionKey: []
paths:
  /v1/health:
    get:
      tags:
        - Account
      summary: Health check.
      description: Returns 200 if the API is reachable. No auth required.
      security: []
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
  /v1/me:
    get:
      tags:
        - Account
      summary: Caller identity.
      description: |
        Returns the project this API key resolves to. Useful for confirming a key
        works before doing real work.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MeResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /v1/usage:
    get:
      tags:
        - Account
      summary: Current period's quota counter.
      description: |
        Returns the in-cycle full-retrieval counter plus the credit-pack balance.
        Period anchors come from the Stripe subscription's `current_period_end`
        (stored as `Subscription.EndDate`), so the cap rolls on the subscriber's
        anniversary day, not the calendar 1st.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UsageResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /v1/tenders/monitored:
    post:
      tags:
        - Tenders
      summary: Tenders matching the project's monitoring criteria.
      description: |
        Backs the app's "Monitorizare Licitații" menu. Internally sets
        `monitoringCriteria=["main"]` + `authorities=["main"]` on the SearchRequest,
        i.e. uses the project's saved CPVs / keywords / authorities. Optional
        `query` further narrows by full-text search.

        Each tender carries the per-project overlay (`tags`, `color`, `comment`).
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SimpleListingRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenderListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /v1/tenders/saved:
    post:
      tags:
        - Tenders
      summary: Tenders the user has bookmarked in this project.
      description: |
        Backs the app's "Salvate" view. Sets `saveStatusFilter=["saved"]`. Returns
        the same summary projection as `/v1/tenders/monitored`, with `tags`,
        `color`, `comment` overlay.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SimpleListingRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenderListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /v1/tenders/all:
    post:
      tags:
        - Tenders
      summary: Recent tenders across the corpus.
      description: |
        Backs the app's "Toate Licitațiile" view. No monitoring filter. Empty
        body returns the most-recent tenders (`searchText="*"`, sort by
        published date desc). Optional `query` for refinement.

        Response uses the same summary projection but **omits** the
        `tags`/`color`/`comment` overlay — these are project-specific and most
        tenders in the corpus won't have annotations for the caller.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SimpleListingRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenderListResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /v1/tenders/byId/{id}:
    get:
      tags:
        - Tenders
      summary: Full tender by internal id.
      description: |
        Returns the full NoticeComplete-shaped object plus the per-project
        overlay. Includes lots, awards, attachments (each with a 1-hour SAS
        download URL), procedure metadata, AI fields. Charges 1 full-retrieval
        per (tender, billing cycle); repeat fetches for the same id in the same
        cycle are free.

        Optional `?includePages=true` embeds the per-attachment extracted text
        inline. No extra charge — uses the same dedup pool.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
          example: cnotice_101341473
        - in: query
          name: includePages
          required: false
          schema:
            type: boolean
            default: false
          description: When true, embed per-attachment extracted text inline.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenderFull'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tenders/byNotice/{noticeNo}:
    get:
      tags:
        - Tenders
      summary: Full tender by public SEAP/SICAP notice number.
      description: |
        Same shape as `/byId/{id}`. Notice numbers look like `SCN1174909`,
        `CN1234567`, `AN1234567`. Charges 1 full-retrieval per (tender,
        billing cycle), deduped with `byId` lookups.
      parameters:
        - in: path
          name: noticeNo
          required: true
          schema:
            type: string
          example: SCN1174909
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenderFull'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tenders/byId/{id}/pages:
    get:
      tags:
        - Tenders
      summary: OCR-extracted text, paginated.
      description: |
        Per-attachment page-by-page extracted text from the cached `pages.json`
        (no live OCR call). Cuts the response off at ~60 000 chars and reports
        `truncated` + `nextPage`/`nextAttachment` so you can paginate. For older
        tenders without `pages.json`, falls back to the legacy V1 flat text
        (returned as a single page 1).

        Shares the byId dedup pool — free this cycle if you've already retrieved
        the tender, otherwise charges 1 full-retrieval.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: query
          name: attachmentId
          required: false
          schema:
            type: string
          description: Optional — return only this attachment's pages.
        - in: query
          name: pageStart
          required: false
          schema:
            type: integer
            minimum: 1
            default: 1
        - in: query
          name: pageEnd
          required: false
          schema:
            type: integer
            minimum: 1
          description: 1-based last page (inclusive). Defaults to as many as fit in the budget.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenderPagesResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tenders/byId/{id}/pages/download:
    get:
      tags:
        - Tenders
      summary: Time-limited download URL for the tender's pages.json + preview.
      description: |
        Returns a 15-minute SAS URL to the cached `pages.json` blob plus a
        per-attachment preview (totalPages + first 200 chars). Suited to
        agents with local filesystem tools that want to download once and
        grep locally rather than paginate through the API. Older tenders
        with no `pages.json` return `pagesJsonAvailable=false`.

        Shares the byId dedup pool — free this cycle if you've already retrieved
        the tender, otherwise charges 1 full-retrieval.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/TenderPagesDownloadResponse'
                  - $ref: '#/components/schemas/TenderPagesDownloadUnavailable'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tenders/byId/{id}/find:
    get:
      tags:
        - Tenders
      summary: Substring search across OCR'd attachment text.
      description: |
        Case-insensitive substring search; returns per-attachment
        `{ pageNumber, snippet }` matches with `contextChars` of surrounding
        context. Romanian diacritics are NOT normalised — search both
        `garanție` and `garantie` if unsure. Capped at `maxHits` total snippets.

        Shares the byId dedup pool — free this cycle if you've already retrieved
        the tender, otherwise charges 1 full-retrieval.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: query
          name: query
          required: true
          schema:
            type: string
          description: Substring to find. Case-insensitive.
        - in: query
          name: attachmentId
          required: false
          schema:
            type: string
          description: Optional — search only this attachment.
        - in: query
          name: contextChars
          required: false
          schema:
            type: integer
            minimum: 50
            maximum: 1000
            default: 200
        - in: query
          name: maxHits
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 30
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenderFindResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tenders/byId/{id}/attachments:
    get:
      tags:
        - Tenders
      summary: List a tender's attachments (originals + searchable) and archive links.
      description: |
        Returns every attachment with an `originalUrl` (1-hour SAS to the raw
        file) and `searchableUrl` (1-hour SAS to the OCR-overlaid, text-selectable
        PDF — null when not a PDF, not yet OCR'd, or already text-searchable).
        `archives.originalsArchiveUrl` and `archives.searchableArchiveUrl` give
        the whole bundle as a single zip. `extractedText.pagesJsonUrl` is a
        1-hour SAS to the per-page extracted-text JSON — fetch it to read tender
        content without downloading PDFs.

        Shares the byId dedup pool — free this cycle if you've already retrieved
        the tender, otherwise charges 1 full-retrieval.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
          example: cnotice_101341473
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenderAttachments'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '402':
          $ref: '#/components/responses/PaymentRequired'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/tenders/byId/{id}/history:
    get:
      tags:
        - Tenders
      summary: Historical activity for the tender's authority + CPVs.
      description: |
        The "Istoric" panel data: similar past tenders from the same
        contracting authority, filtered to the current tender's CPVs and
        categories (or to the explicit filter in the query string), plus
        aggregated supplier statistics.

        No full-retrieval charge.
      parameters:
        - in: path
          name: id
          required: true
          schema:
            type: string
        - in: query
          name: cpvs
          required: false
          schema:
            type: string
          description: Comma-separated CPV codes to narrow the result set. Defaults to the tender's own Cpvs.
        - in: query
          name: categories
          required: false
          schema:
            type: string
          description: Comma-separated category ids to narrow the result set.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TenderHistoryResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
  /v1/awards/fullSearch:
    post:
      tags:
        - Awards
      summary: Full-power awards search (Atribuiri menu).
      description: |
        Pass-through of the `AwardSearchRequest` envelope plus two booleans:
        `applyMonitoringCriteria` (defaults to false; when true, intersects with
        the project's saved monitoring criteria + AI-relevance cache) and
        `onlyOngoingAcquisitions` (defaults to false; when true, returns
        ongoing acquisitions instead of awarded contracts — mirrors the app's
        "Achiziții în curs" view).

        No full-retrieval charge.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AwardSearchRequest'
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AwardSearchResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
  /v1/companies/{cui}:
    get:
      tags:
        - Companies
      summary: Company identity + precomputed statistics.
      description: |
        Two point reads against Azure Table Storage:

        - `company` table (PK=`a`, RK=normalized CUI) → identity.
        - `companyStats` table (PK=normalized CUI, RK=`EntirePeriod`) → stats.

        No on-the-fly aggregation. Stats are precomputed by an offline job and
        cover the entire-period window (all-time). 404 if neither row exists.

        No full-retrieval charge.
      parameters:
        - in: path
          name: cui
          required: true
          schema:
            type: string
          example: '47505956'
          description: Romanian tax ID (with or without RO prefix; normalized server-side).
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompanyResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
components:
  securitySchemes:
    SubscriptionKey:
      type: apiKey
      in: header
      name: Ocp-Apim-Subscription-Key
  schemas:
    MeResponse:
      type: object
      required:
        - projectId
        - projectName
      properties:
        projectId:
          type: string
          description: Internal project id (UUID-shaped string).
        projectName:
          type: string
          description: Display name of the project.
    UsageResponse:
      type: object
      required:
        - fullRetrievalsThisPeriod
        - fullRetrievalsCap
        - overflowCredits
        - callsThisCycle
        - callsCap
        - callsCapFloor
        - creditsUsedThisCycle
        - callsPerCredit
        - periodStart
        - periodEnd
      properties:
        fullRetrievalsThisPeriod:
          type: integer
          format: int64
          description: Number of full retrievals charged in the current billing cycle.
        fullRetrievalsCap:
          type: integer
          description: Per-cycle cap (100 for Premium today).
        overflowCredits:
          type: integer
          description: Remaining balance across non-expired credit packs. Used once the per-cycle cap is exhausted.
        callsThisCycle:
          type: integer
          format: int64
          description: Total operations (any endpoint) counted in the current billing cycle.
        callsCap:
          oneOf:
            - type: integer
            - type: string
              enum:
                - unlimited
          description: >-
            Effective per-cycle operations cap = max(callsCapFloor, creditsUsedThisCycle × callsPerCredit). "unlimited"
            on unlimited tiers.
        callsCapFloor:
          oneOf:
            - type: integer
            - type: string
              enum:
                - unlimited
          description: Floor of the operations cap before any credit-earned headroom (50000 for Premium).
        creditsUsedThisCycle:
          type: integer
          description: Full retrievals charged this cycle (included + pack credits). Each one raises callsCap by callsPerCredit.
        callsPerCredit:
          type: integer
          description: Calls added to the operations cap per credit used (500).
        periodStart:
          type: string
          format: date-time
          description: >-
            Start of the current billing cycle (UTC). Approximately periodEnd - 1 month, anchored to the Stripe
            subscription's anniversary day.
        periodEnd:
          type: string
          format: date-time
          description: End of the current billing cycle (UTC) = Stripe subscription.items.data[0].current_period_end.
    Authority:
      type: object
      description: Contracting authority — minimal projection.
      properties:
        name:
          type: string
          description: Display name as it appears in the source notice.
        cui:
          type: string
          description: Normalized Romanian tax ID. May be null if the source notice didn't carry one.
    Cpv:
      type: object
      properties:
        code:
          type: string
          description: 8-digit CPV code
          e.g. "33123100-9".: null
        name:
          type: string
          description: Romanian display name from the official CPV vocabulary. Optional.
    CategorySummary:
      type: object
      properties:
        code:
          type: string
        name:
          type: string
    SimpleListingRequest:
      type: object
      properties:
        query:
          type: string
          description: Optional full-text refinement. Defaults to "*" (no refinement).
        skip:
          type: integer
          minimum: 0
          default: 0
        top:
          type: integer
          minimum: 1
          maximum: 1000
          default: 25
    TenderListResponse:
      type: object
      required:
        - skip
        - top
        - total
        - tenders
      properties:
        skip:
          type: integer
        top:
          type: integer
        total:
          type: integer
          description: Total matching tenders (independent of skip/top).
        tenders:
          type: array
          items:
            $ref: '#/components/schemas/TenderSummary'
    TenderSummary:
      type: object
      description: >-
        Compact tender row used by listing endpoints. Overlay fields (tags/color/comment) appear only on
        monitored/saved.
      properties:
        id:
          type: string
          example: cnotice_101341473
        noticeNo:
          type: string
          example: SCN1174909
        title:
          type: string
        aiDescription:
          type: string
          nullable: true
          description: AI-generated 1-2 sentence summary.
        publishedDate:
          type: string
          format: date-time
          nullable: true
        deadline:
          type: string
          format: date-time
          nullable: true
        estimatedValue:
          type: number
          nullable: true
          description: Always RON.
        primaryCpvs:
          type: array
          items:
            type: string
        authority:
          $ref: '#/components/schemas/Authority'
          nullable: true
        tags:
          type: array
          items:
            type: string
          nullable: true
          description: Per-project tags from the noticedata table. Only present when overlay applies.
        color:
          type: string
          nullable: true
          enum:
            - green
            - yellow
            - red
            - darkblue
            - lightblue
            - lavender
            - none
          description: Per-project color label.
        comment:
          type: string
          nullable: true
          description: Per-project free-text note from the noticecomment table.
    TenderFull:
      type: object
      description: |
        Raw NoticeComplete object as stored in the `tender.complete` Cosmos container,
        hydrated from blob storage when offloaded, with three adjustments:

        1. Per-project overlay added at the top level: `tags`, `color`, `comment`.
        2. Each attachment's `url` is replaced with a 1-hour SAS download link.
        3. Internal-only properties are stripped: `isOffloaded`, `attachmentVersion`,
           `isNormalized` (on every NoticeCompleteCompany usage), and `text`
           (on every NoticeCompleteAttachment — exposed separately via /pages and /find).

        When `?includePages=true`, a `pages[]` array is appended at the top level with
        the per-attachment OCR text inline. Same dedup pool as /byId — no extra charge
        when added on top of an already-charged retrieval.
      properties:
        id:
          type: string
          description: Internal tender id (e.g. cnotice_101341473).
        noticeNo:
          type: string
          description: Public SEAP/SICAP notice number (e.g. SCN1174909).
        title:
          type: string
        originalTitle:
          type: string
          nullable: true
          description: Pre-translation title for non-Romanian (TED) notices.
        shortDescription:
          type: string
          nullable: true
        originalShortDescription:
          type: string
          nullable: true
        aiDescription:
          type: string
          nullable: true
          description: AI-generated 1-2 sentence summary.
        description:
          type: string
          nullable: true
        originalDescription:
          type: string
          nullable: true
        additionalInformation:
          type: string
          nullable: true
        locationInformation:
          type: string
          nullable: true
        publishedDate:
          type: string
          format: date-time
          nullable: true
        lastModified:
          type: string
          format: date-time
          nullable: true
          description: Polling watermark.
        deadline:
          type: string
          format: date-time
          nullable: true
          description: Offer submission deadline. Null for award-only notices.
        estimatedValue:
          type: number
          nullable: true
          description: Always RON.
        originalEstimatedValue:
          type: number
          nullable: true
          description: Source-currency value (TED).
        originalCurrency:
          type: string
          nullable: true
          description: Source currency code (TED).
        durationDays:
          type: integer
          format: int64
          nullable: true
        durationMonths:
          type: integer
          format: int64
          nullable: true
        clarificationsDeadline:
          type: string
          format: date-time
          nullable: true
        isRenewableContract:
          type: boolean
          nullable: true
        performanceBond:
          type: number
          nullable: true
          description: Bond as a fraction in [0..1] (e.g. 0.1 = 10%).
        noticeSource:
          type: string
          nullable: true
          description: e.g. "SEAP", "WebTenders".
        noticeType:
          type: string
          nullable: true
          description: 'High-level: "Notice" or "Acquisition".'
        noticeSourceType:
          type: string
          nullable: true
          description: >-
            Source-specific: ContractNotice, ContractAward, PiNotice, AdvNotice, McNotice, DirectAcquisition,
            DaAwardNotice.
        authority:
          $ref: '#/components/schemas/NoticeCompleteCompany'
          nullable: true
        areas:
          type: array
          items:
            type: string
          description: Romanian county codes.
        categories:
          type: array
          items:
            type: string
          nullable: true
          description: Tender category IDs.
        secondaryCategories:
          type: array
          items:
            type: string
          nullable: true
        primaryCpvs:
          type: array
          items:
            type: string
          nullable: true
        secondaryCpvs:
          type: array
          items:
            type: string
          nullable: true
        predictedCpvs:
          type: array
          items:
            type: string
          nullable: true
        predictedCpvScores:
          type: array
          items:
            type: number
          nullable: true
        cpvs:
          type: array
          items:
            type: string
          description: 'Computed: deduped union of primary + secondary + predicted + lot CPVs.'
        cpvDivisions:
          type: array
          items:
            type: string
          description: 'Computed: first 2 digits of every cpv code, deduped.'
        url:
          type: string
          nullable: true
          description: Notice URL on the source website.
        documentUrls:
          type: array
          items:
            type: string
          nullable: true
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/NoticeCompleteAttachment'
          nullable: true
        attachmentArchive:
          $ref: '#/components/schemas/NoticeCompleteAttachment'
          nullable: true
          description: ZIP archive of all attachments.
        externalAttachments:
          type: array
          items:
            $ref: '#/components/schemas/ExternalAttachment'
          nullable: true
          description: Direct links to original-source files (TED) — not stored or OCR'd on our side.
        lots:
          type: array
          items:
            $ref: '#/components/schemas/NoticeCompleteLot'
          nullable: true
          description: At least one lot is always present; non-lot-split notices get a synthesized default lot.
        clarifications:
          type: array
          items:
            $ref: '#/components/schemas/NoticeCompleteClarification'
          nullable: true
        errata:
          type: array
          items:
            $ref: '#/components/schemas/NoticeCompleteErratum'
          nullable: true
        awardUrl:
          type: string
          nullable: true
          description: URL of the award notice on the source website (SEAP only).
        awardNumber:
          type: string
          nullable: true
          description: External award notice number.
        landArea:
          type: number
          nullable: true
        procedureStatus:
          type: string
          nullable: true
          description: 'SEAP procedure status: "Atribuita", "Suspendata", "Anulata", "In desfasurare".'
        filesDeleted:
          type: boolean
          description: >-
            True after we've cleaned up the raw attachment files for an expired tender. Text search still works but the
            SAS URL on each attachment will not resolve.
        nuts:
          type: array
          items:
            type: string
          nullable: true
        contractType:
          type: string
          nullable: true
        hasAppeal:
          type: boolean
          nullable: true
        tags:
          type: array
          items:
            type: string
          nullable: true
          description: Per-project tags from the noticedata table.
        color:
          type: string
          nullable: true
          enum:
            - green
            - yellow
            - red
            - darkblue
            - lightblue
            - lavender
            - none
          description: Per-project color label.
        comment:
          type: string
          nullable: true
          description: Per-project free-text note from the noticecomment table.
        pages:
          type: array
          nullable: true
          description: Per-attachment extracted text. Present only when ?includePages=true.
          items:
            type: object
            properties:
              attachmentId:
                type: string
              fileName:
                type: string
              text:
                type: string
                nullable: true
                description: Plain-text concatenation of all pages.
              info:
                type: string
                nullable: true
              isCorrupted:
                type: boolean
                nullable: true
    NoticeCompleteCompany:
      type: object
      description: A company (authority, candidate, awardee). `isNormalized` is internal and not exposed.
      properties:
        name:
          type: string
          nullable: true
        code:
          type: string
          nullable: true
          description: Raw tax ID as it appears in the source.
        normalizedCode:
          type: string
          nullable: true
          description: Numeric-only digits from code (computed).
        address:
          type: object
          nullable: true
          additionalProperties: true
          description: Postal address + contact channels. Shape varies by source.
        role:
          type: string
          nullable: true
    NoticeCompleteAttachment:
      type: object
      description: A single attached file. `text` (OCR'd plain text) is stripped from this response — fetch it via /pages or /find.
      properties:
        fileName:
          type: string
          nullable: true
        size:
          type: integer
          format: int64
          nullable: true
          description: File size in bytes.
        url:
          type: string
          nullable: true
          description: 1-hour SAS download URL. After expiry
          re-fetch the tender.: null
        id:
          type: string
          nullable: true
          description: Stable id
          distinct within the tender.: null
        info:
          type: string
          nullable: true
          description: Original file name when `fileName` is an md5 hash.
        isCorrupted:
          type: boolean
        type:
          type: string
          nullable: true
          description: 'Classifier output: "Spec", "Notice", or null.'
    ExternalAttachment:
      type: object
      properties:
        fileName:
          type: string
          nullable: true
        url:
          type: string
          nullable: true
          description: Direct link to the original source.
    NoticeCompleteLot:
      type: object
      properties:
        number:
          type: string
          nullable: true
          description: Lot number. Pipe-separated when one contract covers multiple lots (e.g. "1|2|3").
        title:
          type: string
          nullable: true
        primaryCpvs:
          type: array
          items:
            type: string
          nullable: true
        secondaryCpvs:
          type: array
          items:
            type: string
          nullable: true
        predictedCpvs:
          type: array
          items:
            type: string
          nullable: true
        cpvs:
          type: array
          items:
            type: string
          description: 'Computed: deduped union of the three CPV lists above.'
        estimatedValue:
          type: number
          nullable: true
          description: Always RON.
        originalEstimatedValue:
          type: number
          nullable: true
        originalCurrency:
          type: string
          nullable: true
        durationDays:
          type: integer
          format: int64
          nullable: true
        durationMonths:
          type: integer
          format: int64
          nullable: true
        tenderGuarantee:
          $ref: '#/components/schemas/TenderAmount'
          nullable: true
          description: Participation guarantee (Garanție de participare).
        procedurePhase:
          type: string
          nullable: true
          description: >-
            SEAP phase: Atribuita / Depunere candidatura / Deliberare / Evaluare financiara / Evaluare calificare si
            tehnica / Depunere oferta.
        procedureLotState:
          type: string
          nullable: true
          description: 'SEAP state: Atribuita / Suspendata / Anulata / In desfasurare / Suspendat prin suspendarea procedurii.'
        candidates:
          type: array
          items:
            $ref: '#/components/schemas/NoticeCompleteCompany'
          nullable: true
          description: Participants. Value not specified per candidate (SEAP only).
        associations:
          type: array
          nullable: true
          description: Groups of companies that participated together as a consortium. Each inner array is one association.
          items:
            type: array
            items:
              $ref: '#/components/schemas/NoticeCompleteCompany'
        awards:
          type: array
          items:
            $ref: '#/components/schemas/NoticeCompleteAward'
          nullable: true
        isRenewableContract:
          type: boolean
          nullable: true
        offerValabilityDeadline:
          type: string
          format: date-time
          nullable: true
    NoticeCompleteAward:
      type: object
      properties:
        company:
          $ref: '#/components/schemas/NoticeCompleteCompany'
        value:
          type: number
          nullable: true
          description: Always RON.
        type:
          type: string
          nullable: true
          enum:
            - Contract
            - Framework
            - Subsequent
            - Candidate
            - null
        date:
          type: string
          format: date-time
          nullable: true
        expiryDate:
          type: string
          format: date-time
          nullable: true
          description: Computed from award date + duration.
        contractTitle:
          type: string
          nullable: true
        id:
          type: string
        contractNumber:
          type: string
          nullable: true
    NoticeCompleteClarification:
      type: object
      properties:
        publishedDate:
          type: string
          format: date-time
          nullable: true
        attachment:
          $ref: '#/components/schemas/NoticeCompleteAttachment'
          nullable: true
        noticeDocumentDescription:
          type: string
          nullable: true
        noticeDocumentName:
          type: string
          nullable: true
        noticeDocumentId:
          type: string
          nullable: true
    NoticeCompleteErratum:
      type: object
      properties:
        publishedDate:
          type: string
          format: date-time
          nullable: true
        title:
          type: string
          nullable: true
        url:
          type: string
          nullable: true
        noticeNo:
          type: string
          nullable: true
    TenderAmount:
      type: object
      properties:
        amount:
          type: number
          nullable: true
        currency:
          type: string
          nullable: true
    TenderPagesResponse:
      type: object
      required:
        - tenderId
        - attachments
        - charBudget
        - charBudgetUsed
        - truncated
      properties:
        tenderId:
          type: string
        noticeNo:
          type: string
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/TenderPagesAttachment'
        truncated:
          type: boolean
          description: True when the response was cut off at the char budget. Use `nextPage` + `nextAttachment` to continue.
        nextPage:
          type: integer
          nullable: true
        nextAttachment:
          type: string
          nullable: true
        charBudget:
          type: integer
          description: Total chars the response budget allows (currently 60 000).
        charBudgetUsed:
          type: integer
        paginationHint:
          type: string
          nullable: true
    TenderPagesAttachment:
      type: object
      properties:
        attachmentId:
          type: string
        fileName:
          type: string
        totalPages:
          type: integer
        returnedRange:
          type: object
          nullable: true
          properties:
            from:
              type: integer
            to:
              type: integer
        pages:
          type: array
          items:
            type: object
            properties:
              pageNumber:
                type: integer
              text:
                type: string
              length:
                type: integer
              isLegacyFlat:
                type: boolean
                nullable: true
                description: True for V1-flat synthetic page-1 entries.
              totalLengthBeforeTrim:
                type: integer
                nullable: true
        format:
          type: string
          enum:
            - v2-paginated
            - v1-flat
            - none
          description: v2-paginated = pages.json hit. v1-flat = legacy texts.txt fallback. none = no OCR text cached.
        note:
          type: string
          nullable: true
    TenderPagesDownloadResponse:
      type: object
      required:
        - tenderId
        - pagesJsonAvailable
        - downloadUrl
        - expiresAt
      properties:
        tenderId:
          type: string
        noticeNo:
          type: string
        pagesJsonAvailable:
          type: boolean
          enum:
            - true
        downloadUrl:
          type: string
          format: uri
          description: 15-minute SAS URL.
        expiresAt:
          type: string
          format: date-time
        expiresInSeconds:
          type: integer
          example: 900
        sizeBytes:
          type: integer
          format: int64
        attachmentCount:
          type: integer
        schema:
          type: string
          description: String describing the pages.json schema for the client.
        downloadHint:
          type: string
        summary:
          type: array
          items:
            type: object
            properties:
              attachmentId:
                type: string
              fileName:
                type: string
              totalPages:
                type: integer
              firstChars:
                type: string
                nullable: true
                description: First ~200 chars of the first page.
    TenderPagesDownloadUnavailable:
      type: object
      required:
        - tenderId
        - pagesJsonAvailable
      properties:
        tenderId:
          type: string
        noticeNo:
          type: string
        pagesJsonAvailable:
          type: boolean
          enum:
            - false
        hint:
          type: string
          description: 'Fallback guidance: use /pages or /find for legacy V1 tenders.'
    TenderFindResponse:
      type: object
      required:
        - tenderId
        - query
        - totalHits
        - results
      properties:
        tenderId:
          type: string
        noticeNo:
          type: string
        query:
          type: string
        contextChars:
          type: integer
        totalHits:
          type: integer
        capped:
          type: boolean
          description: True when totalHits hit the maxHits cap.
        results:
          type: array
          items:
            type: object
            properties:
              attachmentId:
                type: string
              fileName:
                type: string
              totalPages:
                type: integer
              format:
                type: string
                enum:
                  - v2-paginated
                  - v1-flat
                  - none
              hits:
                type: array
                items:
                  type: object
                  properties:
                    pageNumber:
                      type: integer
                    snippet:
                      type: string
    TenderAttachments:
      type: object
      required:
        - tenderId
        - attachmentCount
        - attachments
        - archives
      properties:
        tenderId:
          type: string
        noticeNo:
          type: string
          nullable: true
        title:
          type: string
          nullable: true
        attachmentCount:
          type: integer
        filesDeleted:
          type: boolean
          description: True after raw attachment files for an expired tender were cleaned up — the SAS URLs will not resolve.
        attachments:
          type: array
          items:
            type: object
            properties:
              id:
                type: string
              fileName:
                type: string
                nullable: true
              info:
                type: string
                nullable: true
                description: Original file name when `fileName` is an md5 hash.
              isCorrupted:
                type: boolean
              originalUrl:
                type: string
                format: uri
                description: 1-hour SAS download URL to the raw file.
              searchableUrl:
                type: string
                format: uri
                nullable: true
                description: >-
                  1-hour SAS to the OCR-overlaid searchable PDF. Null when the attachment is not a PDF, OCR has not run
                  yet, or the file was already text-searchable.
        archives:
          type: object
          properties:
            originalsArchiveUrl:
              type: string
              format: uri
              nullable: true
              description: 1-hour SAS to the zip of all original files. Null when no archive exists.
            searchableArchiveUrl:
              type: string
              format: uri
              nullable: true
              description: >-
                1-hour SAS to the zip of all searchable PDFs. Null when not yet generated (pipeline produces it lazily
                after every attachment is OCR'd).
        extractedText:
          type: object
          nullable: true
          description: Present only when the per-page extracted-text JSON exists.
          properties:
            pagesJsonUrl:
              type: string
              format: uri
              description: 1-hour SAS to pages.json — per-attachment text keyed by attachment id then 1-based page number.
            format:
              type: string
              example: v2
            description:
              type: string
    TenderHistoryResponse:
      type: object
      required:
        - tenderId
      properties:
        tenderId:
          type: string
        appliedCpvs:
          type: array
          items:
            type: string
        appliedCategories:
          type: array
          items:
            type: string
        availableCpvs:
          type: array
          items:
            $ref: '#/components/schemas/Cpv'
        availableCategories:
          type: array
          items:
            $ref: '#/components/schemas/CategorySummary'
        totalEstimated:
          type: number
        totalFinal:
          type: number
        topCompaniesByValue:
          type: array
          items:
            $ref: '#/components/schemas/CompanyStat'
        topCompaniesByCount:
          type: array
          items:
            $ref: '#/components/schemas/CompanyStat'
        similarTenders:
          type: array
          description: Similar past tenders from the same authority matching the CPV/category filters.
          items:
            $ref: '#/components/schemas/HistoricalTender'
    CompanyStat:
      type: object
      properties:
        name:
          type: string
        code:
          type: string
          description: Normalized CUI.
        value:
          type: number
          description: Sum (RON) for topCompaniesByValue; count for topCompaniesByCount.
    HistoricalTender:
      type: object
      description: One similar tender (the "Mix" row). Includes the suppliers that won this tender's lots.
      properties:
        id:
          type: string
          nullable: true
        globalId:
          type: string
          nullable: true
        noticeNo:
          type: string
          nullable: true
        title:
          type: string
          nullable: true
        publishedDate:
          type: string
          format: date-time
          nullable: true
        deadline:
          type: string
          format: date-time
          nullable: true
        estimatedValue:
          type: number
          nullable: true
        finalValue:
          type: number
          nullable: true
        suppliers:
          type: array
          items:
            type: object
            properties:
              name:
                type: string
              code:
                type: string
              totalValue:
                type: number
              contractCount:
                type: number
    AwardSearchRequest:
      type: object
      description: |
        Full awards-search envelope. Mirrors what the SPA's Atribuiri menu sends.
      properties:
        skip:
          type: integer
          minimum: 0
          default: 0
        top:
          type: integer
          minimum: 1
          maximum: 100
          default: 50
        sortField:
          type: string
          default: awardDate
          description: e.g. "awardDate", "publishedDate", "expiryDate", "cpv", "authority", "company".
        sortDirection:
          type: string
          enum:
            - asc
            - desc
          default: desc
        searchPhrase:
          type: string
          nullable: true
        cpvFilters:
          type: array
          items:
            type: string
          description: CPV codes; expanded to include children.
        categoryFilters:
          type: array
          items:
            type: string
          description: Tender category IDs.
        areaFilters:
          type: array
          items:
            type: string
          description: Romanian county codes.
        authorityFilters:
          type: array
          items:
            type: string
          description: Authority normalized CUIs.
        companyFilters:
          type: array
          items:
            type: string
          description: Winner company normalized CUIs.
        methodFilters:
          type: array
          items:
            type: string
          description: Notice source types, e.g. DirectAcquisition, ContractAward.
        statusFilters:
          type: array
          items:
            type: string
          description: Procedure-status filters (only for onlyOngoingAcquisitions=true).
        awardDateFrom:
          type: string
          format: date-time
          nullable: true
        awardDateTo:
          type: string
          format: date-time
          nullable: true
        publishedDateFrom:
          type: string
          format: date-time
          nullable: true
        publishedDateTo:
          type: string
          format: date-time
          nullable: true
        expiryDateFrom:
          type: string
          format: date-time
          nullable: true
        expiryDateTo:
          type: string
          format: date-time
          nullable: true
        deadlineTo:
          type: string
          format: date-time
          nullable: true
          description: For ongoing acquisitions.
        estimatedValueFrom:
          type: number
          nullable: true
        estimatedValueTo:
          type: number
          nullable: true
        awardValueFrom:
          type: number
          nullable: true
        awardValueTo:
          type: number
          nullable: true
        applyMonitoringCriteria:
          type: boolean
          default: false
          description: When true
          intersects results with the project's saved monitoring criteria + AI relevance cache.: null
        onlyOngoingAcquisitions:
          type: boolean
          default: false
          description: When true
          returns ongoing acquisitions instead of awarded contracts.: null
    AwardSearchResponse:
      type: object
      required:
        - acquisitions
        - totalCount
      properties:
        acquisitions:
          type: array
          items:
            $ref: '#/components/schemas/AwardRow'
        totalCount:
          type: integer
        timing:
          type: number
          description: Query duration in ms.
        continuationToken:
          type: string
          nullable: true
        warnings:
          type: array
          items:
            type: string
          nullable: true
    AwardRow:
      type: object
      description: Denormalized award row from the tender.awards container, enriched with category names.
      properties:
        id:
          type: string
        noticeId:
          type: string
        noticeNo:
          type: string
        title:
          type: string
        noticeType:
          type: string
          nullable: true
        noticeSourceType:
          type: string
          nullable: true
        noticeSource:
          type: string
          nullable: true
        procedureStatus:
          type: string
          nullable: true
        url:
          type: string
          nullable: true
        authorityName:
          type: string
          nullable: true
        authorityCode:
          type: string
          nullable: true
          description: Normalized CUI of the authority.
        primaryCpvs:
          type: array
          items:
            type: string
        secondaryCpvs:
          type: array
          items:
            type: string
        predictedCpvs:
          type: array
          items:
            type: string
        cpvs:
          type: array
          items:
            type: string
        cpvDivisions:
          type: array
          items:
            type: string
        areas:
          type: array
          items:
            type: string
        noticeEstimatedValue:
          type: number
          nullable: true
        lotNumber:
          type: string
          nullable: true
        lotTitle:
          type: string
          nullable: true
        lotEstimatedValue:
          type: number
          nullable: true
        lotProcedureStatus:
          type: string
          nullable: true
        awardDate:
          type: string
          format: date-time
          nullable: true
        awardValue:
          type: number
          nullable: true
          description: Always RON.
        awardType:
          type: string
          nullable: true
        contractTitle:
          type: string
          nullable: true
        contractNumber:
          type: string
          nullable: true
        expiryDate:
          type: string
          format: date-time
          nullable: true
        publishedDate:
          type: string
          format: date-time
          nullable: true
        deadline:
          type: string
          format: date-time
          nullable: true
        winnerCompanyName:
          type: string
          nullable: true
        winnerCompanyCode:
          type: string
          nullable: true
          description: Normalized CUI of the winning company.
        categories:
          type: array
          items:
            type: string
        awardUrl:
          type: string
          nullable: true
        awardNumber:
          type: string
          nullable: true
        categoryNames:
          type: array
          items:
            type: string
          description: Up to 2 human-readable category names.
    CompanyResponse:
      type: object
      required:
        - cui
      properties:
        cui:
          type: string
          description: Normalized Romanian tax ID (digits only).
        code:
          type: string
          nullable: true
          description: ONRC registration code.
        name:
          type: string
          nullable: true
        address:
          type: string
          nullable: true
        city:
          type: string
          nullable: true
        county:
          type: string
          nullable: true
        country:
          type: string
          nullable: true
        stats:
          $ref: '#/components/schemas/CompanyStats'
          nullable: true
    CompanyStats:
      type: object
      description: |
        Precomputed all-time statistics from the companyStats Azure Table.
        Values are in RON.
      properties:
        acquisitionContractWonValue:
          type: number
          description: Sum of award values won via "Direct Acquisition" contract notices (DA award notices).
        acquisitionWonLotCount:
          type: integer
          description: Number of distinct DA lots won.
        acquisitionWonValue:
          type: number
          description: >-
            Sum of award values from the direct-acquisition lots (tracks DA-style awards from the SEAP
            direct-acquisition catalogue).
        noticeContractWonValue:
          type: number
          description: Sum of award values won under Notice-style contract awards.
        noticeFrameworkWonValue:
          type: number
          description: Sum of award values won under framework agreements.
        noticeSubsequentWonValue:
          type: number
          description: Sum of award values from subsequent (call-off) awards inside framework agreements.
        noticeWonLotCount:
          type: integer
          description: Number of distinct Notice-style lots won.
        awardedNoticeParticipationCount:
          type: integer
          description: >-
            Total Notice tenders the company participated in that ended up awarded (the denominator for bid-to-award
            analysis).
        noticeWonLotEstPercValue:
          type: number
          description: Numerator for computing "won value / estimated value" ratio on Notice lots.
        noticeWonLotEstPercCount:
          type: integer
          description: Denominator (count of lots with a usable estimated value) for the same ratio.
        topAuthorities:
          type: object
          additionalProperties:
            type: number
          description: Top contracting authorities by awarded value to this company. Keys are authority CUIs; values are RON.
          nullable: true
        topCompetitors:
          type: object
          additionalProperties:
            type: number
          description: >-
            Top competing companies by frequency of bidding against this company on the same lots. Keys are competitor
            CUIs; values are counts.
          nullable: true
    Error:
      type: object
      required:
        - code
        - message
      properties:
        code:
          type: string
          description: Stable machine code.
        message:
          type: string
          description: Human-readable description.
        details:
          type: object
          additionalProperties: true
          nullable: true
    ErrorEnvelope:
      type: object
      properties:
        error:
          $ref: '#/components/schemas/Error'
  responses:
    BadRequest:
      description: Invalid request shape.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Unauthorized:
      description: Missing or invalid Ocp-Apim-Subscription-Key, or backend rejected the APIM context.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    Forbidden:
      description: API access requires an active Premium subscription.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    PaymentRequired:
      description: Per-cycle full-retrieval cap exhausted and credit-pack wallet empty.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    NotFound:
      description: No resource matching the given id / notice number / CUI.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
