Tracking and Recovering Bulk Sends
A bulk send delivers one agreement to many recipients at once. Propper creates a parent agreement and, from it, one copy per recipient group. Each copy is its own agreement that its recipients sign independently.
The Propper Sign API lets you follow a bulk send to completion: list its copies with their delivery and signing status, export them as CSV, download the signed documents as a ZIP, resend to recipients who have not yet acted, and cancel copies that have not gone out.
Before you start
- Scopes:
sign:readto read copies, export them, and download the signed documents,sign:sendto resend or cancel. - Starting a bulk send: for now, create and send bulk sends in the Propper web app. Upload your recipient list there and send. The API cannot yet add recipient groups to a bulk agreement. Once the send is underway, everything below works over the API.
Lifecycle at a glance
| Step | Endpoint | Scope |
|---|---|---|
| Check progress | GET /v1/sign/agreements/{id}/bulk/copies | sign:read |
| Export copies to CSV | GET /v1/sign/agreements/{id}/bulk/copies/export | sign:read |
| Download signed documents | GET /v1/sign/agreements/{id}/bulk/download | sign:read |
| Remind outstanding signers | POST /v1/sign/agreements/{id}/bulk/resend | sign:send |
| Stop copies not yet sent | POST /v1/sign/agreements/{id}/bulk/cancel | sign:send |
1. Get the bulk agreement's id
Every endpoint below takes the id of the bulk send's parent agreement. Copy it from the Propper web app: open the bulk send, and the id is the part of the page address after /sign/agreements/. If you pass the id of a copy or of an ordinary agreement, the bulk endpoints return 400 (resend) or an empty result.
2. Check progress
GET /bulk/copies returns one page of copies plus a summary of the whole batch. The summary is the quickest way to tell whether a send is finished.
curl "https://api.propper.ai/v1/sign/agreements/$AGREEMENT_ID/bulk/copies?page=1&limit=25" \
-H "Authorization: Bearer $ACCESS_TOKEN"
{
"data": [
{
"copyId": "5d2b7f0e-8a51-4c43-9d1e-2f7a0c6b9e11",
"copyNumber": 1,
"status": "SENT",
"attempts": 1,
"lastAttemptAt": "2026-10-20T17:00:00.000Z",
"agreementId": "a3c1e9d4-0b7f-4f2a-8e65-91d0c4b7a2f3",
"agreementStatus": "IN_PROGRESS",
"recipients": [
{
"id": "c8e4f1a2-6d3b-4e9c-b0a7-5f2d8e1c3b49",
"name": "Ana Lopez",
"email": "ana.lopez@propper.ai",
"role": "Signer",
"status": "SIGNED",
"deliveryStatus": "delivered"
}
]
}
],
"pagination": { "total": 42, "page": 1, "limit": 25, "hasMore": true },
"summary": {
"copiesByStatus": { "PENDING": 0, "QUEUED": 0, "SENT": 40, "FAILED": 2, "CANCELLED": 0 },
"copiesByRecipientStatus": { "SENT": 10, "VIEWED": 5, "SIGNED": 25, "DECLINED": 0 },
"completedCopies": 25,
"failedCopies": 3,
"roles": [{ "role": "Signer", "order": 1, "total": 42, "byStatus": { "SIGNED": 25 } }],
"fixedRecipients": []
}
}
Copy status
status | Meaning |
|---|---|
PENDING | Waiting to be prepared. |
QUEUED | The copy's agreement exists and is waiting to go out, including copies queued for a reminder during a resend. |
SENT | Delivered to its recipients. Signing progress is on each recipient. |
FAILED | Could not be sent. Check each recipient's deliveryStatus. |
CANCELLED | Stopped before it was sent. |
In the summary, failedCopies also counts copies that were sent but whose waiting recipient's email bounced, so it can be higher than copiesByStatus.FAILED. To list those copies, filter with status=FAILED.
Filtering and sorting
| Parameter | Values |
|---|---|
page, limit | Page number (from 1, default 1) and page size (1–100, default 20). |
status | Comma-separated copy statuses, e.g. FAILED,CANCELLED. |
recipientStatus | One of SENT, VIEWED, SIGNED, DECLINED. |
role | A recipient role name, e.g. Signer. |
search | Matches recipient name or email. |
sortBy | copyNumber (default), status, or lastAttemptAt. |
sortOrder | asc (default) or desc. |
An unknown or malformed value returns 400 rather than being ignored.
fixedRecipients lists recipients who are on every copy, such as an internal countersigner. signEachCopy tells you whether they sign each copy individually.
3. Export copies to CSV
GET /bulk/copies/export takes the same filters (but no page or limit) and returns text/csv with one row per recipient. Use it to fix bad addresses offline when many copies failed:
curl "https://api.propper.ai/v1/sign/agreements/$AGREEMENT_ID/bulk/copies/export?status=FAILED" \
-H "Authorization: Bearer $ACCESS_TOKEN" -o failed-copies.csv
Columns, in order: copy, itemStatus, role, recipientName, recipientEmail, recipientStatus, deliveryStatus, attempts, lastAttemptAt, lastError, displayStatus. The lastError column is always empty in API exports; use deliveryStatus and displayStatus instead. In this file, a queued copy's itemStatus is written as EXPANDED. An export holds at most 10,000 rows, so narrow it with filters on very large sends.
Download the signed documents
GET /bulk/download returns an application/zip archive with one PDF for every copy that has completed signing. Copies that are still in progress, failed, or cancelled are not included, so the archive grows as more copies complete. The filename is <agreement name>-bulk-documents.zip.
curl "https://api.propper.ai/v1/sign/agreements/$AGREEMENT_ID/bulk/download" \
-H "Authorization: Bearer $ACCESS_TOKEN" -o bulk-documents.zip
| Status | Meaning |
|---|---|
200 | The ZIP archive. |
404 | No bulk send with this id exists in your organization. |
409 | No copy has completed yet (error.code is NO_COMPLETED_COPIES). Try again once a copy is signed. |
4. Remind outstanding signers
POST /bulk/resend re-sends to every recipient who has not yet acted on a sent copy:
curl -X POST "https://api.propper.ai/v1/sign/agreements/$AGREEMENT_ID/bulk/resend" \
-H "Authorization: Bearer $ACCESS_TOKEN"
{
"agreementId": "9f6e2d1c-4b8a-4c7e-a3f5-0d2b6e8c1a74",
"resendCount": 15,
"startedAt": "2026-10-20T18:00:00.000Z",
"estimatedCompletionAt": "2026-10-20T18:05:00.000Z"
}
It returns 202 Accepted when reminders are queued and 200 OK with resendCount: 0 when nobody is outstanding. Reminders go out gradually; estimatedCompletionAt says when the last should be sent.
| Status | When |
|---|---|
400 | The agreement is not a bulk send, or it is already completed, declined, voided, or expired. |
404 | No bulk agreement with that id in your organization. |
409 | Another send or resend for this bulk agreement is still running. Wait, then retry. |
429 | A resend ran recently. Wait the number of seconds in the Retry-After header. |
5. Stop copies not yet sent
POST /bulk/cancel cancels every copy still waiting to go out (PENDING or QUEUED), and stops the send or resend that is running. Copies that are SENT, FAILED, or CANCELLED are left alone; SENT recipients already have the email, so to withdraw one of those copies, void its own agreement (agreementId on the copy).
Cancelling is best-effort: an email that was already being delivered when you cancelled may still arrive. A copy cancelled while it was waiting for a reminder keeps its agreement open for signing; only the reminder is skipped.
curl -X POST "https://api.propper.ai/v1/sign/agreements/$AGREEMENT_ID/bulk/cancel" \
-H "Authorization: Bearer $ACCESS_TOKEN"
{ "cancelledCount": 12, "alreadyTerminalCount": 30, "sendStopped": true }
alreadyTerminalCount is the number of copies that were already SENT, FAILED, or CANCELLED. sendStopped is true when this call stopped a send or resend that was still running. Calling cancel again is safe and returns cancelledCount: 0.
Errors
Every failure uses the standard error envelope described in Error Handling.