인증
모든 API 요청은 API 키를 베어러 토큰으로 사용합니다. 앱의 Developer → API keys에서 키를 생성하세요. 키는 읽기 전용 또는 읽기/쓰기.
curl https://savrsoft.com/api/v1/events \ -H "Authorization: Bearer savr_live_your_key_here"
기본 URL: https://savrsoft.com/api/v1 · 모든 응답은 JSON입니다. 오류는 { "error": "…" } 형태로 4xx/5xx 상태 코드와 함께 반환됩니다.
상태 코드
| 코드 | 발생 시점 |
|---|---|
200 | 읽기 성공. |
201 | 생성 성공. 응답 본문은 { "id": 42 } — 나머지 정보가 필요하면 해당 레코드를 조회하세요. |
400 | 필수 항목이 누락된 경우입니다. 예: { "error": "name is required" }, 또는 페이지네이션 커서를 읽을 수 없는 경우입니다. |
401 | API 키가 없거나, 형식이 잘못되었거나, 폐기된 키입니다. |
403 | 쓰기 작업에 읽기 전용 키가 사용되었습니다. 다음 위치에서 읽기/쓰기 키를 생성하세요 개발자 → API 키. |
404 | 해당 레코드가 존재하지 않거나 다른 조직에 속해 있습니다 — 이 두 경우는 의도적으로 구분되지 않습니다. 인식되지 않는 경로나 메서드에 대해서도 동일하게 반환됩니다. |
이 API의 범위
이 API는 읽기 및 생성만 가능합니다. 다음과 같은 엔드포인트는 없습니다 DELETE, PUT 또는 PATCH
엔드포인트 — 이를 사용하는 요청은 다음을 반환합니다 404 Unknown endpoint, 이는 인식되지 않는 경로와 동일합니다. 삭제와
수정은 앱에서 이루어지므로, 통합을 통해 케이터링 업체의 기록을 삭제할 수 없습니다. 생성 이상의 쓰기 권한이 필요하다면,
구축하려는 내용을 알려주세요.
페이지네이션
모든 목록 엔드포인트는 페이지네이션을 지원합니다. limit (기본값 100, 최대 500) 값을 전달하고
next_cursor 가 has_more 가 false:
{
"data": [ ... ],
"has_more": true,
"next_cursor": "eyJuYW1lIjoiQWNtZSIsImlkIjo0Mn0"
}
커서를 그대로 다시 전달하면 다음 페이지를 가져올 수 있습니다. 인코딩 방식이 변경될 수 있으므로 이를 불투명한 값으로 취급하세요:
curl "https://savrsoft.com/api/v1/clients?limit=100&cursor=eyJuYW1lIjoiQWNtZSIsImlkIjo0Mn0" \ -H "Authorization: Bearer savr_live_your_key_here"
페이징은 오프셋이 아닌 마지막으로 받은 행을 기준으로 고정되므로, 페이지를 넘기는 동안 행이 생성되거나 삭제되어도 레코드를 건너뛰거나 중복해서 받는 일이 없습니다. 커서는 무기한 유효합니다.
| 매개변수 | 동작 |
|---|---|
limit | 1–500. 값이 없거나 0, 음수 또는 숫자가 아니면 기본값 100으로 처리되며, 500을 초과하는 값은 500으로 제한됩니다. |
cursor | next_cursor 이전 페이지에서 가져온 값입니다. 첫 페이지에서는 생략하세요. 손상되었거나 다른 엔드포인트에서 발급된 읽을 수 없는 커서는 처음부터 자동으로 다시 시작하는 대신 400 Invalid cursor 를 반환합니다. |
정렬 기준: /events 및 /invoices 는 최신순; /recipes 및
/clients 는 이름순이며, 이름이 같을 경우 id로 순서를 정해 이름이 중복되어도 페이징이 안정적으로 유지됩니다.
엔드포인트
/v1/me귀하의 조직 정보(id, name, currency).
/v1/events이벤트 목록을 최신순으로 조회합니다. 페이지네이션 적용 — 위의 페이지네이션 항목을 참고하세요.
/v1/events/{id}요리가 포함된 이벤트 하나입니다.
/v1/events · 읽기/쓰기 권한 필요이벤트를 생성합니다. Body:
{ "name": "Acme holiday party", "event_date": "2026-12-18",
"guests": 120, "price_per_head": 55, "event_type": "corporate" }
/v1/recipes · GET/v1/recipes/{id}레시피 목록(알레르기 유발 성분 및 식단 플래그 포함)을 조회하거나, 재료가 포함된 레시피 하나를 조회합니다. 목록은 이름순으로 정렬되며 페이지네이션이 적용됩니다.
/v1/clients · POST/v1/clients고객 목록(이름순, 페이지네이션 적용)을 조회하거나 새 고객을 생성합니다. 생성 Body: { "name": "...", "email": "...", "phone": "...", "company": "..." }
/v1/invoices · GET/v1/invoices/{id}계산된 합계가 포함된 청구서 목록을 최신순으로 조회하거나, 항목이 포함된 청구서 하나를 조회합니다. 목록에는 페이지네이션이 적용됩니다.
웹훅
엔드포인트를 다음 위치에 등록하세요: Developer → Webhooks. 이벤트가 발생하면 저희가 POST 방식으로 JSON 페이로드를 전송합니다:
{
"id": "evt_abc123",
"type": "event.inquiry",
"created_at": "2026-07-17T18:20:00.000Z",
"data": { "id": 42, "name": "Corporate lunch — Dana Ruiz", ... }
}
이벤트 유형
| 유형 | 발생 시점 |
|---|---|
event.created | 이벤트가 생성됨 |
event.inquiry | 새로운 온라인 케이터링 요청이 도착함 |
invoice.sent | 인보이스가 발송됨으로 표시됨 |
invoice.paid | 인보이스가 결제완료로 표시됨 |
proposal.approved | 클라이언트가 제안서를 승인함 |
client.created | 클라이언트가 생성됨 |
서명 검증
모든 전송에는 x-savrsoft-signature 헤더가 포함됩니다: 웹훅 서명 시크릿으로 키를 생성한 원본 요청 본문의 HMAC-SHA256입니다. 동일하게 계산하여 비교하세요:
// Node.js
const crypto = require('crypto');
const sig = crypto.createHmac('sha256', SIGNING_SECRET)
.update(rawBody).digest('hex');
if (sig !== req.headers['x-savrsoft-signature']) reject();
