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

# Contracts

> The wire shapes ShoulderTap clients agree on — ContextRequest, ContextProposal, and ConsumerRegistration — with every field.

Three JSON shapes define the ShoulderTap protocol: `ContextRequest` (a consumer asking for context), `ContextProposal` (an expert's answer), and `ConsumerRegistration` (how a consumer signs up). The authoritative JSON Schemas live in `spec/schemas/*.json` in the repository, generated directly from the reference implementation and checked for drift.

## ContextRequest

A consumer's request for context. Four fields are required: `kind`, `topic`, `question`, `consumer`.

<ResponseField name="id" type="string">
  ULID, prefix `req_`. Engine-assigned if omitted.
</ResponseField>

<ResponseField name="org_id" type="string" default="default">
  Fixed to `"default"` in the single-tenant community edition.
</ResponseField>

<ResponseField name="kind" type="string" required>
  The question kind; determines the answer's capture schema and which consumers it routes back to. See [Kinds](/api/kinds).
</ResponseField>

<ResponseField name="topic" type="string" required>
  The routing key. Normalized to lowercase for matching against expert topics.
</ResponseField>

<ResponseField name="question" type="string" required>
  The raw question.
</ResponseField>

<ResponseField name="context" type="object">
  Free-form, shown to the expert. `context.asked_because` becomes the "why I'm asking" line in the ask.
</ResponseField>

<ResponseField name="target_experts" type="array | null" default="null">
  `[{ expert_id, role }]`. Bypasses topic routing entirely and asks these experts directly. `role` defaults to `"primary"`.
</ResponseField>

<ResponseField name="routing_policy" type="object">
  Per-request overrides for the config defaults.

  <Expandable title="fields">
    <ResponseField name="primary_experts" type="array" />

    <ResponseField name="escalation_targets" type="array" />

    <ResponseField name="escalation_after" type="string | null" default="null">
      ISO-8601 duration, for example `"PT2H"`. Falls back to `defaults.escalation_after`.
    </ResponseField>

    <ResponseField name="give_up_after" type="string | null" default="null">
      ISO-8601 duration. Falls back to `defaults.give_up_after`.
    </ResponseField>

    <ResponseField name="priority" type="integer" default="50" />
  </Expandable>
</ResponseField>

<ResponseField name="consumer" type="string" required>
  The submitting consumer's id.
</ResponseField>

<ResponseField name="dedup_key" type="string | null" default="null">
  Requests with the same key from the same consumer, within the dedup window, are deduplicated. See [Concepts](/concepts#deduplication).
</ResponseField>

<ResponseField name="correlation" type="object | null" default="null">
  Free-form passthrough, for example `{ "trace_id": "..." }`.
</ResponseField>

<ResponseField name="created_at" type="string">
  ISO-8601 date-time.
</ResponseField>

### Example

```json theme={null}
{
  "kind": "glossary.definition",
  "topic": "revenue metrics",
  "question": "What does 'active customer' mean for Q2 reporting?",
  "consumer": "bi.assistant",
  "context": {
    "asked_because": "BI agent hit low confidence answering a user query",
    "entity": "dim_customers.active_flag"
  },
  "dedup_key": "glossary:dim_customers.active_flag"
}
```

## ContextProposal

An expert's answer, structured and attributed. Required: `request_id`, `kind`, `answer`, `provenance`, `consumer`.

<ResponseField name="id" type="string">
  ULID, prefix `prop_`. Engine-assigned.
</ResponseField>

<ResponseField name="request_id" type="string" required>
  The request this answers.
</ResponseField>

<ResponseField name="kind" type="string" required>
  Mirrors the request's kind.
</ResponseField>

<ResponseField name="answer" type="string" required>
  The expert's verbatim reply. Never dropped, even if structuring fails.
</ResponseField>

<ResponseField name="structured" type="object | null" default="null">
  The LLM-structured form of the answer, matching the kind's schema. `null` on extraction failure or when confidence is below 0.3.
</ResponseField>

<ResponseField name="confidence" type="number | null" default="null">
  The structurer's self-assessed extraction confidence, independent of whether `structured` ended up null.
</ResponseField>

<ResponseField name="provenance" type="object" required>
  Who answered and how.

  <Expandable title="fields">
    <ResponseField name="expert_id" type="string" required />

    <ResponseField name="expert_name" type="string" required />

    <ResponseField name="answered_via" type="string" required>
      The transport that carried the answer, for example `slack`.
    </ResponseField>

    <ResponseField name="answered_at" type="string" required>
      ISO-8601 date-time.
    </ResponseField>

    <ResponseField name="slack_thread_ts" type="string | null" default="null" />

    <ResponseField name="escalated" type="boolean" default="false" />
  </Expandable>
</ResponseField>

<ResponseField name="consumer" type="string" required />

<ResponseField name="created_at" type="string">
  ISO-8601 date-time.
</ResponseField>

## ConsumerRegistration

How a consumer registers to receive answers. Required: `id`, `handles_kinds`, `delivery`.

<ResponseField name="id" type="string" required>
  The consumer's id, for example `bi.assistant`.
</ResponseField>

<ResponseField name="handles_kinds" type="array" required>
  The kinds this consumer deals with.
</ResponseField>

<ResponseField name="delivery" type="object" required>
  A discriminated union on `type`: `{ "type": "webhook", "url": "..." }` or `{ "type": "inprocess" }` for an SDK-embedded consumer.
</ResponseField>

<ResponseField name="dedup_window" type="string" default="P1D">
  ISO-8601 duration for the deduplication window (default one day).
</ResponseField>

<ResponseField name="auto_accept" type="boolean" default="false">
  Deliver proposals without human review. Off by default — see the warning in [Consumers](/consumers/overview#auto-accept).
</ResponseField>

<ResponseField name="kind_schemas" type="object">
  Optional map of kind name to a custom JSON Schema, overriding the built-in for that kind. See [Kinds](/api/kinds).
</ResponseField>
