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

# Autenticação e isolamento de locatários

> Demonstre locatários segregados na API MKA1 com chaves de API separadas, cotas separadas, políticas separadas e acesso a recursos com escopo de locatário.

No MKA1, o limite prático de locatário é:

* uma **chave de API de conta** distinta
* um **ID de usuário final** delegado opcional via `X-On-Behalf-Of`
* propriedade e uso de recursos downstream registrados contra esse contexto autenticado

Use o passo a passo abaixo quando quiser verificar que dois locatários estão isolados entre si na prática.
Ele fornece um fluxo exato de Locatário A / Locatário B que você pode executar de ponta a ponta.

## Verifique o isolamento de locatários na prática

Trate cada locatário como uma conta separada com sua própria chave de API.
O objetivo é mostrar quatro coisas:

1. Locatário A e Locatário B usam chaves de API diferentes.
2. Locatário A e Locatário B podem ter configurações de cota diferentes.
3. Locatário A e Locatário B podem ter políticas diferentes.
4. Um recurso criado sob o Locatário A não é acessível sob o Locatário B.

### Passo 1: defina dois locatários

Comece com duas chaves de API separadas.

```bash theme={null}
export TENANT_A_KEY="<tenant-a-api-key>"
export TENANT_B_KEY="<tenant-b-api-key>"
export TENANT_A_USER="tenant_a_user"
export TENANT_B_USER="tenant_b_user"
```

Se você já possui duas chaves de produção ou staging, pode usá-las diretamente.
Se precisar provisionar duas chaves com limites diferentes para a demonstração, crie-as separadamente.

```curl theme={null}
curl https://apigw.mka1.com/api/v1/authentication/api-key/create/rate-limited \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Cookie: better-auth.session_token=<tenant-a-session>' \
  --data '{
    "name": "Tenant A demo key",
    "rateLimitEnabled": true,
    "rateLimitTimeWindow": 3600000,
    "rateLimitMax": 60
  }'
```

```curl theme={null}
curl https://apigw.mka1.com/api/v1/authentication/api-key/create/rate-limited \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Cookie: better-auth.session_token=<tenant-b-session>' \
  --data '{
    "name": "Tenant B demo key",
    "rateLimitEnabled": true,
    "rateLimitTimeWindow": 3600000,
    "rateLimitMax": 600
  }'
```

Isso estabelece imediatamente as duas primeiras peças do isolamento:

* Locatário A e Locatário B não compartilham uma chave.
* Locatário A e Locatário B não compartilham a mesma configuração de cota.

### Passo 2: configure políticas diferentes para cada locatário

Agora atribua políticas de guardrail diferentes para os dois locatários.

Configure o Locatário A para bloquear a palavra `confidential`.

```curl theme={null}
curl https://apigw.mka1.com/api/v1/llm/guardrails \
  --request PUT \
  --header 'Content-Type: application/json' \
  --header "Authorization: Bearer ${TENANT_A_KEY}" \
  --data '{
    "guardrails": [
      {
        "mode": "ban_words",
        "enabled": true,
        "config": {
          "words": ["confidential"]
        }
      }
    ]
  }'
```

Configure o Locatário B com uma política diferente.

```curl theme={null}
curl https://apigw.mka1.com/api/v1/llm/guardrails \
  --request PUT \
  --header 'Content-Type: application/json' \
  --header "Authorization: Bearer ${TENANT_B_KEY}" \
  --data '{
    "guardrails": [
      {
        "mode": "ban_words",
        "enabled": true,
        "config": {
          "words": ["internal-only"]
        }
      }
    ]
  }'
```

Depois, teste o mesmo conteúdo em ambos os locatários.

O Locatário A deve bloquear isto:

```curl theme={null}
curl https://apigw.mka1.com/api/v1/llm/guardrails/test \
  --request POST \
  --header 'Content-Type: application/json' \
  --header "Authorization: Bearer ${TENANT_A_KEY}" \
  --data '{
    "content": "Summarize this confidential roadmap."
  }'
```

