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

# Recommend a threshold for a precision or recall target

> The threshold on the raw `p` (bool) or raw `dist[option]` (choice) that
meets the target on the active version's outcomes (§6.10). A document
counts as predicted positive when the raw value is at least the
threshold, and as positive when its outcome is `true` (bool) or equals
`option` (choice).

- For a precision target, the lowest threshold whose precision meets
  it, which keeps the most recall.
- For a recall target, the highest threshold whose recall meets it,
  which keeps the most precision.

The recommendation rests on the outcomes of the current engine epoch:
at least 100, with at least 20 positive and 20 negative (for a choice,
20 of `option` and 20 of the other options). When that epoch has too
few, it uses the previous epoch's and says so with
`from_previous_epoch`, as calibration does. When neither has enough,
`insufficient_data` says why and what to post. Nothing
changes until the threshold is applied with `PATCH`. Score judgments
have no recommendation in v1.5 (`invalid_request`).




## OpenAPI

````yaml /api-reference/openapi.yaml get /namespaces/{ns}/judgments/{name}/thresholds/recommend
openapi: 3.1.0
info:
  title: Vainona API
  version: '1'
  license:
    name: Proprietary
    identifier: LicenseRef-Proprietary
  description: |
    The `/v1` contract from main spec §6. It is locked: changes within `/v1`
    are additive only, and anything breaking is `/v2`.

    - **Base URL.** `https://api.vainona.ai/v1`.
    - **Evolution.** Response objects may gain fields and enums may gain values.
      Clients must ignore what they do not know. Request objects reject unknown
      fields.
    - **Path segments.** Namespace names (`{ns}`) and document ids (`{id}`) may
      contain `/`. Send it percent-encoded as `%2F`, so that
      `acme/prod/tenant_123` is `acme%2Fprod%2Ftenant_123`.
    - **Templates (v1.5).** The judgment routes also take a namespace prefix
      ending in `/*`, such as `acme%2Fprod%2F*`. A judgment created there is a
      template: every namespace under the prefix inherits it, including ones
      created later. The most specific matching prefix wins (§7.7).
    - **Thresholds are settings (v1.5).** Named thresholds belong to the
      judgment, not to a version. They are evaluated at read time against the
      raw fields, so a change applies at once to every answer, with no
      recompute and no new version (§6.5).
    - **Entity judgments (entities, E1).** A judgment can read the documents
      that point at the judged one, through `context.related`, and apply only
      to documents matching `applies_to` (§6.5.2). Its answers carry a
      `watermark`, and creating one that runs `on_change` returns a replay
      estimate of its monthly cost until you confirm (§6.9).
    - **Referenced documents (entities, E3a).** A relation can also read the
      one document the judged document points at, through
      `join: {theirs: "id", mine: "attributes.<name>"}`. A change to what the
      relation renders of that document re-judges the judged documents that
      point at it and are inside `freshness.fanout.scope`: a fan-out. A large
      fan-out is a `fanout` job that waits for confirm (§6.5.2, §6.9).
    - **Idempotency.** Every write operation is idempotent by construction.
      `Idempotency-Key` is accepted on every mutating route and, while the node
      still caches the original response, returns it verbatim.
    - **Errors.** Every error is the `Error` envelope; the HTTP status follows
      the code (see each response).
    - **Rate limits.** Every response carries `X-RateLimit-Limit` and
      `X-RateLimit-Remaining`; a `rate_limited` response adds `Retry-After`,
      except a daily allowance that is used up, which says when it renews in
      `details.resets_at` instead.
    - **Usage.** Every response that bills carries `usage`. Judging is billed in
      engine-neutral judgment units: one judgment answered, per started 1,000
      tokens of its compiled context and its question together, at least 1
      (§9). No response ever carries an
      engine's price.
servers:
  - url: https://api.vainona.ai/v1
security:
  - apiKey: []
