# Output Metadata

> OutputMeta enables usage-based pricing by reporting what your app processes and generates.

**URL:** https://inference.sh/docs/extend/output-meta
**Last updated:** 2026-10-12

---

OutputMeta enables usage-based pricing by reporting what your app processes and generates.

---

## Basic Structure

<div data-type="codegroup">

```python Python
from inferencesh import BaseAppOutput, OutputMeta, TextMeta, ImageMeta, VideoMeta, AudioMeta

class AppOutput(BaseAppOutput):
    result: File = Field(description="Generated output")
    # output_meta is inherited from BaseAppOutput
```

```javascript Node.js
async run(inputData) {
  const result = await this.generate(inputData);
  return {
    result: result.text,
    output_meta: {
      inputs: [{ type: "text", tokens: result.promptTokens }],
      outputs: [{ type: "text", tokens: result.completionTokens }],
    },
  };
}
```

</div>

---

## MetaItem Types

| Type | Class | Key Fields |
|------|-------|-----------|
| Text | `TextMeta` | `tokens`, `cached_tokens`, `cache_write_tokens` |
| Image | `ImageMeta` | `width`, `height`, `resolution_mp`, `steps`, `count` |
| Video | `VideoMeta` | `width`, `height`, `resolution`, `resolution_mp`, `seconds`, `fps` |
| Audio | `AudioMeta` | `seconds` |

---

## Examples

### LLM/Text Generation

Fill in `LLMUsage` from the provider's response and build the token report from it, so the usage your app shows and the tokens it bills are the same numbers:

```python
from inferencesh import OutputMeta
from inferencesh.models.llm import LLMUsage

usage = LLMUsage(
    prompt_tokens=response.usage.prompt_tokens,          # every input token, cached ones included
    cached_tokens=details.cached_tokens,                 # read from the prompt cache
    cache_write_tokens=details.cache_write_tokens,       # written to the prompt cache
    completion_tokens=response.usage.completion_tokens,
)

return AppOutput(
    response=generated_text,
    usage=usage,
    output_meta=OutputMeta.from_llm_usage(usage),
)
```

`prompt_tokens` counts every input token, the way OpenAI, xAI and OpenRouter report it. `cached_tokens` and `cache_write_tokens` are parts of it, so a pricing formula can bill them at the provider's cache rates (see [pricing](/extend/pricing)). Anthropic reports cache reads and writes outside `input_tokens`: add them back into `prompt_tokens`.

For streaming responses, take the counts from the final chunk that carries usage.

### Image Generation

```python
return AppOutput(
    image=File(path=output_path),
    output_meta=OutputMeta(
        outputs=[ImageMeta(
            width=1024,
            height=1024,
            resolution_mp=1.05,
            steps=20,
            count=1
        )]
    )
)
```

### Video Generation

Prefer probing the output file instead of hardcoding dimensions and duration:

```python
from inferencesh import VideoMeta, OutputMeta

return AppOutput(
    video=File(path=output_path),
    output_meta=OutputMeta(
        outputs=[
            VideoMeta.from_file(
                output_path,
                resolution="720p",
                resolution_mp=0.92,
            )
        ]
    )
)
```

`VideoMeta.from_file()` and `probe_video()` ship in **inferencesh ≥ v0.7.10**. They call `ffprobe` (from the `ffmpeg` apt package — add `ffmpeg` to [packages.txt](/docs/extend/configuration#system-packages-packagestxt)).

### Audio Generation

```python
return AppOutput(
    audio=File(path=output_path),
    output_meta=OutputMeta(
        outputs=[AudioMeta(seconds=30.0)]
    )
)
```

---

## Probing video files (Python)

When your app writes a video to disk, use the SDK helpers to populate `VideoMeta` from the actual file:

```python
from inferencesh import probe_video, VideoMeta

# Low-level: returns a dict (empty on failure)
info = probe_video("/path/to/output.mp4")
# keys: width, height, fps, nb_frames, seconds

# High-level: builds VideoMeta with probed fields
meta = VideoMeta.from_file(
    "/path/to/output.mp4",
    resolution="1080p",          # optional kwargs pass through
    extra={"model": "kling"},
)
```

**Duration calculation:** `seconds` is `nb_frames / fps` (frame-accurate), not container metadata. This matches how upstream video APIs (e.g. BytePlus) bill on frame count.

**Failure behavior:** If `ffprobe` is missing, times out (10s), or cannot read a video stream, `probe_video()` returns `{}` and `VideoMeta.from_file()` falls back to zero values for probed fields. Pass explicit `resolution` or other kwargs when you need non-zero metadata on probe failure.

Both helpers are exported from `inferencesh` and `inferencesh.models.output_meta`.

---

## Custom Data

Use `extra` for app-specific pricing factors:

```python
output_meta=OutputMeta(
    outputs=[ImageMeta(
        width=1024,
        height=1024,
        extra={
            "model": "sdxl-turbo",
            "lora_count": 2
        }
    )]
)
```

---

## Public API visibility

`output_meta` is **pricing metadata** for the platform. App authors should always set it on task output, but it is **not returned** on public surfaces:

| Surface | What you see |
|---------|----------------|
| Task `output` (REST, WebSocket, SDK) | `output_meta` is extracted at write time; only your app's user-facing fields remain in `output` |
| App `output_schema` (version DTO, app store, Grid) | `output_meta` is stripped from schemas at deploy and on read |

Your app code still returns `output_meta` in `AppOutput` / run results — the backend stores it for usage-based billing and [CEL pricing formulas](/docs/extend/pricing). Do not rely on clients reading `output_meta` from completed tasks or published schemas.

---

## Best Practices

1. **Always populate `output_meta`** if usage varies per request
2. **Use accurate token counts** from the actual tokenizer
3. **Report actual dimensions** — for video, use `VideoMeta.from_file()` instead of hardcoding width, height, fps, or seconds
4. **Include relevant `extra` data** for pricing flexibility

---

## Next

→ [Usage-based pricing](/docs/extend/pricing) — CEL formulas that read `output_meta`  
→ [Secrets](/docs/extend/secrets) — API keys and sensitive values