O Locatário B deve avaliar o mesmo conteúdo contra sua própria política, em vez da política do Locatário A:

```curl theme={null}
curl https://apigw.mka1.com/api/v1/llm/guardrails/test \
  --request POST \
  --header 'Content-Type: application/json' \
  --header "Authorization: Bearer ${TENANT_B_KEY}" \
  --data '{
    "content": "Summarize this confidential roadmap."
  }'
```

O resultado esperado é:

* O Locatário A falha porque `confidential` está bloqueada na política do Locatário A.
* O Locatário B não herda a política do Locatário A.

Esta é a demonstração prática mais clara de **políticas independentes por locatário**.

### Passo 3: demonstre cotas separadas

Após criar duas chaves com limites diferentes, envie o mesmo tipo de requisição por ambas as chaves.

O Locatário A usa a chave de limite baixo:

```bash theme={null}
for i in $(seq 1 70); do
  curl https://apigw.mka1.com/api/v1/llm/responses \
    --request POST \
    --silent \
    --output /dev/null \
    --write-out "tenant-a request $i -> %{http_code}\n" \
    --header 'Content-Type: application/json' \
    --header "Authorization: Bearer ${TENANT_A_KEY}" \
    --header "X-On-Behalf-Of: ${TENANT_A_USER}" \
    --data '{
      "model": "meetkai:functionary",
      "input": "Reply with the word ok."
    }'
done
```

O Locatário B usa a chave de limite mais alto:

```bash theme={null}
for i in $(seq 1 70); do
  curl https://apigw.mka1.com/api/v1/llm/responses \
    --request POST \
    --silent \
    --output /dev/null \
    --write-out "tenant-b request $i -> %{http_code}\n" \
    --header 'Content-Type: application/json' \
    --header "Authorization: Bearer ${TENANT_B_KEY}" \
    --header "X-On-Behalf-Of: ${TENANT_B_USER}" \
    --data '{
      "model": "meetkai:functionary-pt",
      "input": "Reply with the word ok."
    }'
done
```

O resultado esperado é:

* O Locatário A começa a receber `429` mais cedo.
* O Locatário B continua tendo sucesso porque tem uma chave e uma cota diferente.

Isso mostra que os locatários não compartilham um limitador global.

### Passo 4: demonstre isolamento de recursos

Crie um recurso sob o Locatário A.
Uma conversa é um exemplo fácil porque é visível pela API pública.

```curl theme={null}
curl https://apigw.mka1.com/api/v1/llm/conversations \
  --request POST \
  --header 'Content-Type: application/json' \
  --header "Authorization: Bearer ${TENANT_A_KEY}" \
  --header "X-On-Behalf-Of: ${TENANT_A_USER}" \
  --data '{
    "metadata": {
      "tenant": "A",
      "demo": "segregated-tenants"
    }
  }'
```

Assuma que a resposta retorna `conv_tenant_a_123`.

O Locatário A pode ler:

```curl theme={null}
curl https://apigw.mka1.com/api/v1/llm/conversations/conv_tenant_a_123 \
  --header "Authorization: Bearer ${TENANT_A_KEY}" \
  --header "X-On-Behalf-Of: ${TENANT_A_USER}"
```

O Locatário B não deve conseguir ler o mesmo recurso:

```curl theme={null}
curl https://apigw.mka1.com/api/v1/llm/conversations/conv_tenant_a_123 \
  --header "Authorization: Bearer ${TENANT_B_KEY}" \
  --header "X-On-Behalf-Of: ${TENANT_B_USER}"
```

Você também pode mostrar o isolamento da listagem:

```curl theme={null}
curl 'https://apigw.mka1.com/api/v1/llm/conversations?limit=20' \
  --header "Authorization: Bearer ${TENANT_B_KEY}" \
  --header "X-On-Behalf-Of: ${TENANT_B_USER}"
```

O Locatário B não deve ver a conversa do Locatário A em sua lista.

