POST /api-keys
API · v1 · Homologação · Revisado em · Markdown
Emitir credencial limitada. Contrato v1 · disponível em produção e homologação · operação createApiKeys. Guia funcional · Índice · Schemas · Testar em sandbox.
Autenticação e efeito
Seção intitulada “Autenticação e efeito”Permissão: api_keys:create. Exige sessão humana verificada; API keys não são aceitas. OAuth/MCP ainda não estão disponíveis. Use o access token da sessão humana do proprietário, obtido pelo login do ambiente e mantido somente no processo seguro; a chave de API não substitui essa sessão.
Disponível em produção e homologação; sujeito às capacidades do app e à configuração do provedor. Exige sessão humana verificada. Segredo somente na resposta inicial. Repetição do recibo confirmado retorna 409 secret_not_replayable e metadados sem novo segredo. Campos desconhecidos rejeitados. Consulte os ADRs para regras semânticas além do JSON Schema.
O efeito é autenticado, auditado e pode gerar publicação eventual e eventos do recurso. Links/domínios/templates podem precisar de propagação antes de aparecerem publicamente. Não muda identidade de link implicitamente. Dependências e capabilities podem bloquear a ação. O status confirma a persistência descrita, sem prometer propagação global imediata.
Parâmetros
Seção intitulada “Parâmetros”| Nome | Local | Obrigatório | Tipo e restrições |
|---|---|---|---|
Idempotency-Key |
header | sim | UUID v4 por intenção. Recibo por 24h. Mudança de payload/precondição retorna 409. Segredo de chave não é reexibido. IdempotencyKey; {“type”:“string”,“maxLength”:36,“pattern”:“^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$”} |
Corpo e semântica
Seção intitulada “Corpo e semântica”Content-Type: application/json. Campos desconhecidos são rejeitados no envelope.
| Campo | Tipo | Obrigatório | Significado e limites |
|---|---|---|---|
name |
string | sim | Nome de exibição. Até 100 caracteres |
appIds |
array | sim | Aplicativos permitidos à credencial. Até 100 itens |
permissions |
array | sim | Permissões explícitas, sem wildcard. Até 100 itens |
expiresAt |
string | não | Fim de validade; não amplia concessões. date-time |
Omitir campos opcionais usa o default documentado ou preserva o valor em PATCH. null só é aceito nos campos cujo schema permite null, com efeito de limpar/desvincular indicado no guia. Arrays enviados substituem a coleção quando o endpoint define atualização completa; não implicam merge. Identificadores/owner/app de um recurso existente não são transferidos por PATCH. Herança de templates/UTMs segue o guia, sem alterar links anteriores por cópia automática.
Request mínimo
Seção intitulada “Request mínimo”{ "name": "Backend da loja", "appIds": [ "app_loja" ], "permissions": [ "apps:read", "links:read", "links:create" ]}Request completo de referência
Seção intitulada “Request completo de referência”{ "name": "Backend da loja", "appIds": [ "app_loja" ], "permissions": [ "apps:read", "links:read", "links:create" ]}AGILINK_HUMAN_SESSION deve ficar no servidor. Os placeholders abaixo são fictícios. Preserve Idempotency-Key e payload ao repetir uma intenção após timeout.
curl --request POST 'https://api.staging.agilink.io/v1/api-keys' \ --header 'Authorization: Bearer <credencial>' \ --header 'Idempotency-Key: 10000000-0000-4000-8000-000000000001' \ --header 'Content-Type: application/json' \ --data '{"name":"Backend da loja","appIds":["app_loja"],"permissions":["apps:read","links:read","links:create"]}'JavaScript / TypeScript
Seção intitulada “JavaScript / TypeScript”// Node.js 22+. Defina AGILINK_HUMAN_SESSION no ambiente do servidor.const response = await fetch('https://api.staging.agilink.io/v1/api-keys', { method: 'POST', headers: { "Authorization": `Bearer ${process.env.AGILINK_HUMAN_SESSION}`, "Idempotency-Key": "10000000-0000-4000-8000-000000000001", "Content-Type": "application/json"}, body: JSON.stringify({ "name": "Backend da loja", "appIds": [ "app_loja" ], "permissions": [ "apps:read", "links:read", "links:create" ]}), redirect: 'error', signal: AbortSignal.timeout(30000)});if (!response.ok) throw new Error('HTTP '+response.status+' requestId='+response.headers.get('X-Request-Id'));const result = await response.json();// Guarde result.data.secret no gerenciador de segredos; não registre em logs.# Python 3.10+; pip install requestsimport osimport requestsheaders = { "Authorization": "Bearer " + os.environ["AGILINK_HUMAN_SESSION"], "Idempotency-Key": "10000000-0000-4000-8000-000000000001"}response = requests.request( 'POST', 'https://api.staging.agilink.io/v1/api-keys', headers=headers, json={"name":"Backend da loja","appIds":["app_loja"],"permissions":["apps:read","links:read","links:create"]}, timeout=30, allow_redirects=False)if not 200 <= response.status_code < 300: raise RuntimeError(f'HTTP {response.status_code}; requestId={response.headers.get("X-Request-Id")}')result = response.json()# Guarde result["data"]["secret"] com segurança; não registre em logs.Response 201
Seção intitulada “Response 201”Criado. Exemplo fictício.
| Header | Significado | Exemplo |
|---|---|---|
X-Request-Id |
Identificador gerado pelo servidor, inclusive para falhas e replays. | conforme resposta |
Cache-Control |
Respostas administrativas não podem ser armazenadas. | no-store, no-transform |
ETag |
Revisão forte, a enviar em If-Match na alteração. | “20000000-0000-4000-8000-000000000001” |
Location |
Endereço relativo do recurso criado. | /v1/api-keys/30000000-0000-4000-8000-000000000001 |
{ "data": { "key": { "id": "30000000-0000-4000-8000-000000000001", "name": "Backend da loja", "prefix": "agl_test_AbCdEf", "appIds": [ "app_loja" ], "permissions": [ "apps:read", "links:read", "links:create" ], "expiresAt": "2027-01-17T20:00:00Z", "createdAt": "2026-10-19T20:00:00Z", "lastUsedAt": null, "revokedAt": null, "revision": "20000000-0000-4000-8000-000000000001" }, "secret": "agl_test_AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" }, "meta": { "requestId": "req_00000000000040008000000000000001" }}| Campo | Tipo | Obrigatório | Significado e limites |
|---|---|---|---|
data |
object | sim | Resultado tipado da operação. |
data.key |
object | sim | Valor tipado; formato e restrições completos no schema abaixo. |
data.key.id |
string | sim | Identificador estável do recurso. uuid |
data.key.name |
string | sim | Nome de exibição. Até 100 caracteres |
data.key.prefix |
string | sim | Prefixo de caminho, sem barras. Até 30 caracteres; Formato validado pelo schema |
data.key.appIds |
array | sim | Aplicativos permitidos à credencial. Até 100 itens |
data.key.permissions |
array | sim | Permissões explícitas, sem wildcard. Até 100 itens |
data.key.expiresAt |
string | sim | Fim de validade; não amplia concessões. date-time |
data.key.createdAt |
string | sim | Instante de criação com fuso. date-time |
data.key.lastUsedAt |
string ou null | sim | Valor tipado; formato e restrições completos no schema abaixo. |
data.key.revokedAt |
string ou null | sim | Valor tipado; formato e restrições completos no schema abaixo. |
data.key.revision |
string | sim | Revisão forte para If-Match; muda ao editar. uuid |
data.secret |
string | sim | Exibido apenas na emissão; armazene no servidor. Até 52 caracteres; Formato validado pelo schema |
meta |
object | sim | Correlação e informações da resposta. |
meta.requestId |
RequestId | sim | Identificador de correlação gerado pelo servidor. |
Falhas, revisão e repetição
Seção intitulada “Falhas, revisão e repetição”Idempotency-Key UUID v4 é obrigatório, por intenção e credencial, por 24 horas. Em resultado incerto, repita exatamente o mesmo método, URL, corpo, revisão e chave. Alterar a intenção exige nova chave. Segredos de emissão não são reexibidos. Esta operação não exige If-Match. Não faça retry automático ilimitado.
| Status | Significado |
|---|---|
| 400 | Cabeçalho, query, cursor ou JSON inválido. |
| 401 | Credencial inválida, expirada ou revogada. |
| 403 | Operação sem permissão ou capacidade desabilitada. |
| 404 | Recurso inexistente ou fora dos aplicativos autorizados. |
| 409 | Conflito de identidade/idempotência; operação em andamento ou segredo já exibido. |
| 413 | Corpo excede o limite. |
| 415 | Content-Type incompatível. |
| 422 | JSON bem formado com dados de domínio inválidos. |
| 429 | Quota excedida. Respeitar Retry-After. |
| 500 | Falha interna. Use requestId para diagnóstico. |
| 503 | Dependência indisponível; resultado da mutação pode ser incerto. |
Exemplo 401
Seção intitulada “Exemplo 401”{ "error": { "code": "unauthenticated", "message": "Credencial inválida, expirada ou revogada.", "requestId": "req_00000000000040008000000000000001", "documentationUrl": "https://docs.agilink.io/api/errors/#unauthenticated" }}Exemplo 403
Seção intitulada “Exemplo 403”{ "error": { "code": "insufficient_permission", "message": "Operação sem permissão ou capacidade desabilitada.", "requestId": "req_00000000000040008000000000000001", "documentationUrl": "https://docs.agilink.io/api/errors/#insufficient-permission" }}Exemplo 409
Seção intitulada “Exemplo 409”{ "error": { "code": "secret_not_replayable", "message": "A chave já foi emitida. O segredo não pode ser reexibido.", "requestId": "req_00000000000040008000000000000001", "documentationUrl": "https://docs.agilink.io/api/errors/#secret-not-replayable", "resource": { "type": "api_key", "id": "30000000-0000-4000-8000-000000000001" } }}Exemplo 429
Seção intitulada “Exemplo 429”{ "error": { "code": "rate_limited", "message": "Quota excedida. Respeitar Retry-After.", "requestId": "req_00000000000040008000000000000001", "documentationUrl": "https://docs.agilink.io/api/errors/#rate-limited" }}Schema completo e próximos passos
Seção intitulada “Schema completo e próximos passos”Schema de request
{ "type": "object", "additionalProperties": false, "properties": { "name": { "type": "string", "maxLength": 100, "minLength": 1 }, "appIds": { "type": "array", "items": { "type": "string", "maxLength": 500, "minLength": 1, "pattern": "^[^/\\u0000-\\u001f\\u007f]+$" }, "maxItems": 100, "minItems": 1, "uniqueItems": true }, "permissions": { "type": "array", "items": { "enum": [ "analytics:read", "apps:delete", "apps:read", "apps:update", "assets:create", "assets:delete", "assets:read", "batches:create", "batches:read", "campaigns:create", "campaigns:delete", "campaigns:read", "campaigns:update", "capabilities:read", "dns:create", "dns:delete", "dns:read", "dns:update", "domains:create", "domains:delete", "domains:read", "domains:verify", "exports:create", "exports:read", "folders:create", "folders:delete", "folders:read", "folders:update", "identity:read", "imports:commit", "imports:create", "imports:read", "imports:validate", "link_history:read", "links:create", "links:delete", "links:preview", "links:read", "links:resync", "links:update", "links:validate", "native_config:read", "native_config:update", "operations:cancel", "operations:read", "qr_codes:create", "qr_codes:delete", "qr_codes:read", "qr_codes:update", "recovery_config:read", "recovery_config:update", "routing_rules:read", "routing_rules:update", "tags:create", "tags:delete", "tags:read", "tags:update", "template_bindings:read", "template_bindings:update", "templates:create", "templates:preview", "templates:publish", "templates:read", "templates:retry", "templates:snapshot", "templates:update", "templates:validate", "usage:read", "web_config:read", "web_config:update", "webhooks:create", "webhooks:delete", "webhooks:read", "webhooks:retry", "webhooks:rotate_secret", "webhooks:test", "webhooks:update" ] }, "maxItems": 100, "minItems": 1, "uniqueItems": true }, "expiresAt": { "type": "string", "format": "date-time" } }, "required": [ "name", "appIds", "permissions" ]}Schema de response
{ "type": "object", "additionalProperties": false, "properties": { "data": { "type": "object", "additionalProperties": false, "properties": { "key": { "type": "object", "additionalProperties": false, "properties": { "id": { "type": "string", "format": "uuid" }, "name": { "type": "string", "maxLength": 100, "minLength": 1 }, "prefix": { "type": "string", "maxLength": 30, "pattern": "^agl_(live|test)_[A-Za-z0-9_-]{4,12}$" }, "appIds": { "type": "array", "items": { "type": "string", "maxLength": 500, "minLength": 1, "pattern": "^[^/\\u0000-\\u001f\\u007f]+$" }, "maxItems": 100, "minItems": 1, "uniqueItems": true }, "permissions": { "type": "array", "items": { "enum": [ "analytics:read", "apps:delete", "apps:read", "apps:update", "assets:create", "assets:delete", "assets:read", "batches:create", "batches:read", "campaigns:create", "campaigns:delete", "campaigns:read", "campaigns:update", "capabilities:read", "dns:create", "dns:delete", "dns:read", "dns:update", "domains:create", "domains:delete", "domains:read", "domains:verify", "exports:create", "exports:read", "folders:create", "folders:delete", "folders:read", "folders:update", "identity:read", "imports:commit", "imports:create", "imports:read", "imports:validate", "link_history:read", "links:create", "links:delete", "links:preview", "links:read", "links:resync", "links:update", "links:validate", "native_config:read", "native_config:update", "operations:cancel", "operations:read", "qr_codes:create", "qr_codes:delete", "qr_codes:read", "qr_codes:update", "recovery_config:read", "recovery_config:update", "routing_rules:read", "routing_rules:update", "tags:create", "tags:delete", "tags:read", "tags:update", "template_bindings:read", "template_bindings:update", "templates:create", "templates:preview", "templates:publish", "templates:read", "templates:retry", "templates:snapshot", "templates:update", "templates:validate", "usage:read", "web_config:read", "web_config:update", "webhooks:create", "webhooks:delete", "webhooks:read", "webhooks:retry", "webhooks:rotate_secret", "webhooks:test", "webhooks:update" ] }, "maxItems": 100, "minItems": 1, "uniqueItems": true }, "expiresAt": { "type": "string", "format": "date-time" }, "createdAt": { "type": "string", "format": "date-time" }, "lastUsedAt": { "anyOf": [ { "type": "string", "format": "date-time" }, { "type": "null" } ] }, "revokedAt": { "anyOf": [ { "type": "string", "format": "date-time" }, { "type": "null" } ] }, "revision": { "type": "string", "format": "uuid" } }, "required": [ "id", "name", "prefix", "appIds", "permissions", "expiresAt", "createdAt", "lastUsedAt", "revokedAt", "revision" ] }, "secret": { "type": "string", "maxLength": 52, "pattern": "^agl_(live|test)_[A-Za-z0-9_-]{43}$" } }, "required": [ "key", "secret" ] }, "meta": { "type": "object", "additionalProperties": false, "properties": { "requestId": { "$ref": "#/components/schemas/RequestId" } }, "required": [ "requestId" ] } }, "required": [ "data", "meta" ]}Convenções e limites · Erros · Receitas completas · Guia funcional.