API Reference
Documents

Documents

Compliance documents (NDAs, IP assignment forms, equipment return acknowledgements, and similar exit paperwork) are stored per-offboarding in the compliance_documents table. There are two ways to create one - upload a file directly, or register a record that points at a file you've already stored elsewhere - and a separate set of endpoints for sending them out for e-signature.

Like the checklist endpoints, these are session-authenticated app routes, not part of /api/v1.


Upload a document

POST /api/upload/compliance/:offboardingId

Content-Type: multipart/form-data

FieldTypeDescription
filefilePDF, DOC, DOCX, PNG, or JPG - max 10MB
docTypestringDocument category (defaults to Other)
namestringDisplay name (defaults to the uploaded filename)

The upload is validated against the file's actual magic bytes, not just its extension, and stored in your organization's GCS bucket. Requires the fileUploads plan feature.

Response 201 Created

{
  "id": "doc_01HQ3",
  "offboarding_id": "e3b0c442-...",
  "name": "exit-nda.pdf",
  "doc_type": "NDA",
  "status": "pending",
  "file_url": "compliance/org_123/e3b0c442-.../a1b2c3....pdf",
  "created_at": "2024-06-01T10:00:00.000Z"
}

file_url is the internal storage key, not a public link - fetch a temporary signed URL via GET /api/upload/files/* when you need to display or download the file.


Register a document by reference

POST /api/employees/:id/compliance

Required permission: compliance_manage - superadmin, admin or hr by default, or a custom role that grants it. Managers can view compliance documents but not add, change or delete them.

Use this instead of the upload endpoint when the file already lives somewhere else (e.g. generated by the document templates endpoint, or hosted externally).

Request body

{
  "name": "Exit NDA",
  "docType": "NDA",
  "fileUrl": "https://storage.example.com/nda.pdf"
}
FieldRequiredDescription
nameYesDocument name
docTypeNoDefaults to Other
fileUrlNoMust be a public HTTPS URL - private/internal/loopback hosts are rejected

Response 200 OK - the created compliance_documents row, with status: "pending".


Update a document

PATCH /api/employees/compliance/:docId/status

Required permission: compliance_manage - superadmin, admin or hr by default, or a custom role that grants it.

Request body

FieldDescription
statusNew status - must be one of signed, pending, or completed. Any other value returns 400 { "error": "Invalid status" }.
signed_byWho signed it
signed_atSigning timestamp
signature_referenceWhere the signed original is, when no file is attached - a ticket number, or where the paper copy is kept
expires_onExpiry date, for documents that need periodic renewal
reasonWhy a provider-verified signature is being changed (see below)

Recording a signature by hand. Moving a document to signed or completed from any other status, when the signature didn't come from an e-signature provider, is a statement that someone signed it, so the record has to say who and on what evidence:

  • signed_by is required (sent now, or already on the document). Otherwise 400 with "code": "signer_required".
  • If the document has no file attached, signature_reference (at least 3 characters) is required. Otherwise 400 with "code": "reference_required".

Changing a signature the provider verified. Once an e-signature provider has verified a signature, it is final: changing the document's status, signed_by, signed_at or signature_reference needs an Admin or Super Admin (403 otherwise), plus a reason of 5 to 500 characters that goes into the audit entry (400 with "code": "reason_required" without one).

Response 200 OK - the updated document row.

Returns 409 with "code": "legal_hold" when the document is signed and its offboarding is under legal hold (or was released less than 7 days ago) and the change touches the signature - status, signed_by, signed_at or signature_reference. The signature evidence is frozen while the hold lasts; expires_on can still change.


Delete a document

DELETE /api/employees/compliance/:docId

Required permission: compliance_manage - superadmin, admin or hr by default, or a custom role that grants it.

Deletes the database record and, if a file is attached, removes it from storage too. A reason is required and is written to the audit log.

Request body

{ "reason": "Uploaded against the wrong employee" }
FieldRequiredTypeDescription
reason✅string5 to 500 characters. May be sent as a ?reason= query parameter instead. Missing or out of range returns 400 with "code": "reason_required"

Response 200 OK

{ "success": true }

Returns 404 if the document isn't in your organisation (or department scope), and 409 with "code": "legal_hold" when its offboarding is under legal hold - nothing is deleted; lift the hold first.


Generate from a template

POST /api/document-templates/:id/generate

Renders a saved document template (with merge fields like {{employee_name}} and {{exit_date}} filled in from the offboarding record) into an HTML file and creates a compliance_documents row for it.

Request body

{ "offboardingId": "e3b0c442-..." }

Response 201 Created - the created document, plus a short-lived signed_url for immediate viewing.

Deleting a template that has been used

DELETE /api/document-templates/:id returns 409 Conflict when documents have been generated from the template (they keep a reference to it), with a message saying so; nothing is deleted. Rename or edit the template instead. Managing templates needs the compliance_manage permission.


List signed URLs for all compliance documents

GET /api/upload/compliance-zip/:offboardingId

Returns a short-lived signed URL for every compliance document attached to the offboarding, for bulk download/archival.


E-signature

E-signature is a separate feature, routed under /api/esignature, gated behind the esignature plan feature, and works against a document's id from compliance_documents.

Request a signature

POST /api/esignature/request/:documentId

Request body

{ "provider": "docusign" }

provider is optional - docusign, adobe_sign, or dropbox_sign. If omitted, PurpletGo uses whichever provider your organization has connected under Integrations, falling back to Dropbox Sign if one is globally configured. Fails with 400 if no provider is available or the document has no file attached, and 409 if a request is already pending.

The request goes to the employee's work email unless you say otherwise. These optional fields control who gets it and how they prove who they are:

FieldTypeDescription
signer_emailstringSend to this address instead of the work email - for example a personal address the departing employee still controls. Must be a valid email address; the work address itself is treated as no override
access_codestring4 to 12 letters or digits the signer must enter before signing. Give it to them outside the email; it is never stored
signer_phonestringA mobile number with its country code (E.164, e.g. +44 7700 900123); the provider texts the signer a code they must enter. Stored masked only. Can't be combined with access_code
acknowledge_mailbox_revokedbooleanSend to the work email even though the employee's identity provider access has already been revoked (see below)

A field sent in the wrong form is refused with 400 rather than dropped, so a request never goes out with weaker authentication than you asked for.

If the employee's Google Workspace, Microsoft 365 or Okta access has already been revoked by the offboarding and you haven't given a signer_email, the request is refused with 409 and "code": "signer_mailbox_revoked": the work mailbox may be unreachable, and is in the company's hands, so it is weak evidence of who signed. Send to a personal address, or set acknowledge_mailbox_revoked: true to send to the work email anyway.

Response 200 OK

{ "success": true, "requestId": "abc123", "signingUrl": "https://...", "provider": "docusign" }

Check signature status

GET /api/esignature/status/:documentId

Response 200 OK

{ "status": "signed", "requestId": "abc123", "provider": "docusign" }

If no signature has ever been requested for this document, the response is just { "status": "not_requested" } with no requestId or provider keys.

Cancel a signature request

POST /api/esignature/cancel/:documentId

Cancels a pending request with the provider and sets the document's esignature_status to cancelled (what GET /status reports). The document's own status is not touched.

Response 200 OK

{ "success": true }