tags:
  - name: Namespaces
    description: Namespaces, their settings and cache warming (§6.2, §6.11).
  - name: Documents
    description: Writes, point reads and queries (§6.3 to §6.8).
  - name: Judgments
    description: >-
      Judgment definitions, versions, settings, activation, backfill and
      template detach (§6.5, §6.9, §7.7).
  - name: Outcomes and calibration
    description: |
      Post what actually happened to judged documents, and read how well answers
      match it: the calibration report, calibrated answers and threshold
      recommendations (§6.10). See [calibration](/concepts/calibration).
  - name: Jobs
    description: >-
      Backfill, shadow, periodic, deletion, reference index and fan-out jobs
      (§6.2, §6.9, §7.10.5).
  - name: Engines
    description: The engine registry (§6.5, §7.4.5).
paths:
  /namespaces/{ns}/judgments/{name}/thresholds/recommend:
    parameters:
      - $ref: '#/components/parameters/NamespaceOrTemplate'
      - $ref: '#/components/parameters/JudgmentName'
    get:
      tags:
        - Outcomes and calibration
      summary: Recommend a threshold for a precision or recall target
      description: |
        The threshold on the raw `p` (bool) or raw `dist[option]` (choice) that
        meets the target on the active version's outcomes (§6.10). A document
        counts as predicted positive when the raw value is at least the
        threshold, and as positive when its outcome is `true` (bool) or equals
        `option` (choice).

        - For a precision target, the lowest threshold whose precision meets
          it, which keeps the most recall.
        - For a recall target, the highest threshold whose recall meets it,
          which keeps the most precision.

        The recommendation rests on the outcomes of the current engine epoch:
        at least 100, with at least 20 positive and 20 negative (for a choice,
        20 of `option` and 20 of the other options). When that epoch has too
        few, it uses the previous epoch's and says so with
        `from_previous_epoch`, as calibration does. When neither has enough,
        `insufficient_data` says why and what to post. Nothing
        changes until the threshold is applied with `PATCH`. Score judgments
        have no recommendation in v1.5 (`invalid_request`).
      operationId: recommendThreshold
      parameters:
        - name: target
          in: query
          required: true
          description: >-
            The metric and the value to meet, such as `precision:0.9` or
            `recall:0.8`.
          schema:
            $ref: '#/components/schemas/ThresholdTarget'
        - name: option
          in: query
          description: >-
            Required for a choice judgment, and refused for a bool one. The
            option the threshold is for, such as `fraud`.
          schema:
            type: string
            minLength: 1
      responses:
        '200':
          description: >-
            A recommendation, an unreachable target, or too few outcomes;
            `status` says which.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ThresholdRecommendation'
        '400':
          $ref: '#/components/responses/InvalidRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/Conflict'
        '413':
          $ref: '#/components/responses/TooLarge'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/Internal'