Esta é a prova prática do enclave de locatário:

* a mesma superfície de API é usada
* a mesma plataforma é usada
* mas identidade, política, cota e propriedade de recursos são aplicadas separadamente

### Passo Opcional 5: mostre livros de uso separados

Se quiser mais um ponto de prova visível, consulte o uso separadamente por locatário.

```curl theme={null}
curl 'https://apigw.mka1.com/api/v1/llm/usage/responses?start_time=1704067200&end_time=1704153600&bucket_width=1d&group_by=api_key_id' \
  --header "Authorization: Bearer ${TENANT_A_KEY}"
```

```curl theme={null}
curl 'https://apigw.mka1.com/api/v1/llm/usage/responses?start_time=1704067200&end_time=1704153600&bucket_width=1d&group_by=api_key_id' \
  --header "Authorization: Bearer ${TENANT_B_KEY}"
```

O objetivo deste passo não é apenas faturamento.
Ele mostra que a plataforma registra a atividade contra o contexto autenticado do locatário, em vez de mesclar todo o tráfego em uma conta indiferenciada.

## O que este passo a passo comprova

Se você executar o passo a passo acima, pode fazer estas afirmações exatas:

* **Chaves independentes**: Locatário A e Locatário B autenticam com chaves de API diferentes.
* **Cotas independentes**: Locatário A e Locatário B podem ter configurações de rate-limit diferentes e receber comportamentos `429` distintos.
* **Políticas independentes**: Locatário A e Locatário B podem definir guardrails diferentes e obter resultados diferentes sobre o mesmo conteúdo.
* **Isolamento real**: Um recurso criado sob o Locatário A não é legível sob o Locatário B.

A superfície da API é compartilhada, mas o contexto autenticado do locatário, a aplicação de cotas, a configuração de políticas e a propriedade de recursos não são compartilhados.

## Como funciona o modelo de identidade por baixo

A autenticação na API MKA1 tem três camadas:

* Sua **chave de API** identifica sua conta.
* `X-On-Behalf-Of` identifica o **usuário final** para quem seu servidor está agindo.
* Um **JWT** trocado fornece a um serviço downstream uma credencial de curta duração derivada daquela chave de API e do contexto de usuário final.

A versão curta é simples:

* Envie `Authorization: Bearer <mka1-api-key>` em toda requisição do lado do servidor.
* Adicione `X-On-Behalf-Of` quando a requisição pertencer a um de seus usuários finais.
* Use `POST /api/v1/authentication/api-key/exchange-token` quando outro serviço deve receber um token de curta duração em vez da sua chave de API.

Se você só precisa do padrão de uso, comece pelo [guia de autenticação](/pt/docs/authentication).
Esta página explica o que acontece depois que a requisição chega ao gateway.

## O caminho da requisição

A API MKA1 não pede que serviços downstream validem tokens bearer por conta própria.
As requisições passam primeiro pelo gateway, e o gateway injeta cabeçalhos de identidade confiáveis para o restante da plataforma.

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Kong as API gateway
    participant Auth as mk-authentication
    participant Service as downstream API

    Client->>Kong: Authorization: Bearer <api key>
    Kong->>Auth: validateApiKey
    Auth-->>Kong: user + api key info
    Kong->>Service: X-User-ID, X-Api-Key-ID
    Service-->>Client: API response
```

Quando você também envia `X-On-Behalf-Of`, o gateway mantém essa identidade de usuário final delegada com a requisição:

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Kong as API gateway
    participant Auth as mk-authentication
    participant Service as downstream API

    Client->>Kong: Authorization + X-On-Behalf-Of
    Kong->>Auth: validateApiKey
    Auth-->>Kong: user + api key info
    Kong->>Service: X-User-ID, X-Api-Key-ID, X-Exchange-JWT-External-User-ID
    Service-->>Client: API response scoped to that end user
```

A troca de JWT adiciona um passo extra:

