The Make custom app (docspring-sspkqt, app version 1) on DocSpring API v2, with the full operation set (sc-5981).
The behaviour follows the sc-5981 build spec shared by every DocSpring integration (Zapier, n8n, Node-RED, Pipedream,
Bubble and this app), so they behave and read the same. app/UPLOAD.md says how the files get into Make;
app/APP.md has the app's identifiers and the Make API routes.
Make apps are declarative: each module is JSON describing its HTTP requests, with IML expressions ({{...}}) for
mapping. There's no JavaScript: custom IML functions need Make support to enable them for the app, so this app uses
only Make's built-in IML functions.
- Parameters (unchanged, so existing connections keep working):
region(US, EU, self-hosted),custom_host,token_id,token_secret. - Base URL:
https://api.docspring.com/api/v2,https://api-eu.docspring.com/api/v2, or the self-hosted host (https://prepended when it has no scheme) +/api/v2. - Every request sends
Authorization: Basic base64(token_id:token_secret),Accept: application/jsonandUser-Agent: docspring-make/2.log.sanitizehides the Authorization header in the base and the connection. - The connection test is
GET /api/v2/authentication, valid whenbody.authenticatedis true. A 401 says the token isn't valid. A 404 from a self-hosted host says the server doesn't have API v2 yet and needs the latest release.
- Hosts. Generate a PDF, Generate a batch of PDFs, Combine PDFs, Generate files for a submission, Create a
template and Replace a template's PDF go to the sync host (
sync.api.docspring.com,sync.api-eu.docspring.com, or the self-hosted host) with?wait=true, orwait=falsewhen the user turns off "Wait for the PDF". They use an absolute URL, so they set the auth headers themselves. Everything else uses the base URL. - Bodies are flat JSON.
versionandwaitare query parameters. - Empty values. The app uses manifest 2 processing: an empty input becomes
undefinedand Make leaves it out of the request, so the API never gets""ornullfor an empty optional value.testis sent only when it's true ({{if(parameters.test; true; undefined)}}): v2 rejectstest: falsefrom a test token.metadatais a key/value list turned into an object withtoCollection(), and left out when empty. - Template data. Template fields are mappable inputs from the
templateFieldsRPC, which readsGET /templates/{id}/schema?version=. They go in onedatacollection (the RPC'swrapper), so a field namedtestormetadatacan't clash with the module's own inputs, and the module sendsdataas the user mapped it. Each field gets its type from the schema (boolean, integer, number or text, so Make sends real booleans and numbers; JSON for objects and arrays such as image fields),requiredfrom the schema, and help:YYYY-MM-DDfor a date field, the enum values, the description, or a JSON example. Check data for a template and Create a signature request load the same fields as optional. - Dates. v2 takes
YYYY-MM-DDfor date fields. Make converts adateinput to a UTC timestamp, which can move the day, so date fields are text inputs whose help saysYYYY-MM-DDand suggestsformatDate()for a mapped date. Search submissions'created_afterandcreated_beforeare Makedateinputs: the API takes timestamps (date-time) there. - IDs in paths. Every ID that goes into a URL path goes through
encodeURL(trim(...)), so an ID such as../templates/tpl_xstays one path segment.encodeURL()leaves dots alone, and a URL parser turns a.or..segment into the current or parent path, so each module whose path takes an ID starts with a request-less step that fails first for an ID of.or..(after trimming): "The submission ID can't be "." or ".."." (a DataError, fromresponse.valid). The template field pickers make no request for such an ID. - Lists.
limit(up to 100) andcursor; responses are{data, next_cursor}. The pickers (listTemplates,listFolders) follownext_cursorup to 1,000 items. The searches (Search submissions, Search templates, List folders) follow it up to their Limit input.
- An HTTP error (base
response.error):DocSpring API error <status> <code>: <message>, a line per detail (<pointer or parameter>: <message>), thenSubmission: <id>(or the batch, combined PDF or template ID) andRequest ID: <id>. The details come fromcreateJSON(body.error.details)reshaped withreplace()and regular expressions; if Make reads those differently, the details show as JSON, never[object Object]. The captures skip JSON-escaped characters, so a message with quotes (must be one of: "Red", "Blue") keeps its text, and the quotes and backslashes are unescaped at the end. Every backslash in those regular expressions is inside a character class, so they read the same whether or not IML strings process backslash escapes, which Make doesn't document. - A 201 with a failure state (
error,syntax_error,image_download_failed,image_processing_failed, or a templatedocument.stateofpassword_required,malformed_pdf,unknown_errorordownload_failed): the module'sresponse.validfails with the state,error_messageand the record ID. - A 504
polling_timeout: "The PDF wasn't ready in time. Get it later with Get a submission (), or use the submission.processed trigger." (and the combined PDF, batch and template versions). It's a RuntimeError rather than Make's default for a 5xx (a connection error, which Make retries), so a retry can't generate a second PDF.
29 modules. Labels, descriptions, types and bindings are in app/UPLOAD.md; groups in app/groups.imljson.
| Group | Modules |
|---|---|
| PDFs | Generate a PDF, Generate a batch of PDFs, Get a submission batch, Check data for a template, Combine PDFs, Get a combined PDF |
| Submissions | Get a submission, Search submissions, Generate files for a submission, Expire a submission |
| Signature requests | Create a signature request, Create a signing link, Get a signature request |
| Templates | Search templates, Get a template, Create a template, Replace a template's PDF, Copy a template, Move a template, Publish a template version, Get a template's page images |
| Template fields | List template fields, Add fields to a template, Update fields in a template, Remove fields from a template |
| Folders | List folders, Create a folder |
| Triggers | Watch events |
| Other | Make an API call |
Each module returns the v2 resource as the API returns it. Interfaces and samples are generated from the OpenAPI
spec by scripts/generate_interfaces.py.
- Template pickers. A template select (
rpc://listTemplates) nests a version select (latestby default, which is the latest published version, or the draft when nothing is published), which nests the template fields. - Create a signature request makes three requests: it creates the submission with
signature_requests, makes a signing link for each signature request (arepeatover the IDs, with the chosen link type: email, 7 days, by default; API, 24 hours; or none), and returns the submission plussigning_links,first_signature_request_idandfirst_signing_url. Emails to signers chooses whether DocSpring emails them (send_email, the API's default): its email options (subject, message, CC, reminders, reply-to, notification emails, notify when signed) are nested under "DocSpring emails each signer their link", and the link type under "I send the links". With DocSpring's emails the module makes no links, so a scenario can't send a second one. It's one choice for every signer: the API's per-signersend_emailisn't offered, since a mix would need links for only some of the IDs (Make an API call can do it). Signing order and each signer's Signing step work either way. Check data for a template offers the same Emails to signers, email options, order and steps (no link type) and sends them only with signers, since the API refuses them without. Each signer'sfieldsis a list of field names to type or map (also in Check data for a template), as List template fields shows them: the fullname, such asaddress/street, which a signature request needs (the schema nests it underaddress). It isn't a picker because Make calls an RPC inside an array item with only that item's inputs, so the RPC can't read the module's template (tried 27 Oct 2026). - Make an API call takes a path relative to
/api/v2, a method (GET, POST, PUT or DELETE: API v2 has no PATCH endpoints and answers one with a 405), query string, headers and a body.
One instant trigger on a dedicated webhook, as in v1.
- Parameters: Events (every v4 event except
webhook.*: 24 events), Mode (live, test, or both), Templates (rpc://listTemplates), Include submission data (default on). - attach:
POST /webhookswithurl,event_types,name: "Make",mode,template_idsandinclude_submission_data, neverversion, so DocSpring sends version 4 events. It stores the webhook'sidand first event type. Only submission, signature request and template events can be limited to templates (DocSpring rejects the others with a 422), so with templates chosen, a request-less step first fails with the events that can't be: "Templates only limit submission, signature request and template events. Clear Templates, or remove these events: combined_pdf.processed, ...". - detach: lists the webhooks with that event type, 100 a page (a
repeatthat followsnext_cursoruntil it finds the webhook, up to 20 pages), and deletes the stored webhook only if it's in the list. Make can't treat an HTTP 404 as success, so this is how detach tolerates a webhook someone already deleted in DocSpring. - Output: the version 4 event as delivered (
id= the event ID,event_type,created_at,data= the v2 resource) plusresource_type(the event name before the first dot) andresource_id(data.id).
- Labels follow Make's naming conventions, not the spec's names: an action is a verb and an item with "a" or "an"
("Generate a PDF", "Create a signature request"), a search a plural without one ("Search templates", "List
folders"), all in sentence case. The v1 review asked for it. The module IDs keep the spec's names (
findTemplate). - Signing links are a separate
signing_linkslist, not asigning_urladded to each signature request. IML can't merge a value into each item of a list without custom IML functions.first_signing_urlandfirst_signature_request_idare as specified. - Date fields are text inputs with
YYYY-MM-DDhelp (the spec's first option). Formatting each dynamic date field withformatDate()would need custom IML functions. - The self-hosted 404 message doesn't offer a previous version: the app changes in place, so there's none.
- Pickers stop at 1,000 items, as the spec says. Make's best practice suggests 300 to 500 for an RPC that pages 100 at a time, so a reviewer may ask for less.
- A module times out after 40 seconds (Make's default; Make support can raise it for an app). A PDF that takes longer fails in Make, not with DocSpring's 504; turning off "Wait for the PDF" avoids it.
- A request URL can be at most 512 characters and an error message 1,024.
scripts/check.pychecks both. - A module makes at most 100 requests, 50 of them pages. Create a signature request's link loop stops at 100 signers.
If Make enables custom IML functions for the app (or the token gets the "apps edit" permission), they would allow:
a signing_url on each signature request, date fields as Make date inputs formatted to YYYY-MM-DD, and enum
fields as dropdowns.
Make runs the IML, so there's no test framework. scripts/check.py checks that every file is valid JSON with closed
{{ }}, that nothing calls /api/v1, Make's URL and error-message limits, each module's sections, every rpc://
reference, the groups, and that app/UPLOAD.md names every module and RPC.
It also runs scripts/iml_tests.py, which runs the app's own expressions with scripts/iml.py, a small IML evaluator
that follows Make's documented behaviour (JavaScript operators, 1-based indexes, replace() with /regex/flags). The
tests cover the error message (with and without backslash escapes in IML strings), encoded IDs and the ./.. checks
in every URL, that no field inside an array item calls an RPC, the Watch events template check and its signer email events, Remove template
fields' typed IDs, detach's paging, the request bodies of Create a signature request and Check data for each Emails to signers choice, the Word output
formats, and Generate files for a submission's check of the files it was asked for. The evaluator knows only the functions the tests need, and fails on any other.
The API v2 version was uploaded and tested live on 27 Oct 2026, and the signer email settings and Word outputs on
4 Nov 2026 (README.md). app/UPLOAD.md ("Testing") lists the live tests and the Make behaviour these files rely on
that hasn't been tried.
The v1 app (API v1) was validated live in Make in September 2026: the connection, Find template, Find submission, Generate a PDF, Combine PDFs, Create a signature request, Create a signing link and Watch events (attach, delivery, detach).
All v1 modules were run through the Make API (token in .env), driven from the shell:
- Clone the connection binding from a working scenario: flow module
app#docspring-sspkqt:<module>,parameters: {"__IMTCONN__": <connId>},mapper. POST /scenarios(orPATCH /scenarios/{id}) with the blueprint as a JSON string.POST /scenarios/{id}/startto activate it (on-demand scenarios must be active to run).POST /scenarios/{id}/run{responsive:true}returns anexecutionId; pollGET /scenarios/{id}/logsforstatus:1.- Check the result in DocSpring (the submission, combined PDF or webhook).
For the instant trigger: POST /hooks {typeName:"app#docspring-sspkqt", __IMTCONN__, event_types, mode} creates the
hook (runs attach); a scenario with metadata.instant:true and parameters:{"__IMTHOOK__":<hookId>} binds to it;
DELETE /hooks/{id} runs detach. Team 2910546, connection 10972788.
- A request's
conditionis checked on each pass of itsrepeat, but doesn't end the repeat: Make keeps repeating while the repeat's own condition holds, skipped passes too, and stops the module at the limit with "Maximum number of repeats exceeded". Put the step's condition in the repeat's condition as well. (Found 4 Nov 2026: with DocSpring's emails, Create a signature request's skipped token step looped until the limit after the submission was made.scripts/iml_tests.pyruns repeats this way.) - There's no
array(): build a one-element array withadd(emptyarray; x). get(body.properties; item)inside an RPC'siterateoutput failed in v1:bodywasn't reliably in scope there.templateFieldsnow iteratestoArray(body.properties), so each item carries its own schema, and readsbody.requiredthroughifempty(...; emptyarray), so at worst every field is optional.- Custom IML functions need an "apps edit" permission the API token lacks.
The v1 app went through Make's review in September 2026. What still applies:
- Review prerequisites: a universal "Make an API call" module,
log.sanitizeon the Authorization header and a password-type token secret, a label, description and interface on every module, base and connection error handling, alimiton every search, samples on searches and the trigger,crudon actions, meaningful groups, sentence-case labels and third-person descriptions. - Demo scenarios: one per module with a successful run and clean data (
Jane Doe/jane.doe@example.com), and a scenario that shows a handled API error (the review form's "Scenario with an API Error" field). The Make plan caps active scenarios, so on-demand demos can be off as long as they keep their green run logs. - The review request is UI-only: in the app editor, Publish, make every module visible in the Modules tab, then the
Review tab: paste the API docs link and the demo scenario links and request a review. Prepared answers: we are the
vendor; support
support@docspring.com; category Files & Documents; service URL https://docspring.com; API docs https://docspring.com/docs; trademark and third-party terms: yes (our own API). - The reviewer asked for the signing link
typein the query string. v2 takes it only in the body (a query parameter is a 422), so it's in the body again.