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

# Prompt attribution

> Stamp prompt identifiers on runtime LLM calls so Moda links conversations to the prompt versions that produced them.

Syncing gives every prompt an immutable version; attribution tells Moda which version served each production call. You attribute calls by stamping prompt identifiers on the data you already send — as event fields on the HTTP Ingestion API, as span attributes over OpenTelemetry, or as telemetry metadata with the Vercel AI SDK.

<Note>
  There is no SDK call that renders a prompt and attributes it automatically. Auto-instrumented OpenAI and Anthropic calls made through the Moda SDKs currently carry no prompt attribution — the SDKs expose no option to stamp these fields on auto-generated spans. Use one of the mechanisms on this page.
</Note>

## Where the identifiers come from

`moda prompts sync` writes `.moda/prompts.lock.json` with the registry IDs for every prompt:

```json .moda/prompts.lock.json theme={"dark"}
{
  "version": 1,
  "generatedAt": "2026-08-16T09:12:44.310Z",
  "prompts": {
    "support.triage": {
      "key": "support.triage",
      "sourcePath": "prompts/support/triage.prompt.md",
      "contentHash": "8c1f27ab…",
      "promptId": "prompt_7d1a9c0b3e5f24681357ace9",
      "versionId": "pver_0f31c2ab9d4e8877a1b2c3d4e5f60718"
    }
  }
}
```

Read the lockfile at startup (or bake the values in at build time) and stamp `promptId` and `versionId` on the calls that use that prompt.

## Identifier fields

| Purpose                         | `/v1/ingest` event field     | OTLP span attribute      | Vercel AI SDK metadata key |
| ------------------------------- | ---------------------------- | ------------------------ | -------------------------- |
| Prompt key                      | `prompt_name` (send the key) | `moda.prompt_key`        | `moda.prompt_key`          |
| Registry prompt ID (`prompt_…`) | `prompt_id`                  | `moda.prompt_id`         | `moda.prompt_id`           |
| Display name                    | `prompt_name`                | `moda.prompt_name`       | `moda.prompt_name`         |
| Version string (human-readable) | `prompt_version`             | `moda.prompt_version`    | `moda.prompt_version`      |
| Registry version ID (`pver_…`)  | `prompt_version_id`          | `moda.prompt_version_id` | `moda.prompt_version_id`   |

How much you send determines what you get:

* **Linking to a prompt** requires `prompt_id` equal to the registry prompt ID, or `prompt_name` equal to the prompt key. On OTLP paths, `moda.prompt_key` alone is enough — the ID and name default to the key.
* **Per-version usage** requires `prompt_version_id`. Without it, calls link to the prompt but not to a specific version.
* `prompt_version` is a free-form display string (for example a semver or git SHA of your choosing); it defaults to the version ID when omitted.

Stamp the fields on the assistant events or LLM spans produced by the prompt.

## HTTP Ingestion API

Set the `prompt_*` fields directly on `/v1/ingest` events:

```bash theme={"dark"}
curl -X POST https://moda-ingest.modas.workers.dev/v1/ingest \
  -H "Authorization: Bearer YOUR_MODA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "events": [
      {
        "conversation_id": "conv_support_1042",
        "role": "user",
        "message": "My invoice is wrong",
        "timestamp": "2026-08-16T09:30:00Z"
      },
      {
        "conversation_id": "conv_support_1042",
        "role": "assistant",
        "message": "I can help with that. Let me pull up the invoice.",
        "model": "gpt-4o",
        "provider": "openai",
        "input_tokens": 412,
        "output_tokens": 58,
        "timestamp": "2026-08-16T09:30:02Z",
        "prompt_name": "support.triage",
        "prompt_id": "prompt_7d1a9c0b3e5f24681357ace9",
        "prompt_version_id": "pver_0f31c2ab9d4e8877a1b2c3d4e5f60718"
      }
    ]
  }'
```

```json Response theme={"dark"}
{ "success": true, "count": 2, "requestId": "d4f7c2a1-9b3e-4c58-a1f2-3e8b90c47d15" }
```

See [HTTP API](/ingestion/http-api) for the full event schema, batch limits, and error envelope.

## OpenTelemetry span attributes

