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.
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:
- Locatário A e Locatário B usam chaves de API diferentes.
- Locatário A e Locatário B podem ter configurações de cota diferentes.
- Locatário A e Locatário B podem ter políticas diferentes.
- 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.
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 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 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.
Agora atribua políticas de guardrail diferentes para os dois locatários.
Configure o Locatário 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 Locatário B com uma política 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 em ambos os locatários.
O Locatário 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 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 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:
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:
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 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 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 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 '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 '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 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.
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.
Quando você também envia X-On-Behalf-Of, o gateway mantém essa identidade de usuário final delegada com a requisição:
A troca de JWT adiciona um passo extra:
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 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."
}'
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 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."
}'
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 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 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ç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 |
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.
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 é:
{
"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."
}
Um payload decodificado 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"]
}
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
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
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:
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
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 para a versão curta e exemplos prontos para copiar e colar.
Use a visão geral da API 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.