← 모든 레시피

계약서

약관이 흐르고 마지막에 서명란이 오는 계약서를 만듭니다. 서식 있는 본문, 서명·텍스트·체크 입력칸, 그리고 한 항목이 페이지 경계에서 갈리지 않게 하는 법을 배웁니다.

이 템플릿 가져오기

여기서 배우는 것

  • node-html
  • node-field
  • field-sign
  • field-text
  • field-check
  • field-required
  • block-keep
  • style-lineheight
  • pdfop-fields
  • api-password

편집기로 만들기

  1. 제목 라벨을 놓고, 그 아래에 서식 본문을 넣습니다. 서식 본문은 마크업 한 덩어리를 받아 흐르는 글로 그립니다 — 조항이 늘어도 배치를 다시 잡지 않아도 됩니다.
  2. 본문에 조항을 씁니다. 허용된 태그만 남고 나머지는 태그만 사라집니다 — script · style · iframe 은 내용까지 버립니다. 붙여 넣기 전에 그 사실을 알고 계십시오.
  3. 줄 간격을 1.7 쯤으로 올립니다. 계약서는 눈으로 훑는 문서가 아니라 한 줄씩 읽는 문서입니다.
  4. 입력칸을 넣고 종류를 서명으로 고릅니다. 이름은 signature 로 둡니다 — 받는 쪽 프로그램이 값을 읽을 때 쓰는 열쇠입니다.
  5. 서명 칸에는 반드시 선을 그으십시오. 서명 칸은 PDF 뷰어조차 아무것도 그리지 않으므로, 인쇄하면 빈 공간만 남습니다.
  6. 이름과 날짜를 받을 텍스트 입력칸을 둘 더 놓고, 이름 칸은 필수로 표시합니다. 필수는 표시일 뿐 저희가 검사하지 않으며, 강제 여부는 PDF 를 여는 프로그램이 정합니다.
  7. 동의 확인용 체크 칸을 하나 놓습니다. 옆에는 무엇에 동의하는지 라벨로 적어야 합니다 — 체크 칸 자체에는 글이 없습니다.
  8. 서명란 전체를 사각형 안에 넣고 그 사각형에 "항목을 쪼개지 않기" 를 켜면, 서명란이 페이지 경계에서 반으로 갈리지 않습니다.

스키마 · API 로 만들기

  1. html 노드는 props.html 에 마크업을 받습니다. 라벨의 text 와 달리 태그가 살아 있으므로, 검증 규칙도 실패 모드도 다릅니다 — 그래서 키를 나눠 두었습니다.
  2. {{ }} 는 html 안에서 계산되지 않고 **글자로** 들어갑니다. 값이 필요하면 라벨을 쓰거나, 서버에서 마크업을 만들어 넣으십시오.
  3. 입력칸은 field 노드 하나에 fieldKind 로 종류를 정합니다 — text · check · select · list · radio · button · sign.
  4. 값을 미리 채워 둘 수는 없습니다. 서명을 받은 뒤 값을 넣으려면 POST /v1/files 로 올리고 POST /v1/pdf/fields 로 채우십시오.
  5. 개인정보가 담기므로 password 를 함께 보내 암호를 거는 것을 권합니다. 저희는 암호를 보관하지 않으므로 잃어버리면 저희도 열 수 없습니다.

만든 뒤에

서명받은 뒤 값 채우기

POST /v1/pdf/fields

요청
{
  "file": "file_01J…",
  "values": {
    "signer_name": "Do-hyun Kim",
    "signed_at": "2026-09-01",
    "agree": true
  }
}

완성 문서

아래 JSON 이 위 두 순서가 만드는 바로 그 문서입니다. 저희 테스트가 이 문서를 검증하고 경고 없이 렌더되는지 확인하므로, 그대로 붙여 넣어 쓰셔도 됩니다.

