본문으로 건너뛰기
Sursa Developers

사용자 지정 소스로 시스템 동기화하기

사용자 지정 소스는 기본 제공 커넥터가 없는 시스템을 연결해요. 사내 위키, 티켓 관리 도구, 데이터베이스, 어디서든 내보낸 파일이 대상이에요. 코드가 Sursa로 문서를 보내면 Sursa는 소스에 설정된 컬렉션과 폴더에 카드를 만들어요. 이 카드에는 다른 소스의 카드와 똑같이 검색, 답변, 검증, 권한이 적용돼요.

카드를 직접 쓰는 방식과 달리, 사용자 지정 소스는 보낸 문서를 기록해 둬요. 전체 동기화가 끝나면 Sursa는 보내지 않은 모든 문서를 보관하므로, 회사 시스템에서 삭제한 내용이 추가 작업 없이 Sursa에도 반영돼요.

소스 설정하기

관리자는 Sursa의 Sources → New source → Custom source(소스 → 새 소스 → 사용자 지정 소스)에서 소스를 만들고 다음을 선택해요.

  • 카드가 들어갈 컬렉션과, 필요하면 폴더.
  • 폴더 모드: 모든 카드를 선택한 폴더에 두거나, 각 문서의 path를 그대로 반영한 하위 폴더를 만들어요.
  • 권한: 컬렉션의 권한을 따르거나, 각 문서에 볼 수 있는 사람을 적는 “as in the source”(소스와 동일) 방식이에요.

소스 페이지에 소스 ID가 표시돼요. 예시에서는 42를 써요.

키 고르기

키접두사할 수 있는 일
소스 키kbsrc_이 소스 하나에 문서를 쓰는 일만 할 수 있어요. 읽기나 검색은 할 수 없어요. 소스 페이지의 API keys(API 키) 섹션에서 만들어요.
import:write 스코프가 있는 조직 키kb_조직의 모든 사용자 지정 소스에 쓰고 그 상태를 읽을 수 있어요. 소스와 소스 키를 만들려면 admin 스코프가 필요해요.

연동 시스템이 하나라면 소스 키가 가장 안전해요. 외부 플랫폼에서 실행되는 연동이라면 더욱 그래요. import:write 스코프가 있는 조직 키는 여러 소스에 데이터를 보내는 서비스에 적합해요.

두 키는 같은 방식으로 보내며, 모든 푸시 엔드포인트는 /v1/push/sources/{source_id} 아래에 있어요. 키와 소스의 상태는 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}

문서

필드필수설명
external_id예문서의 고정 ID(최대 512자). 같은 ID로 다시 보내면 문서가 교체돼요.
title예카드 제목(최대 500자). 파일이면 기본값은 파일 이름이에요.
content예(파일이 없는 경우)최대 5MB.
content_type아니요html(기본값), markdown, text. HTML에서는 스크립트, 스타일, 폼, 삽입된 객체가 제거돼요.
url아니요회사 시스템에 있는 문서의 링크. 카드에 표시돼요.
path아니요폴더. 배열(["Support", "Billing"])이나 문자열("Support/Billing")로 지정하며 최대 10단계예요.
permissions아니요{"everyone": true} 또는 {"emails": [...], "groups": [...]}. 소스 권한이 “as in the source”일 때 쓰여요.
tags아니요최대 50개의 태그. 소스의 태그에 더해져요.
author_email아니요조직의 사용자라면 그 사용자가 카드의 담당자가 돼요.
updated_at아니요회사 시스템에서 문서가 바뀐 시각(RFC 3339).
metadata아니요카드에 붙이는 직접 정한 키-값 쌍. 메타데이터를 참고하세요.
category아니요카드의 카테고리. 조직에 있는 카테고리의 키예요.
verifiers, verify_interval_days아니요카드를 검증하는 사람(이메일로 지정한 사용자와 그룹)과 검증 주기(일).
upload_id아니요content 대신 보내는 문서의 파일. 파트로 나누어 업로드해서 받은 값이에요.
attachments아니요문서의 추가 파일 최대 20개. upload_id 목록이에요. 파일마다 소스의 카드가 따로 만들어져요.

content 대신, 문서 하나만 보내는 multipart/form-data 요청에는 최대 100MB의 파일을 담을 수 있어요. 파일은 file 파트에, 위의 필드는 document 파트에 JSON으로 넣으세요. upload_id와 attachments에 쓸 멀티파트 업로드는 import:write 스코프가 있는 조직 키로, 용도를 import_attachment로 지정해 만들어요. 소스 키로는 만들 수 없어요.

문서를 보내는 두 가지 방법

