Integration docs

view as markdown

Integrate your agent framework with taleseal

taleseal is an OTLP/HTTP sink. If your agent framework emits OpenTelemetry GenAI spans — Vercel AI SDK 7, Mastra, Pydantic AI and OpenLLMetry all do — you point the exporter at https://taleseal.com/v1/traces with an API key, run your agent, and about two minutes after the run goes quiet a draft tale appears in your dashboard: title, beats, tool receipts, token counts and the span timeline, composed deterministically from the trace.

Both OTLP/HTTP encodings are accepted (application/x-protobuf and application/json), gzip request bodies work, and /v1/metrics + /v1/logs are accept-and-drop stubs, so an auto-instrumented process pointing all three signals at us never sees 404s. gRPC is not supported.

Pick your framework

Each guide has pinned, tested versions, a copy-paste config, a test run that needs no model API key, and a verification step you can check from the terminal.

Any other framework that exports OTLP/HTTP GenAI spans works the same way: exporter → https://taleseal.com/v1/traces, x-api-key header, then verify as below.

Drafts by default — why your run does not get a public URL

Telemetry is ambient: once content capture is on (it is on by default in every framework above), spans carry your full prompts, tool arguments and tool results, unredacted. Nobody chose to publish any particular run — so OTLP-derived tales are drafts: visible only to you, in your dashboard, with no public URL. Publishing a draft is the same deliberate act it has always been, done from the dashboard. Retraction works too: the trace id is the tale's runId, so DELETE /v1/tales?runId=<trace id hex> covers the panic path.

Authentication

Use your tk_… API key (dashboard → API keys; sign up at taleseal.com/signup). Set it once as an environment variable — it is the only value you need to change in any snippet:

Shell
export TALESEAL_API_KEY=tk_your_key_here

Two header forms are accepted on the wire:

x-api-key: tk_…
Authorization: Bearer tk_…

Prefer x-api-key in OTEL_EXPORTER_OTLP_HEADERS: that variable is comma-and-equals delimited, and some SDKs mangle values containing spaces — Bearer tk_… has one, x-api-key=tk_… does not.

The generic endpoint variable gets /v1/traces appended by the SDK; the traces-specific one is used verbatim. Set one, never both:

Shell
OTEL_EXPORTER_OTLP_ENDPOINT=https://taleseal.com
# or
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=https://taleseal.com/v1/traces
OTEL_EXPORTER_OTLP_HEADERS=x-api-key=$TALESEAL_API_KEY

Verify an integration

After a test run, ask taleseal what it received:

Shell
curl -s https://taleseal.com/v1/otlp/status -H "Authorization: Bearer $TALESEAL_API_KEY"

The response lists your recent runs, newest first:

JSON
{
  "runs": [
    {
      "traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
      "firstSeen": "2026-07-13T14:02:01Z",
      "lastSeen": "2026-07-13T14:02:05Z",
      "spans": 6,
      "inputTokens": 40,
      "outputTokens": 20,
      "errored": false,
      "state": "collecting",
      "title": null,
      "draftUrl": null
    }
  ],
  "generatedAt": "2026-07-13T14:02:11Z"
}

Success = a run with your spans counted and errored: false, within seconds of the flush. state: "collecting" means spans are being received and counted — the integration works; you do not need to wait for anything else. After the run has been idle for about two minutes (plus up to a minute of sweep interval), state becomes "finalised", title is set from the composed draft, and draftUrl points at the draft in your dashboard. "published" means you have since published that draft — deliberately, from the dashboard.

What lands in the draft

  • Title and outcome — from the root span and the run's error latch.
  • Beats — one per agent/workflow and tool span; tool spans carry a receipt with the call's arguments and result, verbatim, when the framework captured content.
  • Metrics — duration, model, token sums counted from inference spans only (agent-span aggregates are deliberately ignored: they duplicate their children).
  • A token chart — output tokens per model call, when the run made three or more calls with token data. Deterministic: every point is read straight off an inference span.
  • Proof layer — every span, as the tale page's timeline.

This is a faithful skeleton, not a narrative — the deterministic composer never invents prose or infers what a number means. To get a richer tale (narrative beat titles, stat-tile and chart widgets, a summary written for a reader), the agent composes it with the SDK during the run — see below. taleseal does not run a model over your run on its own; the narration is yours to author, where the data and the intent already are.

A run finalises when no new span has arrived for the idle gap (default 120 s). Late spans re-open an unpublished draft and it recomposes on the next sweep; published tales are sealed and never change.

Composing a tale yourself — POST /v1/tales

If you skip OTLP and publish a composed tale directly (the @taleseal/sdk openTale() builder or a raw POST /v1/tales), you are the narrator. The schema will accept a lazy tale; readers will not. Rules that make the page worth opening:

  • Prose narrates; payloads are evidence. Never paste raw JSON into prose or summary. Put the payload in the beat's evidence array (kind: "command", the lookup as command, the payload as output) — it renders as a collapsible receipt.
  • Titles are narrative, not tool ids. Checked their Stripe history, not stripe_history.
  • summary renders as text with light formatting*bold*, ` code , bare URLs and <url|label> links, /-` bullets. Do not paste a message formatted for another destination and stop there; write the verdict for a reader on this page.
  • Numbers want widgets. Counts, money and time series belong in widgets (stat_tiles, table, line_chart) on the beat, not in a sentence of digits.
  • Timestamps make it a story. Set trigger.at, per-beat at and outcome.at from the run, not from publish time.
  • Anyone holding the URL can read it. Strip test identifiers and customer PII before publishing; there is no edit, only retraction.

One beat, done well:

TypeScript
draft
  .beat({
    at: "2026-07-13T15:02:41Z",
    title: "Checked their Stripe history",
    prose: "First-ever subscription: one active plan at £104.97/month, one paid invoice.",
    evidence: [
      {
        kind: "command",
        command: "stripe_history(cus_UsVwC0…)",
        output: JSON.stringify(history, null, 2),
      },
    ],
    widgets: [
      {
        kind: "stat_tiles",
        tiles: [
          { label: "Plan", value: "£104.97/mo" },
          { label: "Paid invoices", value: "1" },
          { label: "Customer since", value: "today", tone: "good" },
        ],
      },
    ],
  })

Machine-readable resources


Integration overview · llms.txt · panic path: DELETE /v1/tales?runId=<trace id hex>