{
  "openapi": "3.1.0",
  "info": {
    "title": "Siglio API",
    "version": "1.0.0",
    "summary": "Send a document for signature by email, text, or both, in one call.",
    "description": "Siglio is an e-signature API. You send a PDF with signature tags in it, Siglio delivers it to the signer by email or SMS, and you get the signed PDF and an audit certificate back.\n\nTwo things surprise people on their first integration:\n\n1. **Signature placement comes from tags inside the PDF, not from coordinates in the request.** You put `^S1` where signature one goes, `^I1` for initials, `^D1` for a date that fills itself. Those tags must be in WHITE font, or they print on the finished document as visible carets.\n2. **Answer fields are Enterprise: text and dropdowns (^M, ^T), checkboxes (^C) and choices (^R).** Other accounts get 403 `feature_not_enabled`. On Enterprise, typed answers come back as `field_values` on the envelope and in webhooks.\n\nPricing is 25 cents per envelope with no monthly fee. Every account gets 25 free documents, shared with the document studio, with no card; after that a card is needed. One key does both: there is no test or live mode.",
    "contact": { "name": "Siglio support", "url": "https://esigndev.com/contact" },
    "license": { "name": "Proprietary", "identifier": "LicenseRef-Proprietary" }
  },
  "servers": [
    { "url": "https://api.esigndev.com", "description": "Production" }
  ],
  "security": [ { "apiKey": [] } ],
  "tags": [
    { "name": "Envelopes", "description": "An envelope is one document sent to one or two signers." },
    { "name": "Templates", "description": "The templates you set up in the document studio. Each has one id, shared with the studio." },
    { "name": "Uploads", "description": "For PDFs too large to inline in the request body." }
  ],
  "paths": {
    "/v1/envelopes": {
      "post": {
        "operationId": "createEnvelope",
        "summary": "Create and send an envelope",
        "description": "Creates an envelope and delivers it immediately. The PDF must already contain the signature tags; Siglio does not place them for you.\n\nSupply the document one of three ways: `document_base64` for files up to about 3 MB, `document_id` from POST /v1/uploads for anything larger, up to 15 MB, or `template_id` to send a template you set up in the document studio. Exactly one of the three.\n\nSend an `Idempotency-Key` header. A retry with the same key and the same body returns the original envelope instead of sending a second document to the signer.",
        "tags": ["Envelopes"],
        "parameters": [ { "$ref": "#/components/parameters/IdempotencyKey" } ],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/CreateEnvelopeRequest" },
            "examples": {
              "oneSignerByText": {
                "summary": "One signer, delivered by text",
                "value": { "document_name": "Rental agreement", "document_base64": "JVBERi0xLjcK...", "delivery": "sms",
                  "signer": { "name": "Dana Reyes", "phone": "+18135550142" } }
              },
              "twoSignersMixedChannels": {
                "summary": "Two signers, each on their own channel",
                "value": { "document_name": "Mutual NDA", "document_base64": "JVBERi0xLjcK...", "delivery": "email",
                  "signers": [ { "name": "Dana Reyes", "email": "dana@example.com" },
                               { "name": "Sam Okafor", "phone": "+18135550188", "delivery": "sms" } ] }
              },
              "fromTemplate": {
                "summary": "A studio template, with the signer's details and two custom values",
                "value": { "template_id": "tpl_example4471", "delivery": "both",
                  "signer": { "name": "Dana Reyes", "email": "dana@example.com", "phone": "+18135550142",
                              "address": { "line1": "12 Bay St", "city": "Springfield", "state": "FL", "zip": "33609" } },
                  "merge": { "policy_number": "PN-4471", "job_site": "Lot 9" } }
              }
            } } }
        },
        "responses": {
          "201": { "description": "Created and delivered.",
            "headers": { "X-Request-Id": { "$ref": "#/components/headers/XRequestId" } },
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "400": { "description": "The request is malformed or contradicts itself: for example more than one of `document_base64`, `document_id` and `template_id`, or `merge` or `allow_blanks` without `template_id`. Not billed.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "402": { "$ref": "#/components/responses/PaymentRequired" },
          "403": { "description": "The key lacks the scope, the account is paused or out of allowance, account_under_review: the account's company name is being reviewed and sending is paused, or feature_not_enabled: the document has ^M, ^T, ^C or ^R tags, or the request has form_fields, on an account without data collection (Enterprise). Not billed.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" },
              "examples": {
                "featureNotEnabled": { "value": { "error": { "type": "authorization_error", "code": "feature_not_enabled", "message": "Collecting answers before signing (^M, ^T, ^C or ^R tags, or form_fields) is an Enterprise feature. Contact us to turn it on.", "request_id": "req_01ABC..." } } },
                "accountUnderReview": { "value": { "error": { "type": "authorization_error", "code": "account_under_review", "message": "Your company name is being reviewed, so sending is paused for now. Contact support at https://esigndev.com/contact and we will sort it out.", "request_id": "req_01ABC..." } } } } } } },
          "404": { "description": "`template_not_found`: no template with that id on this account. Not billed.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "422": { "description": "The PDF could not be used (unreadable, too large, or its tags do not match the signers supplied), or the account can't send yet. A feature switched off on the service is refused with its own code, type invalid_request: link_only_disabled, sender_branding_disabled, payments_disabled, attachments_disabled, two_signer_disabled. A document that can't be sent link-only is unsupported_for_link_only (type invalid_document). For a template send, all type invalid_request and none billed: missing_merge_fields (with `error.fields`: name, label, signer of each missing value), unknown_merge_field, conflicting_merge_values, invalid_merge_value, unsupported_characters (with `error.fields`), signer_count_mismatch (the request names a different number of signers than the template has), template_not_ready (a signer on the template has no signature mark), and form_fields or constraints sent with a template. Account codes, all type invalid_request: company_name_required (the account has no company name; add it in the dashboard, or send sender.company_name), invalid_request with the missing fields named (the free trial is over and the business profile is incomplete), email_unverified (confirm the sign-in email before billed sends). Not billed.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" },
              "examples": {
                "missingMergeFields": { "value": { "error": { "type": "invalid_request", "code": "missing_merge_fields", "message": "These values are missing: Policy number. Send them, or set allow_blanks to true to send with them blank.", "request_id": "req_01ABC...", "fields": [ { "name": "policy_number", "label": "Policy number", "signer": 1 } ] } } },
                "companyNameRequired": { "value": { "error": { "type": "invalid_request", "code": "company_name_required", "message": "Your account has no company name yet. Recipients see it as the sender. Add it in your dashboard at https://esigndev.com/app and send again.", "request_id": "req_01ABC..." } } },
                "profileIncomplete": { "value": { "error": { "type": "invalid_request", "code": "invalid_request", "message": "Your free trial is over, so your business profile must be complete. Missing: phone, address.", "request_id": "req_01ABC..." } } },
                "emailUnverified": { "value": { "error": { "type": "invalid_request", "code": "email_unverified", "message": "Confirm your email address before sending billed envelopes: ...", "request_id": "req_01ABC..." } } } } } } },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "description": "Our fault. Send us the request_id. Nothing is billed: if the envelope was partly created it is marked failed and a free document is given back.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/v1/envelopes/{id}": {
      "parameters": [ { "$ref": "#/components/parameters/EnvelopeId" } ],
      "get": {
        "operationId": "getEnvelope",
        "summary": "Retrieve an envelope",
        "description": "Returns the envelope in the same shape the create call returned, with its current state.",
        "tags": ["Envelopes"],
        "responses": {
          "200": { "description": "The envelope.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      },
      "delete": {
        "operationId": "voidEnvelope",
        "summary": "Void an envelope",
        "description": "Cancels an envelope that has not been completed. The signing link stops working immediately and the signer sees a cancellation page. Voiding an already-voided envelope succeeds and changes nothing. A completed envelope cannot be voided. Requires a key with read_write scope.",
        "tags": ["Envelopes"],
        "responses": {
          "200": { "description": "Voided, or already voided.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Envelope" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "422": { "$ref": "#/components/responses/BadRequest" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/envelopes/{id}/attachments/{file}": {
      "parameters": [
        { "$ref": "#/components/parameters/EnvelopeId" },
        { "name": "file", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^att_[0-9a-f]{16}$" },
          "description": "A file id from the envelope's `attachments.items[].files[].id`." }
      ],
      "get": {
        "operationId": "downloadAttachment",
        "summary": "Download a file the signer uploaded",
        "description": "Streams one file the signer uploaded in answer to the envelope's `attachments` request. The response is the file itself (a photo or a PDF) with its content type. Files are deleted 30 days after the envelope closes; after that this returns 410 `document_deleted`.",
        "tags": ["Envelopes"],
        "responses": {
          "200": { "description": "The file.", "content": {
            "image/jpeg": { "schema": { "type": "string", "format": "binary" } },
            "image/png": { "schema": { "type": "string", "format": "binary" } },
            "image/heic": { "schema": { "type": "string", "format": "binary" } },
            "application/pdf": { "schema": { "type": "string", "format": "binary" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "Unknown envelope or file.", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "410": { "description": "The file was deleted. Siglio deletes an envelope's files 30 days after it is completed, voided or failed; download them on `envelope.completed` and keep your copy.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" },
              "example": { "error": { "type": "invalid_request", "code": "document_deleted", "message": "Files are deleted 30 days after the envelope closes. Download them when envelope.completed arrives and keep your copy.", "request_id": "req_01ABC..." } } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/envelopes/{id}/document": {
      "parameters": [ { "$ref": "#/components/parameters/EnvelopeId" } ],
      "get": {
        "operationId": "downloadSignedDocument",
        "summary": "Download the signed PDF",
        "description": "Streams the completed, signed PDF. Available only once the envelope reaches `completed`; earlier states return 404. The response is the file itself, not a redirect. Files are deleted 30 days after the envelope closes; after that this returns 410 `document_deleted`.",
        "tags": ["Envelopes"],
        "responses": {
          "200": { "description": "The signed PDF.",
            "content": { "application/pdf": { "schema": { "type": "string", "format": "binary" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "Unknown envelope, or not completed yet.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "410": { "description": "The signed PDF was deleted. Siglio deletes an envelope's files 30 days after it is completed, voided or failed; download it on `envelope.completed` and keep your copy.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" },
              "example": { "error": { "type": "invalid_request", "code": "document_deleted", "message": "Signed documents are deleted 30 days after completion. Download the signed PDF when envelope.completed arrives and keep your copy.", "request_id": "req_01ABC..." } } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/templates": {
      "get": {
        "operationId": "listTemplates",
        "summary": "List your templates",
        "description": "The templates on your account, newest first. These are the same templates you set up in the document studio, with the same ids. Page with `starting_after`.",
        "tags": ["Templates"],
        "parameters": [
          { "name": "limit", "in": "query", "required": false, "description": "1 to 100. Default 25.", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 25 } },
          { "name": "starting_after", "in": "query", "required": false, "description": "A template id. Returns the templates created before that one.", "schema": { "type": "string" } }
        ],
        "responses": {
          "200": { "description": "A page of templates.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/TemplateList" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/templates/{id}": {
      "parameters": [ { "$ref": "#/components/parameters/TemplateId" } ],
      "get": {
        "operationId": "getTemplate",
        "summary": "Retrieve a template",
        "description": "One template, with every value it asks for: its name, who supplies it and where the value comes from. An id that does not exist on your account is a 404 `template_not_found`.",
        "tags": ["Templates"],
        "responses": {
          "200": { "description": "The template.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Template" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "description": "No template with that id on this account (`template_not_found`).",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    },
    "/v1/uploads": {
      "post": {
        "operationId": "createUpload",
        "summary": "Get a signed URL for a large PDF",
        "description": "For PDFs larger than about 3 MB. Returns a URL to PUT the file to, then pass the returned `id` as `document_id` when creating the envelope.\n\nThe URL expires in 15 minutes, the file must be a PDF of 15 MB or less, and the upload is consumed by a single envelope. Unused uploads are swept after 24 hours.",
        "tags": ["Uploads"],
        "responses": {
          "200": { "description": "Upload target created.",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Upload" } } } },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" }
        }
      }
    }
  },
  "webhooks": {
    "envelopeEvent": {
      "post": {
        "operationId": "envelopeEvent",
        "summary": "Envelope state change",
        "description": "Sent to your configured endpoint when an envelope's state changes. Events fire on transitions, not activity, so each one arrives once per envelope and never out of order.\n\nVerify the `X-Siglio-Signature` header before trusting the body. Acknowledge quickly with a 2xx and process from your own queue; Siglio does not offer manual replay.\n\n`envelope.partially_signed` means signer one of two has finished. It is NOT done. Only `envelope.completed` means every signature is in.",
        "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/WebhookEvent" } } } },
        "responses": { "200": { "description": "Acknowledged. Any 2xx is treated as delivered." } }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http", "scheme": "bearer",
        "description": "Your API key as a bearer token: `Authorization: Bearer sig_...` (sig_ followed by 32 characters). Keys come from your Siglio account and nowhere else. There is one kind of key: the same key sends your 25 free documents and every paid one after them. Up to 10 active keys per account."
      }
    },
    "parameters": {
      "TemplateId": { "name": "id", "in": "path", "required": true,
        "description": "The template id, e.g. `tpl_example4471`. Copy it from the Templates list in the document studio.",
        "schema": { "type": "string", "pattern": "^tpl_[a-z0-9]+$" } },
      "EnvelopeId": { "name": "id", "in": "path", "required": true,
        "description": "The envelope id, e.g. `env_7mmyc1tktzjtjya96cmjbsvb5c`.",
        "schema": { "type": "string", "pattern": "^env_[a-z0-9]+$" } },
      "IdempotencyKey": { "name": "Idempotency-Key", "in": "header", "required": false,
        "description": "Up to 255 characters. Retrying with the same key and body returns the first envelope rather than sending again. The same key with a different body is rejected.",
        "schema": { "type": "string", "maxLength": 255 } }
    },
    "headers": {
      "XRequestId": { "description": "Identifier for this request. Quote it to support.", "schema": { "type": "string" } },
      "RetryAfter": { "description": "Seconds to wait before retrying.", "schema": { "type": "integer" } }
    },
    "schemas": {
      "TemplateSummary": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "example": "tpl_example4471" },
          "name": { "type": "string", "example": "Roofing agreement" },
          "signer_count": { "type": "integer", "enum": [1, 2] },
          "pages": { "type": "integer" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "TemplateList": {
        "type": "object",
        "properties": {
          "templates": { "type": "array", "items": { "$ref": "#/components/schemas/TemplateSummary" } },
          "has_more": { "type": "boolean", "description": "True when there are older templates. Pass the last id as `starting_after`." }
        }
      },
      "MergeField": {
        "type": "object",
        "description": "One value the template prints on the document.",
        "properties": {
          "name": { "type": "string", "description": "What you call it. A custom field's name if it has one, else its key (`custom_4`, `email`).", "example": "policy_number" },
          "label": { "type": "string", "description": "What the sender sees in the studio.", "example": "Policy number" },
          "signer": { "type": "integer", "enum": [1, 2], "description": "Which signer it belongs to." },
          "source": { "type": "string", "enum": ["signer", "merge", "auto"], "description": "`signer`: taken from that signer's own details. `merge`: you send it in `merge`. `auto`: Siglio fills it in." },
          "from": { "type": "array", "items": { "type": "string" }, "description": "For `signer`: the signer keys it is built from, such as `address.city`." },
          "slot": { "type": "integer", "minimum": 1, "maximum": 10, "description": "For a custom field: its slot, also accepted as `merge.custom_N`." },
          "max_length": { "type": "integer", "description": "For a `merge` value: the most characters it can hold." }
        }
      },
      "Template": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "example": "tpl_example4471" },
          "name": { "type": "string" },
          "signer_count": { "type": "integer", "enum": [1, 2] },
          "pages": { "type": "integer" },
          "merge_fields": { "type": "array", "items": { "$ref": "#/components/schemas/MergeField" } },
          "answer_fields": { "type": "array", "items": { "type": "object" }, "description": "Present only when the template has boxes the signer fills in (Enterprise)." },
          "signing_fields": { "type": "object", "description": "Which signers have each kind of mark, such as `{ \"signature\": [1, 2], \"initial\": [], \"date\": [1] }`.",
            "properties": {
              "signature": { "type": "array", "items": { "type": "integer" } },
              "initial": { "type": "array", "items": { "type": "integer" } },
              "date": { "type": "array", "items": { "type": "integer" } } } },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        },
        "example": { "id": "tpl_example4471", "name": "Roofing agreement", "signer_count": 1, "pages": 2,
          "merge_fields": [
            { "name": "full_name", "label": "Full name", "signer": 1, "source": "signer", "from": ["name"] },
            { "name": "full_address", "label": "Full address", "signer": 1, "source": "signer", "from": ["address.line1", "address.line2", "address.city", "address.state", "address.zip"] },
            { "name": "policy_number", "label": "Policy number", "signer": 1, "source": "merge", "slot": 4, "max_length": 100 },
            { "name": "job_site", "label": "Job site", "signer": 1, "source": "merge", "slot": 5, "max_length": 100 },
            { "name": "today", "label": "Today's date", "signer": 1, "source": "auto" } ],
          "signing_fields": { "signature": [1], "initial": [], "date": [1] },
          "created_at": "2026-10-01T14:02:00Z", "updated_at": "2026-10-03T09:15:00Z" }
      },
      "CreateEnvelopeRequest": {
        "type": "object",
        "description": "Name the document one of three ways: `document_base64`, `document_id`, or `template_id`. Exactly one.",
        "properties": {
          "document_name": { "type": "string", "description": "What the signer sees this document called. Required with `document_base64` or `document_id`. With `template_id` it defaults to the template's name." },
          "template_id": { "type": "string", "pattern": "^tpl_[a-z0-9]+$", "description": "A template from the document studio (see GET /v1/templates). Siglio prints your values where the template says, places its signature and date marks, and sends it. Mutually exclusive with document_base64 and document_id. `form_fields` and `constraints` come from the template and are refused in the request." },
          "merge": { "$ref": "#/components/schemas/MergeValues", "description": "With `template_id`: shorthand for signer 1's merge values. For a second signer, put `merge` on that signer." },
          "allow_blanks": { "type": "boolean", "description": "With `template_id`: send even if some values are missing, leaving them blank on the document. Without it, missing values are a 422 `missing_merge_fields` listing each one. It does not excuse values with characters the document cannot print." },
          "document_base64": { "type": "string", "description": "The tagged PDF, base64 encoded. Up to about 3 MB. Mutually exclusive with document_id." },
          "document_id": { "type": "string", "description": "An id from POST /v1/uploads, for larger files. Mutually exclusive with document_base64.", "pattern": "^upl_[a-z0-9]+$" },
          "delivery": { "$ref": "#/components/schemas/Delivery" },
          "signer": { "$ref": "#/components/schemas/SignerInput", "description": "One signer. Use this or `signers`, never both." },
          "signers": { "type": "array", "minItems": 1, "maxItems": 2, "items": { "$ref": "#/components/schemas/SignerInput" },
            "description": "One or two signers. Signing is sequential: signer two is not notified until signer one has signed, and signer two's signing_url (returned at creation) shows a waiting page until then. Exception for now: when a signer's name is in a non-Latin script, signer two's URL works from creation. When the envelope completes, each signer is sent the signed PDF. A PDF tagged for two signers must be sent with two, and vice versa, or the call is rejected." },
          "sender": { "$ref": "#/components/schemas/Sender" },
          "payment": { "$ref": "#/components/schemas/PaymentInput" },
          "attachments": { "$ref": "#/components/schemas/AttachmentsInput" },
          "form_fields": { "type": "array", "maxItems": 100,
            "description": "Enterprise. Names, types, labels, options, formats and rules for the document's ^M / ^T / ^C / ^R fields. Refused with 403 feature_not_enabled on accounts without data collection.",
            "items": { "$ref": "#/components/schemas/FormFieldInput" } },
          "constraints": { "type": "array", "maxItems": 50,
            "description": "Enterprise. Rules across fields (exactly one, at least N, at most N, only one of these, requires). A bad entry, or constraints on a document with no ^M, ^T, ^C or ^R tags, is refused with 422 invalid_constraints naming the entry.",
            "items": { "$ref": "#/components/schemas/Constraint" } }
        }
      },
      "FormFieldInput": {
        "type": "object", "required": ["name"],
        "description": "Enterprise. One entry per named field: a ^M / ^T tag (text or select), a ^C tag (checkbox), several ^C tags sharing a name (checkbox_group), or an ^R group (radio).",
        "properties": {
          "name": { "type": "string", "pattern": "^[A-Za-z0-9_]{1,40}$", "description": "Matches the tag's name, as in ^M1:policy_number." },
          "signer": { "type": "integer", "enum": [1, 2], "description": "Optional; if given it must match the tag." },
          "type": { "type": "string", "enum": ["text", "select", "checkbox", "checkbox_group", "radio"], "default": "text", "description": "Must match the tag; a mismatch is refused, naming the tag." },
          "options": { "type": "array", "maxItems": 100, "items": { "$ref": "#/components/schemas/FieldOption" }, "description": "For select (2 to 100, taken from this list alone), checkbox_group and radio (every value must match a box on the page; the page's words to the right of each box are the default label)." },
          "format": { "type": "string", "enum": ["email", "phone", "number", "date", "zip", "ssn"], "description": "Text fields only. Checked as the signer types. Stored as phone: 10 digits, date: YYYY-MM-DD, ssn: 123-45-6789." },
          "label": { "type": "string", "maxLength": 80, "description": "What the signer reads. Defaults to the words just before the tag, else the name." },
          "required": { "type": "boolean", "description": "Overrides the tag's own ^M (required) or ^T (optional)." },
          "max_length": { "type": "integer", "minimum": 1, "maximum": 500, "description": "Text fields." },
          "width": { "type": "number", "minimum": 60, "maximum": 540, "description": "Points, text and select fields. Default 200." },
          "show_if": { "$ref": "#/components/schemas/FieldCondition" },
          "required_if": { "$ref": "#/components/schemas/FieldCondition" }
        }
      },
      "FieldCondition": {
        "type": "object", "required": ["field"],
        "description": "Exactly one of equals, not_empty, in, checked, includes (each with field), or one of all / any (one level deep). Comparisons ignore case and extra spaces. A radio or select compares by value; a checkbox group matches equals or in when any ticked value does. Checked again at signing.",
        "properties": {
          "field": { "type": "string" },
          "equals": { "type": "string" },
          "not_empty": { "type": "boolean" },
          "in": { "type": "array", "items": { "type": "string" }, "minItems": 1, "maxItems": 50 },
          "checked": { "type": "boolean", "description": "On a checkbox." },
          "includes": { "type": "string", "description": "On a checkbox group." },
          "all": { "type": "array", "items": { "$ref": "#/components/schemas/FieldCondition" }, "minItems": 2, "maxItems": 10 },
          "any": { "type": "array", "items": { "$ref": "#/components/schemas/FieldCondition" }, "minItems": 2, "maxItems": 10 }
        }
      },
      "FieldOption": {
        "type": "object", "required": ["value"],
        "properties": {
          "value": { "type": "string", "pattern": "^[A-Za-z0-9_-]{1,40}$" },
          "label": { "type": "string" }
        }
      },
      "Constraint": {
        "type": "object", "required": ["type"],
        "description": "Enterprise. fields lists 1 to 20 of one signer's fields; a lone field must be a checkbox group. Counting: a ticked checkbox is 1, a group counts its ticked boxes, a radio or text with an answer is 1, a hidden field 0. one_of: filling one clears the others. requires: field is required when the condition holds.",
        "properties": {
          "type": { "type": "string", "enum": ["exactly_one", "at_least", "at_most", "one_of", "requires"] },
          "fields": { "type": "array", "items": { "type": "string" }, "minItems": 1, "maxItems": 20 },
          "n": { "type": "integer", "description": "For at_least and at_most." },
          "field": { "type": "string", "description": "For requires." },
          "when": { "$ref": "#/components/schemas/FieldCondition" },
          "signer": { "type": "integer", "enum": [1, 2], "description": "Returned on GET in form.constraints." }
        }
      },
      "FormField": {
        "description": "A field as the envelope was created with it, returned on GET in form.fields.",
        "allOf": [
          { "$ref": "#/components/schemas/FormFieldInput" },
          { "type": "object", "required": ["signer", "type"], "properties": { "signer": { "type": "integer", "enum": [1, 2] } } }
        ]
      },
      "FieldValue": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "signer": { "type": "integer", "enum": [1, 2] },
          "label": { "type": "string" },
          "type": { "type": "string", "enum": ["text", "select", "checkbox", "checkbox_group", "radio"] },
          "format": { "type": "string", "enum": ["email", "phone", "number", "date", "zip", "ssn"], "description": "Present on formatted text fields. value is the stored shape, in full." },
          "value": { "type": ["string", "boolean", "array", "null"], "items": { "type": "string" }, "description": "Text, select or radio: a string, or null if left blank. Checkbox: true or false. Checkbox group: a list of ticked values, empty when none. Null until that signer has signed, and after deletion." },
          "submitted_at": { "type": ["string", "null"], "format": "date-time" },
          "deleted": { "type": "boolean", "description": "Present only after the 30-day deletion." }
        }
      },
      "AttachmentsInput": {
        "type": "object",
        "required": ["when", "items"],
        "description": "Ask the signer to upload files: a photo of their ID, proof of insurance, a W-9. They upload photos or PDFs on a Siglio-hosted page, from their phone. Needs email, sms or both delivery, so the signer can be sent the upload link. If the envelope also has a `payment` at the same timing, one page and one message cover both.",
        "properties": {
          "when": { "type": "string", "enum": ["before", "after"],
            "description": "No default; say which. `before`: the document is held, the signer gets the upload link instead of the signing link, and the signing link goes out once every required file is in (and any `before` payment is paid). The envelope stays in `created` and is not billed until it goes out. `after`: the document goes out as normal and the upload link is sent the moment it is completed." },
          "items": { "type": "array", "minItems": 1, "maxItems": 5,
            "items": { "type": "object", "required": ["label"], "properties": {
              "label": { "type": "string", "maxLength": 60, "description": "What to upload, as the signer will read it. \"Photo ID\", \"Last two pay stubs\"." },
              "required": { "type": "boolean", "default": true, "description": "Optional items can be left empty." } } } }
        }
      },
      "Attachments": {
        "type": "object",
        "description": "Present only on envelopes that asked for files.",
        "properties": {
          "when": { "type": "string", "enum": ["before", "after"] },
          "status": { "type": "string", "enum": ["pending", "received", "waived", "cancelled"],
            "description": "`received`: the signer uploaded every required file and confirmed. `waived`: the sender skipped the files in the studio. `cancelled`: a held envelope was voided first." },
          "items": { "type": "array", "items": { "type": "object", "properties": {
            "key": { "type": "string" },
            "label": { "type": "string" },
            "required": { "type": "boolean" },
            "files": { "type": "array", "description": "Up to 3 per item. Photos (JPG, PNG, HEIC) or PDFs, 15 MB each at most.", "items": { "type": "object", "properties": {
              "id": { "type": "string", "pattern": "^att_[0-9a-f]{16}$", "description": "Download with GET /v1/envelopes/{id}/attachments/{file}." },
              "name": { "type": "string" },
              "type": { "type": "string" },
              "size": { "type": "integer" },
              "uploaded_at": { "type": "string", "format": "date-time" } } } } } } },
          "received_at": { "type": ["string", "null"], "format": "date-time" },
          "upload_url": { "type": "string", "description": "The Siglio-hosted upload page for this envelope. Safe to share with the signer." }
        }
      },
      "PaymentInput": {
        "type": "object",
        "required": ["amount"],
        "description": "Ask the signer for a payment. It is collected by Stripe straight into the account's connected Stripe account (connect it once in the document studio: choose Connect Stripe). Siglio takes no fee; Stripe's own card fees apply. Needs email, sms or both delivery, so the signer can be sent the payment link.",
        "properties": {
          "amount": { "type": "integer", "minimum": 50, "maximum": 99999900, "description": "Whole cents. 15000 is $150.00." },
          "currency": { "type": "string", "enum": ["usd"], "default": "usd" },
          "description": { "type": "string", "maxLength": 120, "description": "What the payment is for. Shown on the payment page and in the signer's messages." },
          "when": { "type": "string", "enum": ["before", "after"], "default": "after",
            "description": "`after` (the default): the document goes out as normal and the signer is sent a payment link the moment it is completed. `before`: the document is held. The signer is sent the payment link instead of the signing link, the envelope stays in `created`, and the signing link goes out when the payment lands. A held envelope is not billed until it goes out." }
        }
      },
      "Payment": {
        "type": "object",
        "description": "Present only on envelopes that asked for a payment.",
        "properties": {
          "amount": { "type": "integer", "description": "Whole cents." },
          "currency": { "type": "string", "enum": ["usd"] },
          "description": { "type": ["string", "null"] },
          "when": { "type": "string", "enum": ["before", "after"] },
          "status": { "type": "string", "enum": ["pending", "paid", "waived", "cancelled"],
            "description": "`waived`: the sender marked it as paid outside Stripe. `cancelled`: a held envelope was voided before it was paid." },
          "paid_at": { "type": ["string", "null"], "format": "date-time" },
          "pay_url": { "type": "string", "description": "The Siglio-hosted payment page for this envelope. Safe to share with the signer; it never exposes the sender's Stripe details." }
        }
      },
      "Delivery": { "type": "string", "enum": ["auto", "email", "sms", "both", "none"], "default": "auto",
        "description": "How the signer is notified. `auto` (the default when delivery is left out) sends by email and text when the signer has both an email and a phone, otherwise by whichever one they have. `email` requires the signer's email, `sms` their phone, `both` both. `none`: you hand out the signing link yourself and Siglio contacts nobody (no request, reminder or signed copy); it applies to the whole envelope, each signer still needs an email or phone for the certificate, payment and attachments are refused with it, and the create response always carries `signers` with each `signing_url`. Responses and webhooks always carry the resolved value, never `auto`." },
      "DeliveryResolved": { "type": "string", "enum": ["email", "sms", "both", "none"],
        "description": "How this signer was actually notified, after `auto` was resolved." },
      "MergeValues": {
        "type": "object",
        "description": "Values for a template's custom fields, by name or by slot. A field named `policy_number` in slot 4 can be sent as `policy_number` or `custom_4`; sending both with different values is a 422 `conflicting_merge_values`. `reference` is also accepted when the template prints one. Each value is text or a number, up to 100 characters (reference: 60). Names are case-insensitive. A key the template does not print for that signer is a 422 `unknown_merge_field`. GET /v1/templates/{id} lists the fields.",
        "additionalProperties": { "oneOf": [ { "type": "string" }, { "type": "number" } ] },
        "example": { "policy_number": "PN-4471", "job_site": "Lot 9" }
      },
      "SignerInput": {
        "type": "object", "required": ["name"],
        "properties": {
          "name": { "type": "string", "description": "With `template_id`, first and last name are split from this on the first space." },
          "email": { "type": "string", "format": "email", "description": "Required when this signer is reached by email." },
          "phone": { "type": "string", "description": "E.164 preferred, e.g. +18135550142. Required when this signer is reached by text." },
          "phone_other": { "type": "string", "maxLength": 25, "description": "With `template_id`: a second phone number, for a template that prints one." },
          "company": { "type": "string", "maxLength": 100, "description": "With `template_id`: printed where the template has a company field." },
          "job_title": { "type": "string", "maxLength": 100, "description": "With `template_id`: printed where the template has a job title field." },
          "address": { "type": "object", "description": "With `template_id`: the signer's address, for the template's address fields.",
            "properties": {
              "line1": { "type": "string", "maxLength": 150 }, "line2": { "type": "string", "maxLength": 150 },
              "city": { "type": "string", "maxLength": 60 }, "state": { "type": "string", "maxLength": 30 },
              "zip": { "type": "string", "maxLength": 15 }, "country": { "type": "string", "maxLength": 60 } } },
          "merge": { "$ref": "#/components/schemas/MergeValues", "description": "With `template_id`: this signer's merge values." },
          "delivery": { "$ref": "#/components/schemas/Delivery", "description": "Per-signer override. Defaults to the envelope's delivery as sent; an envelope on `auto` resolves each signer from that signer's own details." }
        }
      },
      "Sender": {
        "type": "object", "required": ["company_name"],
        "description": "Identifies the business sending this document. It appears on the signer's notifications and on the signature certificate. Any field you omit falls back to the business profile saved on your account. During the free trial only company_name is required and fields neither provides are left blank on the certificate; after the trial the full profile (company name, phone, email, address) is required.",
        "properties": {
          "company_name": { "type": "string", "maxLength": 100 },
          "phone": { "type": "string", "maxLength": 25 },
          "email": { "type": "string", "format": "email", "maxLength": 254 },
          "address": { "type": "string", "maxLength": 150 },
          "city": { "type": "string", "maxLength": 60 },
          "state": { "type": "string", "maxLength": 30 },
          "zip": { "type": "string", "maxLength": 15 },
          "representative": { "type": "string", "maxLength": 100 }
        }
      },
      "Envelope": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "pattern": "^env_[a-z0-9]+$" },
          "object": { "type": "string", "const": "envelope" },
          "state": { "$ref": "#/components/schemas/EnvelopeState" },
          "document_name": { "type": "string" },
          "delivery": { "$ref": "#/components/schemas/DeliveryResolved", "description": "How signer one was notified." },
          "signer": { "$ref": "#/components/schemas/Signer", "description": "Signer one. Null only in unusual states." },
          "signers": { "type": "array", "items": { "$ref": "#/components/schemas/Signer" },
            "description": "Present on two-signer envelopes, with each signer's own signing URL, and always in the create response for delivery `none`." },
          "signing_url": { "type": ["string", "null"], "description": "Siglio-hosted link for signer one. Safe to share; it never exposes an underlying vendor." },
          "sender": { "$ref": "#/components/schemas/Sender", "description": "Echoed back as resolved, present when sender identity applies." },
          "created_at": { "type": "string", "format": "date-time" },
          "completed_at": { "type": ["string", "null"], "format": "date-time" },
          "payment": { "$ref": "#/components/schemas/Payment", "description": "Present when a payment was requested. While a `before` payment is pending, `signing_url` is the payment page." },
          "attachments": { "$ref": "#/components/schemas/Attachments", "description": "Present when files were requested. While `before` files are pending, `signing_url` is the upload page (which also takes any `before` payment)." },
          "field_values": { "type": "array", "items": { "$ref": "#/components/schemas/FieldValue" }, "description": "Enterprise. Present only on envelopes with ^M / ^T / ^C / ^R fields. Signer one's answers arrive with envelope.partially_signed, everyone's with envelope.completed. Answers are personal data." },
          "form": { "type": ["object", "null"], "description": "Enterprise. On GET /v1/envelopes/{id} only: the field definitions and group rules the envelope was created with. Null on envelopes with no answer fields.",
            "properties": {
              "fields": { "type": "array", "items": { "$ref": "#/components/schemas/FormField" } },
              "constraints": { "type": "array", "items": { "$ref": "#/components/schemas/Constraint" } }
            } }
        }
      },
      "Signer": {
        "type": "object",
        "properties": {
          "name": { "type": "string" },
          "email": { "type": ["string", "null"] },
          "phone": { "type": ["string", "null"] },
          "signed_at": { "type": ["string", "null"], "format": "date-time", "description": "When this signer signed. Null means no signature time was recorded for them, not that they have not signed: on a completed envelope every signer has signed. Never an approximation." },
          "delivery": { "type": ["string", "null"], "enum": ["email", "sms", "both", "none", null], "description": "Present in the `signers` array: how this signer was notified, resolved (`none`: not contacted by Siglio). Null on envelopes created before 28 Sep 2026." },
          "signing_url": { "type": "string", "description": "Present in the `signers` array." }
        }
      },
      "EnvelopeState": {
        "type": "string",
        "enum": ["created", "delivered", "viewed", "partially_signed", "completed", "voided", "failed"],
        "description": "created: held, not sent yet, because a `before` payment or `before` files are still due; the signing link goes out when they are in, and it is not billed until then. delivered: sent to the signer. viewed: opened. partially_signed: signer one of two has signed, NOT done. completed: every signature is in. voided: voided by you, or expired unsigned 30 days after sending. failed: could not be delivered."
      },
      "Upload": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "pattern": "^upl_[a-z0-9]+$", "description": "Pass as document_id when creating the envelope." },
          "upload_url": { "type": "string", "format": "uri", "description": "PUT the PDF here." },
          "expires_at": { "type": "string", "format": "date-time", "description": "15 minutes from issue." }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "pattern": "^evt_[a-z0-9]+$" },
          "type": { "type": "string", "enum": ["envelope.delivered", "envelope.viewed", "envelope.partially_signed", "envelope.completed", "envelope.voided", "envelope.paid", "envelope.attachments_received"],
            "description": "`envelope.paid` fires when a requested payment is recorded (paid or waived). `envelope.attachments_received` fires when the signer has uploaded every required file. Each carries the envelope with `payment` / `attachments`; for `before` steps the last one to finish is followed by the `envelope.delivered` of the release." },
          "occurred_at": { "type": "string", "format": "date-time" },
          "data": { "type": "object", "properties": { "envelope": { "$ref": "#/components/schemas/Envelope" } } }
        }
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "type": { "type": "string", "enum": ["authentication_error", "authorization_error", "invalid_request", "invalid_document", "missing_required_tag", "new_envelopes_paused", "account_paused", "self_imposed_cap_reached",
 "usage_limit_reached", "payment_required", "rate_limited", "service_unavailable", "internal_error"] },
              "code": { "type": "string", "description": "The specific reason. New codes may be added; switch on type, log code. Account codes on a send include payment_required, company_name_required, account_under_review, email_unverified, feature_not_enabled." },
              "message": { "type": "string" },
              "request_id": { "type": ["string", "null"], "description": "Quote this to support." },
              "fields": { "type": "array", "description": "On template send errors: which values the error is about. missing_merge_fields and unsupported_characters give name, label and signer; unknown_merge_field, conflicting_merge_values and invalid_merge_value give name and signer.",
                "items": { "type": "object", "properties": { "name": { "type": "string" }, "label": { "type": "string" }, "signer": { "type": "integer" } } } }
            }
          }
        }
      }
    },
    "responses": {
      "BadRequest": { "description": "The request is malformed or contradicts itself.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "Unauthorized": { "description": "Missing, unknown, revoked or expired key.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "PaymentRequired": { "description": "The account's 25 free documents are used and there is no card on file. Add a card in the dashboard at https://esigndev.com/app. Not billed.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" },
          "example": { "error": { "type": "payment_required", "code": "payment_required", "message": "Your 25 free documents are used. Add a card to keep sending.", "request_id": "req_01ABC..." } } } } },
      "Forbidden": { "description": "The key lacks the scope, or the account is paused or out of allowance.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "NotFound": { "description": "No such envelope on this account.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "UnprocessableDocument": { "description": "The PDF could not be used: unreadable, too large, or its tags do not match the signers supplied.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "RateLimited": { "description": "120 requests per minute per API key.",
        "headers": { "Retry-After": { "$ref": "#/components/headers/RetryAfter" } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "ServiceUnavailable": { "description": "Temporary. Safe to retry with your idempotency key.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } },
      "InternalError": { "description": "Our fault. Send us the request_id.",
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } } }
    }
  }
}