```mermaid theme={null}
sequenceDiagram
    participant Client
    participant Auth as auth endpoint
    participant Kong as API gateway
    participant Downstream as downstream API

    Client->>Auth: POST /api-key/exchange-token
    Auth-->>Client: short-lived JWT
    Client->>Kong: Authorization: Bearer <exchange JWT>
    Kong->>Auth: validateApiKeyExchangeJWT
    Auth-->>Kong: user + api key + external user info
    Kong->>Downstream: trusted identity headers
    Downstream-->>Client: API response
```

## Os três padrões principais

### Padrão 1: requisições apenas backend

Use este quando seu backend estiver chamando a API MKA1 para seu próprio fluxo de trabalho e não houver usuário final separado para rastrear.

```curl theme={null}
curl https://apigw.mka1.com/api/v1/llm/responses \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <mka1-api-key>' \
  --data '{
    "model": "meetkai:functionary-pt",
    "input": "Write a short release note for our API update."
  }'
```

```ts theme={null}
const response = await fetch("https://apigw.mka1.com/api/v1/llm/responses", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.MKA1_API_KEY}`,
  },
  body: JSON.stringify({
    model: "meetkai:functionary-pt",
    input: "Write a short release note for our API update.",
  }),
});
```

### Padrão 2: integração servidor multiusuário

Use este quando seu backend estiver fazendo a requisição para um dos usuários da sua aplicação.

```curl theme={null}
curl https://apigw.mka1.com/api/v1/llm/responses \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <mka1-api-key>' \
  --header 'X-On-Behalf-Of: user_123' \
  --data '{
    "model": "meetkai:functionary-pt",
    "input": "Summarize this support ticket."
  }'
```

```ts theme={null}
const response = await fetch("https://apigw.mka1.com/api/v1/llm/responses", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.MKA1_API_KEY}`,
    "X-On-Behalf-Of": "user_123",
  },
  body: JSON.stringify({
    model: "meetkai:functionary-pt",
    input: "Summarize this support ticket.",
  }),
});
```

Este é o padrão correto quando você quer que requisições, arquivos, respostas, conversas e uso fiquem associados a um ID de usuário final estável do seu próprio sistema.

### Padrão 3: troque sua chave de API por um JWT de curta duração

Use este quando outro serviço deve receber um token com tempo limitado em vez da sua chave de API de longa duração.

```curl theme={null}
curl https://apigw.mka1.com/api/v1/authentication/api-key/exchange-token \
  --request POST \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer <mka1-api-key>' \
  --data '{
    "audience": "https://my-service.example.com",
    "externalUserId": "user_123",
    "expiresIn": 3600,
    "permissions": ["agent:create", "agent:read"]
  }'
```

```ts theme={null}
const response = await fetch("https://apigw.mka1.com/api/v1/authentication/api-key/exchange-token", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    Authorization: `Bearer ${process.env.MKA1_API_KEY}`,
  },
  body: JSON.stringify({
    audience: "https://my-service.example.com",
    externalUserId: "user_123",
    expiresIn: 3600,
    permissions: ["agent:create", "agent:read"],
  }),
});

const { token } = await response.json();
```

## Quais cabeçalhos você envia vs quais a plataforma injeta

### Cabeçalhos que você envia

| Cabeçalho        | Obrigatório | Significado                                                                               |
| ---------------- | ----------- | ----------------------------------------------------------------------------------------- |
| `Authorization`  | Sim         | Sua chave de API MKA1 ou JWT trocado no formato bearer                                    |
| `X-On-Behalf-Of` | Não         | Seu identificador estável de usuário final para requisições delegadas do lado do servidor |

### Cabeçalhos confiáveis injetados dentro da plataforma