직접 업데이트는 헬프데스크의 웹훅이나 Zapier의 단계처럼 변경이 생길 때마다 알려 주는 시스템에 적합해요. 문서가 생성되거나 바뀌면 문서를 보내고, 제거되면 삭제하세요.

요청하는 일
POST /v1/push/sources/{source_id}/documents문서 하나를 생성하거나 교체해요.
POST /v1/push/sources/{source_id}/documents/batch여러 문서를 한 번에 생성하거나 교체해요.
DELETE /v1/push/sources/{source_id}/documents/{external_id}문서 하나를 제거해요. ID는 URL 인코딩하세요.

변경 사항은 약 1분 안에 카드에 반영되며, 연달아 생긴 여러 변경은 함께 처리돼요.

동기화는 정기적인 내보내기에 적합해요. 모든 문서나 바뀐 문서를 모두 보내면 Sursa가 대조해요. 이 가이드의 나머지 부분은 동기화를 다뤄요.

전체 동기화 실행하기

동기화는 열기, 보내기, 닫기의 세 단계로 이루어져요.

1. 열기

POST /v1/push/sources/{source_id}/syncs는 동기화를 열고 동기화 번호를 반환해요.

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이고, 동기화가 한 번 완료된 뒤에는 incremental이에요. 완료된 동기화가 없는데 incremental 동기화를 열면 sources.push_initial_required 오류를 반환해요.
  • 동기화는 한 번에 하나만 열 수 있어요. 두 번째 동기화를 열면 409 sources.push_sync_open 오류를 반환해요.
  • 동기화는 max_minutes(기본 60, 최대 1440) 동안 열려 있어요. 그 뒤에는 만료되어 아무것도 보관되지 않고, 그 번호는 더 이상 받지 않아요. 내보내기에 걸리는 시간보다 넉넉하게 긴 시간을 고르세요.

2. 문서 보내기

문서를 최대 500개씩 배치로 나누어 동기화 번호와 함께 보내세요. 배치 본문은 최대 50MiB이며, 배치 안의 파일은 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"}}
    ]
  }'

응답에는 보낸 순서대로 문서마다 결과가 담겨요.

{
  "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…"}
  ]
}
필드의미
result이번 요청에서 문서에 일어난 일: created, updated, unchanged, deleted, not_found, error, skipped(적용되지 않은 원자적 배치에서).
status문서가 카드가 되기까지의 진행 상태: pending, processing(카드가 저장되어 인덱싱 중), indexed, failed, removed.
card_id문서의 카드. 카드가 생긴 뒤에 채워져요.
version내용과 필드의 해시. 같은 문서를 다시 보내면 버전이 같고 결과는 unchanged예요.
error, reason문서가 실패한 이유. reason은 고정된 코드로 content_invalid, file_rejected, too_large, limit_reached, index_failed, batch_rejected(원자적 배치의 다른 문서가 실패함) 중 하나예요.

기본적으로 배치의 각 문서는 따로 적용돼요. 잘못된 문서 하나가 다른 문서를 막지 않아요.

지난 동기화 이후 바뀌지 않은 문서는 다시 보낼 필요가 없어요. 전체 대조에서 유지되도록 POST /v1/push/sources/{source_id}/syncs/{sync_number}/touch로 ID를 확인해 주세요. 요청당 최대 1000개의 ID를 보낼 수 있어요.

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. 닫기

POST /v1/push/sources/{source_id}/syncs/{sync_number}/close는 동기화를 끝내요.

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효과
complete전체 대조. 이번 동기화에서 보내지도 확인되지도 않은 소스의 모든 문서가 보관돼요. 모든 문서를 보냈을 때만 쓰세요.
complete_incremental보낸 내용을 적용하고 아무것도 보관하지 않아요.
failed아무것도 보관하지 않고 이미 보낸 문서는 유지해요. 로그를 위해 reason(예: API_ERROR)과 error_details를 덧붙이세요.

내보내기가 도중에 멈추면 항상 failed로 닫으세요. 일부만 내보낸 뒤 complete로 닫으면 미처 보내지 못한 모든 문서가 보관돼요. 같은 닫기 요청을 반복하면 같은 결과를 반환해요.

닫은 뒤 Sursa는 약 1분 안에 문서를 카드로 만들어요. 소스 페이지에는 각 동기화가 보낸 문서 수, 보관된 문서 수와 함께 표시되고, GET /v1/push/sources/{source_id}/syncs/{sync_number}도 같은 숫자를 반환해요.

증분 동기화

전체 동기화 사이에는 바뀐 내용만 보내세요.

  1. "type": "incremental"로 여세요.
  2. 바뀐 문서를 보내고, 제거된 문서의 ID는 deletes에 넣으세요(아래 참고).
  3. complete_incremental로 닫으세요.

