---
title: Receitas completas de integração
description: Jornadas verificáveis em JavaScript/TypeScript, Python e cURL.
---

Use um backend com chave limitada. Os [clientes de exemplo JavaScript](/examples/agilink-client.mjs) e [Python](/examples/agilink_client.py) usam homologação por padrão; para produção, selecione explicitamente `environment: "production"` no JavaScript ou `environment="production"` no Python. Incluem timeout, até três tentativas com a mesma intenção, leitura de Retry-After, paginação e consulta de operações. São exemplos, não SDKs com garantia de estabilidade. Nenhuma chamada ocorre ao importar. Salve os arquivos no seu servidor e defina `AGILINK_API_KEY` no ambiente seguro.

Todos os 143 endpoints têm cURL, JS/TS e Python na [referência por operação](/api/operacoes/). Os exemplos usam IDs fictícios; substitua-os pelos recursos autorizados. Downloads são autenticados. Nunca registre o segredo de uma chave ou webhook em logs.

## Criar app, domínio e primeiro link

Criar app exige sessão humana verificada. Obtenha-a pelo login do produto, mantenha-a no servidor seguro e não use credencial administrativa de provedor. Conectar domínio depende da capacidade e autorização do app. Aguardar operação não comprova associação nativa em aparelho.

```js
import {AgilinkClient} from './agilink-client.mjs';
const human = new AgilinkClient(process.env.AGILINK_HUMAN_SESSION);
const {data: created} = await human.request('/apps', {method:'POST',body:{name:'Minha loja'}});
const appId = created.data.id;
const {data: setup} = await human.request('/domains', {method:'POST',body:{appId,mode:'platform',prefix:'minha-loja'}});
const connected = await human.waitOperation(setup.data.id);
if (connected.status !== 'succeeded') throw Error('Confira o domínio antes de continuar.');
const domains = await human.request('/domains?appId='+encodeURIComponent(appId));
const domain = domains.data.data.find(d=>d.links==='active' && !d.detached);
if (!domain) throw Error('Aguarde propriedade, DNS e HTTPS.');
const {data: link} = await human.request('/links', {method:'POST',body:{appId,domain:domain.hostname,slug:'produto',desktopUrl:'https://shop.example.com/produto',webOnly:true}});
const publication = await human.request('/links/'+encodeURIComponent(link.data.id)+'/publication');
// Confira publication.data.data; a propagação pública é eventual.
```

```python
import os
from agilink_client import AgilinkClient
human = AgilinkClient(os.environ['AGILINK_HUMAN_SESSION'])
app_id = human.request('/apps', method='POST', body={'name':'Minha loja'})['data']['data']['id']
setup = human.request('/domains', method='POST', body={'appId':app_id,'mode':'platform','prefix':'minha-loja'})
state = human.wait_operation(setup['data']['data']['id'])
if state['status'] != 'succeeded':
    raise RuntimeError('Confira o domínio antes de continuar')
domains = human.request('/domains?appId='+app_id)['data']['data']
domain = next((d for d in domains if d['links'] == 'active' and not d['detached']), None)
if domain is None:
    raise RuntimeError('Aguarde propriedade, DNS e HTTPS')
link = human.request('/links', method='POST', body={'appId':app_id,'domain':domain['hostname'],'slug':'produto','desktopUrl':'https://shop.example.com/produto','webOnly':True})
from urllib.parse import quote
publication = human.request('/links/'+quote(link['data']['data']['id'], safe='')+'/publication')
```

[Aplicativos](/produto/aplicativos-ativos/) · [Domínios](/produto/dominios/) · [Abrir e medir](/produto/primeiros-passos/).

## Listar com filtros e cursor

```js
import {AgilinkClient} from './agilink-client.mjs';
const api = new AgilinkClient();
for await (const links of api.pages('/links?appId=app_loja&group=operational&q=produto')) {
  for (const link of links) console.log(link.id, link.title);
}
```

```python
from agilink_client import AgilinkClient
api = AgilinkClient()
for links in api.pages('/links?appId=app_loja&group=operational&q=produto'):
    for link in links:
        print(link['id'], link['title'])
```

Mantenha credencial, app, filtros, ordenação e limite; cursor expira em 15 minutos. A lista pode mudar entre páginas. Para arquivo limitado na execução, use exportação.

## Editar com ETag e reconciliar conflito

```js
import {AgilinkClient, AgilinkError} from './agilink-client.mjs';
const api = new AgilinkClient(), path = '/links/'+encodeURIComponent('go.example.com~-ofertas~-tenis');
const current = await api.request(path);
try { await api.request(path,{method:'PATCH',revision:current.data.data.revision,body:{title:'Título revisado'}}); }
catch (error) {
  if (!(error instanceof AgilinkError) || error.status !== 412) throw error;
  const latest = await api.request(path);
  // Mostre latest e a intenção ao usuário; reconcilie antes de criar uma NOVA edição.
}
```