If you export OTLP traces to Moda from your own OpenTelemetry setup, set the `moda.prompt_*` attributes on each LLM span (see [OpenTelemetry](/ingestion/opentelemetry) for exporter setup). Moda also reads `gen_ai.prompt.name` as a fallback for the prompt name.

With the Moda SDK for Node.js, manual capture via `withLLMCall` exposes the underlying OTel span, so you can stamp the attributes there. Use this for calls the SDK does not auto-instrument:

```typescript app.ts theme={"dark"}
import { Moda, type Message } from 'moda-ai';
import { readFileSync } from 'node:fs';

await Moda.init(process.env.MODA_API_KEY!);

const lock = JSON.parse(readFileSync('.moda/prompts.lock.json', 'utf8'));
const triage = lock.prompts['support.triage'];

const messages: Message[] = [
  { role: 'system', content: 'Route urgent account issues before answering.' },
  { role: 'user', content: 'My invoice is wrong.' },
];

Moda.conversationId = 'conv_support_1042';

const data = await Moda.withLLMCall({ vendor: 'openai', type: 'chat' }, async ({ span }) => {
  span.rawSpan.setAttribute('moda.prompt_key', 'support.triage');
  span.rawSpan.setAttribute('moda.prompt_id', triage.promptId);
  span.rawSpan.setAttribute('moda.prompt_version_id', triage.versionId);
  span.reportRequest({ model: 'gpt-4o', messages });

  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ model: 'gpt-4o', messages }),
  });
  const result = await response.json();

  span.reportResponse({
    model: result.model,
    usage: result.usage,
    completions: result.choices,
  });
  return result;
});

console.log(data.choices[0]?.message?.content);
await Moda.flush();
```

`withLLMCall` is Node.js only. The Moda SDK for Python has no manual-capture equivalent; from Python, send attributed events through the [HTTP API](/ingestion/http-api) or set the attributes in your own OpenTelemetry instrumentation.

## Vercel AI SDK

Pass the identifiers in the `metadata` of `Moda.getVercelAITelemetry`. The AI SDK forwards metadata keys as span attributes that Moda reads:

```typescript app.ts theme={"dark"}
import { Moda } from 'moda-ai';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { readFileSync } from 'node:fs';

await Moda.init(process.env.MODA_API_KEY!);

const lock = JSON.parse(readFileSync('.moda/prompts.lock.json', 'utf8'));
const triage = lock.prompts['support.triage'];

const userMessage = 'My invoice is wrong.';

Moda.conversationId = 'conv_support_1042';

const result = await generateText({
  model: openai('gpt-4o'),
  system: 'Route urgent account issues before answering.',
  prompt: userMessage,
  experimental_telemetry: Moda.getVercelAITelemetry({
    metadata: {
      'moda.prompt_key': 'support.triage',
      'moda.prompt_id': triage.promptId,
      'moda.prompt_version_id': triage.versionId,
    },
  }),
});

console.log(result.text);
await Moda.flush();
```

<Warning>
  `Moda.getVercelAITelemetry()` snapshots the conversation and user context at the moment it is called. Set `Moda.conversationId` (or wrap the call in `withConversationId`) before creating the telemetry config, or the call is grouped without your conversation ID.
</Warning>

See [Vercel AI SDK](/ingestion/vercel-ai-sdk) for the full integration, including streaming and tool calls.

## What attribution unlocks

* **Runtime usage per version.** The prompt detail page (Dashboard → Prompts → your prompt, Versions tab) shows calls, conversations, and input/output tokens grouped by version. Rows appear within minutes of ingestion.
* **Version-aware analysis.** Attributed conversations let you compare how different versions behave in production and give [prompt experiments](/prompt-management/experiments) a baseline tied to real versions.

Calls without `prompt_version_id` still count toward the prompt's overall usage as long as they carry the prompt key or ID.

## Next steps

* [Prompt workflow](/prompt-management/workflow) — where the lockfile and version IDs come from.
* [Prompt experiments](/prompt-management/experiments) — replay-based comparison of prompt versions.
* [HTTP API](/ingestion/http-api) — full `/v1/ingest` event schema and limits.
* [OpenTelemetry](/ingestion/opentelemetry) — exporting OTLP traces to Moda from your own setup.
