Sincronizar o seu sistema com uma fonte personalizada
Uma fonte personalizada conecta um sistema que não tem conector próprio: a sua wiki, a sua ferramenta de chamados, um banco de dados, uma exportação de qualquer origem. O seu código envia documentos ao Sursa, e o Sursa os transforma em cartões na coleção e nas pastas para as quais a fonte está configurada, com a mesma busca, as mesmas respostas, a mesma verificação e as mesmas permissões dos cartões de qualquer outra fonte.
Ao contrário da gravação direta de cartões, uma fonte personalizada acompanha o que você enviou. No fim de uma sincronização completa, o Sursa arquiva todos os documentos que você não enviou, e assim as exclusões no seu sistema chegam ao Sursa sem trabalho extra.
Configurar a fonte
Um administrador cria a fonte no Sursa em Sources → New source → Custom source (fontes, nova fonte, fonte personalizada) e escolhe:
- a coleção e, se quiser, uma pasta para os cartões dela;
- o modo de pastas: todos os cartões na pasta escolhida ou subpastas que reproduzem o
pathde cada documento; - as permissões: as permissões da coleção ou “as in the source” (como na fonte), em que cada documento lista quem pode vê-lo.
A página da fonte mostra o ID dela; os exemplos usam 42.
Escolher uma chave
| Chave | Começa com | O que pode fazer |
|---|---|---|
| Chave da fonte | kbsrc_ | Gravar documentos nesta fonte. Nada além disso: ela não pode ler nem buscar. Crie-a na página da fonte, em API keys (chaves de API da fonte). |
Chave da organização com o escopo import:write | kb_ | Gravar em qualquer fonte personalizada da organização e ler o estado dela. Criar uma fonte e as chaves dela exige o escopo admin. |
A chave da fonte é a escolha mais segura para uma única integração, principalmente uma que funciona em uma plataforma
de terceiros. Uma chave da organização com import:write é adequada para um serviço que alimenta várias fontes.
As duas chaves são enviadas da mesma forma, e todos os endpoints de envio ficam em /v1/push/sources/{source_id}.
Confira a chave e o estado da fonte com GET /v1/push/sources/{source_id}:
curl https://api.sursa.com/v1/push/sources/42 \
-H "Authorization: Bearer $SURSA_SOURCE_KEY"{"source_id": 42, "name": "Helpdesk articles", "documents": 1840, "open_sync": null}
O documento
| Campo | Obrigatório | Descrição |
|---|---|---|
external_id | sim | O seu ID estável do documento, com até 512 caracteres. Enviar o mesmo ID de novo substitui o documento. |
title | sim | Título do cartão, com até 500 caracteres. Para um arquivo, o padrão é o nome do arquivo. |
content | sim, a menos que haja um arquivo | Até 5 MB. |
content_type | não | html (padrão), markdown ou text. O HTML é limpo de scripts, estilos, formulários e objetos incorporados. |
url | não | Link para o documento no seu sistema, mostrado no cartão. |
path | não | Pastas, como array (["Support", "Billing"]) ou string ("Support/Billing"), com até 10 níveis. |
permissions | não | {"everyone": true} ou {"emails": [...], "groups": [...]}. Usado quando as permissões da fonte são “as in the source”. |
tags | não | Até 50 etiquetas, acrescentadas às etiquetas da fonte. |
author_email | não | Se for um usuário da organização, ele se torna o responsável pelo cartão. |
updated_at | não | Quando o documento mudou no seu sistema (RFC 3339). |
metadata | não | Os seus pares chave-valor no cartão. Veja Metadados. |
category | não | Categoria do cartão: a chave de uma categoria da organização. |
verifiers, verify_interval_days | não | Quem verifica o cartão (usuários por e-mail e grupos) e com que frequência, em dias. |
upload_id | não | O arquivo do documento vindo de um upload em partes, em vez de content. |
attachments | não | Até 20 arquivos adicionais do documento, como uma lista de upload_id. Cada um vira um cartão próprio da fonte. |
Em vez de content, um único documento pode levar um arquivo de até 100 MB em uma requisição multipart/form-data: o
arquivo na parte file e os campos acima, em JSON, na parte document. Os uploads em partes para upload_id e
attachments são criados com a finalidade import_attachment e uma chave da organização que tenha o escopo
import:write; uma chave de fonte não consegue criá-los.
Duas formas de enviar
As atualizações diretas são adequadas para sistemas que avisam sobre cada alteração, como um webhook do seu helpdesk ou uma etapa no Zapier. Envie um documento quando ele for criado ou alterado e exclua-o quando for removido:
| Requisição | O que faz |
|---|---|
POST /v1/push/sources/{source_id}/documents | Cria ou substitui um documento. |
POST /v1/push/sources/{source_id}/documents/batch | Cria ou substitui muitos documentos de uma vez. |
DELETE /v1/push/sources/{source_id}/documents/{external_id} | Remove um documento. Codifique o ID para URL. |
As alterações viram cartões em cerca de um minuto; várias alterações seguidas são processadas juntas.
As sincronizações são adequadas para exportações periódicas: você envia tudo, ou tudo o que mudou, e o Sursa reconcilia. O restante deste guia trata das sincronizações.
Executar uma sincronização completa
Uma sincronização tem três passos: abrir, enviar, fechar.
1. Abrir
POST /v1/push/sources/{source_id}/syncs abre uma sincronização e retorna o
número dela:
curl -X POST https://api.sursa.com/v1/push/sources/42/syncs \
-H "Authorization: Bearer $SURSA_SOURCE_KEY" \
-H "Idempotency-Key: sync-2026-10-24" \
-H "Content-Type: application/json" \
-d '{"type": "initial", "max_minutes": 180}'{"sync_number": 7, "type": "initial", "status": "open", "opened_at": "2026-10-24T02:00:00Z",
"expires_at": "2026-10-24T05:00:00Z", "pushed": 0, "removed": 0}
typeéinitialna primeira sincronização da fonte eincrementaldepois que uma sincronização foi concluída. Uma sincronizaçãoincrementalantes de qualquer sincronização concluída retornasources.push_initial_required.- Só uma sincronização pode ficar aberta por vez: uma segunda retorna
409sources.push_sync_open. - Uma sincronização fica aberta por
max_minutes(60 por padrão, até 1440). Depois disso, ela expira: nada é arquivado, e o número dela deixa de ser aceito. Escolha um tempo bem maior do que a sua exportação leva.
2. Enviar os documentos
Envie os documentos em lotes de até 500, com o número da sincronização. O corpo de um lote tem até 50 MiB; os
arquivos dentro de um lote são passados como upload_id.
curl -X POST https://api.sursa.com/v1/push/sources/42/documents/batch \
-H "Authorization: Bearer $SURSA_SOURCE_KEY" \
-H "Idempotency-Key: sync-7-batch-1" \
-H "Content-Type: application/json" \
-d '{
"sync_number": 7,
"documents": [
{"external_id": "faq-shipping", "title": "Shipping", "content_type": "markdown",
"content": "# Shipping\n\nWe ship worldwide within 5 days.", "path": ["Support"]},
{"external_id": "faq-refunds", "title": "Refunds", "content_type": "html",
"content": "<p>Refunds take 14 days.</p>", "path": ["Support", "Billing"],
"permissions": {"groups": ["Support"]}, "metadata": {"article_id": "1042"}}
]
}'A resposta traz um resultado para cada documento, na ordem em que você os enviou:
{
"results": [
{"external_id": "faq-shipping", "result": "unchanged", "status": "indexed", "card_id": "8c1d…", "version": "5e0f…"},
{"external_id": "faq-refunds", "result": "updated", "status": "pending", "card_id": "3f2c…", "version": "a91b…"}
]
}
| Campo | Significado |
|---|---|
result | O que aconteceu agora com o documento: created, updated, unchanged, deleted, not_found, error ou skipped (em um lote atômico que não foi aplicado). |
status | Em que ponto o documento está no caminho até virar um cartão: pending, processing (o cartão já foi gravado e está sendo indexado), indexed, failed ou removed. |
card_id | O cartão do documento, assim que ele existir. |
version | Um hash do conteúdo e dos campos. O mesmo documento enviado de novo tem a mesma versão e fica unchanged. |
error, reason | Por que o documento falhou. reason é um código estável: content_invalid, file_rejected, too_large, limit_reached, index_failed ou batch_rejected (outro documento de um lote atômico falhou). |
Por padrão, cada documento de um lote é aplicado individualmente: um documento com problema não impede os outros.
Os documentos que não mudaram desde a última sincronização não precisam ser enviados de novo. Confirme-os pelo ID com
POST /v1/push/sources/{source_id}/syncs/{sync_number}/touch, até 1000
IDs por requisição, para que a reconciliação completa os mantenha:
curl -X POST https://api.sursa.com/v1/push/sources/42/syncs/7/touch \
-H "Authorization: Bearer $SURSA_SOURCE_KEY" \
-H "Content-Type: application/json" \
-d '{"external_ids": ["faq-contact", "faq-hours"]}'3. Fechar
POST /v1/push/sources/{source_id}/syncs/{sync_number}/close encerra a
sincronização:
curl -X POST https://api.sursa.com/v1/push/sources/42/syncs/7/close \
-H "Authorization: Bearer $SURSA_SOURCE_KEY" \
-H "Content-Type: application/json" \
-d '{"status": "complete"}'status | Efeito |
|---|---|
complete | Reconciliação completa. Todo documento da fonte que não foi enviado nem confirmado nesta sincronização é arquivado. Use-o só quando você enviou tudo. |
complete_incremental | Aplica o que você enviou e não arquiva nada. |
failed | Não arquiva nada e mantém o que já foi enviado. Acrescente reason (por exemplo, API_ERROR) e error_details para o registro. |
Feche com failed sempre que a sua exportação parar no meio do caminho: um complete depois de uma exportação
parcial arquivaria todos os documentos aos quais você não chegou. Repetir o mesmo fechamento retorna o mesmo
resultado.
Depois do fechamento, o Sursa transforma os documentos em cartões em cerca de um minuto. A página da fonte mostra cada
sincronização com o número de documentos enviados e arquivados, e
GET /v1/push/sources/{source_id}/syncs/{sync_number} retorna os mesmos
contadores.
Sincronizações incrementais
Entre as sincronizações completas, envie só o que mudou:
- Abra com
"type": "incremental". - Envie os documentos alterados e coloque os IDs dos removidos em
deletes(abaixo). - Feche com
complete_incremental.
Execute uma sincronização completa de tempos em tempos, por exemplo semanalmente, para captar o que uma execução incremental tenha deixado passar.
Excluir documentos em um lote
Um lote pode remover documentos junto com os que ele grava. Liste os IDs deles em deletes:
{
"sync_number": 8,
"documents": [{"external_id": "faq-shipping", "title": "Shipping", "content": "…"}],
"deletes": ["faq-old-returns", "faq-fax"]
}
Cada ID excluído recebe uma entrada própria em results, com result igual a deleted, ou not_found se a fonte
não tiver esse documento. Excluir o mesmo ID de novo não causa danos.
Lotes atômicos
Algumas alterações só fazem sentido juntas, por exemplo um documento e aquele que o substitui. Envie "atomic": true,
e o lote é aplicado por inteiro ou não é aplicado: se algum documento falhar nas verificações, nada do lote é
aplicado, e a resposta é 422 com o código sources.push_batch_rejected e o resultado de cada documento em
error.details.results: os documentos que falharam têm error, os outros, skipped.
{"sync_number": 8, "atomic": true, "documents": […], "deletes": ["faq-old-returns"]}
Sem atomic, um lote é aplicado documento por documento. Use lotes atômicos para alterações relacionadas e lotes
comuns para cargas em massa, em que um documento com problema não deve segurar o restante.
Conferir o resultado de cada documento
A resposta do lote informa como cada documento foi gravado. A transformação em cartão acontece depois, então status
ainda pode ser pending. Para encontrar os documentos de uma sincronização que não viraram cartões:
curl "https://api.sursa.com/v1/push/sources/42/syncs/7/documents?status=failed" \
-H "Authorization: Bearer $SURSA_SOURCE_KEY"Cada entrada tem external_id, title, status, card_id, version e reason, e a lista é paginada com cursor.
status também aceita pending, processing, indexed e removed.
Você também pode ser avisado, em vez de perguntar: os eventos import.sync_completed (uma sincronização foi fechada e
processada, com os totais dela) e import.document_failed (um documento não virou cartão, com o reason dele) chegam
por webhook e no feed de eventos.
Novas tentativas
- Envie uma
Idempotency-Keyem cadaPOST. Um lote repetido com a mesma chave retorna o primeiro resultado. - Mesmo sem a chave, um documento com o mesmo
external_idnunca vira um segundo cartão: ele ficaunchangedouupdated. - Se o processo parar no meio de uma sincronização, abra uma nova depois que a antiga expirar, ou feche a antiga com
failedse ainda tiver o número dela, e execute a sincronização de novo.
Limites
| Limite | Valor |
|---|---|
| Conteúdo de um documento | 5 MB |
| Arquivo em uma requisição de um único documento | 100 MB |
| Documentos por lote | 500 |
| Corpo da requisição de um lote | 50 MiB |
IDs por requisição touch | 1000 |
| Documentos por fonte | Depende do plano: a partir de 100.000 |
| Sincronização aberta | 60 minutos por padrão, até 24 horas |
Um documento acima do limite de documentos da fonte falha com o motivo limit_reached em um lote; uma requisição de
documento único responde 409 com o código
sources.push_too_many_documents. Os limites de taxa valem como para
qualquer outra requisição; veja Limites de taxa e cotas.
Erros comuns
| Resposta | Causa | O que fazer |
|---|---|---|
401 sources.push_key_invalid | A chave está errada, foi revogada ou pertence a outra fonte. | Confira a chave e o ID da fonte no caminho. |
400 sources.push_document_invalid | Falta um campo obrigatório ou um limite foi ultrapassado. | Corrija o documento; em um lote comum, só esse documento falha. |
409 sources.push_sync_open | Outra sincronização ainda está aberta. | Feche-a ou espere até que ela expire. |
409 sources.push_sync_closed | A sincronização foi fechada ou expirou. | Abra uma nova sincronização; aumente max_minutes para exportações longas. |
403 sources.feature_disabled | As fontes não fazem parte do plano da organização. | Fale com o administrador. |
429 | Requisições demais. | Aguarde o Retry-After; envie menos lotes, e maiores. |