Document signing
Documents your parties sign electronically (agreements, waivers, forms), sent from signing templates through the e-signature service, or recorded when they were signed elsewhere.
| Operation | Scope |
|---|---|
GET /signing_templates |
read |
GET /signing_templates/:id |
read |
GET /signing_requests |
read |
GET /signing_requests/:id |
read |
POST /signing_requests |
read_write |
POST /signing_requests/:id/resend |
read_write |
POST /signing_requests/:id/invalidate |
read_write |
POST /signing_requests/upload_external |
read_write |
GET /signing_requests/:id/download |
read |
Rules for every signing route
- Reads always work; writes need document signing on. Every write is 403
feature_disabledwhen document signing is off on the account. - No signing link is ever returned. The link a signer uses is sent to the signer by email and shown only on the party's own compliance portal. Neither it, nor any provider id or token, appears in any response, so a signing seat's entry in the party's outreach has
linknull. To reach a signer who lost the email, useresend. - Templates are read-only through the API. Build and edit them on the dashboard.
- Individuals: the signing requests of your individuals are 404 on every
/signing_requests/:idroute and never listed; their ids asparty_idin a body or query are 422 namingparty_id. - Order of checks on a write: the feature gate (403
feature_disabled), then ids in the path (404), then the body (422validation_failed, and the 422signing_template_invalidcases known before any call to the e-signature service), then conflicts (409conflict), then the e-signature service (422signing_template_invalidor 502upstream_failed). A request refused by a validation is never answered with a conflict. updated_sincesees record edits. A request marked no longer needed (by any path) and a request completing do updateupdated_at; signatures and reminder counters on its recipients do not.
Signing templates
{
"id": 7,
"name": "Master Subcontract Agreement",
"description": null,
"document_name": "Subcontract Agreement",
"active": true,
"repeatable": false,
"send_automatically": false,
"sending_trigger": null,
"trigger_coverage_prefix": null,
"state_restriction": { "type": null, "states": [] },
"adds_approval_override": true,
"approval_override_coverage_prefix": "wc",
"approval_override_term": { "type": "years", "amount": 1, "prefilled_field_id": null },
"succeeds_template_id": null,
"recipients": [
{ "id": 21, "role_name": "Subcontractor", "signer": "party", "name": null, "email_address": null },
{ "id": 22, "role_name": "Project Manager", "signer": "fixed", "name": "Sam Ortiz", "email_address": "[email protected]" }
],
"prefilled_fields": [
{ "id": 40, "name": "Waiver End Date", "field_type": "single_line_text_field", "source": "value", "required": false }
]
}
| Field | Meaning |
|---|---|
document_name |
The name signers see, when it differs from name |
repeatable |
The template allows several open requests per party at once (for example one per payment draw) |
send_automatically, sending_trigger, trigger_coverage_prefix |
Automatic sending, as configured on the dashboard |
state_restriction |
type is only_selected, exclude_selected or null; states are two-letter codes |
adds_approval_override, approval_override_coverage_prefix, approval_override_term |
Completing the document grants an approval override for that coverage. approval_override_term.type is forever, days, years, prefilled_field (the end date comes from a prefilled field, prefilled_field_id) or field (a legacy term read from the signed document). The whole term is null when the template adds no override. prefilled_field_id is null when that field is missing or disabled: such a template cannot be sent or uploaded through the API (422 signing_template_invalid) |
succeeds_template_id |
The template this one replaces as a newer version |
recipients |
The signing seats: signer is party (filled with the party's contact for that seat) or fixed (always the same person; only fixed seats show a name and email_address) |
prefilled_fields |
Values written into the document before it is sent. source is party (from the party's record), date (computed by HoundDog) or value (given by the sender). Definitions only, never values. required is true only for the field an override term reads, when the template adds an override and that field takes a value with no default |
GET /signing_templates
Filter: active (true or false). Collection envelope.
GET /signing_templates/:id
One template.
Signing requests
{
"id": 9012,
"party_id": 191537,
"project_id": null,
"signing_template_id": 7,
"status": "pending",
"title": "Master Subcontract Agreement for Acme Electric",
"message": null,
"signing_order": "sequential",
"completed_at": null,
"invalidated_at": null,
"invalidated_reason": null,
"uploaded_externally": false,
"concurrent": false,
"recipients": [
{ "id": 30111, "name": "Dana Ruiz", "email_address": "[email protected]", "role_name": "Subcontractor", "signer": "party",
"signed": false, "signed_at": null, "contact_count": 1, "last_contacted": "2026-10-07T14:03:30Z" }
],
"created_at": "2026-10-07T14:03:11Z",
"updated_at": "2026-10-07T14:03:11Z",
"links": { "self": "/api/v2/signing_requests/9012", "party": "/api/v2/parties/191537" }
}
| Field | Meaning |
|---|---|
status |
pending, completed or no_longer_needed |
party_id |
null for an old request sent to a person outside any party (no longer possible to create) |
title |
The display name: the stored title, or when none was stored, "<template name> for <party name>" (which shows the template's internal name) |
signing_order |
sequential or parallel, served lowercase (stored uppercase); null for an external upload |
invalidated_at, invalidated_reason |
When and why the request was marked no longer needed |
uploaded_externally |
Recorded with upload_external |
concurrent |
Created as one of several allowed open requests of a repeatable template |
recipients |
One per seat; empty for an external upload. No link of any kind |
links |
self, party (when the request has one), and download once the request is completed |
Fields of features your account does not use are present with an empty value.
The approval override a completed request generates is not linked from this entity; read the party's approval overrides.
GET /signing_requests
Filters: party_id, signing_template_id, project_id, status, updated_since, plus page, per_page. Old party-less requests are listed; party_id excludes them.
GET /signing_requests/:id
One request.
POST /signing_requests
Builds a document from a template, creates it in the e-signature service and sends it.
| Body field | Meaning |
|---|---|
signing_template_id |
Required; the template must be active (422 naming it otherwise) |
party_id |
Required |
project_id |
Scopes the one-open-request rule to that project |
title |
Repeatable templates only (422 naming title for any other template; at most 255 characters): the label signers see for this request. Omitted, it is stored blank (the template's internal name is never used as the label) |
message |
A note included in the signing email |
prefilled_values |
{"<prefilled field id>": "value"} for fields whose source is value, each value a string. Naming any other field is 422 naming prefilled_values.<id>. The field an override term reads takes an ISO 8601 date (2027-03-01) and is required when the template adds an override, its term type is prefilled_field, and that field takes a value with no default |
supersede |
Default false; see conflicts below |
Seats are filled as on the dashboard.
With contact routing, each party seat is filled by the contact who holds that seat's role (per-slot holder), else the contact who holds the signing role (generic holder), else the primary contact.
Without contact routing, every party seat is the primary contact.
A party seat that resolves to no email address is 422 validation_failed naming party_id; a blank fixed seat is 422 signing_template_invalid. A template whose override term is prefilled_field is 422 signing_template_invalid when the override could never be granted: the term's field is missing or disabled, the field is filled from the party's record, or the field's own value (its default, or a computed date, when you give none) is not a date the override can be read from (a value you give must be an ISO 8601 date).
project_id is 403 feature_disabled naming the field: it is accepted only on project-compliance accounts. Sent empty (null or blank) it is ignored, on the create and on upload_external.
Conflicts. When the party (and the project_id, where given) already has an open, non-repeatable request for this template, the create is 409 conflict, its conflict member naming that request ({"resource": "signing_request", "id": n}). Send supersede: true to mark the open request no longer needed (Signing Request Invalidated) and create the new one in one transaction: if the create then fails, the open request stays open. Repeatability is read per open request (a request created as concurrent, or whose template now allows several open requests), and a repeatable open request never conflicts and is never superseded, so a repeatable template's create always adds a request. If another process opens a request for the same template at the same moment, you get 409 conflict naming the winner.
The response may already be no_longer_needed (201): when the party already meets what the document would grant, it is created closed (on accounts where closing satisfied requests is on, the default; not for archived, paused or incomplete parties).
What goes out. The initial emails are vendor outreach: with vendor email off nothing is emailed (and a signing request sends no SMS). An archived party is 422; a paused party is accepted.
On a multi-factor compliance party with two or more required factors, only seats routed away from the primary contact are emailed at creation; the primary's own seats are covered by the combined compliance email, and fixed co-signer seats are first emailed by the signing follow-up schedule or when an earlier seat signs. On such a party, a request for a template the profile does not require, or whose signing requirement is already satisfied, is emailed to its party seat by no schedule at all: use resend to reach the signer. Any write that creates a record on such a party can email the main contact at once (the combined email).
E-signature service failures. 422 signing_template_invalid (naming the template) when the template cannot be sent as configured: a signer seat with no signature field, any refusal from the service other than rate limiting, a request timeout or an authorization failure (a template it no longer has included), or a create answer that cannot be matched to the template's signing seats. 502 upstream_failed for a template read that answers without a usable template, rate limiting, a request timeout, an authorization failure on our side, or a server error that persists after retries, a timeout or a dropped connection, and when the request's time budget for the e-signature service (about 20 seconds in all) runs out. The service's own message is never relayed. In every failure case no request is kept, and a superseded request stays open. Retry a 502 with the same Idempotency-Key.
Response 201 with the entity. Events: Message Sent for each initial email sent.
POST /signing_requests/:id/resend
Sends one reminder to each current signer who has not signed and has an email address, and records one Manual Signing Reminder Sent event for each. No body.
- 200 with the entity even when nobody was emailed (every signer signed or has no address).
- A signer whose address is suppressed is not emailed either (still 200). A suppressed address keeps its
Manual Signing Reminder Sentevent and gains a childEmail Skipped (Suppressed)event linked byparent_event_id. To learn who was actually reached, read the children:GET /events?parent_event_id=<reminder event id>. - 422
validation_failedwhen the request is completed or no longer needed, when the party is archived, or when the account cannot send vendor email ("Vendor email is disabled for this account"). A paused party is reminded.
POST /signing_requests/:id/invalidate
Marks the request no longer needed. One-way. Body: reason (optional, at most 255 characters; stored as invalidated_reason, which the party can see on the request page; omitted, it reads "marked No Longer Needed through the API"). Event: Signing Request Invalidated.
- 422
validation_failedwhen it is already completed or already no longer needed (including when another process closed it at the same moment). - Recomputes the party's compliance. On a multi-factor compliance party whose profile requires this template, a replacement request follows: created and sent a few seconds later in the background when nothing else satisfies the requirement, or at the next nightly renewal check when the last signature has lapsed. Nothing is re-created for an archived or paused party or on an account that disables requests (the invalidation itself is accepted). On a party with two or more required factors the combined compliance email can follow under its own schedule.
- The response is the invalidated request and never contains the replacement. Poll
GET /signing_requests?party_id=<id>&status=pending(or useupdated_since) to find it.
POST /signing_requests/upload_external
Records a document signed outside the e-signature service as completed. multipart/form-data:
| Field | Meaning |
|---|---|
file |
Required: the signed document |
signing_template_id, party_id |
Required. An inactive template is accepted |
project_id |
The project the document is for |
supersede |
Default false; see conflicts |
prefilled_values |
In this multipart request prefilled_values is a JSON-encoded string, for example prefilled_values={"40":"2027-03-01"}, holding only the override-term field, under the create's rule (422 naming prefilled_values when it is not a JSON object; the create's 422 signing_template_invalid cases for a prefilled_field term apply too) |
completed_atis the upload time.recipientsis empty.- Conflicts follow the create's rule: an open, non-repeatable request for the same template version and the same project (no project matches no project) is 409
conflictunlesssupersede: true, which marks it no longer needed ("satisfied by an externally uploaded signed document") in the same transaction as the upload. Repeatable open requests are never touched. - The override term. When the template adds an approval override, the upload grants it for a
forever,daysoryearsterm, and for aprefilled_fieldterm from the value you send inprefilled_values. A template whose term is the legacyfieldtype is 422signing_template_invalid, before anything is created. - An archived party is 422
validation_failed, and so is an empty file.
Side effects: the granted override recomputes compliance, so the party can become compliant (Override Added, then the compliance events). Where the account's JobTread integration copies signed documents and the party is linked, the file is pushed to JobTread. Event: External Signed Document Uploaded, naming your key.
On a multi-factor compliance account the party's compliance is recomputed at once.
Re-read the party's requests after an upload. A request another process opened in the same moment stays open, and every compliance recompute also closes any other open request whose override is now satisfied (Signing Request Invalidated; on accounts where closing satisfied requests is on, the default; not for archived, paused or incomplete parties), possibly for other templates, versions or projects than the one you uploaded. This applies to every write on this page that recomputes compliance.
It applies to every write on W-9s that recomputes compliance too.
Response 201 with the entity.
GET /signing_requests/:id/download
Streams the signed document (Content-Disposition: attachment). For a document signed through the e-signature service it is the copy without the service's audit page (which lists signer IP addresses); when that copy cannot be produced, the full document with the audit page is served, as on the dashboard. For an external upload, the stored file under its own name and type. 404 until completed and when there is nothing to download; 502 upstream_failed when the document cannot be retrieved in time (one budget of about 25 seconds covers the whole retrieval; when little of it is left after the fetch, the full document with the audit page is served instead of the stripped copy). Against a very slow e-signature service the time limit is best effort: each read is bounded, but a read that keeps trickling data can run past it.