| Cabeçalho                         | Produzido por | Significado                          |
| --------------------------------- | ------------- | ------------------------------------ |
| `X-User-ID`                       | Gateway       | O ID do usuário autenticado da conta |
| `X-Api-Key-ID`                    | Gateway       | O ID da chave de API resolvida       |
| `X-User-Role`                     | Gateway       | O contexto de função autenticado     |
| `X-Api-Key-Permissions`           | Gateway       | Permissões anexadas à chave de API   |
| `X-Exchange-JWT-External-User-ID` | Gateway       | O ID do usuário final delegado       |
| `X-Exchange-JWT-Permissions`      | Gateway       | Permissões embutidas no JWT trocado  |

<Note>
  Clientes enviam `Authorization` e às vezes `X-On-Behalf-Of`.
  Clientes não enviam diretamente os cabeçalhos internos `X-User-ID` ou `X-Api-Key-ID`.
  Eles são derivados pelo gateway após validação.
</Note>

## Como funciona a troca de JWT

`POST /api/v1/authentication/api-key/exchange-token` transforma uma chave de API de longa duração em um token de curta duração para outro serviço.

O corpo da requisição tem quatro campos significativos:

* `audience`: a URL exata do serviço que deve aceitar o JWT
* `externalUserId`: o ID do usuário final colocado no subject do JWT
* `expiresIn`: tempo de vida do token em segundos, de `300` a `2592000`
* `permissions`: um subconjunto opcional das permissões da chave de API

A resposta é:

```json theme={null}
{
  "token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```

Um payload decodificado do token se parece com isto:

```json theme={null}
{
  "ak": "cm4xqw8ij0001w8lcmj9tn6v7",
  "sub": "user_123",
  "iat": 1734624000,
  "exp": 1734627600,
  "iss": "https://exchange.meetkai.com",
  "aud": "https://my-service.example.com",
  "permissions": ["agent:create", "agent:read"]
}
```

Estes claims são os mais importantes:

* `ak`: o ID da chave de API usado para lookup e aplicação de rate-limit
* `sub`: sua identidade de usuário final delegada
* `aud`: o serviço que deve aceitar o token
* `permissions`: o conjunto de capacidades permitidas para aquele token

## Como o escopo de locatário funciona na prática

A escolha de design mais importante é que a identidade do usuário final é explícita.
Se você enviar `X-On-Behalf-Of: user_123`, os serviços downstream podem manter recursos e uso associados a esse usuário.

Isso é importante para:

* conversas salvas
* respostas armazenadas
* arquivos e vetores
* uso e trilhas de auditoria por usuário
* verificações de autorização delegada

Quando você omite `X-On-Behalf-Of`, as requisições rodam como trabalho de conta apenas backend.
Quando você inclui, a requisição se torna delegada para um usuário final específico.

Use um identificador estável do seu próprio sistema.
Não use um nome de exibição mutável, a menos que esse já seja seu ID canônico de usuário.

## Limites de taxa e o que os chamadores devem esperar

Chaves de API podem carregar configurações de rate-limit personalizadas.
O caminho de autenticação aplica limites de taxa por chave de API antes da requisição chegar aos serviços downstream.

Na prática, isso significa:

* duas chaves de API diferentes podem ter limites diferentes
* uma chave de API sobrecarregada não implica que outra chave está esgotada
* um `429` faz parte do caminho de autenticação, não é um erro do modelo downstream

O fluxo de troca de token também permanece vinculado à chave de API de origem.
Isso significa que um JWT downstream ainda herda o comportamento de rate-limit associado à chave de origem.

Se você quiser pools de tráfego separados para diferentes aplicações ou workloads, use chaves de API separadas.

## Erros comuns

### Enviar `X-On-Behalf-Of` a partir de código de navegador

Não exponha sua chave de API em código de navegador ou cliente mobile.
Seu servidor deve chamar a API MKA1 e anexar `X-On-Behalf-Of` lá.

### Usar um identificador de usuário final instável

Use um ID interno durável como `user_123`.
Não alterne entre emails, nomes de usuário e nomes de exibição para o mesmo usuário.

### Usar o `audience` errado em JWTs trocados

O serviço downstream deve validar o token para o público pretendido.
Defina `audience` para a URL real do serviço que deve aceitar o JWT.