components:
  parameters:
    NamespaceOrTemplate:
      name: ns
      in: path
      required: true
      description: |
        A namespace name, or a template prefix ending in `/*` (§7.7), with any
        `/` sent as `%2F`: `acme%2Fprod%2Ftenant_123` or `acme%2Fprod%2F*`.
      schema:
        $ref: '#/components/schemas/NamespaceOrTemplate'
    JudgmentName:
      name: name
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/Name'
  schemas:
    ThresholdTarget:
      type: string
      description: A metric and the value to meet, such as `precision:0.9` or `recall:0.8`.
      pattern: ^(precision|recall):(0?\.[0-9]*[1-9][0-9]*|1|1\.0+)$
    ThresholdRecommendation:
      type: object
      description: |
        Discriminated on `status`: `recommended`, `unreachable` when no
        threshold meets the target on these outcomes, or `insufficient_data`
        below 100 outcomes or 20 of either class (§6.10).
      oneOf:
        - $ref: '#/components/schemas/ThresholdRecommended'
        - $ref: '#/components/schemas/ThresholdUnreachable'
        - $ref: '#/components/schemas/ThresholdInsufficientData'
      discriminator:
        propertyName: status
        mapping:
          recommended:
            $ref: '#/components/schemas/ThresholdRecommended'
          unreachable:
            $ref: '#/components/schemas/ThresholdUnreachable'
          insufficient_data:
            $ref: '#/components/schemas/ThresholdInsufficientData'
    NamespaceOrTemplate:
      type: string
      description: |
        A namespace name, or a template prefix: a namespace path ending in
        `/*`, such as `acme/prod/*`, which every namespace under `acme/prod/`
        inherits judgments from (§7.7). Up to 256 bytes.
      pattern: ^[A-Za-z0-9._:/-]+(/\*)?$
      minLength: 1
      maxLength: 256
    Name:
      type: string
      description: >-
        A judgment, attribute or threshold name. Names are path segments in
        field references, so they never contain `.`.
      pattern: ^[A-Za-z0-9_-]+$
      minLength: 1
      maxLength: 128
    ThresholdRecommended:
      type: object
      description: >-
        Apply `threshold` with the judgment `PATCH`: `{"escalate": threshold}`
        for a bool judgment, `{"fraud": {"value": option, "gte": threshold}}`
        for a choice.
      required:
        - status
        - threshold
        - precision
        - recall
        - interval
        - outcomes
        - from_previous_epoch
        - curve
      properties:
        status:
          const: recommended
        threshold:
          $ref: '#/components/schemas/Probability'
          description: The threshold on the raw value, one of the curve's points.
        precision:
          $ref: '#/components/schemas/Probability'
          description: Precision on these outcomes at the threshold.
        recall:
          $ref: '#/components/schemas/Probability'
          description: Recall on these outcomes at the threshold.
        interval:
          $ref: '#/components/schemas/MetricInterval'
          description: The 95% interval of the targeted metric at the threshold.
        outcomes:
          $ref: '#/components/schemas/RecommendationOutcomes'
        from_previous_epoch:
          $ref: '#/components/schemas/RecommendationFromPreviousEpoch'
        curve:
          $ref: '#/components/schemas/PrecisionRecallCurve'
    ThresholdUnreachable:
      type: object
      description: No threshold meets the target. The curve shows what is reachable.
      required:
        - status
        - outcomes
        - from_previous_epoch
        - curve
      properties:
        status:
          const: unreachable
        outcomes:
          $ref: '#/components/schemas/RecommendationOutcomes'
        from_previous_epoch:
          $ref: '#/components/schemas/RecommendationFromPreviousEpoch'
        curve:
          $ref: '#/components/schemas/PrecisionRecallCurve'
    ThresholdInsufficientData:
      type: object
      description: |
        Too few outcomes, so nothing is recommended: fewer than 100, or fewer
        than 20 positive or 20 negative ones. `reason` and `message` say which,
        and what to post.
      required:
        - status
        - outcomes
        - reason
        - message
      properties:
        status:
          const: insufficient_data
        outcomes:
          type: integer
          format: int64
          minimum: 0
          description: >-
            The outcomes there are, in the current epoch or, when it has more,
            the previous one.
        reason:
          type: string
          enum:
            - too_few_outcomes
            - too_few_positives
            - too_few_negatives
          description: See `CalibrationShortfall`.
        message:
          type: string
          description: >-
            What to post, such as "Only positive outcomes so far: post outcomes
            for documents where it did not happen."
    Error:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              $ref: '#/components/schemas/ErrorCode'
            message:
              type: string
            details:
              type: object
              description: >-
                Code-specific detail, such as the scan estimate on a refused
                query. Every `internal` error has `request_id`.
    Probability:
      type: number
      minimum: 0
      maximum: 1
    MetricInterval:
      type: object
      required:
        - lower
        - upper
      properties:
        lower:
          $ref: '#/components/schemas/Probability'
        upper:
          $ref: '#/components/schemas/Probability'
    RecommendationOutcomes:
      type: integer
      format: int64
      minimum: 100
      description: The number of outcomes the recommendation rests on.
    RecommendationFromPreviousEpoch:
      type: boolean
      description: >-
        True when the current engine epoch has too few outcomes (fewer than 100,
        or fewer than 20 of either class) and the previous epoch's were used.
    PrecisionRecallCurve:
      type: array
      description: |
        Precision and recall at each threshold from 0.00 to 1.00 in steps of
        0.01, lowest first: 101 points.
      maxItems: 101
      items:
        $ref: '#/components/schemas/PrecisionRecallPoint'
    ErrorCode:
      type: string
      description: |
        HTTP status by code: `invalid_request` 400, `unauthorized` 401,
        `budget_exceeded` 402, `forbidden` 403, `not_found` 404, `conflict` 409,
        `too_large` 413, `engine_version_unavailable` 422,
        `insufficient_labels` 422, `rate_limited` 429, `internal` 500,
        `engine_unavailable` 503.
      enum:
        - invalid_request
        - unauthorized
        - forbidden
        - not_found
        - conflict
        - too_large
        - rate_limited
        - budget_exceeded
        - engine_unavailable
        - engine_version_unavailable
        - insufficient_labels
        - internal
    PrecisionRecallPoint:
      type: object
      required:
        - threshold
        - precision
        - recall
      properties:
        threshold:
          $ref: '#/components/schemas/Probability'
        precision:
          description: Null when no outcome's raw value reaches the threshold.
          oneOf:
            - $ref: '#/components/schemas/Probability'
            - type: 'null'
        recall:
          $ref: '#/components/schemas/Probability'
  headers:
    RateLimitLimit:
      description: Requests allowed per second for this key.
      schema:
        type: integer
        minimum: 0
    RateLimitRemaining:
      description: Requests left in the current window.
      schema:
        type: integer
        minimum: 0
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer
        minimum: 0
  responses:
    InvalidRequest:
      description: '`invalid_request`: the request is malformed or breaks a rule of §6.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Unauthorized:
      description: '`unauthorized`: the key is missing, unknown or revoked.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Forbidden:
      description: '`forbidden`: the key''s role or namespace prefix does not allow this.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    NotFound:
      description: '`not_found`: the namespace, document, judgment or job does not exist.'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Conflict:
      description: >-
        `conflict`: the namespace is being deleted, an object a read needs was
        compacted away and the read (or the query, without its cursor) must
        restart (§7.5.11), or the judgment is inherited from a template and this
        namespace cannot create, activate or delete it (§7.7).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    TooLarge:
      description: >-
        `too_large`: the request body (on any route), a document, or the
        estimated query scan is over its limit. `details` carries the estimate
        for queries. It also answers a write that would take
        `default/quickstart` past its size cap (10,000 documents or 100 MB),
        with `details.max_documents` and `details.max_bytes`.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: >-
        `rate_limited`: slow down. `Retry-After` says for how long, except when
        a daily allowance is used up: then `details.resets_at` says when it
        renews, and there is no `Retry-After`.
      headers:
        X-RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    Internal:
      description: >-
        `internal`: something failed on our side. Nothing partial is returned,
        and a request never commits part of itself. Most `internal` errors
        committed nothing. A write can instead fail with a message saying it may
        have been committed, when we could not tell whether its batch landed
        (§7.2.10): it then committed all of it or none of it. Retrying a write
        is safe either way, because writes are idempotent. The message is
        generic and carries a request id, also in `details.request_id`, which
        support uses to find the cause.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: |
        An organization API key. Keys carry a role (`read_write` or
        `read_only`) and may be restricted to a namespace prefix such as
        `acme/*`, or to one namespace such as `acme/prod/tenant_1`. A prefix
        matches on a `/` boundary: `acme/prod/tenant_1*` covers
        `acme/prod/tenant_1` and everything under `acme/prod/tenant_1/`,
        never `acme/prod/tenant_12`.

````