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

# Tags

> Label your API calls with key/value tags, such as client=acme or project=invoices, to split your usage and cost per customer, project or environment.

export const TechArticleSchema = ({title, description, path, articleSection, about, proficiencyLevel = "Beginner", dependencies, keywords = [], datePublished, dateModified, image, inLanguage = "en"}) => {
  const baseUrl = "https://www.edenai.co/docs";
  const canonicalUrl = `${baseUrl}/${path}`.replace(/\/+$/, "");
  const ogParams = new URLSearchParams({
    division: articleSection || "",
    title: title || "",
    description: description || ""
  });
  const resolvedImage = image || `https://edenai.mintlify.app/_mintlify/api/og?${ogParams.toString()}`;
  const data = {
    "@context": "https://schema.org",
    "@type": "TechArticle",
    "@id": `${canonicalUrl}#techarticle`,
    mainEntityOfPage: {
      "@type": "WebPage",
      "@id": canonicalUrl
    },
    headline: title,
    name: title,
    description: description,
    url: canonicalUrl,
    inLanguage: inLanguage,
    isPartOf: {
      "@type": "WebSite",
      name: "Eden AI Documentation",
      url: baseUrl
    },
    author: [{
      "@type": "Organization",
      name: "Eden AI",
      url: "https://www.edenai.co/"
    }],
    publisher: {
      "@type": "Organization",
      name: "Eden AI",
      url: "https://www.edenai.co/",
      logo: {
        "@type": "ImageObject",
        url: "https://www.edenai.co/assets/logo.png"
      }
    }
  };
  if (articleSection) data.articleSection = articleSection;
  if (about) data.about = {
    "@type": "Thing",
    name: about
  };
  if (proficiencyLevel) data.proficiencyLevel = proficiencyLevel;
  if (dependencies) data.dependencies = dependencies;
  if (keywords && keywords.length) data.keywords = keywords;
  if (datePublished) data.datePublished = datePublished;
  if (dateModified) data.dateModified = dateModified;
  data.image = Array.isArray(resolvedImage) ? resolvedImage : [resolvedImage];
  const json = JSON.stringify(data);
  const schemaId = `techarticle-${canonicalUrl}`;
  React.useEffect(() => {
    if (typeof document === "undefined") return;
    document.querySelectorAll(`script[data-schema-id="${schemaId}"]`).forEach(n => n.remove());
    const script = document.createElement("script");
    script.type = "application/ld+json";
    script.dataset.schemaId = schemaId;
    script.textContent = json;
    document.head.appendChild(script);
    return () => script.remove();
  }, [json, schemaId]);
  return null;
};

<TechArticleSchema title={"Tags"} description={"Label your API calls with key/value tags, such as client=acme or project=invoices, to split your usage and cost per customer, project or environment."} path="v3/general/tags" articleSection="General" about={"API Configuration"} proficiencyLevel="Beginner" keywords={["Eden AI", "AI API", "tags", "cost attribution", "usage", "monitoring", "billing"]} datePublished="2026-09-24T00:00:00Z" dateModified="2026-09-25T00:00:00Z" />

Tags are key/value labels you put on a call, such as `client=acme` or `project=invoices`. Eden AI stores them with the request, so you can split your usage and cost per customer, project or environment in the dashboard, without creating one API key for each.

There is nothing to set up: add tags to a call and they appear in your usage within a minute.

## Adding tags to a call

### With the `X-EdenAI-Tags` header

The header works on every v3 endpoint that runs a model, including the ones that take a file (transcriptions, image edits, [video generation](/docs/v3/llms/video-generation)) and collections. Write `key=value` pairs separated by commas:

```bash cURL theme={null}
curl -X POST https://api.edenai.run/v3/chat/completions \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-EdenAI-Tags: client=acme,project=invoices" \
  -d '{
    "model": "openai/gpt-latest",
    "messages": [{"role": "user", "content": "Hello"}]
  }'
```

Spaces around a pair are ignored. If the header is sent more than once, for example because a proxy adds its own, all of its pairs are kept.

### With a `tags` field in the body

On JSON endpoints, send a `tags` object instead: chat completions, Responses, Messages, embeddings, image generation, moderations, decisions, text to speech and Universal AI.

```python Python theme={null}
import requests

url = "https://api.edenai.run/v3/chat/completions"
headers = {
    "Authorization": "Bearer YOUR_API_KEY",
    "Content-Type": "application/json",
}
payload = {
    "model": "openai/gpt-latest",
    "messages": [{"role": "user", "content": "Hello"}],
    "tags": {"client": "acme", "project": "invoices"},
}

response = requests.post(url, headers=headers, json=payload)
print(response.json()["choices"][0]["message"]["content"])
```

