공휴일 API로 배송 예정일 계산하기: 월별 캐시와 영업일 규칙 설계

배송 예정일을 계산하는 코드는 처음에는 단순합니다. 주문일에 이틀을 더하고, 토요일과 일요일이면 다음 평일로 넘깁니다. 그런데 연휴가 끼거나 회사 자체 휴무일이 생기면 고객 화면과 물류팀의 날짜가 달라집니다. 공휴일 목록을 화면마다 따로 관리하면 같은 날짜를 수정하는 작업도 여러 곳에서 반복됩니다.
이 글에서는 APICK 공휴일 조회 API를 배송 일정 계산에 연결하는 방법을 살펴봅니다. 핵심은 월별 공휴일 데이터를 서버에서 읽고, 실제 출고 가능일은 우리 서비스의 규칙으로 계산하는 구조입니다. 달력 데이터의 수집과 회사 운영 규칙을 나누면 예약 서비스, 고객지원 일정, 정기 작업의 날짜 계산에도 같은 구조를 활용할 수 있습니다.
1. 달력 데이터와 출고 규칙의 책임을 나누기
공휴일 API는 특정 연도와 월의 공휴일 이름 및 날짜를 반환합니다. 이 정보만으로 모든 회사의 영업일이 결정되지는 않습니다. 주말 출고 여부, 창고별 휴무일, 주문 마감 시각, 재고 준비 기간은 서비스에서 정해야 합니다. 예를 들어 금요일 오후 주문을 월요일 접수로 볼지, 금요일 접수 후 다음 영업일부터 셀지는 물류팀과 먼저 합의해야 합니다.
데이터 모델을 만들 때 공휴일 목록, 회사 휴무 목록, 요일별 운영표를 각각 두는 편이 유지보수에 유리합니다. 최종 날짜에는 적용한 규칙 버전과 계산 시각도 남깁니다. 고객이 같은 주문을 다시 열었을 때 예상일이 바뀌었다면 어느 입력이나 운영표가 바뀌었는지 설명할 수 있어야 하기 때문입니다. API 응답을 곧바로 출고 확정 약속으로 사용하는 설계는 피하세요.
2. 요청과 응답에서 확인할 정확한 필드
공식 요청 명세의 엔드포인트는 POST https://apick.app/rest/holiday_info입니다. 인증은 Authorization: Bearer 헤더, 본문은 multipart/form-data를 사용합니다. 필수 입력 year는 문자열이며 범위는 1900~2200, month는 문자열이며 1~12입니다. 월을 두 자리로 통일하면 자체 캐시 키와 로그를 읽기 쉽습니다. 전송 시 boundary를 포함한 Content-Type은 클라이언트가 만들도록 둡니다.
| 필드 | 형식과 처리 기준 |
|---|---|
data.result | 공휴일 객체의 배열. 정상 조회의 빈 배열과 실패를 구분합니다. |
data.result[].date | 날짜 문자열. 공식 예시는 YYYYMMDD 형식입니다. |
data.result[].dateName | 공휴일 이름 문자열. 화면에는 텍스트로 출력합니다. |
data.success | 정수. 문서의 상태 값은 0 실패, 1 성공, 3 시간 초과입니다. |
api.success | API 서버 정상 응답 여부를 나타내는 불리언입니다. |
api.cost / api.ms / api.pl_id | 각각 사용 요금, 응답 시간, 결제 로그 ID입니다. |
성공이라는 단어가 두 군데 있으므로 역할을 분리해서 읽어야 합니다. HTTP 요청 성공, API 공통 상태, 개별 조회 상태가 모두 정상일 때 목록을 반영합니다. 공휴일이 없는 월이라면 빈 배열 자체는 유용한 결과입니다. 반면 응답 필드가 없거나 조회가 실패했는데 빈 배열로 바꿔 저장하면 장애가 있는 달을 모두 영업일로 계산할 수 있습니다. 기존 정상 데이터가 있다면 보관하고, 갱신 실패 상태를 별도로 표시하세요.
3. curl로 월별 요청 구조 확인하기
다음 예시는 2026년 10월을 조회하는 요청 형식입니다. 실제 응답 목록을 이 글에 단정해서 넣지 않았으며 실행하면 계정의 현재 정책에 따라 비용이 발생할 수 있습니다. API 키는 셸 환경변수에서 읽고 명령어나 블로그 본문에 실제 값을 넣지 않습니다.
curl --fail-with-body --request POST 'https://apick.app/rest/holiday_info' \
--header "Authorization: Bearer ${APICK_API_KEY:?Set APICK_API_KEY}" \
--form-string 'year=2026' \
--form-string 'month=10'4. Node.js에서 실패를 빈 달력으로 바꾸지 않기
서버 ESM 환경의 fetch와 FormData를 사용하는 예시입니다. 날짜 형식이 예상과 다르면 조용히 누락하지 않고 오류로 돌려보냅니다. 아래 검사는 기본적인 응답 방어 코드이며, 실제 서비스에서는 월과 날짜의 일치 여부 및 중복 날짜도 테스트할 수 있습니다.
export async function readHolidayMonth(year, month) {
if (!Number.isInteger(year) || year < 1900 || year > 2200)
throw new RangeError("year must be 1900..2200");
if (!Number.isInteger(month) || month < 1 || month > 12)
throw new RangeError("month must be 1..12");
const apiKey = process.env.APICK_API_KEY;
if (!apiKey) throw new Error("APICK_API_KEY is required");
const form = new FormData();
form.append("year", String(year));
form.append("month", String(month).padStart(2, "0"));
const response = await fetch("https://apick.app/rest/holiday_info", {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}` },
body: form,
signal: AbortSignal.timeout(120_000)
});
if (!response.ok) throw new Error(`Holiday HTTP ${response.status}`);
const payload = await response.json();
if (payload.api?.success !== true || payload.data?.success !== 1)
throw new Error("Holiday lookup did not succeed");
if (!Array.isArray(payload.data.result))
throw new Error("Unexpected holiday list");
const holidays = payload.data.result.map(item => {
if (typeof item.date !== "string" || !/^\d{8}$/.test(item.date)
|| typeof item.dateName !== "string")
throw new Error("Unexpected holiday item");
return { date: item.date, name: item.dateName };
});
return { holidays, cost: payload.api.cost, logId: payload.api.pl_id };
}
const month = await readHolidayMonth(2026, 10);
console.log({ count: month.holidays.length, cost: month.cost });공식 SDK에서는 client.holidays(year, month) 메서드를 제공합니다. 직접 REST를 호출하는 코드의 공통 메타데이터 경로와 SDK가 정리해 주는 반환값은 그대로 같다고 가정하지 마세요. SDK는 결과와 비용 메타데이터를 구분하므로 선택한 방식의 응답 계약을 기준으로 어댑터를 한 곳에 두면 변경에 대응하기 쉽습니다.
5. 월 경계, 시간대, 캐시 갱신에서 생기는 실수
배송일을 하루씩 전진시키다 다음 달로 넘어가면 다음 달의 목록도 필요합니다. 현재 달만 읽어 둔 상태에서 다음 달 첫날을 무조건 평일로 계산하는 오류를 막으려면 계산 범위가 닿는 월을 먼저 모으세요. 연말에는 연도도 바뀝니다. 문자열 키를 연도와 월의 조합으로 만들고, 다른 고객의 요청이 같은 키를 동시에 조회할 때는 진행 중인 요청을 공유하도록 설계할 수 있습니다.
공휴일은 시각이 없는 날짜입니다. 주문 시각을 서비스의 기준 시간대에 따라 날짜로 바꾼 뒤 달력과 비교하세요. 서버의 기본 시간대에 우연히 맞춰 놓으면 배포 위치가 달라졌을 때 전날 또는 다음 날이 선택될 수 있습니다. 날짜 계산 테스트는 월말, 연말, 주말 연속 구간, 공휴일 다음 날, 주문 마감 직전과 직후를 포함하는 편이 좋습니다.
캐시는 성능과 비용을 함께 줄일 수 있지만 오래된 결과를 영구 보관하는 것은 별개의 문제입니다. 향후 일정이 바뀔 수 있으므로 다음 몇 개월은 정기적으로 다시 확인하고, 운영자가 수동으로 갱신할 수 있게 만드세요. 이 글에서 특정 갱신 주기를 APICK의 보장 정책으로 제시하지는 않습니다. 서비스의 일정 변경 민감도에 맞춰 주기를 정하고 마지막 성공 갱신 시각을 관리하는 방식입니다.
6. 3P 단가로 월별 조회 비용 계산하기
2026-10-11 UTC에 확인한 공식 요금표는 공휴일 조회를 건당 3P로 안내합니다. 한 해의 12개월을 각각 한 번 조회한다는 단순 가정에서는 12 × 3P = 36P입니다. 같은 연도 데이터를 여러 고객이 쓰더라도 서버가 공유하는 월별 캐시를 활용하면 고객 수만큼 동일한 조회를 반복할 필요가 줄어듭니다. 갱신이나 추가 조회가 있으면 그 횟수를 예산에 포함하세요.
월별 화면을 열 때마다 1,000회 조회하는 설계라면 단순 계산은 3,000P입니다. 반대로 이미 조회한 월의 정상 결과를 재사용하면 조회 횟수가 줄어드는 만큼 예상 사용량도 달라집니다. 이 비교는 설계상의 계산이며 실제 절감률이나 과금 면제를 보장하지 않습니다. 운영 장부에는 api.cost와 api.pl_id를 남기고 사용 내역과 대조하세요. 포인트를 원화로 임의 환산하지 않습니다.
7. 공개 달력 데이터라도 API 키는 서버에서 관리
달력 자체가 개인 정보를 포함하지 않더라도 브라우저 코드에 키가 노출되면 다른 사람이 계정의 호출 권한을 사용할 수 있습니다. 프런트엔드는 자체 서버의 달력 경로만 호출하고, APICK 인증은 서버에서 처리하는 구성이 적절합니다. 요청 가능한 연도 범위와 조회 빈도를 서비스에서 더 좁히면 불필요한 대량 조회를 막는 데 도움이 됩니다. API 응답의 이름을 HTML 문자열로 삽입하기보다 텍스트로 렌더링하는 습관도 유지하세요.
도입 검수에서는 조회 실패 시 주문 자체를 막을지, 마지막 정상 계산값과 안내를 보여줄지 운영팀과 정합니다. 화면에는 공휴일이라는 원천 사실과 예상 출고일이라는 자체 계산을 구분해 표시하세요. 고객에게 중요한 것은 날짜가 어떻게 계산됐는지와 변경될 때 어떤 안내를 받는지입니다. API를 연결하는 단계에서 이 기준까지 정하면 담당자가 수작업으로 매번 예외를 설명하는 부담을 줄일 수 있습니다.
자주 묻는 질문
공휴일 목록만 있으면 영업일 계산이 끝나나요?
회사 휴무일, 요일별 운영 여부, 주문 마감 시각을 추가로 적용해야 합니다. 최종 일정은 서비스 정책의 결과입니다.
빈 배열이 오면 장애인가요?
공통 상태와 조회 상태가 정상이고 목록이 빈 배열이면 공휴일이 없는 결과로 처리할 수 있습니다. 누락된 필드나 실패 응답과 구분하세요.
연간 목록을 한 번에 요청할 수 있나요?
이 글에서 확인한 엔드포인트는 연도와 월을 필수로 받습니다. 연간 데이터는 필요한 12개월을 각각 조회하는 방식으로 계획하세요.
다음 단계: 실제 화면 하나에 연결해 보기
연동을 시작할 때는 APICK 공식 개발가이드에서 현재 요청 규격을 확인하고, 요금 안내에서 적용 단가를 다시 확인하세요. 서버 프로젝트에서 SDK를 사용한다면 공식 Node.js 한국어 가이드도 함께 볼 수 있습니다. 테스트 범위를 작게 정한 뒤 입력 검증, 오류 화면, 사용 포인트 기록까지 연결하면 운영 단계에서 바꿀 부분이 선명해집니다.
확인 기준: 2026-10-11 UTC. 이 글의 API 필드와 단가는 위 공식 자료를 확인해 작성했습니다. 코드는 설명용 예시이며 실제 유료 API 호출로 실행 검증하지 않았습니다. 예제의 입력은 가상 데이터 또는 사용자가 직접 설정할 자리표시자입니다. 서비스 정책과 문서는 변경될 수 있습니다.
댓글
댓글 쓰기