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

# Submit Feedback

> Tell HydraDB how a query performed, so retrieval quality improves.

export const Field = ({name, type, required, recommended}) => {
  const label = required ? 'required' : recommended ? 'recommended' : null;
  const typeLabel = typeof type === 'string' ? type : null;
  const ariaParts = [name, typeLabel && `${typeLabel}`, label].filter(Boolean);
  return <span aria-label={ariaParts.join(', ')} className={label ? 'field-wrap has-field-tip' : 'field-wrap'} style={{
    position: 'relative',
    cursor: label ? 'default' : undefined
  }} tabIndex={label ? 0 : undefined}>
      <span className="field-name-row">
        <code>{name}</code>
        {required && <span className="field-req"> *</span>}
        {recommended && <span className="field-rec"> ●</span>}
      </span>
      {type && <span className="field-type">{type}</span>}
      {label && <span className="field-tip" role="tooltip">
          {label}
        </span>}
    </span>;
};

Report back on a query that already ran  -  what was missing, what was wrong, or that it was exactly right. Feedback feeds retrieval-quality work; it does **not** change the result of the query it refers to.

Both people and agents can submit. An agent that can tell a retrieval was unhelpful is often the best source of signal you have, so `source` labels which one it was.

## Linking feedback to a query

Every HydraDB response carries a `request_id` in `meta`, and the same value in the `X-Request-ID` header. Send that id back and we can line your comment up with the exact query it is about  -  the text queried, what came back, how long it took.

```json Query response {6} theme={"dark"}
{
  "success": true,
  "data": { "chunks": [ /* ... */ ] },
  "error": null,
  "meta": {
    "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d",
    "api_version": "2.0.1",
    "latency_ms": 412.8
  }
}
```

<Note>
  Send `request_id` back **exactly as you received it**. It must be the UUID from `meta.request_id` (or the `X-Request-ID` header)  -  any other value is rejected with `400`.
</Note>

Submit feedback for queries that **returned**. If the query itself failed, handle the error instead  -  there is no retrieval to judge, and the fix is in the request rather than in the index.

## Fields

<Field name="request_id" type="string" required>
  The `request_id` from the target query's `meta`. Must be a UUID.
</Field>

<Field name="feedback" type="string">
  What was right or wrong, in your own words. Up to 8000 characters.

  Required **unless** you send `ground_truth`  -  every submission needs at least one of the two.
</Field>

