Skip to main content

Resolve Bindings

POST /v1/binding-resolutions answers which registry fields supply your target fields, on what evidence — a cost ladder with an opt-in LLM synthesis rung.

POST /v1/binding-resolutions answers one question over the wire: "which registry fields supply these target fields, and on what evidence?" For each target field you name, the resolver returns ranked binding plans — deterministic recipes that map one or more [field-registry](registry-query) entries onto the target, each graded empirically against your corpus. It exists so an external agent (typically via MCP) can inspect a binding before anything is written with it.

Resolution climbs a cost ladder and stops at the first rung that satisfies the request: L0 returns memoized plans already stored for the target; L1 retrieves candidates by name similarity against the registry; L2 mines the corpus, testing hypothesized plans against documents where both the plan's sources and an authored label are present; L3 asks an LLM to synthesize a plan from the target's name, type, and description. L3 costs orders of magnitude more than L0–L2, so it is gated behind allow_llm and off by default; where the deployment has no LLM configured the resolver simply stops after L2, so passing the flag is always safe.

The request body carries targets[] — 1 to 100 entries of { field_key, data_type?, description? }. field_key (≤200 chars) is the schema field you want supplied; data_type (≤200 chars) and description (≤2000 chars) are the disambiguating context the L3 rung reads, and they matter: a bare name resemblance is exactly how a policy-number field once got bound to a contract id. min_trust names the empirical grade that ends the climb — inferred (default), mined, or verified — so a caller that only wants corpus-proven plans sets mined and lets the ladder keep climbing past cheap guesses.

The route takes `read` scope on a POST, deliberately — the same shape as [POST /v1/registry/query](registry-query). The handler mutates none of your data: nothing here activates, retires, or verifies a plan, no document row is read, and no document id or cell value crosses the wire. The one intended side effect is memoization: every plan the resolver proposes is written back as an inert candidate, so your workspace pays for a ladder rung at most once per target-and-plan — repeat calls answer from L0. A candidate plan does nothing until it is promoted through the platform's own verification flow.

Like other value-bearing surfaces, the endpoint requires a single-tenant credential: a key operating in the cross-tenant master view is rejected 400 ("Select one tenant for binding resolution") before the resolver runs — the memoization write-back must never land in an ambiguous tenant. The successful status is 200, not 201: you are asking a question, not creating a durable resource you manage.

Abstention contract: a target nothing could bind comes back with an empty bindings[] array — an abstention, never an omission. resolutions[] always has exactly one entry per requested target, in request order, so you can zip the response against your targets by index.
POST/v1/binding-resolutions

Body fields

targets*array1–100 target fields to resolve. Each entry: { field_key, data_type?, description? }.
targets[].field_key*stringThe schema field to supply (max 200 chars).
targets[].data_typestringOptional expected type (max 200 chars) — disambiguating context for retrieval and the L3 rung.
targets[].descriptionstringOptional prose description (max 2000 chars). The strongest guard against plausible-but-wrong name matches.
allow_llmbooleanPermit the L3 (LLM synthesis) rung for targets nothing cheaper satisfied. Ignored gracefully where no LLM is configured — the ladder stops after L2. Default: false
min_truststringTrust grade that ends the climb: inferred, mined, or verified. Default: inferred

curl

curl -s -X POST https://api.talonic.com/v1/binding-resolutions \
  -H "Authorization: Bearer tlnc_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "targets": [
      {
        "field_key": "invoice_number",
        "data_type": "string",
        "description": "The vendor invoice number as printed on the document header"
      },
      { "field_key": "total_gross_amount", "data_type": "number" }
    ],
    "min_trust": "mined"
  }'

Response (200)

{
  "resolutions": [
    {
      "field_key": "invoice_number",
      "bindings": [
        {
          "plan_id": "4a6c8e0b-2d4f-4a7c-9e1b-3f5d7a9c1e3b",
          "trust": "mined",
          "status": "candidate",
          "ladder": "L2",
          "evidence": {
            "n": 184,
            "agree": 179,
            "agreement": 0.9728,
            "ci95_low": 0.9391,
            "distinct_values": 172,
            "method": "mining",
            "notes": null
          },
          "sources": [
            {
              "registry_id": "9b1d3f5a-7c9e-4b2d-8a4c-6e0f2b4d6a8c",
              "role": "rechnungsnummer",
              "canonical_name": "Rechnungsnummer"
            }
          ],
          "combine": "first_of",
          "guard": {
            "doc_type_id_in": null,
            "doc_type_name_in": ["Rechnung", "Invoice"]
          }
        }
      ]
    },
    {
      "field_key": "total_gross_amount",
      "bindings": []
    }
  ]
}

Errors

Error responses

400VALIDATION_ERRORA master-view credential ("Select one tenant for binding resolution"), an empty or over-100 targets array, or a field exceeding its length cap.
401unauthorizedMissing or invalid API key.
429rate_limitedDaily quota for the read namespace exhausted; retry after the period in Retry-After.

Frequently asked questions

Why does a POST only need read scope?+
Because it is a question, not a write surface. The handler mutates no tenant data; the resolver's only side effect is memoizing the plans it proposes as inert candidates so the same question is never paid for twice. Nothing on this path activates, retires, or verifies a plan — that requires the platform's own promotion flow.
When should I set allow_llm: true?+
When a target came back with empty bindings[] and you have supplied a good description. L3 synthesizes a plan from the target's name, type, and description, and the proposal is then graded against your corpus like any other. Because it is the expensive rung, keep it off for routine lookups — memoization means you pay for it at most once per target-and-plan anyway.
Does calling this endpoint change how my pipelines behave?+
No. Memoized plans are stored as status "candidate", which is inert: pipeline transfer only executes plans your workspace has activated. The endpoint is safe to call speculatively from agents — the worst case is that your registry accumulates well-labeled candidate plans that make future resolutions cheaper.
What does an empty bindings[] array mean, and is it an error?+
It is an abstention, and it is deliberate: the resolver found no plan meeting your min_trust for that target at the rungs it was allowed to climb. The target still appears in resolutions[] in request order — a missing answer is never expressed by omission. Retry with a description and allow_llm: true, or lower min_trust to see cheaper hypotheses.
Is document data exposed through this endpoint?+
No. The response is registry- and plan-level metadata only: plan ids, trust grades, ladder rungs, evidence statistics, source registry ids with canonical names, combine ops, and doc-class guards. No document row is read and no document id or cell value crosses the wire, which is why the surface needs no per-document access filtering.