Na MKA1, o limite prático entre tenants é:
- uma API key de conta distinta
- um ID de end user delegado opcional via
X-On-Behalf-Of
- a propriedade de recursos downstream e o uso registrados nesse contexto autenticado
Use o passo a passo abaixo quando quiser verificar, na prática, que dois tenants ficam isolados um do outro.
Ele fornece um fluxo exato de Tenant A e Tenant B que você pode executar de ponta a ponta.
Verifique o isolamento entre tenants na prática
Trate cada tenant como uma conta separada com sua própria API key.
O objetivo é demonstrar quatro coisas:
- Tenant A e Tenant B usam API keys diferentes.
- Tenant A e Tenant B podem ter configurações de cota diferentes.
- Tenant A e Tenant B podem ter policies diferentes.
- Um recurso criado sob o Tenant A não fica acessível sob o Tenant B.
Etapa 1: defina dois tenants
Comece com duas API keys separadas.
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á tiver 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 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 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 já estabelece imediatamente as duas primeiras partes do isolamento:
- Tenant A e Tenant B não compartilham a mesma chave.
- Tenant A e Tenant B não compartilham a mesma configuração de cota.
Agora atribua policies de guardrail diferentes aos dois tenants.
Configure o Tenant A para bloquear a palavra confidential.
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 Tenant B com uma policy diferente.
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 contra os dois tenants.
O Tenant A deve bloquear isto:
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 Tenant B deve avaliar o mesmo conteúdo com a própria policy dele, e não com a policy do Tenant A:
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 é:
- Tenant A falha porque
confidential está bloqueado na policy do Tenant A.
- Tenant B não herda a policy do Tenant A.
Esta é a demonstração prática mais clara de policies independentes por tenant.
Etapa 3: demonstre cotas separadas
Depois de criar duas chaves com limites diferentes, envie o mesmo tipo de requisição pelas duas chaves.
O Tenant A usa a chave de limite baixo:
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": "openai:gpt-5-mini",
"input": "Reply with the word ok."
}'
done
O Tenant B usa a chave de limite mais alto:
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": "openai:gpt-5-mini",
"input": "Reply with the word ok."
}'
done
O resultado esperado é:
- Tenant A começa a receber
429 mais cedo.
- Tenant B continua a responder com sucesso porque tem uma chave diferente e uma cota diferente.
Isso mostra que os tenants não compartilham um único limitador global.
Etapa 4: demonstre isolamento de recursos
Crie um recurso sob o Tenant A.
Uma conversation é um exemplo simples porque ela fica visível pela API pública.
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 Tenant A consegue lê-la:
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 Tenant B não deve conseguir ler o mesmo recurso:
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 demonstrar o isolamento em listagem:
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 Tenant B não deve ver a conversation do Tenant A na própria lista.
Esta é a prova prática do enclave por tenant:
- a mesma superfície de API é usada
- a mesma plataforma é usada
- mas identidade, policy, cota e propriedade de recurso são aplicadas separadamente
Etapa 5 opcional: mostre registros de uso separados
Se quiser mais uma evidência visível, consulte o uso separadamente por tenant.
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 '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 desta etapa não é apenas faturamento.
Ela mostra que a plataforma registra a atividade no contexto autenticado do tenant, em vez de juntar todo o tráfego em uma única conta indiferenciada.
O que este passo a passo comprova
Se você executar o passo a passo acima, poderá fazer estas afirmações exatas:
- Chaves independentes: Tenant A e Tenant B se autenticam com API keys diferentes.
- Cotas independentes: Tenant A e Tenant B podem ter configurações de rate limit diferentes e receber comportamentos de
429 diferentes.
- Policies independentes: Tenant A e Tenant B podem definir guardrails diferentes e receber resultados diferentes para o mesmo conteúdo.
- Isolamento real: um recurso criado sob o Tenant A não pode ser lido sob o Tenant B.
A superfície da API é compartilhada, mas o contexto autenticado do tenant, a aplicação de cotas, a configuração de policies e a propriedade de recursos não são compartilhados.
Como o modelo de identidade funciona por baixo
A autenticação na MKA1 API tem três camadas:
- Sua API key identifica a sua conta.
X-On-Behalf-Of identifica o end user para quem o seu servidor está agindo.
- Um JWT trocado dá a um serviço downstream uma credencial de curta duração derivada dessa API key e do contexto de end user.
A versão curta é simples:
- Envie
Authorization: Bearer <mka1-api-key> em toda requisição server-side.
- Adicione
X-On-Behalf-Of quando a requisição pertencer a um dos seus end users.
- Use
POST /api/v1/authentication/api-key/exchange-token quando outro serviço precisar receber um token de curta duração em vez da sua API key bruta.
Se você só precisa do padrão de uso, comece pelo guia de autenticação.
Esta página explica o que acontece depois que a requisição chega ao gateway.
O caminho da requisição
A MKA1 API não pede para serviços downstream validarem 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.
Quando você também envia X-On-Behalf-Of, o gateway mantém essa identidade delegada do end user junto com a requisição:
A troca por JWT adiciona uma etapa extra:
Os três padrões principais
Padrão 1: requisições apenas do backend
Use este padrão quando o seu backend chama a MKA1 API para o próprio fluxo interno e não há um end user separado para rastrear.
curl https://apigw.mka1.com/api/v1/llm/responses \
--request POST \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer <mka1-api-key>' \
--data '{
"model": "openai:gpt-5-mini",
"input": "Write a short release note for our API update."
}'
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: "openai:gpt-5-mini",
input: "Write a short release note for our API update.",
}),
});
Use este padrão quando o seu backend faz a requisição em nome de um dos usuários da sua própria aplicação.
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": "openai:gpt-5-mini",
"input": "Summarize this support ticket."
}'
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: "openai:gpt-5-mini",
input: "Summarize this support ticket.",
}),
});
Este é o padrão correto quando você quer que requisições, files, responses, conversations e uso permaneçam associados a um ID estável de end user do seu próprio sistema.
Padrão 3: troque sua API key por um JWT de curta duração
Use este padrão quando outro serviço deve receber um token temporário em vez da sua API key de longa duração.
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"]
}'
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();
Cabeçalhos que você envia
| Cabeçalho | Obrigatório | Significado |
|---|
Authorization | Sim | Sua API key da MKA1 ou o JWT trocado no formato bearer |
X-On-Behalf-Of | Não | Seu identificador estável de end user para requisições server-side delegadas |
| 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 API key resolvida |
X-User-Role | Gateway | O contexto de papel autenticado |
X-Api-Key-Permissions | Gateway | As permissões anexadas à API key |
X-Exchange-JWT-External-User-ID | Gateway | O ID delegado do end user |
X-Exchange-JWT-Permissions | Gateway | As permissões embutidas no JWT trocado |
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 depois da validação.
Como funciona a troca por JWT
POST /api/v1/authentication/api-key/exchange-token transforma uma API key de longa duração em um token de curta duração para outro serviço.
O corpo da requisição tem quatro campos importantes:
audience: a URL exata do serviço que deve aceitar o JWT
externalUserId: o ID de end user colocado no subject do JWT
expiresIn: a vida útil do token em segundos, de 300 a 2592000
permissions: um subconjunto opcional das permissões da API key
A resposta é:
{
"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Uma carga útil decodificada do token se parece com isto:
{
"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"]
}
Estas claims são as mais importantes:
ak: o ID da API key usado para lookup e para enforcement de rate limit
sub: sua identidade delegada de end user
aud: o serviço que deve aceitar o token
permissions: o conjunto de capacidades permitido para esse token
Como o escopo por tenant funciona na prática
A decisão de design mais importante é que a identidade de end user é 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 importa para:
- conversations salvas
- responses armazenadas
- files e vector stores
- uso por usuário e trilhas de auditoria
- checagens de autorização delegada
Quando você omite X-On-Behalf-Of, as requisições rodam como trabalho de conta apenas do backend.
Quando você o inclui, a requisição se torna uma requisição delegada para um end user 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 ele já seja seu ID canônico de usuário.
Rate limits e o que os callers devem esperar
As API keys podem carregar configurações personalizadas de rate limit.
O caminho de autenticação aplica os rate limits por API key antes que a requisição chegue aos serviços downstream.
Na prática, isso significa:
- duas API keys diferentes podem ter limites diferentes
- uma API key sobrecarregada não implica que outra chave também esteja esgotada
- um
429 faz parte do caminho de autenticação, e não de um erro downstream do modelo
O fluxo de exchange-token também permanece vinculado à API key de origem.
Isso significa que um JWT downstream ainda herda o comportamento de rate limit em nível de conta associado à chave de origem.
Se você quiser pools de tráfego separados para aplicações ou cargas de trabalho diferentes, use API keys separadas.
Erros comuns
Enviar X-On-Behalf-Of a partir de código no navegador
Não exponha sua API key em código de navegador ou cliente móvel.
Seu servidor deve chamar a MKA1 API e anexar X-On-Behalf-Of ali.
Usar um identificador instável de end user
Use um ID interno durável como user_123.
Não alterne entre email, username e display name para o mesmo usuário.
Usar o audience errado em JWTs trocados
O serviço downstream deve validar o token para a audiência pretendida.
Defina audience como a URL real do serviço que deve aceitar o JWT.
Solicitar permissões mais amplas do que a API key possui
O endpoint exchange-token só permite um subconjunto das permissões da API key.
Se você solicitar uma permissão que a API key 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 Authorization.
Apêndice de caminho de código
Os trechos a seguir mostram o formato principal de implementação por trás do comportamento público.
O Kong valida o bearer token e injeta cabeçalhos confiáveis
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 que os serviços downstream podem confiar em X-User-ID, X-Api-Key-ID e X-Exchange-JWT-External-User-ID depois da validação no gateway.
O endpoint exchange-token verifica a API key, checa permissões e assina o JWT
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 uma carga útil que inclui o ID da API key, o subject delegado do end user e as permissões filtradas:
payload: {
ak: verifiedKey.id,
sub: validatedBody.externalUserId,
iat: DateTime.now().toUTC().toUnixInteger(),
permissions: permissions,
}
Serviços downstream leem a identidade a partir de cabeçalhos confiáveis
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 que serviços atrás do gateway geralmente não fazem parse de bearer tokens por conta própria.
Eles operam sobre um contexto de identidade já validado.
Próximos passos
Use o guia de autenticação para a versão curta e para exemplos de copiar e colar.
Use a visão geral da API para inspecionar a referência gerada e a OpenAPI spec ao vivo.
Depois revise as páginas relevantes de endpoint na Referência da API para ver detalhes exatos de requisição e resposta.