// back-office
월말 정산 자동 적재
매달 같은 자료를 손으로 내려받아 정산 시스템에 넣고 있다면. 월말 입출금·승인내역을 회사 내부 시스템에 자동으로 채웁니다.
// 상황
이런 상황이라면
매달 같은 다운로드
월말마다 은행과 카드에서 같은 자료를 손으로 내려받아 정산 시스템에 붙입니다.
붙여 넣다 생기는 실수
수기 입력은 누락·중복·오타를 부릅니다.
마감이 사람에 묶인다
담당자가 자리에 없으면 마감이 밀립니다.
// 흐름
어떻게 동작하나
- 01
자격증명 등록
정산에 쓰는 은행·법인카드 자격증명을 등록합니다.
- 02
월말 자동 수집
스케줄에 맞춰 지난달 입출금·승인내역을 받습니다.
- 03
정산 시스템에 적재
표준 데이터 형식으로 온 결과를 내부 정산 DB·어드민에 그대로 넣습니다.
// schemas
사용 데이터 형식
이 시나리오에서 받는 데이터입니다. 기관이 달라도 같은 모양으로 옵니다. 필드 전체 명세는 각 데이터 형식 문서에서 확인할 수 있습니다.
// 결과물
손에 남는 것
- 월말 입출금·카드 승인내역 자동 적재
- 수기 다운로드·붙여넣기 제거
- 정해진 시각에 빠짐없이 실행
- 담당자 부재와 무관한 마감
// 실행 가이드
그대로 따라 하면 됩니다
사전 점검부터 검증까지 이 페이지에서 끝납니다. 실행 방식이 여럿이면 하나만 고르면 됩니다.
01사전 점검
아래 명령을 붙여 넣어 현재 환경이 준비됐는지 확인합니다.
어드민 백엔드 환경에 H6S_API_KEY 가 들어 있다
echo $H6S_API_KEYh6s_live_... 로 시작하는 키가 출력된다
대안으로
콘솔(https://h6s.ai)에서 발급 → 어드민 백엔드의 secret 매니저(.env · k8s secret · AWS Secrets Manager 등)에 등록워크스페이스에 은행·홈택스 자격증명이 매칭돼 있다
h6s credentials list은행(CB_*) 1건 + 홈택스(HOMETAX) 1건이 보인다
대안으로
h6s credentials create --interactive --cert어드민 백엔드에 incoming 엔드포인트가 준비돼 있다
curl -s -o /dev/null -w "%{http_code}" -X POST $ADMIN_INGEST_URL -H "Authorization: Bearer $ADMIN_TOKEN" -H "Content-Type: application/json" -d '{"schema":"bank.transactions.cb.v1","month":"2026-04","records":[]}'200 또는 204
대안으로
어드민 백엔드에 schema / month / records 를 받는 라우트 추가 (예: NestJS @Post('settlement/incoming') · Next.js route handler · Spring @PostMapping)
02실행
어드민 백엔드의 cron 핸들러가 매월 1일 09:00 KST 에 실행되는 시퀀스입니다. data-job 생성 → polling → 결과 수신 → 어드민 incoming 엔드포인트 POST 순서로 진행됩니다.
# 1) 어드민 백엔드의 스케줄러가 매월 1일 트리거.
# (자체 cron · k8s CronJob · EventBridge · 백엔드 background job 어느 쪽이든)
# 2) 4개 schema 의 data-job 을 만든다. 아래는 첫 호출 예시.
curl -s -X POST https://api.h6s.ai/api/v1/data-jobs \
-H "Authorization: Bearer $H6S_API_KEY" \
-H "Content-Type: application/json" \
-d '{"providerCode":"CB_KB","schema":"bank.transactions.cb.v1","params":{"dateRangeStart":"2026-04-01","dateRangeEnd":"2026-04-30"}}'
# 같은 호출을 hometax.tax-invoices.sales.v1 / .purchase.v1 / hometax.cash-receipts.sales.v1 까지 반복 (총 4건).
# 3) 각 응답의 id 로 status 가 SUCCEEDED 될 때까지 polling.
curl -s https://api.h6s.ai/api/v1/data-jobs/$JOB_ID \
-H "Authorization: Bearer $H6S_API_KEY"
# 4) 결과(ContractRecord 배열) 수신.
curl -s https://api.h6s.ai/api/v1/data-jobs/$JOB_ID/results \
-H "Authorization: Bearer $H6S_API_KEY"
# 5) 받은 records 를 그대로 자체 어드민 백엔드로 POST.
# 도메인 검증·중복 제거·정산 매칭은 어드민이 책임진다.
curl -s -X POST $ADMIN_INGEST_URL \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d "@results.json"위 시퀀스의 한 파일 구현체입니다. Next.js admin 의 route handler 또는 NestJS @Cron 데코레이터 안에서 호출할 수 있습니다.
// monthly-ingest.mjs 파일. 어드민 백엔드의 매월 1일 cron 핸들러.
// Next.js admin route, NestJS @Cron, k8s CronJob 어디서 호출해도 동작한다.
const H6S_API_KEY = process.env.H6S_API_KEY;
const ADMIN_INGEST_URL = process.env.ADMIN_INGEST_URL;
const ADMIN_TOKEN = process.env.ADMIN_TOKEN;
const now = new Date();
const start = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - 1, 1));
const end = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth() + 1, 0));
const iso = (d) => d.toISOString().slice(0, 10);
const month = iso(start).slice(0, 7);
const jobs = [
{ providerCode: "CB_KB", schema: "bank.transactions.cb.v1" },
{ providerCode: "HOMETAX", schema: "hometax.tax-invoices.sales.v1" },
{ providerCode: "HOMETAX", schema: "hometax.tax-invoices.purchase.v1" },
{ providerCode: "HOMETAX", schema: "hometax.cash-receipts.sales.v1" },
];
const h6s = (path, init = {}) =>
fetch(`https://api.h6s.ai/api/v1${path}`, {
...init,
headers: {
Authorization: `Bearer ${H6S_API_KEY}`,
"Content-Type": "application/json",
...(init.headers ?? {}),
},
}).then(async (r) => {
if (!r.ok) throw new Error(`${r.status} ${await r.text()}`);
return r.json();
});
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
for (const { providerCode, schema } of jobs) {
const created = await h6s("/data-jobs", {
method: "POST",
body: JSON.stringify({
providerCode,
schema,
params: { dateRangeStart: iso(start), dateRangeEnd: iso(end) },
}),
});
let status = created.status;
while (status !== "SUCCEEDED" && status !== "FAILED") {
await wait(5_000);
({ status } = await h6s(`/data-jobs/${created.id}`));
}
if (status === "FAILED") throw new Error(`${schema} job ${created.id} failed`);
const { results } = await h6s(`/data-jobs/${created.id}/results`);
await fetch(ADMIN_INGEST_URL, {
method: "POST",
headers: {
Authorization: `Bearer ${ADMIN_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ schema, month, records: results }),
});
}ContractRecord 셰이프와 schema id 는 /api-reference 의 카탈로그가 진실 원천입니다. 어드민 백엔드의 DTO 가 그 셰이프와 1:1 매핑되도록 유지합니다.
03검증
- 스케줄러가 한 번 돌면 어드민 백엔드 로그에 4개 schema 의 incoming 호출이 남습니다 (bank · sales · purchase · cash-receipt).
- 어드민 UI에서 그 월을 열면 4개 데이터가 정산 매칭 화면에 표시됩니다. 매칭·확인은 어드민 안에서 진행합니다.
- 재실행해도 어드민의 중복 제거 룰에 따라 같은 행이 두 번 들어가지 않습니다.
04흔한 에러
NO_API_KEY어드민 백엔드 환경에 H6S_API_KEY 가 없거나 빈 문자열입니다.
해결 secret 매니저에 H6S_API_KEY 등록 후 백엔드 재시작.
CREDENTIAL_INSUFFICIENT_FOR_PROVIDER은행(CB_KB) 또는 홈택스(HOMETAX) 에 매칭되는 자격증명이 워크스페이스에 없습니다.
해결 h6s credentials create --interactive --cert (공동인증서 1개로 전 기관 공용).
data-job polling 이 끝나지 않음수집이 오래 걸리는데 어드민 핸들러가 동기적으로 대기 중입니다.
해결 cron 핸들러의 timeout 을 늘리거나, 핸들러는 data-job 생성만 하고 적재는 별도 큐/웹훅으로 분리.
어드민 incoming 이 422 로 거절어드민의 DTO 가 ContractRecord 셰이프와 어긋났다 (필드명·타입 mismatch).
해결 /api-reference 의 schema 카탈로그와 어드민 DTO 를 다시 맞춥니다. 신규 필드는 어드민의 unknown-field 정책으로 흡수합니다.
4개 중 일부만 SUCCEEDED특정 schema만 자격증명·세션 문제로 실패했습니다. 위 데모는 첫 실패에서 예외를 던지고 멈춥니다.
해결 운영에서는 try/catch로 schema별로 감싸 성공한 것은 적재하고 실패한 것만 재시도 큐에 적재합니다.
05변형
같은 사례에서 자주 바꾸는 옵션. 다른 사례는 아래 이전/다음에서.
주간 cadence 로 전환
// 직전 주의 월~일 범위로 바꾼다.
const now = new Date();
const day = now.getUTCDay() || 7; // 일=7
const end = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - day));
const start = new Date(end.getTime() - 6 * 24 * 60 * 60 * 1000);여러 은행 동시 수집
// jobs 배열에 provider 별로 복제. 같은 schema, 다른 providerCode.
const jobs = [
{ providerCode: "CB_KB", schema: "bank.transactions.cb.v1" },
{ providerCode: "CB_SHINHAN", schema: "bank.transactions.cb.v1" },
{ providerCode: "CB_IBK", schema: "bank.transactions.cb.v1" },
{ providerCode: "HOMETAX", schema: "hometax.tax-invoices.sales.v1" },
// ... 나머지 schema 동일
];// 더 보기
관련 시나리오
입출금내역 2026-04 · IBK 132건
- data/bank/2026-04.csv
- data/bank/_summary.md
월말 입출금내역을 PR 로 받기
매월 1일 09:00 KST 에 전월 한 달치 입출금내역이 별도 브랜치 + PR 로 올라온다. plain-text accounting 의 표준 진입점.
주간 외부 데이터 2026-W18 · 5종 합본
- data/bank/2026-W18.csv
- data/hometax/sales/2026-W18.csv
- data/hometax/purchase/2026-W18.csv
- +2개 파일
주간 다종 수집을 한 PR로 합본
은행 + 매출/매입 세금계산서 + 매출/매입 현금영수증 5종을 matrix 로 병렬 수집해 단일 PR 에 합본.
// faq
자주 묻는 질문
정해진 날짜에 자동으로 돌릴 수 있나요?
스케줄러나 GitHub Actions 로 매월 같은 수집 요청을 자동 실행할 수 있습니다.
은행과 카드를 같이 받나요?
네. 입출금내역과 법인카드 승인내역을 같은 흐름에서 받습니다.
우리 정산 시스템에 어떻게 넣나요?
결과는 표준 데이터 형식으로 오므로 내부 적재 API 에 한 번만 매핑하면 됩니다.
// related
함께 보면 좋은 흐름
같은 데이터를 제품 연동, 내부 시스템, 자동화 작업 중 어디에 둘지에 따라 구현 방식이 달라집니다.
API 연동 — 플랫폼
API 연동 — 자체 ERP·어드민
CLI·AI 연동 — 재무 업무 자동화