Endpoints that take a file upload (transcriptions, image edits) read the header only. Video generation does too, and refuses a `tags` field in its body.

A `tags` field that is not an object, such as the list some SDKs send, is ignored rather than refused.

### As default tags on an API key

Tags set on an API key are added to every call made with it. A key that serves one customer or one environment then needs nothing in its requests.

Set them when you create or edit a key: in the dashboard, under **Default Tags** on the [API Keys](https://app.edenai.run/settings/api-keys) page, or with the key's [`default_tags`](/docs/v3/general/custom-api-keys#default-tags) field in the Management API. They follow the rules below, with at most 10 per key, and apply from the next call.

### Combining sources

When a call gets tags from more than one place, they are merged by key and the call's own tags win: the header over the key's defaults, the body over both.

| API key | Header | Body | Recorded |
| - | - | - | - |
| `client=acme,env=dev` | `env=prod` | `{"project": "invoices"}` | `client=acme`, `env=prod`, `project=invoices` |
| `client=acme` | | `{"client": "globex"}` | `client=globex` |
| | `client=acme,env=prod` | `{"client": "globex"}` | `client=globex`, `env=prod` |

## Rules

| | Rule |
| - | - |
| Number | At most 10 tags per call, counted after the merge. A key default that the call overrides counts once |
| Keys | 1 to 64 characters, stored in lowercase: `Client` is recorded as `client` |
| Values | 1 to 128 characters, kept as sent: `Acme` and `acme` are two different values |
| Characters | Keys: letters `A-Z` and `a-z`, digits, and `-` `_` `.` `:` `/`. Values: the same, plus `@`, `+` and single spaces between words, so `ops+eu@acme.com` and `Claude Code` are valid. No commas, `=`, accented letters, or spaces at the start or end of a value |
| Types | Values are strings. In the body, send `"42"`, not `42` |
| Reserved | Keys starting with `eden:` |
| Duplicates | A key can appear once in each source. Keys are compared in lowercase, so `Client=a,client=b` in one header is a duplicate |

<Warning>
  Tags are kept with your usage data and are visible to everyone in your organization with access to Monitoring. Avoid personal data such as emails or names; to split spend per person, use a pseudonymous id from your own system. Every distinct value is a separate line in your usage breakdown, so use a small set of stable values, such as a customer, project, environment or feature name, and do not tag per request or per session.
</Warning>

## Examples by client

<CodeGroup>
  ```python OpenAI SDK theme={null}
  from openai import OpenAI

  client = OpenAI(
      api_key="YOUR_API_KEY",
      base_url="https://api.edenai.run/v3",
      # Sent on every call made with this client
      default_headers={"X-EdenAI-Tags": "client=acme,env=prod"},
  )

  response = client.chat.completions.create(
      model="openai/gpt-latest",
      messages=[{"role": "user", "content": "Hello"}],
      # Added to this call only; wins over the header on the same key
      extra_body={"tags": {"project": "invoices"}},
  )
  print(response.choices[0].message.content)
  ```

  ```python Anthropic SDK theme={null}
  from anthropic import Anthropic

  client = Anthropic(
      api_key="YOUR_API_KEY",
      base_url="https://api.edenai.run/v3",
      default_headers={"X-EdenAI-Tags": "client=acme,env=prod"},
  )

  message = client.messages.create(
      model="anthropic/claude-haiku-latest",
      max_tokens=256,
      messages=[{"role": "user", "content": "Hello"}],
      extra_body={"tags": {"project": "invoices"}},
  )
  print(message.content[0].text)
  ```

  ```python LiteLLM theme={null}
  import os
  from litellm import completion

  os.environ["EDENAI_API_KEY"] = "YOUR_API_KEY"

  response = completion(
      model="edenai/openai/gpt-mini-latest",
      messages=[{"role": "user", "content": "Hello"}],
      extra_headers={"X-EdenAI-Tags": "client=acme,project=invoices"},
  )
  print(response.choices[0].message.content)
  ```

  ```bash Claude Code theme={null}
  # Add to the variables from the Claude Code setup page
  export ANTHROPIC_CUSTOM_HEADERS="X-EdenAI-Tags: client=acme,project=invoices"
  ```

  ```json OpenCode theme={null}
  {
    "$schema": "https://opencode.ai/config.json",
    "provider": {
      "edenai": {
        "options": {
          "baseURL": "https://api.edenai.run/v3",
          "headers": {
            "X-EdenAI-Tags": "client=acme,project=invoices"
          }
        }
      }
    }
  }
  ```
</CodeGroup>

For [Claude Code](/docs/v3/integrations/claude-code) and [OpenCode](/docs/v3/integrations/opencode), start from their setup pages; the lines above only add the tags. LiteLLM's `edenai/` provider needs LiteLLM 1.104 or later.

## Reading the tags back

Add the `x-edenai-metadata: enabled` header and the response lists the tags recorded for the call in `edenai_metadata.tags`, after the merge and with keys in lowercase:

```json theme={null}
{
  "edenai_metadata": {
    "...": "...",
    "tags": {
      "client": "acme",
      "project": "invoices"
    }
  }
}
```

An untagged call returns `"tags": {}`. See [Request Metadata](/docs/v3/llms/request-metadata) for the rest of the block and the endpoints that return it.

## Invalid tags

A call with an invalid tag is refused with HTTP `400` before any provider is called, so it is not billed. The message names the tag and the rule it breaks, and the error uses the endpoint's own format:

<CodeGroup>
  ```json OpenAI-compatible endpoints theme={null}
  {
    "error": {
      "message": "tag client=ac;me: values allow only letters, digits, - _ . : / @ + and single spaces between words",
      "type": "invalid_request_error",
      "param": null,
      "code": "invalid_parameter"
    }
  }
  ```

  ```json Messages theme={null}
  {
    "type": "error",
    "error": {
      "type": "invalid_request_error",
      "message": "tag client=ac;me: values allow only letters, digits, - _ . : / @ + and single spaces between words"
    }
  }
  ```

  ```json Universal AI theme={null}
  {
    "detail": {
      "error": "Invalid tags",
      "message": "tag client=ac;me: values allow only letters, digits, - _ . : / @ + and single spaces between words"
    }
  }
  ```
</CodeGroup>

Other messages you may see:

| Problem | Message |
| - | - |
| More than 10 tags | `at most 10 tags per call, got 11`, ending with `(3 set on the API key)` when some come from the key |
| A key with other characters | `tag user@x=v: keys allow only letters, digits and - _ . : /` |
| A header segment without `=` | `tag 'client' in the header is not key=value` |
| The same key twice | `tag 'client' appears twice in the header` |
| A value that is not a string | `tag 'build': keys and values must be strings` |
| A value that is too long | `tag 'client': value must be 1 to 128 characters` |
| An `eden:` key | `tag 'eden:source': the 'eden:' prefix is reserved` |

The refused call still appears in your request log with status `400`.

## Seeing your usage by tag

In the [dashboard](https://app.edenai.run/), open **Monitoring**:

* **Filters**, then **Tags**: pick a key, then one or more values, to include or exclude them. Active filters show as badges above the charts.
* **Explore**, **Group by**, then **Tag**: choose a key to split cost and calls by its values. Calls without that key are grouped as **Untagged**.
* **Requests**: each request shows its tags. In a request's details, click a tag to filter the list by it.
* **Export CSV** on the Requests tab: every request in the selected period and filters, with one column per tag key. An export holds up to 100,000 requests and 50 tag columns; tags beyond those are grouped in an `other_tags` column. You can export 20 times per hour.

Tags apply from the call that carries them: calls made before you started tagging show as untagged.

## Good to know

* Tags are never sent to the model provider.
* `/v3/upload` only stores a file for later calls and is not part of your usage, so tags sent to it are ignored. Tag the call that uses the file.
* Two calls that differ only by their tags return the same [cached](/docs/v3/general/caching) response.
* The OpenAI `metadata` and `user` fields are not tags, and tagging does not change how they are handled.
* `/v2` endpoints ignore tags, including API key default tags.

## Next Steps

<CardGroup cols={2}>
  <Card title="Request Metadata" icon="circle-info" href="/docs/v3/llms/request-metadata">
    See the tags and routing recorded for a call
  </Card>

  <Card title="Monitoring" icon="chart-line" href="/docs/v3/general/monitoring">
    Pull account-level usage into your own tools
  </Card>

  <Card title="Custom API Keys" icon="key" href="/docs/v3/general/custom-api-keys">
    Separate keys with their own spending limits
  </Card>

  <Card title="Caching" icon="database" href="/docs/v3/general/caching">
    How repeated calls are served
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.