증분 실행에서 놓친 부분을 잡을 수 있도록 가끔(예: 매주) 전체 동기화를 실행하세요.

배치로 문서 삭제하기

배치는 쓰는 문서와 함께 다른 문서를 제거할 수도 있어요. 제거할 ID를 deletes에 나열하세요.

{
  "sync_number": 8,
  "documents": [{"external_id": "faq-shipping", "title": "Shipping", "content": "…"}],
  "deletes": ["faq-old-returns", "faq-fax"]
}

삭제한 각 ID는 results에 별도 항목으로 들어가며, result는 deleted이거나, 소스에 그런 문서가 없으면 not_found예요. 같은 ID를 다시 삭제해도 문제가 없어요.

원자적 배치

문서와 그 문서를 대체하는 문서처럼 함께 바뀌어야 의미가 있는 변경도 있어요. "atomic": true를 보내면 배치가 전부 적용되거나 전혀 적용되지 않아요. 문서 하나라도 검사를 통과하지 못하면 배치의 어떤 내용도 적용되지 않으며, 응답은 422 상태와 sources.push_batch_rejected 코드를 반환하고 모든 문서의 결과를 error.details.results에 담아요. 실패한 문서의 결과는 error, 나머지 문서의 결과는 skipped예요.

{"sync_number": 8, "atomic": true, "documents": […], "deletes": ["faq-old-returns"]}

atomic 없이 보낸 배치는 문서별로 적용돼요. 관련된 변경에는 원자적 배치를, 잘못된 문서 하나가 나머지를 막으면 안 되는 대량 적재에는 일반 배치를 쓰세요.

문서별 결과 확인하기

배치 응답은 각 문서가 어떻게 쓰였는지 알려 줘요. 카드로 만드는 과정은 그 뒤에 진행되므로 status가 아직 pending일 수 있어요. 동기화에서 카드가 되지 못한 문서를 찾으려면 다음과 같이 요청하세요.

curl "https://api.sursa.com/v1/push/sources/42/syncs/7/documents?status=failed" \
  -H "Authorization: Bearer $SURSA_SOURCE_KEY"

각 항목에는 external_id, title, status, card_id, version, reason이 있으며, 목록은 cursor로 페이지를 넘겨요. status에는 pending, processing, indexed, removed도 지정할 수 있어요.

직접 확인하는 대신 알림을 받을 수도 있어요. import.sync_completed(동기화가 닫히고 처리됨, 합계 포함) 이벤트와 import.document_failed(문서가 카드가 되지 못함, reason 포함) 이벤트가 웹훅과 이벤트 피드로 전달돼요.

재시도

  • 모든 POST 요청에 Idempotency-Key를 보내세요. 같은 키로 반복한 배치는 첫 결과를 반환해요.
  • 키가 없어도 같은 external_id의 문서는 두 번째 카드가 되지 않아요. 결과는 unchanged나 updated예요.
  • 동기화 도중 프로세스가 멈추면, 이전 동기화가 만료된 뒤 새 동기화를 열거나, 이전 동기화의 번호를 아직 가지고 있다면 failed로 닫은 다음 동기화를 다시 실행하세요.

한도

한도값
문서 하나의 내용5MB
문서 하나를 보내는 요청의 파일100MB
배치당 문서 수500
배치 요청 본문50MiB
touch 요청당 ID 수1000
소스당 문서 수요금제에 따라 달라요. 최소 100,000
열린 동기화기본 60분, 최대 24시간

소스의 문서 한도를 넘는 문서는 배치에서는 limit_reached 이유로 실패하고, 문서 하나만 보내는 요청에서는 409 상태와 sources.push_too_many_documents 코드로 응답해요. 호출 한도는 다른 요청과 똑같이 적용돼요. 호출 한도와 할당량을 참고하세요.

자주 발생하는 오류

응답원인조치
401 sources.push_key_invalid키가 잘못되었거나, 폐기되었거나, 다른 소스의 키예요.키와 경로의 소스 ID를 확인하세요.
400 sources.push_document_invalid필수 필드가 없거나 한도를 넘었어요.문서를 고치세요. 일반 배치에서는 그 문서만 실패해요.
409 sources.push_sync_open다른 동기화가 아직 열려 있어요.그 동기화를 닫거나 만료될 때까지 기다리세요.
409 sources.push_sync_closed동기화가 닫혔거나 만료되었어요.새 동기화를 여세요. 오래 걸리는 내보내기에는 max_minutes 값을 늘리세요.
403 sources.feature_disabled조직의 요금제에 소스가 포함되어 있지 않아요.관리자에게 문의하세요.
429요청이 너무 많아요.Retry-After만큼 기다리세요. 더 큰 배치로 요청 수를 줄이세요.