예시
{
  "v": 1,
  "unit": "px",
  "page": {
    "size": "a4",
    "orientation": "portrait",
    "margin": {
      "t": 48,
      "r": 48,
      "b": 56,
      "l": 48
    },
    "header": "",
    "footer": "{{ data.title }} · [[page]] / [[pages]]"
  },
  "fonts": [],
  "styles": [
    {
      "id": "s_h1",
      "fontFamily": "Liberation Sans",
      "fontSize": 22,
      "fontWeight": 700,
      "italic": false,
      "color": "#111111",
      "bg": "transparent",
      "align": "left",
      "valign": "top",
      "lineHeight": 1.5,
      "border": "none",
      "radius": 0,
      "opacity": 1
    },
    {
      "id": "s_body",
      "fontFamily": "Liberation Sans",
      "fontSize": 12,
      "fontWeight": 400,
      "italic": false,
      "color": "#222222",
      "bg": "transparent",
      "align": "left",
      "valign": "top",
      "lineHeight": 1.7,
      "border": "none",
      "radius": 0,
      "opacity": 1
    },
    {
      "id": "s_cap",
      "fontFamily": "Liberation Sans",
      "fontSize": 11,
      "fontWeight": 400,
      "italic": false,
      "color": "#555555",
      "bg": "transparent",
      "align": "left",
      "valign": "top",
      "lineHeight": 1.5,
      "border": "none",
      "radius": 0,
      "opacity": 1
    },
    {
      "id": "s_field",
      "fontFamily": "Liberation Sans",
      "fontSize": 12,
      "fontWeight": 400,
      "italic": false,
      "color": "#111111",
      "bg": "transparent",
      "align": "left",
      "valign": "top",
      "lineHeight": 1.5,
      "border": "none",
      "radius": 0,
      "opacity": 1
    }
  ],
  "nodes": [
    {
      "id": "n_title",
      "parent": "",
      "order": 0,
      "type": "label",
      "x": 0,
      "y": 0,
      "w": 697,
      "h": 30,
      "show": "",
      "styleId": "s_h1",
      "props": {
        "text": "{{ data.title }}",
        "src": "",
        "fit": "",
        "thickness": 0,
        "stroke": "",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Title"
    },
    {
      "id": "n_body",
      "parent": "",
      "order": 1,
      "type": "html",
      "x": 0,
      "y": 48,
      "w": 697,
      "h": 0,
      "show": "",
      "styleId": "s_body",
      "props": {
        "text": "",
        "src": "",
        "fit": "",
        "thickness": 0,
        "stroke": "",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": "<p>This agreement is entered into between the Client and the Supplier for the provision of services on the terms below.</p><h3>1. Purpose</h3><p>This agreement sets out the scope of the services the Supplier provides to the Client and the fee payable for them.</p><h3>2. Term</h3><p>The term is six months from the commencement date.</p><h3>3. Payment</h3><ul><li>30% on commencement</li><li>40% at the midpoint</li><li>30% on completion</li></ul>"
      },
      "name": "Clauses"
    },
    {
      "id": "n_signbox",
      "parent": "",
      "order": 2,
      "type": "rect",
      "x": 0,
      "y": 420,
      "w": 697,
      "h": 140,
      "show": "",
      "styleId": "s_cap",
      "props": {
        "text": "",
        "src": "",
        "fit": "",
        "thickness": 0,
        "stroke": "",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "together",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Signature block"
    },
    {
      "id": "n_agree",
      "parent": "n_signbox",
      "order": 3,
      "type": "field",
      "x": 12,
      "y": 12,
      "w": 18,
      "h": 18,
      "show": "",
      "styleId": "s_field",
      "props": {
        "text": "",
        "src": "",
        "fit": "",
        "thickness": 1,
        "stroke": "#111111",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "check",
        "fieldName": "agree",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Consent box"
    },
    {
      "id": "n_agree_l",
      "parent": "n_signbox",
      "order": 4,
      "type": "label",
      "x": 38,
      "y": 12,
      "w": 400,
      "h": 18,
      "show": "",
      "styleId": "s_cap",
      "props": {
        "text": "I have read and understood the clauses above and agree to them.",
        "src": "",
        "fit": "",
        "thickness": 0,
        "stroke": "",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Consent line"
    },
    {
      "id": "n_name",
      "parent": "n_signbox",
      "order": 5,
      "type": "field",
      "x": 12,
      "y": 52,
      "w": 260,
      "h": 28,
      "show": "",
      "styleId": "s_field",
      "props": {
        "text": "",
        "src": "",
        "fit": "",
        "thickness": 1,
        "stroke": "#8a93a0",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "text",
        "fieldName": "signer_name",
        "options": [],
        "required": true,
        "readonly": false,
        "html": ""
      },
      "name": "Name field"
    },
    {
      "id": "n_name_l",
      "parent": "n_signbox",
      "order": 6,
      "type": "label",
      "x": 12,
      "y": 84,
      "w": 260,
      "h": 16,
      "show": "",
      "styleId": "s_cap",
      "props": {
        "text": "Full name (required)",
        "src": "",
        "fit": "",
        "thickness": 0,
        "stroke": "",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Name caption"
    },
    {
      "id": "n_date",
      "parent": "n_signbox",
      "order": 7,
      "type": "field",
      "x": 292,
      "y": 52,
      "w": 160,
      "h": 28,
      "show": "",
      "styleId": "s_field",
      "props": {
        "text": "",
        "src": "",
        "fit": "",
        "thickness": 1,
        "stroke": "#8a93a0",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "text",
        "fieldName": "signed_at",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Date field"
    },
    {
      "id": "n_date_l",
      "parent": "n_signbox",
      "order": 8,
      "type": "label",
      "x": 292,
      "y": 84,
      "w": 160,
      "h": 16,
      "show": "",
      "styleId": "s_cap",
      "props": {
        "text": "Date signed",
        "src": "",
        "fit": "",
        "thickness": 0,
        "stroke": "",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Date caption"
    },
    {
      "id": "n_sign",
      "parent": "n_signbox",
      "order": 9,
      "type": "field",
      "x": 472,
      "y": 52,
      "w": 213,
      "h": 56,
      "show": "",
      "styleId": "s_field",
      "props": {
        "text": "",
        "src": "",
        "fit": "",
        "thickness": 1,
        "stroke": "#8a93a0",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "sign",
        "fieldName": "signature",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Signature field"
    },
    {
      "id": "n_sign_l",
      "parent": "n_signbox",
      "order": 10,
      "type": "label",
      "x": 472,
      "y": 112,
      "w": 213,
      "h": 16,
      "show": "",
      "styleId": "s_cap",
      "props": {
        "text": "Signature",
        "src": "",
        "fit": "",
        "thickness": 0,
        "stroke": "",
        "fill": "",
        "repeat": "",
        "as": "",
        "break": "",
        "keep": "",
        "padding": 0,
        "padY": 0,
        "padX": 0,
        "align": "",
        "valign": "",
        "wrap": "",
        "bind": "",
        "headerHeight": 0,
        "rowHeight": 0,
        "max": 0,
        "columns": [],
        "rows": 0,
        "cells": [],
        "chartKind": "",
        "codeKind": "",
        "ecc": "",
        "fieldKind": "",
        "fieldName": "",
        "options": [],
        "required": false,
        "readonly": false,
        "html": ""
      },
      "name": "Signature caption"
    }
  ]
}

요청 본문입니다. template 자리에 위 문서를, data 자리에 아래 데이터를 넣습니다.

요청
{
  "template": "(the document above)",
  "data": {
    "title": "Services agreement"
  },
  "password": "{{open password for this contract}}",
  "permissions": {
    "modify": false,
    "copy": false
  }
}