```python
from agilink_client import AgilinkClient, AgilinkError
api = AgilinkClient()
path = '/links/go.example.com~-ofertas~-tenis'
current = api.request(path)
try:
    api.request(path, method='PATCH', revision=current['data']['data']['revision'], body={'title':'Título revisado'})
except AgilinkError as error:
    if error.status != 412:
        raise
    latest = api.request(path)
    # Reconciliar com a pessoa antes de nova intenção; não sobrescrever cegamente.
```

## Timeout sem duplicar criação

Gere um UUID por intenção e preserve corpo, caminho, revisão e credencial em todas as tentativas. Os clientes de exemplo conservam esses valores nas até três tentativas. Se esgotar, guarde `error.intentId` (JS) / `error.intent_id` (Python) e consulte o recurso antes de decidir repetir. Para emissão de segredo, 409 secret_not_replayable exige consultar/revogar o recurso identificado; não gerar nova chave automaticamente.

```js
import {AgilinkClient} from './agilink-client.mjs';
const api = new AgilinkClient(), intentId = crypto.randomUUID();
const body = {appId:'app_loja',domain:'go.example.com',slug:'produto',desktopUrl:'https://shop.example.com/produto'};
const result = await api.request('/links',{method:'POST',body,intentId});
// Em resultado incerto, preserve intentId e body; não chame randomUUID para o mesmo retry.
```

## Gerar QR e baixar

```js
import {writeFile} from 'node:fs/promises';
import {AgilinkClient} from './agilink-client.mjs';
const api = new AgilinkClient();
const {data: saved} = await api.request('/qr-codes',{method:'POST',body:{appId:'app_loja',linkId:'go.example.com~-ofertas~-tenis',name:'Vitrine'}});
const image = await api.request('/qr-codes/'+saved.data.id+'/download?format=png',{binary:true});
await writeFile('vitrine.png',new Uint8Array(image.data));
```

```python
from agilink_client import AgilinkClient
api = AgilinkClient()
saved = api.request('/qr-codes', method='POST', body={'appId':'app_loja','linkId':'go.example.com~-ofertas~-tenis','name':'Vitrine'})
image = api.request('/qr-codes/'+saved['data']['data']['id']+'/download?format=png', binary=True)
with open('vitrine.png','wb') as output:
    output.write(image['data'])
```

Confira câmera e impressão em aparelho real. [Limites do QR](/produto/qr/).

## Criar campanha e consultar métricas

POST /campaigns com appId/name/utm → GET /links/{linkId} → PATCH com campaignId e cópia explícita de utm usando revisão → GET /analytics/campaigns/{campaignId}?start=…&end=…. Copiar UTMs não sincroniza alterações futuras. Métricas seguem associações atuais e cobertura dos eventos retidos.

```js
import {AgilinkClient} from './agilink-client.mjs';
const api = new AgilinkClient();
const campaign = (await api.request('/campaigns',{method:'POST',body:{appId:'app_loja',name:'Verão',utm:{utm_campaign:'verao'}}})).data.data;
const path = '/links/'+encodeURIComponent('go.example.com~-ofertas~-tenis');
const link = (await api.request(path)).data.data;
await api.request(path,{method:'PATCH',revision:link.revision,body:{campaignId:campaign.id,utm:campaign.utm}});
const metric = await api.request('/analytics/campaigns/'+campaign.id+'?start=2026-10-01T00%3A00%3A00Z&end=2026-11-01T00%3A00%3A00Z');
```

## Publicar template e restaurar

GET /template-catalog?appId=… → POST /templates com modelo disponível → PUT /templates/{id}/draft com revisão e variáveis válidas → POST /templates/{id}/validate → POST /templates/{id}/versions para snapshot imutável → POST /templates/{id}/publish → GET /template-publications/{id} até confirmação ou falha → POST /templates/{id}/restore para versão anterior.

A publicação usa schema próprio de intenção/alvos; copie os requests validados da referência, não um objeto arbitrário. Antes de restaurar, consulte a revisão atual; restauração é nova intenção e não reescreve snapshots. [Requests completos e alvos](/api/endpoints-a3b/) · [Herança e editor](/produto/templates/).

## Importar, corrigir e confirmar

```js
import {AgilinkClient} from './agilink-client.mjs';
const api = new AgilinkClient();
const uploaded = (await api.request('/imports',{method:'POST',body:{appId:'app_loja',format:'csv',content:'domain,slug,desktopUrl\ngo.example.com,produto,https://shop.example.com/produto'}})).data.data;
const checked = (await api.request('/imports/'+uploaded.id+'/validate',{method:'POST',body:{}})).data.data;
const rows = (await api.request('/imports/'+uploaded.id+'/items')).data.data;
if (checked.failed) throw Error('Confira rows; corrija e envie apenas linhas necessárias em nova importação.');
const queued = (await api.request('/imports/'+uploaded.id+'/commit',{method:'POST',revision:checked.revision,body:{}})).data.data;
const outcome = await api.waitOperation(queued.operationId);
// Consulte itens novamente; parcialmente concluído não significa rollback de todos.
```

