Pular para o conteúdo
Sursa Developers

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 path de 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

ChaveComeça comO que pode fazer
Chave da fontekbsrc_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:writekb_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

CampoObrigatórioDescrição
external_idsimO seu ID estável do documento, com até 512 caracteres. Enviar o mesmo ID de novo substitui o documento.
titlesimTítulo do cartão, com até 500 caracteres. Para um arquivo, o padrão é o nome do arquivo.
contentsim, a menos que haja um arquivoAté 5 MB.
content_typenãohtml (padrão), markdown ou text. O HTML é limpo de scripts, estilos, formulários e objetos incorporados.
urlnãoLink para o documento no seu sistema, mostrado no cartão.
pathnãoPastas, como array (["Support", "Billing"]) ou string ("Support/Billing"), com até 10 níveis.
permissionsnão{"everyone": true} ou {"emails": [...], "groups": [...]}. Usado quando as permissões da fonte são “as in the source”.
tagsnãoAté 50 etiquetas, acrescentadas às etiquetas da fonte.
author_emailnãoSe for um usuário da organização, ele se torna o responsável pelo cartão.
updated_atnãoQuando o documento mudou no seu sistema (RFC 3339).
metadatanãoOs seus pares chave-valor no cartão. Veja Metadados.
categorynãoCategoria do cartão: a chave de uma categoria da organização.
verifiers, verify_interval_daysnãoQuem verifica o cartão (usuários por e-mail e grupos) e com que frequência, em dias.
upload_idnãoO arquivo do documento vindo de um upload em partes, em vez de content.
attachmentsnãoAté 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çãoO que faz
POST /v1/push/sources/{source_id}/documentsCria ou substitui um documento.
POST /v1/push/sources/{source_id}/documents/batchCria 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 é initial na primeira sincronização da fonte e incremental depois que uma sincronização foi concluída. Uma sincronização incremental antes de qualquer sincronização concluída retorna sources.push_initial_required.
  • Só uma sincronização pode ficar aberta por vez: uma segunda retorna 409 sources.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…"}
  ]
}
CampoSignificado
resultO 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).
statusEm 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_idO cartão do documento, assim que ele existir.
versionUm hash do conteúdo e dos campos. O mesmo documento enviado de novo tem a mesma versão e fica unchanged.
error, reasonPor 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"}'
statusEfeito
completeReconciliaçã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_incrementalAplica o que você enviou e não arquiva nada.
failedNã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:

  1. Abra com "type": "incremental".
  2. Envie os documentos alterados e coloque os IDs dos removidos em deletes (abaixo).
  3. 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-Key em cada POST. Um lote repetido com a mesma chave retorna o primeiro resultado.
  • Mesmo sem a chave, um documento com o mesmo external_id nunca vira um segundo cartão: ele fica unchanged ou updated.
  • Se o processo parar no meio de uma sincronização, abra uma nova depois que a antiga expirar, ou feche a antiga com failed se ainda tiver o número dela, e execute a sincronização de novo.

Limites

LimiteValor
Conteúdo de um documento5 MB
Arquivo em uma requisição de um único documento100 MB
Documentos por lote500
Corpo da requisição de um lote50 MiB
IDs por requisição touch1000
Documentos por fonteDepende do plano: a partir de 100.000
Sincronização aberta60 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

RespostaCausaO que fazer
401 sources.push_key_invalidA chave está errada, foi revogada ou pertence a outra fonte.Confira a chave e o ID da fonte no caminho.
400 sources.push_document_invalidFalta um campo obrigatório ou um limite foi ultrapassado.Corrija o documento; em um lote comum, só esse documento falha.
409 sources.push_sync_openOutra sincronização ainda está aberta.Feche-a ou espere até que ela expire.
409 sources.push_sync_closedA sincronização foi fechada ou expirou.Abra uma nova sincronização; aumente max_minutes para exportações longas.
403 sources.feature_disabledAs fontes não fazem parte do plano da organização.Fale com o administrador.
429Requisições demais.Aguarde o Retry-After; envie menos lotes, e maiores.