Partner Endpoints
Partner endpoints live under /api/v2/partner/* and are authenticated with X-API-Key. They mirror the internal endpoints but are scoped to your organization.
Endpoints
E-sign + envelope lifecycle
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /partner/envelopes | envelopes:read | List envelopes |
| GET | /partner/envelopes/:id | envelopes:read | Get envelope |
| GET | /partner/envelopes/:id/status | envelopes:read | Get status only (lightweight polling) |
| POST | /partner/envelopes | envelopes:write | Create envelope (e-sign or paper) |
| POST | /partner/envelopes/:id/upload | envelopes:write | Upload pre-signed PDF (paper flow) |
| POST | /partner/envelopes/:id/void | envelopes:write | Void / cancel envelope: closes the signing session (emailed links stop working), emails nobody; executed documents can't be voided |
| DELETE | /partner/envelopes/:id | envelopes:write | Delete a voided or never-sent envelope (soft delete; its uploaded file is removed). Anything out for signature, signed or vaulted can't be deleted |
| POST | /partner/envelopes/:id/remind | envelopes:write | Send signing reminder(s) |
| GET | /partner/envelopes/:id/signing-links | envelopes:read | Get per-signer signing URLs |
| POST | /partner/envelopes/:id/send | envelopes:write (+ vault:write for auto-vault) | Send an uploaded document for signature |
Customers
| Method | Path | Scope | Description |
|---|---|---|---|
| GET | /partner/customers | customers:read | Look up a customer by external client id or email |
| POST | /partner/customers/:customer_id/documents | envelopes:write | Upload a document to a customer's general folder |
E-vault — vault-in, retrieval, vault-out
| Method | Path | Scope | Description |
|---|---|---|---|
| POST | /partner/envelopes/:id/vault-in-signed | vault:write | Vault a completed e-sign envelope (manual / recovery) |
| POST | /partner/envelopes/:id/vault-in-upload | vault:write | Vault a paper / uploaded PDF |
| GET | /partner/envelopes/:id/vault/signed-url | vault:read | Download URL (authoritative) |
| GET | /partner/envelopes/:id/vault/copy/signed-url | vault:read | Download URL (non-authoritative copy) |
| GET | /partner/envelopes/:id/vault/certificate | compliance:read | UCC §9-105 compliance certificate JSON |
| GET | /partner/envelopes/:id/compliance | compliance:read | Run compliance check synchronously |
| POST | /partner/envelopes/:id/vault-out | vault:release | Release authoritative copy out of the live vault |
Configuration
| Method | Path | Scope | Description |
|---|---|---|---|
| PATCH | /partner/webhook | envelopes:write | Update webhook URL / secret |
| POST | /embed/token | esign:write / vault:read | Mint embed token |
How partner endpoints work
- Auth: All partner endpoints use
X-API-Key: lk_... - Scoping: Every response is automatically scoped to the organization tied to the key
- No org header required: The organization is derived from the API key
- Scoped by permission: Each endpoint requires an explicit scope on your key — see Scopes
Creating an envelope
POST /partner/envelopes supports two flows:
envelope_kind: "esign"(default) — DocuSeal-driven signing. Passtemplate_idand an optionalsigners[]array for multi-signer deals. Returns asign_url.envelope_kind: "paper"— Partner-uploaded pre-signed PDF. Skips DocuSeal. Next step isPOST /partner/envelopes/:id/upload.
E-sign flow
{
"customer_email": "buyer@example.com",
"customer_name": "Jane Buyer",
"template_id": "tmpl_abc123",
"signers": [
{ "email": "buyer@example.com", "name": "Jane Buyer", "role": "Buyer" },
{ "email": "cosigner@example.com", "name": "John Cosigner", "role": "Co-Signer" }
]
}
customer_email and customer_name are always required (used for customer record lookup/upsert). The signers array, when provided, determines the DocuSeal submitter list — each entry maps to one signing URL returned by GET .../signing-links.
When using signers[], the role values must match the submitter role names defined in your DocuSeal template. If omitted, roles default to Signer 1, Signer 2, etc.
Paper / pre-signed PDF flow
{
"customer_email": "buyer@example.com",
"customer_name": "Jane Buyer",
"envelope_kind": "paper"
}
Returns an envelope with status: "uploaded" and envelope_kind: "paper". Then upload the PDF (see below).
Uploading a paper PDF
POST /partner/envelopes/:id/upload accepts multipart/form-data with field file. Allowed types: application/pdf, image/png, image/jpeg. Max 50 MB. The type is determined from the file's content, not the multipart Content-Type: a file whose bytes aren't a PDF/PNG/JPEG is rejected with 415. PDFs are stored with embedded JavaScript, launch/submit actions and attachments removed; a PDF that can't be parsed (e.g. password-protected) is rejected with 422.
curl -X POST "https://api.stg.loyva.net/api/v2/partner/envelopes/env_abc/upload" \
-H "X-API-Key: lk_..." \
-F "file=@./signed-contract.pdf"
Response (201 Created):
{
"data": {
"path": "org_abc/env_abc/upload_20260427120000.pdf",
"hash": "<sha256>",
"size": 184320,
"content_type": "application/pdf"
}
}
The PDF lives in ucc-uploads until you trigger vault-in (next section).
Looking up a customer
GET /partner/customers?client_id=... returns the customer whose external client id (the external_client_id column of the client bulk upload) matches, case-insensitively. GET /partner/customers?email=... returns the customer with that email. Pass exactly one. 404 when none matches; 409 with candidates (customer_id, name, client_id) when several customers share the email — look them up by client_id instead.
curl "https://api.stg.loyva.net/api/v2/partner/customers?email=buyer@example.com" \
-H "X-API-Key: lk_..."
{
"data": {
"customer_id": "cust_abc123",
"name": "Jane Buyer",
"email": "buyer@example.com",
"phone": "555-0100",
"company": "Acme Corp",
"entity_type": "individual",
"created_at": "2026-03-01T09:00:00.000Z",
"updated_at": "2026-03-01T09:00:00.000Z"
}
}
Uploading a document to a customer's general folder
POST /partner/customers/:customer_id/documents accepts multipart/form-data with field file (same type/size limits as the paper PDF upload above: application/pdf, image/png, image/jpeg, max 50 MB). Use this for documents that aren't part of an e-sign or vaulting flow — they land in the customer's "General Files" view in the Loyva dashboard.
Optional form fields, applied when a Loyva user later sends the document for signature:
| Field | Type | Description |
|---|---|---|
name | string | Document name shown in Loyva and in signing emails. Defaults to the file name. |
signers | JSON string | Optional. [{ "email", "name", "role" }, …] in signing order. Saved on the document: Send for Signature in the Loyva app opens with these signers and roles already filled in, and POST /partner/envelopes/:id/send uses them when called without signers. For a PDF whose fields are tagged with roles, each role must be one the document names (else 422). Emails and roles must be unique. |
auto_vault_enabled | "true" / "false" | Vault the signed document automatically once every party signs. Records intent only — the sender still accepts the Vault-In Certification Terms before sending (in the app, or with vault_terms_accepted_by_email on POST /partner/envelopes/:id/send), and nothing is vaulted without that acceptance. |
kba_required | "true" / "false" | Require Knowledge-Based Authentication from every signer (billed per verified signer). The sender can still change it until the document is prepared for signature. |
Both default to false; any other value returns 422. They are echoed back in the response.
curl -X POST "https://api.stg.loyva.net/api/v2/partner/customers/cust_abc123/documents" \
-H "X-API-Key: lk_..." \
-F "file=@./intake-form.pdf"
Response (201 Created):
{
"data": {
"customer_id": "cust_abc123",
"envelope_id": "env_x7k9m2p4q1w3",
"path": "org_abc/env_x7k9m2p4q1w3/upload_20260427120000.pdf",
"hash": "<sha256>",
"size": 184320,
"contentType": "application/pdf",
"auto_vault_enabled": false,
"kba_required": false
}
}
Internally this creates an envelope record (status: "uploaded", is_upload: true) to hold the file — it will show up in GET /partner/envelopes like any other envelope — but it is never dispatched through DocuSeal and is not vaulted unless you separately call the vault-in endpoints below.
Fillable PDFs and signer roles
If the uploaded PDF contains fillable form fields, Loyva reads them when a user chooses Send for Signature and offers a Role dropdown per signer. Roles are read from the PDF's document Info dictionary (the same place as /Title and /Author), as JSON strings:
| Info key | Value | Example |
|---|---|---|
/SignerRoles | Ordered list of signing parties | ["Customer","Company","Manager"] |
/FieldRoles | Field name → role | {"Purchase Price":"Company","Customer Signature":"Customer"} |
A field whose name is missing from /FieldRoles falls back to its tooltip (/TU), then to the first signer. The sender picks which signer signs as each role; fields arrive in the signing editor already assigned. Upload the original generated PDF: a file re-saved by DocuSeal, Preview or a browser loses this metadata. If no roles are found, no Role dropdown is shown.
To send it without the Loyva app, use POST /partner/envelopes/:id/send.
Sending an uploaded document for signature
POST /partner/envelopes/:id/send (scope envelopes:write; also vault:write with auto_vault_enabled) does what Send for Signature does in the Loyva app for a document uploaded with POST /partner/customers/:customer_id/documents. The PDF must have fillable fields tagged with the party that fills them (see above).
- The signing document is built from the PDF's fields, each owned by its role.
- The signers are attached in order, each signing as the
roleyou give. - With
auto_vault_enabled, the Vault-In Certification Terms are accepted for this envelope byvault_terms_accepted_by_email, so the signed document is vaulted as soon as the last signer finishes. That person is recorded asvaulted_byand becomes the custodian. - The document is sent. From here signing proceeds by email: with
signing_order: "preserved"(default) only the first signer is emailed, and each next signer when the previous one finishes.
| Field | Description |
|---|---|
signers[] | { email, name, role } in signing order. Each role must own at least one field in the PDF; emails and roles must be unique. Omit to use the signers saved when the document was uploaded. |
signing_order | "preserved" (default) or "random" (everyone at once). |
sender_email | Org member the document is sent as (audit trail, signing-email sender). Defaults to vault_terms_accepted_by_email. |
auto_vault_enabled | true to vault automatically on completion. |
vault_terms_accepted_by_email | Org member accepting the Vault-In Certification Terms. Required with auto_vault_enabled. |
message | Optional { subject, body } for the signing email when your org sends its own branded emails. |
curl -X POST "https://api.stg.loyva.net/api/v2/partner/envelopes/env_x7k9m2p4q1w3/send" \
-H "X-API-Key: lk_..." -H "Content-Type: application/json" \
-d '{
"signers": [
{ "email": "sales@company.com", "name": "Sales Rep", "role": "Company" },
{ "email": "manager@company.com", "name": "Manager", "role": "Manager" },
{ "email": "buyer@example.com", "name": "Jane Buyer", "role": "Customer" },
{ "email": "president@company.com", "name": "President", "role": "President" }
],
"auto_vault_enabled": true,
"vault_terms_accepted_by_email": "manager@company.com"
}'
Response (200 OK):
{
"data": {
"envelope_id": "env_x7k9m2p4q1w3",
"status": "sent",
"signing_order": "preserved",
"signers": [
{ "email": "sales@company.com", "name": "Sales Rep", "role": "Company" },
{ "email": "manager@company.com", "name": "Manager", "role": "Manager" },
{ "email": "buyer@example.com", "name": "Jane Buyer", "role": "Customer" },
{ "email": "president@company.com", "name": "President", "role": "President" }
],
"auto_vault_enabled": true,
"vault_terms_accepted_by": "manager@company.com",
"sent_by": "manager@company.com"
}
}
| Status | When |
|---|---|
404 | Envelope not found in your org. |
409 | Already sent. |
422 | Not an uploaded document, the PDF has no fillable fields, no signers (none passed or saved at upload), a signer's role owns no field, or sender_email / vault_terms_accepted_by_email is not a member of your org. |
502 | The signing provider or the signing email failed. The envelope stays unsent; retry the same call. |
Vault-in — moving a document into the UCC vault
There are two vault-in endpoints, picked based on how the document was acquired.
POST /partner/envelopes/:id/vault-in-signed — for completed e-sign envelopes
Use when the envelope was signed via DocuSeal. The queue worker normally vaults automatically once the submission.completed webhook fires; this endpoint is a manual trigger / recovery path if the queue missed the message. Idempotent — returns 200 with the existing envelope row if vault_file_path is already set.
The pipeline runs synchronously: download signed PDF → SHA-256 → upload authoritative + non-authoritative copies → generate UCC §9-105 compliance certificate → generate vault custody record PDF → assign custodian → set status to vaulted. The vault.stored webhook fires.
POST /partner/envelopes/:id/vault-in-upload — for paper / uploaded PDFs
Use after POST /envelopes/:id/upload. Same pipeline, no DocuSeal step. Requires is_upload: true and a non-null upload_file_path on the envelope.
Both endpoints return the updated envelope row:
{
"data": {
"envelope_id": "env_abc",
"status": "vaulted",
"...": "..."
}
}
Vault-out — releasing the authoritative copy
POST /partner/envelopes/:id/vault-out is the end-of-lifecycle action: it releases the authoritative package out of the live vault to a designated recipient (typically a secured party at loan funding).
Vault-out copies the live vault package into an immutable archive prefix (vault-out-archive/{stamp}/) and deletes the live authoritative path as part of UCC single-locus enforcement. Status becomes vaulted_out and live vault pointers are cleared. The archive is preserved for audit but cannot be re-vaulted.
Request body — all fields required, two acknowledgments must be the literal true:
{
"recipient_name": "First National Bank",
"recipient_organization": "First National Bank, N.A.",
"recipient_email": "loans@first-national.example",
"purpose_of_release": "Loan funded — release to secured party",
"acknowledge_authoritative_copy": true,
"acknowledge_chain_of_custody": true,
"authorized_signature": "Jane Doe, Operations Manager"
}
Response — four short-lived (1 hr) signed URLs into the archive:
{
"data": {
"authoritative_signed_url": "https://...",
"authoritative_filename": "authoritative.pdf",
"certified_vaulted_copy_signed_url": "https://...",
"certified_vaulted_copy_filename": "certified-vaulted-copy.pdf",
"compliance_certificate_signed_url": "https://...",
"compliance_certificate_filename": "ucc-compliance-certificate.json",
"vault_custody_record_signed_url": "https://...",
"vault_custody_record_filename": "vault-custody-record.pdf"
}
}
The four artifacts:
authoritative_signed_url— the signed PDF that was vaulted (now released).certified_vaulted_copy_signed_url— human-readable release certificate (PDF) capturing recipient, purpose, chain of custody, and authorized signature.compliance_certificate_signed_url— UCC §9-105 compliance certificate JSON, captured at vault-in time.vault_custody_record_signed_url— vault custody record PDF (historical record from vault-in).
URLs expire after 1 hour. Re-call the endpoint to mint fresh URLs.
Voiding an envelope
POST /partner/envelopes/:id/void cancels the envelope. Terminal envelopes (status completed, declined, or already voided) return a 409.
{ "reason": "Customer withdrew before signing" }
The reason field is optional. An envelope.voided webhook event is dispatched to your configured endpoint immediately after the void.
Sending signing reminders
POST /partner/envelopes/:id/remind re-sends the original signing email (with a fresh copy of each signer's link) to signers who have not yet signed. Only works for envelopes with status sent or viewed.
Signing order is preserved: on an envelope sent with signing_order: "preserved", only the signer whose turn it is receives the reminder. Signers further down the order are emailed automatically when the signer before them completes, and asking to remind one of them directly returns 422.
{ "signer_email": "buyer@example.com" }
Omit signer_email to remind all pending signers at once. Returns the list of reminded addresses and an envelope.reminder_sent webhook event.
{
"data": {
"reminded": ["buyer@example.com"],
"count": 1
}
}
Managing your webhook
PATCH /partner/webhook lets you update your delivery URL and HMAC signing secret without contacting Loyva. Changes take effect on the next delivery.
{
"webhook_url": "https://your-app.com/webhooks/loyva",
"webhook_secret": "a-random-string-at-least-16-chars"
}
Pass null for either field to clear it. The response confirms what changed but never echoes the secret value. See Webhooks for signature verification details.
End-to-end flows
E-sign + auto-vault (typical)
POST /partner/envelopeswithtemplate_id→ returnssign_url- Customer signs via DocuSeal
vault.storedwebhook fires automaticallyGET /partner/envelopes/:id/vault/signed-urlto download the authoritative PDF- (Optional)
POST /partner/envelopes/:id/vault-outat end of lifecycle
E-sign with manual vault recovery
If the queue worker missed the submission.completed event:
- ... envelope reaches
status: "completed"butvaulted: false POST /partner/envelopes/:id/vault-in-signed→ re-runs the pipeline
Paper / pre-signed PDF
POST /partner/envelopeswithenvelope_kind: "paper"→ envelope created withstatus: "uploaded"POST /partner/envelopes/:id/upload(multipart) → PDF stored inucc-uploadsPOST /partner/envelopes/:id/vault-in-upload→ vaulted, certificate generated- (Optional)
POST /partner/envelopes/:id/vault-outat end of lifecycle