```python
from agilink_client import AgilinkClient
api = AgilinkClient()
uploaded = api.request('/imports',method='POST',body={'appId':'app_loja','format':'json','content':'[{"domain":"go.example.com","slug":"produto","desktopUrl":"https://shop.example.com/produto"}]'})['data']['data']
checked = api.request('/imports/'+uploaded['id']+'/validate',method='POST',body={})['data']['data']
if checked['failed']:
    raise RuntimeError('Consulte os itens e corrija as linhas')
queued = api.request('/imports/'+uploaded['id']+'/commit',method='POST',revision=checked['revision'],body={})['data']['data']
outcome = api.wait_operation(queued['operationId'])
```

## Exportar e baixar antes de expirar

```js
import {writeFile} from 'node:fs/promises';
import {AgilinkClient} from './agilink-client.mjs';
const api = new AgilinkClient();
const queued = (await api.request('/exports',{method:'POST',body:{appId:'app_loja',dataset:'links',format:'csv'}})).data.data;
const operation = await api.waitOperation(queued.operationId);
if(operation.status!=='succeeded') throw Error('Confira limite, autorização e erro da operação.');
const file = await api.request('/exports/'+queued.id+'/download',{binary:true});
await writeFile('links.csv',new Uint8Array(file.data));
```

```python
from agilink_client import AgilinkClient
api = AgilinkClient()
queued = api.request('/exports',method='POST',body={'appId':'app_loja','dataset':'links','format':'csv'})['data']['data']
operation = api.wait_operation(queued['operationId'])
if operation['status'] != 'succeeded':
    raise RuntimeError('Confira limite, autorização e erro')
with open('links.csv','wb') as output:
    output.write(api.request('/exports/'+queued['id']+'/download',binary=True)['data'])
```

Máximo 1.000 registros ou 1 MiB. Arquivo por 24h; download exige leitura atual do conjunto original. CSV neutraliza fórmulas: preserve essa proteção.

## Receber webhook, verificar e deduplicar

1. Cadastre seu endpoint HTTPS público IPv4/443 em POST /webhooks, eventos e app. Guarde o segredo uma vez no servidor.
2. Receba corpo bruto; valide Agilink-Signature com timestamp até 300s e HMAC-SHA256 antes de interpretar o JSON.
3. Faça deduplicação durável por event.id em transação antes de aplicar efeito; responda 2xx rapidamente e coloque trabalho longo na sua fila.
4. Use POST /webhooks/{id}/test; confira test:true e histórico. Timeout/3xx/5xx têm retentativas; não há ordem global.
5. Para mudar segredo, rotacione com revisão e sobreposição, atualize receptor e só depois retire o anterior. Destino alterado cancela pendências da revisão antiga; reentrega é explícita.

[Verificador HMAC completo e política de retentativas](/api/automacao-lotes-webhooks/#verificar-a-assinatura-no-receptor). Não exponha esse segredo no mobile ou no portal público.

## Integrar Android, iOS e Flutter

Configure a associação de domínio, pacote Android/certificados e bundle iOS/team conforme sua distribuição; configure appPath relativo. Instale o [SDK Flutter](/sdk/flutter/), trate as entradas HTTPS/scheme/colagem e registre abertura/conteúdo com IDs estáveis. App Links/Universal Links e recuperação pós-instalação são etapas distintas. Faça os testes em aparelho real pelas lojas, incluindo sem app instalado, instalado, link inválido, repetição e ausência de resultado elegível. [Recebimento e parâmetros](/sdk/contratos-publicos/) · [Recuperação](/produto/recuperacao/).

```dart
// Dentro do seu handler de URI, depois de receber pelo fluxo configurado no SDK:
void tratarUri(Uri uri) {
  final produto = uri.queryParameters['produto'];
  if (produto == null || produto.isEmpty) return;
  // Valide o produto no backend do aplicativo antes de navegar.
}
```

O exemplo Dart é tratamento de parâmetro, não uma API inventada do SDK. Use as assinaturas do pacote e o guia Flutter da versão integrada.

## Rotacionar/revogar e interpretar 401/403

Na área Desenvolvedores, selecione chave, revise alcance/prazo e rotacione. Via API humana: GET /api-keys/{id} → POST /api-keys/{id}/rotate com If-Match e overlapSeconds → guarde novo segredo sem log → atualize consumidor → teste leitura autorizada → DELETE da anterior se ainda vigente.

401 indica credencial/sessão/validade/revogação; 403 indica permissão/capacidade; não resolva ambos emitindo uma chave mais ampla automaticamente. Para reduzir, PATCH com subconjunto de apps/permissões/prazo. [Requests humanos completos](/api/fundacao/) · [Diagnóstico](/diagnostico/).