<Field name="ground_truth" type="object">
  What you already know the right answer to be. See [Ground truth](#ground-truth).

  <Expandable title="properties">
    <Field name="answer" type="string">
      The response you expected. Up to 8000 characters.
    </Field>

    <Field name="source_ids" type="string[]">
      IDs of the sources that actually contain the answer. Up to 100.
    </Field>
  </Expandable>
</Field>

<Field name="rating" type="string">
  `positive`, `negative`, or `neutral`. Optional  -  leaving it out is not the same as `neutral`; it records that you sent a comment without a rating.
</Field>

<Field name="source" type="string">
  `user` *(default)* or `agent`  -  who is submitting.
</Field>

<Field name="database" type="string">
  Optional. Scopes the feedback to a database. Must be one your API key can reach.
</Field>

<Field name="collection" type="string">
  Optional. Requires `database`  -  a collection is scoped to a database, so sending it alone returns `400`.
</Field>

<Field name="metadata" type="object">
  Optional string-to-string map for your own context (agent name, conversation id, eval run). Up to 20 entries; keys up to 64 characters, values up to 512.
</Field>

<RequestExample>
  ```python Python SDK theme={"dark"}
  result = client.query(
      database="acme_corp",
      query="What is our refund policy?",
  )

  client.feedback.submit(
      request_id=result.meta.request_id,
      feedback="Returned the 2023 policy - the current one is in the Q3 handbook.",
      rating="negative",
      source="agent",
      database="acme_corp",
      metadata={"agent": "support-bot", "conversation": "c-8891"},
  )
  ```

  ```typescript TypeScript SDK theme={"dark"}
  const result = await client.query({
    database: "acme_corp",
    query: "What is our refund policy?",
  });

  await client.feedback.submit({
    requestId: result.meta.requestId,
    feedback: "Returned the 2023 policy - the current one is in the Q3 handbook.",
    rating: "negative",
    source: "agent",
    database: "acme_corp",
    metadata: { agent: "support-bot", conversation: "c-8891" },
  });
  ```

  ```bash cURL theme={"dark"}
  curl -X POST 'https://api.hydradb.com/feedback' \
    -H "Authorization: Bearer <your_api_key>" \
    -H "API-Version: 2" \
    -H "Content-Type: application/json" \
    -d '{
      "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d",
      "feedback": "Returned the 2023 policy - the current one is in the Q3 handbook.",
      "rating": "negative",
      "source": "agent",
      "database": "acme_corp",
      "metadata": { "agent": "support-bot" }
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json Success theme={"dark"}
  {
    "success": true,
    "data": {
      "feedback_id": "0f5c2e18-7b41-4a92-9d0c-5e1f7a3b6c84",
      "request_id": "9d13aef4-02f4-4e73-8c62-4c2601d04f9d",
      "recorded": true,
      "created_at": "2026-08-10T09:11:55.593287923Z",
      "message": "Feedback recorded. Thank you - it is used to validate and improve retrieval quality."
    },
    "error": null,
    "meta": {
      "request_id": "84467490-be20-4640-8693-f604a03227cc",
      "api_version": "2.0.1",
      "latency_ms": 102.1
    }
  }
  ```

  ```json Invalid request_id theme={"dark"}
  {
    "success": false,
    "data": null,
    "error": {
      "code": "INVALID_INPUT",
      "message": "request_id \"abc-123\" is not a valid request id: it must be the UUID returned in response.meta.request_id (or the X-Request-ID header) of the query you are giving feedback on"
    },
    "meta": {
      "request_id": "b4340e7c-0082-4c20-829d-5fd53f81a54d",
      "api_version": "2.0.1"
    }
  }
  ```

  ```json Rate limited theme={"dark"}
  {
    "success": false,
    "data": null,
    "error": {
      "code": "RATE_LIMITED",
      "message": "feedback per_min rate limit exceeded (limit: 100). Please retry in 37 second(s)."
    },
    "meta": {
      "request_id": "7c1e9a44-0b2d-4f88-a3e1-9d6c2b5f0e73",
      "api_version": "2.0.1"
    }
  }
  ```
</ResponseExample>

## Ground truth

If you already know the right answer  -  you are running an evaluation set, or you know which document the user needed  -  send it. It is a much stronger signal than a comment, because we can score it without a human reading it.

```json theme={"dark"}
"ground_truth": {
  "answer": "Refunds are processed within 14 days.",
  "source_ids": ["policy_2024", "handbook_q3"]
}
```

* **`answer`**  -  the response you expected.
* **`source_ids`**  -  the sources that actually contain the answer. This is the one that grades retrieval: it tells us whether the query surfaced those documents, and where they ranked.

Send either on its own or both together. If `ground_truth` is your only signal, at least one of the two has to carry something  -  values that are empty or all whitespace are treated as not sent.

<Note>
  When you send `ground_truth`, the `feedback` comment becomes optional  -  an evaluation run with an answer key does not need prose for every row. A submission with neither is rejected.
</Note>

```python Evaluation run theme={"dark"}
for case in eval_set:
    result = client.query(database="acme_corp", query=case.question)

    try:
        client.feedback.submit(
            request_id=result.meta.request_id,
            source="agent",
            ground_truth={
                "answer": case.expected_answer,
                "source_ids": case.expected_sources,
            },
        )
    except Exception:
        continue  # one unrecorded case should not abort the run
```

At eval volumes you may brush the rate limit, so keep the submission from ending the loop: an unguarded call means a single `429` loses every remaining case, not just the one it failed on.

Duplicate `source_ids` are collapsed and blank entries dropped, so you do not need to de-duplicate or filter your answer key first  -  a list that still has one real id in it is scored on that id.

## Submitting more than once

Each submission is stored separately  -  a second comment about the same query does not replace the first. Send several as your understanding of a bad result develops, and file feedback from more than one user on the same query.

## Rate limit

100 submissions per minute per organization. Over that, you get `429` with a `Retry-After` header and a message naming the seconds to wait  -  it is safe to retry after waiting.

The ceiling is well above normal use; an agent reporting on every query it makes will stay comfortably under it.

## Errors

| Status | When                                                                                                                                                                                                                |
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `request_id` missing or not a UUID; no usable signal  -  `feedback` blank or absent **and** `ground_truth` absent, empty, or blank; `feedback` too long; unknown `rating`/`source`; `collection` without `database` |
| `401`  | Missing or invalid API key                                                                                                                                                                                          |
| `404`  | `database` does not exist or is not reachable by this key                                                                                                                                                           |
| `429`  | Over the rate limit  -  see `Retry-After`                                                                                                                                                                           |
| `500`  | Feedback could not be stored. Nothing was recorded; retrying is safe                                                                                                                                                |

A `500` means the submission was **not** saved, so a retry cannot create a duplicate of something already stored.


## OpenAPI

````yaml api-reference/v2/openapi.json POST /feedback
openapi: 3.1.0
info:
  contact:
    email: support@hydradb.com
    name: HydraDB Support
  description: >-
    HydraDB Application API — knowledge ingestion, search, and memory
    management.
  license:
    name: Proprietary
  title: HydraDB Application API
  version: 0.1.0
servers:
  - description: Production server
    url: https://api.hydradb.com
security: []
externalDocs:
  description: ''
  url: ''
paths:
  /feedback:
    post:
      tags:
        - feedback
      summary: Submit feedback for a query
      description: >-
        Record feedback about a query that already ran, correlated by the
        `request_id` returned in that query's `response.meta.request_id`.
        Accepts a free-text comment plus an optional positive/negative/neutral
        rating, and is intended for both end users and agents (`source`). Feeds
        internal retrieval-quality validation; it does not change the result of
        the original query.
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/feedback.SubmitRequest'
        description: Feedback submission
        required: true
      responses:
        '201':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/handler.Envelope-feedback_SubmitResponse'
          description: Created
        '400':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/handler.ErrorResponse'
          description: Bad Request
        '404':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/handler.ErrorResponse'
          description: Not Found
        '500':
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/handler.ErrorResponse'
          description: Internal Server Error
      security:
        - BearerAuth: []
components:
  schemas:
    feedback.SubmitRequest:
      anyOf:
        - properties:
            feedback:
              minLength: 1
              pattern: \S
          required:
            - feedback
        - properties:
            ground_truth:
              anyOf:
                - properties:
                    answer:
                      minLength: 1
                      pattern: \S
                  required:
                    - answer
                - properties:
                    source_ids:
                      contains:
                        minLength: 1
                        pattern: \S
                  required:
                    - source_ids
          required:
            - ground_truth
      dependentSchemas:
        collection:
          anyOf:
            - required:
                - database
            - required:
                - tenant_id
        sub_tenant_id:
          anyOf:
            - required:
                - database
            - required:
                - tenant_id
      properties:
        collection:
          description: >-
            Optional collection scope for this feedback. A collection is scoped
            to a database, so `database` must be sent alongside it; sending
            `collection` on its own is rejected. If you also send the deprecated
            `sub_tenant_id`, the two must carry the same value — they name one
            thing, and conflicting values are rejected rather than one silently
            winning.
          example: team_docs
          minLength: 1
          type: string
        database:
          description: >-
            Optional database scope for this feedback. If you also send the
            deprecated `tenant_id`, the two must carry the same value — they
            name one thing, and conflicting values are rejected rather than one
            silently winning.
          example: acme_corp
          minLength: 1
          type: string
        feedback:
          description: >-
            Free-text comment describing what was right or wrong about the
            results. Required unless `ground_truth` is supplied.
          maxLength: 8000
          type: string
        ground_truth:
          $ref: '#/components/schemas/feedback.GroundTruth'
          description: >-
            What you already know the right answer to be, when you know it.
            Supply an expected `answer`, the `source_ids` that contain it, or
            both — at least one is required if the field is present.
            Machine-checkable, so it is a stronger signal than a comment: submit
            it alone and `feedback` becomes optional.
          example:
            source_ids:
              - HydraDoc1234
              - HydraDoc4567
        metadata:
          additionalProperties:
            maxLength: 512
            type: string
          description: >-
            Free-form key-value context stored alongside the feedback (e.g.
            agent name, conversation or eval-run ID).
          example:
            agent: support-bot
            conversation: c-8891
          maxProperties: 20
          propertyNames:
            maxLength: 64
          type: object
        rating:
          $ref: '#/components/schemas/feedback.Rating'
          description: >-
            Optional overall judgement: `positive`, `negative`, or `neutral`.
            Omit to send a comment with no rating.
        request_id:
          description: >-
            The `request_id` from `response.meta` of the query this feedback is
            about. Required — it is what links the feedback to the query that
            ran.
          example: 9d13aef4-02f4-4e73-8c62-4c2601d04f9d
          format: uuid
          type: string
        source:
          $ref: '#/components/schemas/feedback.Source'
          description: 'Who is submitting: `user` (default) or `agent`.'
        sub_tenant_id:
          deprecated: true
          description: >-
            Deprecated — use `collection`. Still accepted, and may be sent
            alongside `collection` during a migration only if both carry the
            same value; conflicting values are rejected with 400.
          example: sub_tenant_4567
          minLength: 1
          type: string
          x-deprecated: 'true'
          x-deprecated-since: 2.0.1
        tenant_id:
          deprecated: true
          description: >-
            Deprecated — use `database`. Still accepted, and may be sent
            alongside `database` during a migration only if both carry the same
            value; conflicting values are rejected with 400.
          example: tenant_1234
          minLength: 1
          type: string
          x-deprecated: 'true'
          x-deprecated-since: 2.0.1
      required:
        - request_id
      type: object
    handler.Envelope-feedback_SubmitResponse:
      properties:
        data:
          $ref: '#/components/schemas/feedback.SubmitResponse'
          example:
            created_at: '2026-07-02T10:00:00Z'
            message: Success
            recorded: true
            request_id: 9d13aef4-02f4-4e73-8c62-4c2601d04f9d
        error:
          $ref: '#/components/schemas/handler.apiError'
          description: Error message, empty string on success.
          example:
            code: DATABASE_NOT_FOUND
            message: Database not found
        meta:
          $ref: '#/components/schemas/handler.responseMeta'
          example:
            collection: team_docs
            database: acme_corp
            latency_ms: 12.3
            request_id: 9d13aef4-02f4-4e73-8c62-4c2601d04f9d
            source_type: file
            sub_tenant_id: sub_tenant_4567
            tenant_id: tenant_1234
        success:
          description: Whether the request succeeded.
          example: true
          type: boolean
      type: object
    handler.ErrorResponse:
      properties:
        data: {}
        detail:
          $ref: '#/components/schemas/handler.ErrorDetail'
          description: Structured error detail with code, message, and deprecation hints.
          example:
            deprecated: true
            deprecated_field: tenant_id
            error_code: VALIDATION_ERROR
            message: Request validation failed
            preferred_field: database
            success: true
        error:
          $ref: '#/components/schemas/handler.apiError'
          description: Error message, empty string on success.
          example:
            code: DATABASE_NOT_FOUND
            message: Database not found
        meta:
          $ref: '#/components/schemas/handler.ErrorMeta'
          example:
            latency_ms: 12.3
            request_id: 9d13aef4-02f4-4e73-8c62-4c2601d04f9d
        success:
          description: Whether the request succeeded.
          example: true
          type: boolean
      type: object
    feedback.GroundTruth:
      properties:
        answer:
          description: >-
            Answer is the response the caller expected — the text a correct
            system

            would have produced from the retrieved context.
          maxLength: 8000
          type: string
        source_ids:
          description: >-
            SourceIDs are the ingested source IDs that actually contain the
            answer,

            as returned in query results and accepted by /context endpoints.
          example:
            - HydraDoc1234
            - HydraDoc4567
          items:
            maxLength: 256
            type: string
          maxItems: 100
          type: array
          uniqueItems: false
      type: object
    feedback.Rating:
      enum:
        - positive
        - negative
        - neutral
      type: string
      x-enum-varnames:
        - RatingPositive
        - RatingNegative
        - RatingNeutral
    feedback.Source:
      enum:
        - user
        - agent
      type: string
      x-enum-varnames:
        - SourceUser
        - SourceAgent
    feedback.SubmitResponse:
      properties:
        created_at:
          description: RFC3339 timestamp when the feedback was recorded.
          example: '2026-07-02T10:00:00Z'
          type: string
        feedback_id:
          description: Unique identifier assigned to this feedback submission.
          type: string
        message:
          description: Human-readable result message.
          example: Success
          type: string
        recorded:
          description: Whether the feedback was durably stored.
          example: true
          type: boolean
        request_id:
          description: The query request ID this feedback was recorded against.
          example: 9d13aef4-02f4-4e73-8c62-4c2601d04f9d
          type: string
      type: object
    handler.apiError:
      properties:
        code:
          description: Machine-readable error code (e.g. `DATABASE_NOT_FOUND`).
          example: DATABASE_NOT_FOUND
          type: string
        message:
          description: Human-readable description of the error.
          example: Database not found
          type: string
      type: object
    handler.responseMeta:
      properties:
        api_version:
          description: >-
            APIVersion echoes the version of the API that served the request
            (PRO-1209),

            sourced from reqmeta.APIVersion — the same value carried by OpenAPI

            info.version and /health — so a client always knows which API
            version

            produced a response. Always present (no omitempty).
          type: string
        collection:
          description: >-
            Collection scope. Defaults to the default collection when omitted.
            Formerly `sub_tenant_id`; the `sub_tenant_id` alias is still
            accepted (deprecated).
          example: team_docs
          type: string
        database:
          description: >-
            Owning database. Formerly `tenant_id`; the `tenant_id` alias is
            still accepted (deprecated).
          example: acme_corp
          type: string
        deprecation:
          description: >-
            Deprecation lists any migration nudges that apply to this request —
            the

            caller used a legacy /tenants route, a legacy
            tenant_id/sub_tenant_id field,

            or the deprecated sub_tenant_ids selector. It is a non-breaking
            signal (the

            status code is unchanged); omitempty keeps it absent for
            fully-migrated

            requests. A list so independent deprecations coexist without
            clobbering.
          items:
            $ref: '#/components/schemas/handler.deprecationNotice'
          type: array
          uniqueItems: false
        latency_ms:
          description: Server-side processing time in milliseconds.
          example: 12.3
          type: number
        request_id:
          description: Unique identifier for this request, useful for support and tracing.
          example: 9d13aef4-02f4-4e73-8c62-4c2601d04f9d
          type: string
        source_type:
          description: Type of the parent source (e.g. `file`, `slack`, `notion`).
          example: file
          type: string
        sub_tenant_id:
          deprecated: true
          example: sub_tenant_4567
          type: string
          x-deprecated: 'true'
        tenant_id:
          deprecated: true
          example: tenant_1234
          type: string
          x-deprecated: 'true'
      type: object
    handler.ErrorDetail:
      properties:
        deprecated:
          description: Whether this response concerns a deprecated field or route.
          example: true
          type: boolean
        deprecated_field:
          description: The deprecated field name.
          example: tenant_id
          type: string
        error_code:
          description: Machine-readable error classification code.
          example: VALIDATION_ERROR
          type: string
        message:
          description: Human-readable description of the error.
          example: Request validation failed
          type: string
        preferred_field:
          description: The canonical replacement for the deprecated field.
          example: database
          type: string
        success:
          description: Always false for error responses.
          example: true
          type: boolean
      type: object
    handler.ErrorMeta:
      properties:
        api_version:
          type: string
        latency_ms:
          example: 12.3
          type: number
        request_id:
          description: Unique identifier for this request, useful for support and tracing.
          example: 9d13aef4-02f4-4e73-8c62-4c2601d04f9d
          type: string
      type: object
    handler.deprecationNotice:
      properties:
        deprecated:
          description: Whether this response concerns a deprecated field or route.
          example: true
          type: boolean
        deprecated_field:
          description: The deprecated field name.
          example: tenant_id
          type: string
        deprecated_since:
          description: API version when the field was deprecated.
          example: 2.0.1
          type: string
        message:
          description: Migration guidance message.
          example: tenant_id is deprecated; use database instead.
          type: string
        preferred_field:
          description: The canonical replacement for the deprecated field.
          example: database
          type: string
      type: object
  securitySchemes:
    BearerAuth:
      bearerFormat: API key
      description: 'API key sent as a Bearer token: "Bearer prefix.secret"'
      scheme: bearer
      type: http

````