Este documento descreve uma hipotese de contrato para permitir providers
genericos na integracao ai_usage.
Ele nao substitui os contratos atuais de codex e ollama_cloud. A ideia aqui
e responder: se a integracao aceitasse um provider desconhecido previamente,
quais dados o source teria que enviar para que o Home Assistant conseguisse
criar devices e sensores de forma consistente?
Um provider generico nao deve ser um JSON livre.
Mesmo no modo generico, a integracao precisa receber dados em formato padronizado para criar entidades estaveis no Home Assistant. A flexibilidade deve estar na lista de entidades enviadas pelo source, nao na interpretacao de campos arbitrarios pela integracao.
Recomendacao importante:
Nao usar provider = "generic" como identificador do provider real.
O campo top-level provider deve continuar representando o provider real, em
formato estavel e seguro para automacoes:
"provider": "anthropic_console"O modo generico deve ser indicado dentro de provider_data:
"provider_data": {
"contract": "generic_provider.v1"
}Isso evita criar todos os providers genericos em um unico device chamado
generic e mantem unique_id, devices e dashboards organizados por provider
real.
Usar provider generico quando:
- O provider ainda nao tem parser especifico na integracao.
- O source ja consegue normalizar os dados para sensores do Home Assistant.
- O conjunto de sensores pode variar por provider ou por conta.
- O objetivo e aceitar novos providers sem alterar o codigo da integracao.
Nao usar provider generico quando:
- A integracao precisa interpretar regras especificas do provider.
- O payload bruto precisa ser parseado dentro do Home Assistant.
- A semantica dos campos nao e estavel.
- O source nao consegue fornecer identificadores estaveis.
O source deve:
- Enviar o identificador estavel do provider em
provider. - Enviar o nome exibivel do provider em
provider_data.provider.name. - Enviar metadados do provider, como fabricante, URL de configuracao e imagem.
- Enviar um identificador estavel da conta em
account_data.stable_id. - Normalizar todos os sensores em uma lista padronizada.
- Garantir que cada entidade tenha
keyestavel. - Converter timestamps para ISO 8601 UTC.
- Nao enviar cookies, tokens, HTML bruto, API keys ou segredos.
- Nao depender de email, username ou nome visual para
key,provideroustable_id.
A integracao deve:
- Validar o envelope base.
- Validar que
provider_data.contractegeneric_provider.v1. - Validar
provider,account_data.stable_ide as chaves de entidades. - Criar ou atualizar o device da conta dinamicamente.
- Criar ou atualizar entidades comuns da conta.
- Criar ou atualizar entidades genericas declaradas em
provider_data.entities. - Ignorar entidades invalidas sem derrubar o webhook inteiro, quando possivel.
- Registrar erro de contrato quando o payload nao puder ser usado.
O envelope base continua parecido com docs/payload-contract.md.
Exemplo minimo:
{
"schema_version": "1.0",
"source": "browser_extension",
"source_version": "0.1.0",
"collected_at": "2026-06-02T15:40:00.000Z",
"provider": "anthropic_console",
"status": "ok",
"account_data": {
"stable_id": "anthropic-user-123",
"email": "user@example.com"
},
"plan_data": {
"type": "pro"
},
"provider_data": {
"contract": "generic_provider.v1",
"provider": {
"name": "Anthropic Console"
},
"entities": []
},
"error": null
}Identificador estavel do provider real.
Regras:
- Deve ser enviado pelo source.
- Deve usar
snake_case. - Deve conter apenas letras minusculas, numeros e underscore.
- Deve representar o provider ou produto real, nao o modo generico.
- Nao deve mudar quando o nome comercial mudar.
Exemplos validos:
anthropic_console
openrouter
google_ai_studio
mistral_platform
perplexity
Exemplos invalidos:
generic
Anthropic Console
anthropic-console
user@example.com
https://console.anthropic.com
Identifica o contrato generico.
Valor atual:
generic_provider.v1
Metadados exibiveis do provider.
Formato:
{
"name": "Anthropic Console",
"manufacturer": "Anthropic",
"model": "AI usage account",
"configuration_url": "https://console.anthropic.com/",
"image_url": "https://example.com/anthropic.png",
"icon": "mdi:brain"
}Campos:
| Campo | Obrigatorio | Uso |
|---|---|---|
name |
sim | Nome exibivel do provider no device. |
manufacturer |
nao | DeviceInfo.manufacturer. Se ausente, usar name. |
model |
nao | DeviceInfo.model. Se ausente, usar Generic AI account. |
configuration_url |
nao | Link de configuracao do provider. |
image_url |
nao | Imagem externa ou endpoint local para entity_picture. |
icon |
nao | Icone padrao para sensores sem icone especifico. |
Observacao: image_url so deve apontar para recurso publico e seguro. O source
nao deve enviar imagem base64 no payload.
Identificador estavel da conta no provider.
Para provider generico, este campo deve ser obrigatorio. Sem ele, a integracao
nao tem uma base confiavel para criar unique_id e DeviceInfo.identifiers.
Formato sugerido:
{
"stable_id": "anthropic-user-123",
"account_id": "acct_123",
"user_id": "user_123",
"display_name": "Personal",
"username": "alves-dev",
"email": "user@example.com"
}Regras:
stable_idpode ser um ID do provider.- Se o provider nao fornecer ID, o source pode gerar um ID local estavel.
- Nao usar email ou username como
stable_iddiretamente. - Email e username podem ser atributos de exibicao.
O bloco generico principal e provider_data.entities.
{
"provider_data": {
"contract": "generic_provider.v1",
"provider": {
"name": "Anthropic Console"
},
"entities": []
}
}Cada item em entities descreve uma entidade que a integracao deve criar ou
atualizar.
Plataformas aceitas nesta hipotese:
sensor
binary_sensor
Outras plataformas, como image, number, switch ou button, devem ficar
fora da primeira versao do provider generico.
Formato:
{
"platform": "sensor",
"key": "daily_usage_percent",
"name": "Daily usage used",
"native_value": 42.5,
"native_unit_of_measurement": "%",
"device_class": null,
"state_class": "measurement",
"entity_category": null,
"icon": "mdi:gauge",
"suggested_display_precision": 0,
"attributes": {
"window": "daily"
}
}Campos:
| Campo | Obrigatorio | Uso |
|---|---|---|
platform |
sim | Deve ser sensor. |
key |
sim | Chave estavel da entidade. |
name |
sim | Nome da entidade com has_entity_name = True. |
native_value |
sim | Estado nativo do sensor. Pode ser null. |
native_unit_of_measurement |
nao | Unidade nativa, como %, s, requests, USD. |
device_class |
nao | Classe HA quando aplicavel, como timestamp, duration, enum. |
state_class |
nao | measurement, total ou total_increasing. |
entity_category |
nao | diagnostic quando a entidade for tecnica/debug. |
icon |
nao | Icone MDI quando nao houver device_class. |
suggested_display_precision |
nao | Precisao sugerida para valores numericos. |
attributes |
nao | Atributos extras pequenos e estaveis. |
Regras para key:
^[a-z0-9_]{1,64}$
Chaves reservadas que o source nao deve usar em entidades genericas:
account
plan
status
problem
last_error
collected_at
last_received_at
source
request_count
Essas chaves pertencem as entidades comuns da conta.
Formato:
{
"platform": "binary_sensor",
"key": "limit_reached",
"name": "Limit reached",
"is_on": false,
"device_class": "problem",
"entity_category": null,
"icon": null,
"attributes": {
"reason": null
}
}Campos:
| Campo | Obrigatorio | Uso |
|---|---|---|
platform |
sim | Deve ser binary_sensor. |
key |
sim | Chave estavel da entidade. |
name |
sim | Nome da entidade com has_entity_name = True. |
is_on |
sim | Booleano. |
device_class |
nao | Classe HA, como problem ou connectivity. |
entity_category |
nao | diagnostic quando for tecnico/debug. |
icon |
nao | Icone MDI quando nao houver device_class. |
attributes |
nao | Atributos extras pequenos e estaveis. |
Usar para percentuais, contadores, custos, creditos e duracoes.
Exemplo:
{
"platform": "sensor",
"key": "monthly_spend_usd",
"name": "Monthly spend",
"native_value": 12.34,
"native_unit_of_measurement": "USD",
"state_class": "measurement",
"icon": "mdi:cash"
}Regras:
native_valuenumerico.- Unidade
%. - Valor recomendado de
0a100. state_classnormalmentemeasurement.
Exemplo:
{
"platform": "sensor",
"key": "daily_usage_percent",
"name": "Daily usage used",
"native_value": 42.5,
"native_unit_of_measurement": "%",
"state_class": "measurement",
"suggested_display_precision": 1,
"attributes": {
"window": "daily"
}
}Regras:
native_valuedeve ser ISO 8601 UTC.device_classdeve sertimestamp.
Exemplo:
{
"platform": "sensor",
"key": "daily_reset_at",
"name": "Daily reset at",
"native_value": "2026-06-03T00:00:00.000Z",
"device_class": "timestamp",
"attributes": {
"window": "daily"
}
}Regras:
native_valuenumerico.- Unidade recomendada:
s. device_classdeve serdurationquando suportado.
Exemplo:
{
"platform": "sensor",
"key": "daily_reset_after",
"name": "Daily reset after",
"native_value": 3600,
"native_unit_of_measurement": "s",
"device_class": "duration",
"attributes": {
"reset_at": "2026-06-03T00:00:00.000Z"
}
}Usar para estados textuais pequenos e estaveis.
Exemplo:
{
"platform": "sensor",
"key": "active_model",
"name": "Active model",
"native_value": "claude-opus-4",
"icon": "mdi:brain"
}Usar quando o conjunto de valores for conhecido.
Exemplo:
{
"platform": "sensor",
"key": "quota_state",
"name": "Quota state",
"native_value": "available",
"device_class": "enum",
"options": [
"available",
"limited",
"exhausted",
"unknown"
],
"icon": "mdi:list-status"
}Para cada provider + account_data.stable_id, a integracao cria um device de
conta.
Entrada:
{
"provider": "anthropic_console",
"account_data": {
"stable_id": "anthropic-user-123",
"email": "user@example.com"
},
"provider_data": {
"provider": {
"name": "Anthropic Console",
"manufacturer": "Anthropic",
"model": "Console account",
"configuration_url": "https://console.anthropic.com/"
}
}
}DeviceInfo resultante:
{
"identifiers": [["ai_usage", "<config_entry_id>:anthropic_console:acct_43aeb78c10cc0172"]],
"entry_type": "service",
"manufacturer": "Anthropic",
"model": "Console account",
"name": "AI Usage Anthropic Console user@example.com",
"via_device": ["ai_usage", "<config_entry_id>"],
"configuration_url": "https://console.anthropic.com/"
}Regras de nome:
device_name = "AI Usage " + provider_data.provider.name + " " + account_label
account_label deve seguir esta ordem:
account_data.display_name
account_data.email
account_data.username
account_data.account_id
account_data.user_id
account_data.stable_id
account_key
O provider e o stable_id devem ser usados apenas como entrada para gerar uma
chave opaca.
Formato recomendado:
account_key = "acct_" + sha256("<provider>:stable_id:<stable_id>")[0:16]
device_key = "<config_entry_id>:<provider>:<account_key>"
common_entity_unique_id = "<device_key>:<common_key>"
generic_entity_unique_id = "<device_key>:generic:<entity.key>"
Exemplo:
provider = "anthropic_console"
stable_id = "anthropic-user-123"
account_key = "acct_43aeb78c10cc0172"
unique_id daily_usage_percent = "<config_entry_id>:anthropic_console:acct_43aeb78c10cc0172:generic:daily_usage_percent"
Mesmo no provider generico, a integracao deve criar os sensores comuns definidos no contrato de devices e sensores.
Exemplo de sensor.account:
platform: sensor
unique_id: "<device_key>:account"
device: "AI Usage Anthropic Console user@example.com"
has_entity_name: true
name: "Account"
native_value: "user@example.com"
entity_picture: "https://example.com/anthropic.png"
attributes:
provider: anthropic_console
provider_name: "Anthropic Console"
account_key: "acct_43aeb78c10cc0172"
account_key_quality: stable
stable_id: "anthropic-user-123"
email: "user@example.com"
plan_type: proExemplo de sensor.plan:
platform: sensor
unique_id: "<device_key>:plan"
device: "AI Usage Anthropic Console user@example.com"
has_entity_name: true
name: "Plan"
device_class: enum
native_value: pro
attributes:
provider: anthropic_consoleExemplo de sensor.status:
platform: sensor
unique_id: "<device_key>:status"
device: "AI Usage Anthropic Console user@example.com"
has_entity_name: true
name: "Status"
device_class: enum
native_value: ok
attributes:
provider: anthropic_console
generic_contract: "generic_provider.v1"Exemplo de binary_sensor.problem:
platform: binary_sensor
unique_id: "<device_key>:problem"
device: "AI Usage Anthropic Console user@example.com"
has_entity_name: true
name: "Problem"
device_class: problem
is_on: false
attributes:
status: ok
error_code: nullA integracao deve criar uma entidade para cada item valido em
provider_data.entities.
Payload:
{
"platform": "sensor",
"key": "daily_usage_percent",
"name": "Daily usage used",
"native_value": 42.5,
"native_unit_of_measurement": "%",
"state_class": "measurement",
"suggested_display_precision": 1,
"attributes": {
"window": "daily"
}
}Entidade criada:
platform: sensor
unique_id: "<device_key>:generic:daily_usage_percent"
device: "AI Usage Anthropic Console user@example.com"
has_entity_name: true
name: "Daily usage used"
native_value: 42.5
native_unit_of_measurement: "%"
state_class: measurement
suggested_display_precision: 0
attributes:
window: dailyPayload:
{
"platform": "sensor",
"key": "daily_reset_at",
"name": "Daily reset at",
"native_value": "2026-06-03T00:00:00.000Z",
"device_class": "timestamp",
"attributes": {
"window": "daily"
}
}Entidade criada:
platform: sensor
unique_id: "<device_key>:generic:daily_reset_at"
device: "AI Usage Anthropic Console user@example.com"
has_entity_name: true
name: "Daily reset at"
device_class: timestamp
native_value: "2026-06-03T00:00:00+00:00"
attributes:
window: dailyPayload:
{
"platform": "sensor",
"key": "quota_state",
"name": "Quota state",
"native_value": "available",
"device_class": "enum",
"options": [
"available",
"limited",
"exhausted",
"unknown"
]
}Entidade criada:
platform: sensor
unique_id: "<device_key>:generic:quota_state"
device: "AI Usage Anthropic Console user@example.com"
has_entity_name: true
name: "Quota state"
device_class: enum
options:
- available
- limited
- exhausted
- unknown
native_value: availablePayload:
{
"platform": "binary_sensor",
"key": "limit_reached",
"name": "Limit reached",
"is_on": false,
"device_class": "problem",
"attributes": {
"reason": null
}
}Entidade criada:
platform: binary_sensor
unique_id: "<device_key>:generic:limit_reached"
device: "AI Usage Anthropic Console user@example.com"
has_entity_name: true
name: "Limit reached"
device_class: problem
is_on: false
attributes:
reason: null{
"schema_version": "1.0",
"source": "browser_extension",
"source_version": "0.1.0",
"collected_at": "2026-06-02T15:40:00.000Z",
"provider": "anthropic_console",
"status": "ok",
"account_data": {
"stable_id": "anthropic-user-123",
"account_id": "acct_123",
"display_name": "Personal",
"email": "user@example.com"
},
"plan_data": {
"type": "pro",
"display_name": "Pro"
},
"provider_data": {
"contract": "generic_provider.v1",
"provider": {
"name": "Anthropic Console",
"manufacturer": "Anthropic",
"model": "Console account",
"configuration_url": "https://console.anthropic.com/",
"image_url": "https://example.com/anthropic.png",
"icon": "mdi:brain"
},
"entities": [
{
"platform": "sensor",
"key": "daily_usage_percent",
"name": "Daily usage used",
"native_value": 42.5,
"native_unit_of_measurement": "%",
"state_class": "measurement",
"suggested_display_precision": 1,
"attributes": {
"window": "daily"
}
},
{
"platform": "sensor",
"key": "daily_reset_at",
"name": "Daily reset at",
"native_value": "2026-06-03T00:00:00.000Z",
"device_class": "timestamp",
"attributes": {
"window": "daily"
}
},
{
"platform": "sensor",
"key": "quota_state",
"name": "Quota state",
"native_value": "available",
"device_class": "enum",
"options": [
"available",
"limited",
"exhausted",
"unknown"
]
},
{
"platform": "binary_sensor",
"key": "limit_reached",
"name": "Limit reached",
"is_on": false,
"device_class": "problem",
"attributes": {
"reason": null
}
}
]
},
"error": null
}Quando o source consegue identificar provider e conta, o erro pertence ao device da conta.
{
"schema_version": "1.0",
"source": "browser_extension",
"source_version": "0.1.0",
"collected_at": "2026-06-02T15:40:00.000Z",
"provider": "anthropic_console",
"status": "not_authenticated",
"account_data": {
"stable_id": "anthropic-user-123",
"email": "user@example.com"
},
"plan_data": {},
"provider_data": {
"contract": "generic_provider.v1",
"provider": {
"name": "Anthropic Console"
},
"entities": []
},
"error": {
"code": "not_authenticated",
"message": "User is not logged in"
}
}Entidades atualizadas:
device: "AI Usage Anthropic Console user@example.com"
entity: "sensor.status"
native_value: not_authenticateddevice: "AI Usage Anthropic Console user@example.com"
entity: "binary_sensor.problem"
is_on: true
attributes:
error_code: not_authenticated
error_message: "User is not logged in"device: "AI Usage Anthropic Console user@example.com"
entity: "sensor.last_error"
native_value: not_authenticated
attributes:
message: "User is not logged in"A integracao deve rejeitar o payload inteiro quando:
providerestiver ausente, vazio ou invalido.provider_data.contractnao forgeneric_provider.v1.provider_data.provider.nameestiver ausente.account_data.stable_idestiver ausente.provider_data.entitiesnao for uma lista.
A integracao pode aceitar o payload e ignorar apenas uma entidade quando:
entity.keyfor invalido.entity.keycolidir com chave reservada.entity.platformnao for suportado.native_valuetiver tipo incompativel comdevice_class.optionsde enum nao contivernative_value.attributesnao for objeto.
Quando uma entidade for ignorada, o device da integracao deve registrar um erro diagnostico, por exemplo:
entity: "sensor.last_ingest_status"
native_value: invalid_contract
attributes:
ignored_entities:
- key: "bad key"
reason: "invalid_key"Atributos devem ser pequenos, estaveis e uteis para automacoes ou dashboards.
Permitido:
{
"window": "daily",
"limit": 100,
"reset_at": "2026-06-03T00:00:00.000Z"
}Evitar:
{
"raw_payload": {},
"html": "<html>...</html>",
"token": "secret",
"events": []
}Regra pratica: se um atributo muda sempre e e importante, ele provavelmente deve ser uma entidade propria.
Provider especifico:
provider = codex
provider_data.rate_limit.primary_window.used_percent
Vantagem:
- A integracao conhece a semantica.
- Pode criar sensores nomeados e otimizados.
- Pode validar regras especificas do provider.
Provider generico:
provider = anthropic_console
provider_data.contract = generic_provider.v1
provider_data.entities[0].key = daily_usage_percent
Vantagem:
- A integracao nao precisa conhecer o provider antes.
- O source controla quais sensores fazem sentido.
- Novos providers podem ser adicionados sem mudar codigo.
Custo:
- O source precisa fazer mais trabalho.
- A integracao confia menos na semantica.
- Erros de nomenclatura no source podem gerar sensores ruins ou inconsistentes.
Se essa hipotese virar contrato real, a recomendacao e:
- Manter
providercomo identificador real do provider. - Usar
provider_data.contract = "generic_provider.v1"para ativar o modo generico. - Tornar
account_data.stable_idobrigatorio para provider generico. - Aceitar apenas
sensorebinary_sensorna primeira versao. - Usar
provider_data.entitiescomo lista declarativa de entidades. - Criar
unique_idopaco com hash deprovider + stable_id. - Tratar provider generico como caminho de extensibilidade, nao como substituto dos providers especificos quando a integracao precisar conhecer a semantica.
- A integracao deve permitir
image_urlexterna ou servir apenas imagens locais cadastradas pelo provider? - Entidades genericas devem ser removidas quando somem do payload, ou apenas
ficar
unknown? - O source pode mudar
namede uma entidade sem quebrar dashboards? - Deve existir uma allowlist de
device_class,state_classe unidades? - O contrato deve aceitar uma entidade desabilitada por padrao com payload bruto sanitizado para debug?