{"openapi":"3.1.0","info":{"title":"SignPen API","version":"1.0.0","description":"SignPen 전자계약 REST API 입니다. 문서 업로드 → 서명 요청 발송 → 상태 조회 → 체결본 다운로드를 프로그램으로 처리할 수 있습니다.\n\n## 인증 · 요금제\n대시보드 **설정 → API 키**에서 키를 발급받아 `Authorization: Bearer sp_live_...` 헤더로 보냅니다. 키는 워크스페이스 단위입니다.\n- **운영 키(sp_live_)** 와 **웹훅**은 STARTER 이상 요금제에서 쓸 수 있습니다. 발송 1건마다 건수가 차감됩니다.\n- **테스트 키(sp_test_)** 는 모든 요금제에서 쓸 수 있습니다. 건수를 차감하지 않고, 체결본 모든 페이지에 '테스트 문서 · 법적 효력 없음'이 찍힙니다. 연동 개발에 쓰세요.\n- 테스트 키로 만든 문서는 **워크스페이스 멤버의 이메일로만** 메일을 보냅니다. 외부 주소로 보내려면 `delivery: \"none\"` 으로 받은 서명 링크를 쓰거나 운영 키를 쓰세요.\n- 유료 요금제가 필요한 기능(웹훅, 대량 발송)을 FREE 에서 부르면 402 `plan_required` 를 돌려줍니다.\n\n## 기본 흐름\n1. `POST /files` 로 PDF(또는 JPG/PNG)를 올려 `file_id` 를 받습니다.\n2. `POST /envelopes` 에 문서·받는 사람·필드를 넣고 `\"send\": true` 로 바로 발송합니다. (또는 템플릿: `POST /templates/{id}/envelopes`)\n3. 웹훅(`envelope.completed` 등)으로 결과를 받거나 `GET /envelopes/{id}` 로 조회합니다.\n4. 체결되면 `GET /envelopes/{id}/completed` 로 감사추적 인증서가 붙은 PDF를 받습니다.\n\n## 서명 위치 지정 (3가지)\n1. **좌표**: 필드의 `x, y, w, h` 를 `unit` 으로 지정합니다. `ratio`(기본, 페이지 대비 0~1) · `pt` · `mm`. 원점은 **왼쪽 위**입니다. 페이지 크기(pt)는 파일 응답의 `pages` 에 있습니다. 예: `{\"type\":\"signature\",\"recipient\":\"worker\",\"unit\":\"mm\",\"page\":1,\"x\":120,\"y\":250,\"w\":50,\"h\":15}`\n2. **앵커 글자**: 좌표 대신 `anchor` 로 문서 속 글자를 찾아 그 자리에 놓습니다. 예: `{\"type\":\"signature\",\"recipient\":\"worker\",\"anchor\":{\"text\":\"(서명)\",\"occurrence\":1,\"place\":\"over\"}}`. `place`: over(글자 위, 기본) · right(오른쪽) · below(아래), `offset_x/offset_y` 로 미세 조정, 크기를 안 주면 종류별 기본 크기. 템플릿에 앵커 필드를 넣으면 발송할 때마다 문서에서 찾습니다.\n3. **서명자가 직접**: 받는 사람에 `\"placement\": \"signer\"` 를 주면 필드를 미리 놓지 않아도 됩니다. 서명자가 서명 화면에서 서명·도장·날짜를 원하는 위치에 놓고 서명합니다.\n\n## 서류관리 (템플릿)\n같은 양식에 대상자만 바뀌는 문서는 템플릿으로 미리 세팅해 두고 `POST /templates/{code}/envelopes` 에 받는 사람만 보내면 됩니다. 템플릿에 `code`(예: `employment-2026`)를 정하면 ID 대신 그 코드로 부를 수 있고, `category` 로 분류합니다. 여러 명은 `/templates/{code}/bulk` (최대 100행, STARTER 이상). 템플릿 경로의 `{id}` 자리에는 모두 템플릿 ID(`tpl_...`) 또는 code 를 쓸 수 있습니다.\n\n## 임베드 서명\n받는 사람의 `delivery` 를 `none` 으로 두면 메일을 보내지 않고, 발송 응답의 `signing_links` 로 서명 URL 을 돌려줍니다. 고객님 서비스 화면에서 바로 열거나 iframe 으로 띄우세요. 서명이 끝나면 페이지가 `window.parent.postMessage({ type: 'signpen.signed' }, '*')` 를 보냅니다.\n\n## 웹훅 서명 검증\n요청 헤더 `SignPen-Signature: t=<unix초>,v1=<서명>` 의 v1 은 `HMAC-SHA256(secret, t + \".\" + 원문 본문)` 의 hex 값입니다. 5분 넘게 지난 t 는 버리세요. 이벤트는 응답과 별도로(비동기) 보내며, 실패하면 1분·5분·30분·2시간·12시간 간격으로 재시도합니다. 같은 이벤트가 두 번 올 수 있으니 `SignPen-Delivery` 로 중복을 거르세요.\n- **secret 은 등록할 때(와 `POST /webhooks/{id}/rotate-secret` 으로 재발급할 때)만** 응답에 들어갑니다. 안전한 곳에 보관하세요.\n- URL 은 **https, 기본 포트(443), 공개 도메인**만 됩니다. IP 주소, localhost, `.local`·`.internal` 같은 내부 도메인, `user:pass@` 는 쓸 수 없습니다. 리다이렉트는 따라가지 않습니다.\n- 유료 요금제가 끝나면 등록은 남지만 발송이 멈추고, 대기 중인 재시도도 실패로 닫힙니다. 전송 기록은 30일 보관합니다.\n\n## 오류\n모든 오류는 `{ \"error\": { \"code\": \"...\", \"message\": \"...\" } }` 형식입니다. 요청 한도는 API 키당 분당 120회입니다.","contact":{"name":"SignPen","email":"help@signpen.app","url":"https://signpen.app"}},"servers":[{"url":"https://signpen.app/api/v1"}],"security":[{"bearerAuth":[]}],"tags":[{"name":"Files","description":"문서 파일 업로드"},{"name":"Envelopes","description":"서명 요청(1건 = 과금 단위)"},{"name":"Templates","description":"반복 발송용 템플릿, 대량 발송"},{"name":"Webhooks","description":"이벤트 알림 (STARTER 이상)"},{"name":"Public links","description":"링크·QR 로 누구나 서명 (동의서·신청서)"},{"name":"Account","description":"워크스페이스·잔여 건수"}],"paths":{"/me":{"get":{"tags":["Account"],"summary":"내 워크스페이스와 잔여 건수","responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/credits":{"get":{"tags":["Account"],"summary":"잔여 건수와 사용 내역","responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Credits"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/files":{"post":{"tags":["Files"],"summary":"파일 올리기","description":"PDF, JPG, PNG (최대 30MB, 100쪽. 요청 본문이 41MB를 넘으면 413). 이미지는 A4 PDF 로 바뀌고, 회전된 페이지는 보이는 방향으로 정리됩니다. multipart, JSON(base64), 원본 바이트 세 방식을 받습니다.","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file"],"properties":{"file":{"type":"string","format":"binary"},"filename":{"type":"string"}}}},"application/json":{"schema":{"type":"object","required":["content_base64"],"properties":{"content_base64":{"type":"string"},"filename":{"type":"string","example":"근로계약서.pdf"}}}},"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"responses":{"201":{"description":"업로드됨","content":{"application/json":{"schema":{"$ref":"#/components/schemas/File"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"413":{"description":"파일이 너무 큼","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/files/{id}":{"get":{"tags":["Files"],"summary":"파일 정보","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"file_id"}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/File"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/files/{id}/content":{"get":{"tags":["Files"],"summary":"파일 내려받기 (PDF)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"file_id"}],"responses":{"200":{"description":"PDF 파일","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/envelopes":{"get":{"tags":["Envelopes"],"summary":"서명 요청 목록","parameters":[{"name":"status","in":"query","schema":{"type":"string"},"description":"쉼표로 여러 개: draft,sent,in_progress,completed,declined,canceled,expired (없는 값이면 400)"},{"name":"q","in":"query","schema":{"type":"string"},"description":"제목·받는 사람 이름·이메일 검색"},{"name":"category","in":"query","schema":{"type":"string"}},{"name":"template_id","in":"query","schema":{"type":"string"}},{"name":"created_from","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"created_to","in":"query","schema":{"type":"string","format":"date-time"}},{"name":"page","in":"query","schema":{"type":"integer","default":1}},{"name":"limit","in":"query","schema":{"type":"integer","default":20,"maximum":100}}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"type":"object","properties":{"object":{"const":"list"},"data":{"type":"array","items":{"$ref":"#/components/schemas/Envelope"}},"page":{"type":"integer"},"limit":{"type":"integer"},"has_more":{"type":"boolean"},"counts":{"type":"object","additionalProperties":{"type":"integer"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Envelopes"],"summary":"서명 요청 만들기 (바로 발송 가능)","description":"`send: true` 면 만들자마자 발송하고 1건을 차감합니다. 없으면 draft 로 저장합니다. 발송에 실패하면 만들던 draft 는 지워집니다.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":100},"description":"같은 키로 다시 보내면(24시간 내) 첫 응답을 그대로 돌려줍니다(`Idempotent-Replayed: true`). 첫 요청을 처리하는 중이면 409 `idempotency_in_progress`, 같은 키에 다른 본문이면 422 `idempotency_mismatch`. 오류(4xx·5xx로 실패한 요청)는 저장하지 않아 같은 키로 다시 시도할 수 있습니다."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvelopeCreate"},"examples":{"employment":{"summary":"근로계약서 1명 서명","value":{"title":"근로계약서 — 이하늘","message":"근로계약서입니다. 확인 후 서명 부탁드립니다.","documents":[{"file_id":"f_xxxxxxxxxxxxxxxxxxxx"}],"recipients":[{"key":"worker","name":"이하늘","email":"sky@example.com"}],"fields":[{"page":1,"type":"text","recipient":null,"value":"10,320원","label":"시급","x":0.3,"y":0.42,"w":0.2,"h":0.025},{"page":1,"type":"signature","recipient":"worker","x":0.62,"y":0.86,"w":0.25,"h":0.06},{"page":1,"type":"date","recipient":"worker","x":0.62,"y":0.82,"w":0.2,"h":0.025}],"expires_in_days":7,"metadata":{"employee_no":"E-0012"},"send":true}}}}}},"responses":{"201":{"description":"생성됨","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvelopeWithLinks"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/NoCredits"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"422":{"$ref":"#/components/responses/IdempotencyMismatch"}}}},"/envelopes/{id}":{"get":{"tags":["Envelopes"],"summary":"서명 요청 조회 (필드 포함)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"envelope_id"}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Envelopes"],"summary":"draft 수정","description":"보낸 항목만 바뀝니다. recipients·fields 를 보내면 통째로 바뀝니다.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"envelope_id"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvelopeCreate"}}}},"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}},"delete":{"tags":["Envelopes"],"summary":"draft 삭제","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"envelope_id"}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"deleted":{"type":"boolean"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/envelopes/{id}/duplicate":{"post":{"tags":["Envelopes"],"summary":"복제해서 새 draft 만들기","description":"같은 문서·받는 사람·필드로 새 draft 를 만듭니다. 서명 값은 비웁니다.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"envelope_id"}],"responses":{"201":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/envelopes/{id}/fields/{field_id}/file":{"get":{"tags":["Envelopes"],"summary":"서명자가 올린 첨부파일 내려받기","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"envelope_id"},{"name":"field_id","in":"path","required":true,"schema":{"type":"string"},"description":"attachment 필드 id"}],"responses":{"200":{"description":"파일"},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/envelopes/{id}/send":{"post":{"tags":["Envelopes"],"summary":"draft 발송","description":"1건을 차감합니다. 취소·만료되면 자동 환급됩니다.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"envelope_id"},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":100},"description":"같은 키로 다시 보내면(24시간 내) 첫 응답을 그대로 돌려줍니다(`Idempotent-Replayed: true`). 첫 요청을 처리하는 중이면 409 `idempotency_in_progress`, 같은 키에 다른 본문이면 422 `idempotency_mismatch`. 오류(4xx·5xx로 실패한 요청)는 저장하지 않아 같은 키로 다시 시도할 수 있습니다."}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvelopeWithLinks"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/NoCredits"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"description":"이미 발송됨(invalid_state) 또는 같은 Idempotency-Key 처리 중","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"422":{"$ref":"#/components/responses/IdempotencyMismatch"}}}},"/envelopes/{id}/cancel":{"post":{"tags":["Envelopes"],"summary":"진행 중인 요청 취소 (건수 환급)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"envelope_id"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string"}}}}}},"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/envelopes/{id}/remind":{"post":{"tags":["Envelopes"],"summary":"아직 서명하지 않은 사람에게 리마인드 메일","description":"받는 사람마다 1시간에 1번까지. 새 링크가 발급됩니다.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"envelope_id"}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"reminded":{"type":"integer"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/envelopes/{id}/recipients/{recipient_id}":{"patch":{"tags":["Envelopes"],"summary":"받는 사람 이름·이메일 고치기 (새 링크로 재발송)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"envelope_id"},{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string"},"description":"recipient id (rcp_...)"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string","format":"email"}}}}}},"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Envelope"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/envelopes/{id}/recipients/{recipient_id}/signing-link":{"post":{"tags":["Envelopes"],"summary":"임베드 서명 링크 (재)발급","description":"이전 링크는 무효가 됩니다. 체결 후에는 완료본 열람 링크로 쓰입니다.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"envelope_id"},{"name":"recipient_id","in":"path","required":true,"schema":{"type":"string"},"description":"recipient id"}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SigningLink"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/envelopes/{id}/documents/{index}":{"get":{"tags":["Envelopes"],"summary":"원본 문서 PDF","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"envelope_id"},{"name":"index","in":"path","required":true,"schema":{"type":"integer"}}],"responses":{"200":{"description":"PDF 파일","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/envelopes/{id}/completed":{"get":{"tags":["Envelopes"],"summary":"체결본 PDF (감사추적 인증서 포함)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"envelope_id"},{"name":"download","in":"query","schema":{"type":"string","enum":["1"]},"description":"1 이면 attachment 로"}],"responses":{"200":{"description":"PDF 파일","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/Conflict"}}}},"/envelopes/{id}/audit":{"get":{"tags":["Envelopes"],"summary":"감사추적 이벤트 (해시 체인)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"envelope_id"}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditTrail"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/templates":{"get":{"tags":["Templates"],"summary":"템플릿 목록","responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Template"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Templates"],"summary":"템플릿 만들기","description":"직접 정의하거나 `from_envelope` 로 기존 문서를 템플릿으로 저장합니다. 요청자 입력 필드(recipient 없음)에 `label` 을 주면 변수가 되어 발송 때 `prefill` 로 채웁니다.","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateCreate"}}}},"responses":{"201":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanLimit"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/templates/{id}":{"get":{"tags":["Templates"],"summary":"템플릿 조회","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"template id(tpl_...) 또는 code"}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"put":{"tags":["Templates"],"summary":"템플릿 교체","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"template id(tpl_...) 또는 code"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TemplateCreate"}}}},"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Template"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Templates"],"summary":"템플릿 삭제","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"template id(tpl_...) 또는 code"}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/templates/{id}/envelopes":{"post":{"tags":["Templates"],"summary":"템플릿으로 서명 요청 (기본: 바로 발송)","description":"서류관리: 미리 세팅한 양식에 받는 사람(과 변수 값)만 바꿔 보냅니다. 발송에 실패하면 만들던 draft 는 지워집니다.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"template id(tpl_...) 또는 code (예: employment-2026)"},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":100},"description":"같은 키로 다시 보내면(24시간 내) 첫 응답을 그대로 돌려줍니다(`Idempotent-Replayed: true`). 첫 요청을 처리하는 중이면 409 `idempotency_in_progress`, 같은 키에 다른 본문이면 422 `idempotency_mismatch`. 오류(4xx·5xx로 실패한 요청)는 저장하지 않아 같은 키로 다시 시도할 수 있습니다."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FromTemplate"}}}},"responses":{"201":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/EnvelopeWithLinks"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/NoCredits"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"422":{"$ref":"#/components/responses/IdempotencyMismatch"}}}},"/templates/{id}/bulk":{"post":{"tags":["Templates"],"summary":"대량 발송 (행마다 개별 문서, 최대 100행, STARTER 이상)","description":"행마다 문서를 만들어 바로 발송합니다. 행 하나가 실패해도 나머지는 계속 보내고, 결과는 `results[]` 에 행 번호(0부터)·`ok`·`envelope_id` 또는 `error` 로 돌려줍니다(HTTP 200). 실패한 행의 draft 는 남지 않습니다. 잔여 건수가 떨어지면 그 뒤 행은 보내지 않고 실패로 표시합니다. 재시도 시 중복 발송을 막으려면 묶음마다 Idempotency-Key 를 쓰세요.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"template id(tpl_...) 또는 code"},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":100},"description":"같은 키로 다시 보내면(24시간 내) 첫 응답을 그대로 돌려줍니다(`Idempotent-Replayed: true`). 첫 요청을 처리하는 중이면 409 `idempotency_in_progress`, 같은 키에 다른 본문이면 422 `idempotency_mismatch`. 오류(4xx·5xx로 실패한 요청)는 저장하지 않아 같은 키로 다시 시도할 수 있습니다."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["rows"],"properties":{"rows":{"type":"array","maxItems":100,"items":{"$ref":"#/components/schemas/FromTemplate"}}}}}}},"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"type":"object","properties":{"total":{"type":"integer"},"sent":{"type":"integer"},"results":{"type":"array","items":{"type":"object","properties":{"row":{"type":"integer"},"ok":{"type":"boolean"},"envelope_id":{"type":"string"},"error":{"type":"string"}}}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"404":{"$ref":"#/components/responses/NotFound"},"409":{"$ref":"#/components/responses/IdempotencyInProgress"},"422":{"$ref":"#/components/responses/IdempotencyMismatch"}}}},"/public-links":{"get":{"tags":["Public links"],"summary":"공개 링크 목록","responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/PublicLink"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Public links"],"summary":"공개 링크 만들기 (링크·QR 로 누구나 서명)","description":"역할이 서명자 1개인 템플릿을 링크로 엽니다. 방문자가 이름·이메일을 넣으면 그 사람 앞으로 문서가 만들어지고 바로 서명합니다. 응답 1건마다 1건 차감. 남용을 막기 위해 방문자 IP당 시간당 10건, 링크당 시간당 30건까지 받습니다.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["template_id"],"properties":{"template_id":{"type":"string","description":"템플릿 id 또는 code"},"title":{"type":"string"},"auth":{"type":"string","enum":["email_otp","none"],"default":"email_otp"},"max_responses":{"type":["integer","null"],"minimum":1,"description":"1 이상의 정수 또는 null(무제한). 그 밖의 값은 400"},"expires_at":{"type":"string","format":"date-time"}}}}}},"responses":{"201":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicLink"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/public-links/{id}":{"patch":{"tags":["Public links"],"summary":"공개 링크 수정 (닫기·인원·기한)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"link id"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"active":{"type":"boolean"},"title":{"type":"string"},"max_responses":{"type":["integer","null"],"minimum":1},"expires_at":{"type":["string","null"],"format":"date-time"}}}}}},"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublicLink"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Public links"],"summary":"공개 링크 닫기","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"link id"}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks":{"get":{"tags":["Webhooks"],"summary":"웹훅 목록","responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Webhook"}}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"post":{"tags":["Webhooks"],"summary":"웹훅 등록 (응답에만 secret)","description":"owner·admin 만. URL 은 https · 443 포트 · 공개 도메인만 됩니다 (IP 주소, localhost, .local/.internal 등 내부 도메인, user:pass@ 불가). 워크스페이스당 10개.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["url"],"properties":{"url":{"type":"string","format":"uri","example":"https://example.com/hooks/signpen"},"events":{"type":"array","items":{"type":"string","enum":["*","envelope.sent","envelope.completed","envelope.declined","envelope.canceled","envelope.expired","recipient.sent","recipient.viewed","recipient.signed","recipient.declined","recipient.completed_viewed"]},"default":["*"]}}}}}},"responses":{"201":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookCreated"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks/{id}":{"get":{"tags":["Webhooks"],"summary":"웹훅 조회 (secret 은 돌려주지 않음)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"webhook id"}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"patch":{"tags":["Webhooks"],"summary":"웹훅 수정","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"webhook id"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"active":{"type":"boolean","description":"true/false 만 (그 밖의 값은 400)"}}}}}},"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Webhook"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}},"delete":{"tags":["Webhooks"],"summary":"웹훅 삭제","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"webhook id"}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks/{id}/test":{"post":{"tags":["Webhooks"],"summary":"ping 이벤트 보내 보기 (owner·admin)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"webhook id"}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"type":"object","properties":{"ok":{"type":"boolean"},"status":{"type":["integer","null"]},"error":{"type":["string","null"],"description":"실패 이유 (예: HTTP 500, 시간 초과). 상대 서버 응답 본문은 저장하지 않습니다"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"402":{"$ref":"#/components/responses/PlanRequired"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks/{id}/rotate-secret":{"post":{"tags":["Webhooks"],"summary":"secret 재발급 (owner·admin, 이전 secret 은 바로 무효)","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"webhook id"}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string"},"secret":{"type":"string","description":"whsec_... 지금 한 번만 보여 줍니다"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}},"/webhooks/{id}/deliveries":{"get":{"tags":["Webhooks"],"summary":"최근 전송 기록 50건","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"webhook id"}],"responses":{"200":{"description":"성공","content":{"application/json":{"schema":{"type":"object"}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"404":{"$ref":"#/components/responses/NotFound"}}}}},"webhooks":{"envelope.sent":{"post":{"summary":"envelope.sent","parameters":[{"name":"SignPen-Signature","in":"header","schema":{"type":"string","example":"t=1760000000,v1=5257a869e7..."}},{"name":"SignPen-Event","in":"header","schema":{"type":"string","example":"envelope.sent"}},{"name":"SignPen-Delivery","in":"header","schema":{"type":"string","example":"evt_..."},"description":"멱등 처리에 쓰세요"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"2xx 로 응답하면 성공으로 봅니다 (10초 안에)"}}}},"envelope.completed":{"post":{"summary":"envelope.completed","parameters":[{"name":"SignPen-Signature","in":"header","schema":{"type":"string","example":"t=1760000000,v1=5257a869e7..."}},{"name":"SignPen-Event","in":"header","schema":{"type":"string","example":"envelope.completed"}},{"name":"SignPen-Delivery","in":"header","schema":{"type":"string","example":"evt_..."},"description":"멱등 처리에 쓰세요"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"2xx 로 응답하면 성공으로 봅니다 (10초 안에)"}}}},"envelope.declined":{"post":{"summary":"envelope.declined","parameters":[{"name":"SignPen-Signature","in":"header","schema":{"type":"string","example":"t=1760000000,v1=5257a869e7..."}},{"name":"SignPen-Event","in":"header","schema":{"type":"string","example":"envelope.declined"}},{"name":"SignPen-Delivery","in":"header","schema":{"type":"string","example":"evt_..."},"description":"멱등 처리에 쓰세요"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"2xx 로 응답하면 성공으로 봅니다 (10초 안에)"}}}},"envelope.canceled":{"post":{"summary":"envelope.canceled","parameters":[{"name":"SignPen-Signature","in":"header","schema":{"type":"string","example":"t=1760000000,v1=5257a869e7..."}},{"name":"SignPen-Event","in":"header","schema":{"type":"string","example":"envelope.canceled"}},{"name":"SignPen-Delivery","in":"header","schema":{"type":"string","example":"evt_..."},"description":"멱등 처리에 쓰세요"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"2xx 로 응답하면 성공으로 봅니다 (10초 안에)"}}}},"envelope.expired":{"post":{"summary":"envelope.expired","parameters":[{"name":"SignPen-Signature","in":"header","schema":{"type":"string","example":"t=1760000000,v1=5257a869e7..."}},{"name":"SignPen-Event","in":"header","schema":{"type":"string","example":"envelope.expired"}},{"name":"SignPen-Delivery","in":"header","schema":{"type":"string","example":"evt_..."},"description":"멱등 처리에 쓰세요"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"2xx 로 응답하면 성공으로 봅니다 (10초 안에)"}}}},"recipient.sent":{"post":{"summary":"recipient.sent","parameters":[{"name":"SignPen-Signature","in":"header","schema":{"type":"string","example":"t=1760000000,v1=5257a869e7..."}},{"name":"SignPen-Event","in":"header","schema":{"type":"string","example":"recipient.sent"}},{"name":"SignPen-Delivery","in":"header","schema":{"type":"string","example":"evt_..."},"description":"멱등 처리에 쓰세요"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"2xx 로 응답하면 성공으로 봅니다 (10초 안에)"}}}},"recipient.viewed":{"post":{"summary":"recipient.viewed","parameters":[{"name":"SignPen-Signature","in":"header","schema":{"type":"string","example":"t=1760000000,v1=5257a869e7..."}},{"name":"SignPen-Event","in":"header","schema":{"type":"string","example":"recipient.viewed"}},{"name":"SignPen-Delivery","in":"header","schema":{"type":"string","example":"evt_..."},"description":"멱등 처리에 쓰세요"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"2xx 로 응답하면 성공으로 봅니다 (10초 안에)"}}}},"recipient.signed":{"post":{"summary":"recipient.signed","parameters":[{"name":"SignPen-Signature","in":"header","schema":{"type":"string","example":"t=1760000000,v1=5257a869e7..."}},{"name":"SignPen-Event","in":"header","schema":{"type":"string","example":"recipient.signed"}},{"name":"SignPen-Delivery","in":"header","schema":{"type":"string","example":"evt_..."},"description":"멱등 처리에 쓰세요"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"2xx 로 응답하면 성공으로 봅니다 (10초 안에)"}}}},"recipient.declined":{"post":{"summary":"recipient.declined","parameters":[{"name":"SignPen-Signature","in":"header","schema":{"type":"string","example":"t=1760000000,v1=5257a869e7..."}},{"name":"SignPen-Event","in":"header","schema":{"type":"string","example":"recipient.declined"}},{"name":"SignPen-Delivery","in":"header","schema":{"type":"string","example":"evt_..."},"description":"멱등 처리에 쓰세요"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"2xx 로 응답하면 성공으로 봅니다 (10초 안에)"}}}},"recipient.completed_viewed":{"post":{"summary":"recipient.completed_viewed","parameters":[{"name":"SignPen-Signature","in":"header","schema":{"type":"string","example":"t=1760000000,v1=5257a869e7..."}},{"name":"SignPen-Event","in":"header","schema":{"type":"string","example":"recipient.completed_viewed"}},{"name":"SignPen-Delivery","in":"header","schema":{"type":"string","example":"evt_..."},"description":"멱등 처리에 쓰세요"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEvent"}}}},"responses":{"200":{"description":"2xx 로 응답하면 성공으로 봅니다 (10초 안에)"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"sp_live_..."}},"responses":{"BadRequest":{"description":"요청 오류","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"validation_error","message":"recipients[0].email: 이메일 형식이 올바르지 않습니다.","details":{"errors":["..."]}}}}}},"Unauthorized":{"description":"인증 실패","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_api_key","message":"유효하지 않거나 폐기된 API 키입니다."}}}}},"NotFound":{"description":"없음","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Conflict":{"description":"상태가 맞지 않음","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"invalid_state","message":"이미 발송된 문서입니다."}}}}},"NoCredits":{"description":"잔여 건수 없음","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"insufficient_credits","message":"남은 발송 건수가 없습니다."}}}}},"PlanLimit":{"description":"요금제 한도","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"PlanRequired":{"description":"STARTER 이상 요금제 필요","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"plan_required","message":"웹훅은 STARTER 이상 요금제에서 쓸 수 있습니다."}}}}},"IdempotencyInProgress":{"description":"같은 Idempotency-Key 요청을 처리 중","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"idempotency_in_progress","message":"같은 Idempotency-Key 요청을 처리하고 있습니다. 잠시 후 다시 시도해 주세요."}}}}},"IdempotencyMismatch":{"description":"같은 Idempotency-Key 에 다른 본문","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"},"example":{"error":{"code":"idempotency_mismatch","message":"같은 Idempotency-Key 로 다른 내용의 요청을 보냈습니다. 새 키를 쓰세요."}}}}}},"schemas":{"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{}}}}},"Me":{"type":"object","properties":{"workspace":{"type":"object","properties":{"id":{"type":"integer"},"name":{"type":"string"},"plan":{"type":"string","enum":["free","starter","business","enterprise"]}}},"credits":{"type":"integer","description":"지금 쓸 수 있는 발송 건수"}}},"Credits":{"type":"object","properties":{"balance":{"type":"integer"},"lots":{"type":"array","items":{"type":"object"}},"ledger":{"type":"array","items":{"type":"object"}}}},"File":{"type":"object","properties":{"id":{"type":"string","example":"f_8Hk2..."},"filename":{"type":"string"},"size":{"type":"integer"},"page_count":{"type":"integer"},"pages":{"type":"array","items":{"type":"object","properties":{"w":{"type":"number"},"h":{"type":"number"}}},"description":"페이지 크기(pt)"},"sha256":{"type":"string"},"created_at":{"type":"string","format":"date-time"}}},"RecipientInput":{"type":"object","required":["name"],"properties":{"key":{"type":"string","description":"필드와 연결하는 키. 없으면 r1, r2...","example":"worker"},"name":{"type":"string","example":"이하늘"},"email":{"type":"string","format":"email"},"phone":{"type":"string"},"type":{"type":"string","enum":["signer","cc"],"default":"signer","description":"cc 는 체결본만 받습니다"},"order":{"type":"integer","description":"signing_order=sequential 일 때 서명 순서 (같은 번호는 동시에)"},"delivery":{"type":"string","enum":["email","none"],"default":"email","description":"none = 메일 없이 signing_links 로 직접 전달(임베드)"},"auth":{"type":"string","enum":["none","email_otp","password"],"description":"본인확인. 기본: email 이면 email_otp, none 이면 none"},"password":{"type":"string","description":"auth=password 일 때 열람 비밀번호"},"placement":{"type":"string","enum":["fixed","signer"],"default":"fixed","description":"signer = 서명자가 서명할 때 서명 위치를 직접 정함 (필드를 미리 놓지 않아도 됨)"}}},"FieldInput":{"type":"object","required":["type"],"description":"좌표(page·x·y·w·h) 또는 anchor 중 하나로 위치를 정합니다.","properties":{"document":{"type":"integer","default":0,"description":"documents 배열 순번 (anchor 면 생략 시 모든 문서에서 찾음)"},"page":{"type":"integer","minimum":1},"type":{"type":"string","enum":["signature","stamp","initial","text","date","checkbox","name","email","attachment"],"description":"attachment = 서명자가 파일(신분증·통장사본 등)을 올리는 칸"},"unit":{"type":"string","enum":["ratio","pt","mm"],"default":"ratio"},"anchor":{"type":"object","required":["text"],"properties":{"text":{"type":"string","example":"(서명)"},"occurrence":{"oneOf":[{"type":"string","enum":["first","last","all"]},{"type":"integer","minimum":1}],"default":"first"},"place":{"type":"string","enum":["over","right","below"],"default":"over"},"offset_x":{"type":"number","description":"unit 기준"},"offset_y":{"type":"number"},"ignore_missing":{"type":"boolean","default":false}}},"recipient":{"type":["string","null"],"description":"입력할 받는 사람 key. null 이면 요청자가 미리 채우는 값(value 필수)"},"x":{"type":"number","description":"unit 기준. ratio 는 0~1"},"y":{"type":"number"},"w":{"type":"number","description":"생략하면 종류별 기본 크기"},"h":{"type":"number"},"required":{"type":"boolean","default":true},"label":{"type":"string","description":"안내문 / 템플릿 변수명"},"value":{"type":"string","description":"요청자 입력값(recipient 가 null 일 때)"},"options":{"type":"object","properties":{"font_size":{"type":"number"},"validation":{"type":"string","enum":["number","phone"]},"placeholder":{"type":"string"}}}}},"EnvelopeCreate":{"type":"object","required":["title","documents","recipients"],"properties":{"title":{"type":"string","maxLength":200},"category":{"type":"string","description":"분류(폴더)"},"unit":{"type":"string","enum":["ratio","pt","mm"],"description":"fields 의 기본 좌표 단위"},"message":{"type":"string","description":"요청 메일에 들어갈 메시지"},"documents":{"type":"array","items":{"type":"object","required":["file_id"],"properties":{"file_id":{"type":"string"},"name":{"type":"string"}}},"maxItems":10},"recipients":{"type":"array","items":{"$ref":"#/components/schemas/RecipientInput"},"maxItems":20},"fields":{"type":"array","items":{"$ref":"#/components/schemas/FieldInput"}},"signing_order":{"type":"string","enum":["parallel","sequential"],"default":"parallel"},"expires_in_days":{"type":"integer","minimum":1,"maximum":90,"default":14},"reminders":{"type":"boolean","default":true,"description":"만료 3일·1일 전, 당일 자동 리마인드"},"metadata":{"type":"object","description":"자유 데이터(4KB). 웹훅에 그대로 돌아갑니다","example":{"order_no":"A-1024"}},"send":{"type":"boolean","default":false}}},"Recipient":{"type":"object","properties":{"id":{"type":"string"},"key":{"type":"string"},"type":{"type":"string"},"order":{"type":"integer"},"name":{"type":"string"},"email":{"type":["string","null"]},"delivery":{"type":"string"},"auth":{"type":"string"},"status":{"type":"string","enum":["waiting","sent","viewed","signed","declined"]},"decline_reason":{"type":["string","null"]},"sent_at":{"type":["string","null"],"format":"date-time"},"viewed_at":{"type":["string","null"],"format":"date-time"},"signed_at":{"type":["string","null"],"format":"date-time"},"completed_viewed_at":{"type":["string","null"],"format":"date-time","description":"체결본 열람 시각 (교부 확인)"}}},"Envelope":{"type":"object","properties":{"id":{"type":"string","example":"env_..."},"object":{"const":"envelope"},"title":{"type":"string"},"status":{"type":"string","enum":["draft","sent","in_progress","completed","declined","canceled","expired"]},"signing_order":{"type":"string"},"metadata":{"type":["object","null"]},"created_at":{"type":"string","format":"date-time"},"sent_at":{"type":["string","null"],"format":"date-time"},"completed_at":{"type":["string","null"],"format":"date-time"},"expires_at":{"type":["string","null"],"format":"date-time"},"original_sha256":{"type":["string","null"]},"completed_sha256":{"type":["string","null"],"description":"체결본 PDF 의 SHA-256 (위변조 검증)"},"documents":{"type":"array","items":{"type":"object"}},"recipients":{"type":"array","items":{"$ref":"#/components/schemas/Recipient"}},"fields":{"type":"array","items":{"type":"object"}}}},"SigningLink":{"type":"object","properties":{"recipient_id":{"type":"string"},"key":{"type":"string"},"signing_url":{"type":"string","format":"uri"}}},"EnvelopeWithLinks":{"allOf":[{"$ref":"#/components/schemas/Envelope"},{"type":"object","properties":{"signing_links":{"type":"array","items":{"$ref":"#/components/schemas/SigningLink"},"description":"delivery=none 인 받는 사람의 서명 URL"}}}]},"AuditTrail":{"type":"object","properties":{"envelope_id":{"type":"string"},"chain_valid":{"type":"boolean"},"events":{"type":"array","items":{"type":"object","properties":{"event":{"type":"string"},"label":{"type":"string"},"actor":{"type":"string"},"ip":{"type":"string"},"occurred_at":{"type":"string","format":"date-time"},"hash":{"type":"string"}}}}}},"TemplateCreate":{"type":"object","properties":{"name":{"type":"string","example":"알바 근로계약서"},"code":{"type":"string","example":"employment-2026","description":"API 에서 ID 대신 쓰는 고정 코드 (영문·숫자·-·_)"},"category":{"type":"string","example":"근로계약"},"message":{"type":"string"},"documents":{"type":"array","items":{"type":"object","properties":{"file_id":{"type":"string"},"name":{"type":"string"}}}},"roles":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string","example":"worker"},"label":{"type":"string","example":"근로자"},"order":{"type":"integer"},"type":{"type":"string","enum":["signer","cc"]},"placement":{"type":"string","enum":["fixed","signer"]}}}},"fields":{"type":"array","items":{"$ref":"#/components/schemas/FieldInput"},"description":"recipient 에는 roles 의 key"},"signing_order":{"type":"string","enum":["parallel","sequential"]},"from_envelope":{"type":"string","description":"이 문서를 그대로 템플릿으로 (다른 항목 무시)"}}},"PublicLink":{"type":"object","properties":{"id":{"type":"integer"},"template_id":{"type":"string"},"slug":{"type":"string"},"url":{"type":"string","format":"uri"},"title":{"type":"string"},"role":{"type":"string"},"auth":{"type":"string"},"max_responses":{"type":["integer","null"]},"responses":{"type":"integer"},"expires_at":{"type":["string","null"]},"active":{"type":"boolean"}}},"Template":{"type":"object","properties":{"id":{"type":"string"},"code":{"type":["string","null"]},"category":{"type":["string","null"]},"api_path":{"type":"string"},"name":{"type":"string"},"roles":{"type":"array","items":{"type":"object"}},"fields":{"type":"array","items":{"type":"object"}},"variables":{"type":"array","items":{"type":"string"},"description":"prefill 로 채울 변수 이름"}}},"FromTemplate":{"type":"object","required":["recipients"],"properties":{"title":{"type":"string"},"message":{"type":"string"},"recipients":{"type":"array","items":{"allOf":[{"type":"object","required":["role"],"properties":{"role":{"type":"string","description":"템플릿 roles 의 key"}}},{"$ref":"#/components/schemas/RecipientInput"}]}},"prefill":{"type":"object","additionalProperties":{"type":"string"},"example":{"시급":"10,320원","근무시간":"10:00~16:00"}},"expires_in_days":{"type":"integer"},"reminders":{"type":"boolean"},"metadata":{"type":"object"},"send":{"type":"boolean","default":true}}},"Webhook":{"type":"object","properties":{"id":{"type":"string"},"url":{"type":"string"},"events":{"type":"array","items":{"type":"string"}},"active":{"type":"boolean"},"created_at":{"type":"string","format":"date-time"}}},"WebhookCreated":{"allOf":[{"$ref":"#/components/schemas/Webhook"},{"type":"object","properties":{"secret":{"type":"string","description":"whsec_... 서명 검증용. 등록할 때만 돌려줍니다"}}}]},"WebhookEvent":{"type":"object","properties":{"id":{"type":"string","example":"evt_..."},"type":{"type":"string","enum":["envelope.sent","envelope.completed","envelope.declined","envelope.canceled","envelope.expired","recipient.sent","recipient.viewed","recipient.signed","recipient.declined","recipient.completed_viewed"]},"created_at":{"type":"string","format":"date-time"},"data":{"type":"object","description":"envelope.* 이벤트는 { envelope }, recipient.* 이벤트는 { envelope_id, recipient }"}}}}}}