### Solicitar permissões mais amplas que a chave de API

O endpoint de troca de token só permite um subconjunto das permissões da chave de API.
Se você solicitar uma permissão que a chave não possui, a troca falha.

### Tratar cabeçalhos internos propagados como cabeçalhos públicos de cliente

Cabeçalhos como `X-User-ID` e `X-Api-Key-ID` fazem parte do caminho interno confiável da requisição.
Eles não substituem o `Authorization`.

## Apêndice de caminhos de código

Os trechos a seguir mostram a forma principal da implementação por trás do comportamento público.

### Kong valida o token bearer e injeta cabeçalhos confiáveis

```go theme={null}
authorizationHeader = utils.StripBearer(authorizationHeader)

if utils.IsApiKey(authorizationHeader) {
    resp, err := c.authenticationClient.ValidateApiKey(ctx, authorizationHeader)
    if err != nil {
        c.handleAuthenticationServiceError(kong, err)
        return
    }

    c.writeApiKeyInfoToUnderlyingHeaders(kong, resp.GetApiKeyInfo())
    c.writeUserInfoToUnderlyingHeaders(kong, resp.GetUserInfo())

    onBehalfOfHeader, _ := kong.Request.GetHeader(headerOnBehalfOf)
    if onBehalfOfHeader != "" {
        c.writeExchangeJwtInfoToUnderlyingHeaders(kong, &authentication.ExchangeJWTInfo{
            ExternalUserId: onBehalfOfHeader,
            Permissions:    resp.GetApiKeyInfo().GetPermissions(),
        })
    }

    return
}
```

Por isso, serviços downstream podem confiar em `X-User-ID`, `X-Api-Key-ID` e `X-Exchange-JWT-External-User-ID` após a validação do gateway.

### O endpoint de troca de token verifica a chave de API, checa permissões e assina o JWT

```ts theme={null}
const apiKeyResult = await auth.api.verifyApiKey({
  body: {
    key: apiKey,
  },
});

if (!apiKeyResult.valid || !apiKeyResult.key) {
  ctx.set.status = 401;
  return { message: "Invalid API key" };
}

const apiKeyPermissions = Object.entries(verifiedKey.permissions ?? {}).map(
  ([key, value]) => `${key}:${value}`,
);
const permissions = validatedBody.permissions ?? apiKeyPermissions;

if (permissions.some((permission) => !apiKeyPermissions.includes(permission))) {
  ctx.set.status = 401;
  return { message: "Permissions mismatch" };
}
```

Depois, ele assina um payload que inclui o ID da chave de API, o subject do usuário final delegado e as permissões filtradas:

```ts theme={null}
payload: {
  ak: verifiedKey.id,
  sub: validatedBody.externalUserId,
  iat: DateTime.now().toUTC().toUnixInteger(),
  permissions: permissions,
}
```

### Serviços downstream leem a identidade dos cabeçalhos confiáveis

```ts theme={null}
const apiKey = reqHeaders.get(KONG_API_KEY_HEADER)?.trim();
const userId = reqHeaders.get(KONG_USER_ID_HEADER)?.trim();
const externalUserId = reqHeaders.get(KONG_EXTERNAL_USER_ID_HEADER)?.trim() ?? "";

if (!apiKey?.length || !userId?.length) {
  throw KONG_AUTH_ERROR;
}

return { apiKey, userId, externalUserId, userRole };
```

Por isso, serviços atrás do gateway normalmente não analisam tokens bearer por conta própria.
Eles operam em um contexto de identidade já validado.

## Próximos passos

Use o [guia de autenticação](/pt/docs/authentication) para a versão curta e exemplos prontos para copiar e colar.
Use a [visão geral da API](/pt/api-reference/introduction) para inspecionar a referência gerada e o OpenAPI spec ao vivo.
Depois, revise as páginas de endpoints relevantes na Referência da API para detalhes exatos de requisição e resposta.
