This tutorial helps you create and manage Zefort Sign signing requests via API.
Zefort Sign allows users to electronically sign documents and review related attachments. The signing can be done with either a basic authentication (SMS or email) or strong authentication (Bank ID or similar).
The signing request can include multiple parties with different roles. You can also define the order in which the document is signed or reviewed.
Every signing request must include a document to be signed and one or more parties that receive the request.
The document must be stored in Zefort before creating the request. The parties may be Zefort users or external users.
The request is initially created in a draft state and must be separately activated before it is sent out to the receiving parties.
Key parameters:
contract | Zefort’s unique ID for the contract to be signed. |
parties | This section identifies all individual recipients of the signing request. Typically all parties are added when creating the signing request. However, you can also add parties one by one later with a separate API request. |
name, email, user |
However, if the recipient has a Zefort account, you can replace name and email with `user along with the user’s Zefort user ID. In this case, the name and email will be populated from Zefort’s user account records. |
role | Either
|
import requests
ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
CONTRACT_ID = "ct_1JrjJRTzEMlHVHls"
payload = {
"contract": CONTRACT_ID,
"parties": [
{
"name": "John Doe",
"email": "john.doe@mycompany.com",
"role": "signer",
"authentication_type": "email",
},
{
"name": "Jane Doe",
"email": "jane.doe@customer.com",
"role": "signer",
"authentication_type": "email",
},
{
"user": "user_1Kcre8baSbmRNhTQIG",
"role": "approver",
"authentication_type": "email",
}
]
}
response = requests.post("https://sandbox.zefort.com/api/esigns/",
auth=(ZEFORT_APIKEY, ""),
json=payload)
print(response.json())
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
curl -X POST "https://sandbox.zefort.com/api/esigns/" \
-u $ZEFORT_APIKEY: \
-H "Content-Type: application/json" \
-d '{"contract": "ct_1JrjJRTzEMlHVHls", "parties": [{"name": "John Doe", "email": "john.doe@mycompany.com", "role": "signer", "authentication_type": "email"},{"name": "Jane Doe", "email": "jane.doe@customer.com", "role": "signer", "authentication_type": "email"},{"user": "user_1Kcre8baSbmRNhTQIG", "role": "approver", "authentication_type": "email"}]}'
Response:
{
"id": "esgn_lHVHls1JrjJRTzEM",
"owner": {...},
"contract": "ct_1JrjJRTzEMlHVHls",
"contract_binders": [],
"title": "",
"sign_order": "parallel",
"greetings": null,
"need_auth_to_view_documents": false,
"signed": false,
"status": "draft",
"activated": false,
...
"parties": [
{
"id": "sgnr_JrjJRTzEMlHVHls1",
"name": "John Doe",
"additional_info": null,
"email": "john.doe@mycompany.com",
"phone_number": null,
"language": "en-us",
"personal_id": null,
"status": "draft",
"role": "signer",
"ordinal": 2,
"authentication_type": "email",
"need_auth_to_view_documents": false,
...
},
...
],
"documents": [
{
"id": "esdc_1KgKFkdqWRU7keVFq5",
"content_type": "application/pdf",
"filename": "test.pdf",
"signable": true,
"needs_to_be_viewed": true
}
]
}
The signing request is initially created in a draft state and must be separately activated. Once activated, the signing requests will be sent to all parties whose signing or approval is not completed.
If you want to add signing parties after creating the signing request, you should use own endpoints for each additional party (/api/esign/<id>/parties/
)
import requests
ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"
response = requests.post(
f"https://sandbox.zefort.com/api/esigns/{ESIGN_ID}/activate/",
auth=(ZEFORT_APIKEY, "")
)
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
ESIGN_ID="esgn_lHVHls1JrjJRTzEM"
curl -X POST "https://sandbox.zefort.com/api/esigns/$ESIGN_ID/activate/" \
-u $ZEFORT_APIKEY:
Signing requests can only have a single document to be signed. However, the request can include multiple documents used as attachments. While the attachments are not signed, their filenames and checksums will be included in the signed document.
The easiest way to add attachments is to add them to the main contract stored in Zefort. In this case, the main document will be signed and the other documents will be shown as attachments.
You can also explicitly pick documents from other contracts stored in Zefort. For example, if you have a separate General Terms and Conditions document, you can re-use that for your signing request.
Key parameters:
contract | Mandatory: Zefort’s unique ID for the contract to be signed. If the contract has multiple documents, the main document will be signed and the other documents will be automatically included as attachments. |
document | Optional: Explicitly defines the document to be signed. If document is given, no attachments will be added automatically. Document needs to belong to given contract. |
attachments | Optional: Additional documents stored in Zefort. These can belong to any contract stored in Zefort. |
needs_to_be_viewed | If set to true all attachments must be opened and viewed before signing or approval is possible. The default value is false meaning that only the signed document must be opened and viewed. |
import requests
ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
CONTRACT_ID = "ct_1JrjJRTzEMlHVHls"
DOCUMENT_ID = "doc_2VHlsJrjJRTzEMlH"
ATTACHMENT1_ID = "doc_5zEMlHVHlsJrjJRT"
ATTACHMENT2_ID = "doc_7RTzEMlHVHlsJrjJ"
payload = {
"contract": CONTRACT_ID,
"document": DOCUMENT_ID,
"attachments": [ATTACHMENT1_ID, ATTACHMENT2_ID],
"needs_to_be_viewed": True,
}
response = requests.post("https://sandbox.zefort.com/api/esigns/",
auth=(ZEFORT_APIKEY, ""),
json=payload)
print(response.json())
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
curl -X POST "https://sandbox.zefort.com/api/esigns/" \
-u $ZEFORT_APIKEY: \
-H "Content-Type: application/json" \
-d '{"contract": "ct_1JrjJRTzEMlHVHls", "document": "doc_2VHlsJrjJRTzEMlH", "attachments": ["doc_5zEMlHVHlsJrjJRT", "doc_7RTzEMlHVHlsJrjJ"], "needs_to_be_viewed": true}'
Zefort Sign provides several authentication methods for identifying the users signing or approving documents.
SMS and email authentication are so-called basic authentication methods. Strong authentication methods include Bank ID authentication or other country-specific authentication methods.
You can define the authentication type individually for each party in the signing request.
Key parameters:
authentication_type |
|
personal_id | Optional parameter for strong authentication. If defined, the value received from the external authentication service must match this value to authenticate the user. |
need_auth_to_view_documents | Optional parameter. When set to true, all parties have to use either SMS or strong authentication to view or sign the document. |
NOTE: As new authentication types are added continuously, please refer to the technical API documentation for a complete list of authentication types.
import requests
ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
CONTRACT_ID = "ct_1JrjJRTzEMlHVHls"
payload = {
"contract": CONTRACT_ID,
"need_auth_to_view_documents": True,
"parties": [
{
"name": "John Doe",
"email": "john.doe@mycompany.com",
"role": "signer",
"authentication_type": "sms",
"phone_number": "+3581234567",
},
{
"name": "Jane Doe",
"email": "jane.doe@customer.com",
"role": "signer",
"authentication_type": "finnish_trust_network",
"personal_id": "070770-905D",
},
{
"name": "Jakob Doe",
"email": "jakob.doe@customer.com",
"role": "signer",
"authentication_type": "strong_auth_sweden",
},
]
}
response = requests.post("https://sandbox.zefort.com/api/esigns/",
auth=(ZEFORT_APIKEY, ""),
json=payload)
print(response.json())
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
curl -X POST "https://sandbox.zefort.com/api/esigns/" \
-u $ZEFORT_APIKEY: \
-H "Content-Type: application/json" \
-d '{"contract": "ct_1JrjJRTzEMlHVHls", "need_auth_to_view_documents": "true", "parties": [{"name": "John Doe", "email": "john.doe@mycompany.com", "role": "signer", "authentication_type": "sms", "phone_number": "+3581234567"},{"name": "Jane Doe", "email": "jane.doe@customer.com", "role": "signer", "authentication_type": "finnish_trust_network", "personal_id": "070770-905D"},{"name": "Jakob Doe", "email": "jakob.doe@customer.com", "role": "signer", "authentication_type": "strong_auth_sweden"]}'
You can also define the signing order for each signing party. There are three options for sign_order: parallel
, consecutive
and flexible
. Sign order limits how ordinal can be set to signing parties.
Parallel sign order is the simplest option. All approvers will first get an approval request. Once all approvers have approved documents, all signers will get a request to sign the contract. At the end, all signers and CC's will receive a link to the final, signed documents.
In consecutive signing order, approvers and signers will approve or sign the contract in a consecutive order. CC's won't have ordinal and will always just receive final documents.
Easiest way to set a signing order is to send parties in one array with desired order. It's also possible to update parties' ordinals with patch request. New parties without ordinal will be set as the last one to sign or approve.
In flexible signing order there are no limitations for setting ordinals. When all parties with a given ordinal have completed their signing or approval, parties with the next ordinal will receive an invitation to sign or approve.
If parties are added without ordinal, those will be set as last in the process.
import requests
ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
CONTRACT_ID = "ct_1JrjJRTzEMlHVHls"
payload = {
"contract": CONTRACT_ID,
"sign_order": "consecutive",
"parties": [
{
"name": "John Doe",
"email": "john.doe@mycompany.com",
"role": "signer",
"authentication_type": "email",
"ordinal": 1,
},
{
"name": "Jack Doe",
"email": "jack.doe@customer.com",
"role": "approver",
"authentication_type": "email",
"ordinal": 2,
},
{
"name": "Jane Doe",
"email": "jane.doe@customer.com",
"role": "signer",
"authentication_type": "email",
"ordinal": 3,
},
]
}
response = requests.post("https://sandbox.zefort.com/api/esigns/",
auth=(ZEFORT_APIKEY, ""),
json=payload)
print(response.json())
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
curl -X POST "https://sandbox.zefort.com/api/esigns/" \
-u $ZEFORT_APIKEY: \
-H "Content-Type: application/json" \
-d '{"contract": "ct_1JrjJRTzEMlHVHls", "sign_order": "consecutive", "parties": [{"name": "John Doe", "email": "john.doe@mycompany.com", "role": "signer", "authentication_type": "email", "ordinal": 1},{"name": "Jack Doe", "email": "jack.doe@mycompany.com", "role": "approver", "authentication_type": "email", "ordinal": 2},{"name": "Jane Doe", "email": "jane.doe@customer.com", "role": "signer", "authentication_type": "email", "ordinal": 3}]}'
When the signing request has been activated,signing parties can still be added and existing parties edited and deleted as long as the party has not yet signed or approved the document. When signing is completed, no changes to parties are allowed. Documents cannot be edited after activation.
Here is an example of editing an email address for a signer. In this case, a new signing request will be sent automatically.
import requests
ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"
PARTY_ID = "sgnr_JrjJRTzEMlHVHls1"
response = requests.patch(
f"https://sandbox.zefort.com/api/esigns/{ESIGN_ID}/parties/{PARTY_ID}/",
auth=(ZEFORT_APIKEY, ""),
json={"email": "john.doe@greatcompany.com"}
)
print(response.json())
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
ESIGN_ID="esgn_lHVHls1JrjJRTzEM"
PARTY_ID="sgnr_JrjJRTzEMlHVHls1"
curl -X PATCH "https://sandbox.zefort.com/api/esigns/$ESIGN_ID/parties/$PARTY_ID/" \
-u $ZEFORT_APIKEY: \
-H "Content-Type: application/json" \
-d '{"email": "john.doe@greatcompany.com"}'
It is possible to cancel a signing request. When a request is canceled, all parties will be informed via email.
import requests
ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"
response = requests.post(
f"https://sandbox.zefort.com/api/esigns/{ESIGN_ID}/cancel/",
auth=(ZEFORT_APIKEY, "")
)
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"
curl -X POST "https://sandbox.zefort.com/api/esigns/$ESIGN_ID/cancel/" \
-u $ZEFORT_APIKEY:
An expired or cancelled signing request can be reactivated for signing. If a signing request has expired, the deadline needs to be updated before reactivation.
import datetime
import requests
ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"
deadline = datetime.datetime.now() + datetime.timedelta(weeks=2)
response = requests.patch(
f"https://sandbox.zefort.com/api/esigns/{ESIGN_ID}/",
auth=(ZEFORT_APIKEY, ""),
json={"deadline": str(deadline)}
)
response = requests.post(
f"https://sandbox.zefort.com/api/esigns/{ESIGN_ID}/reactivate/",
auth=(ZEFORT_APIKEY, "")
)
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"
curl -X PATCH "https://sandbox.zefort.com/api/esigns/$ESIGN_ID/" \
-u $ZEFORT_APIKEY: \
-H "Content-Type: application/json" \
-d '{"deadline": "2023-12-31 00:00:00"}'
curl -X POST "https://sandbox.zefort.com/api/esigns/$ESIGN_ID/reactivate/" \
-u $ZEFORT_APIKEY:
When signing has been completed, the availability of documents can be changed by updating the available_until
field. Links to final documents can be resent to all parties who have originally received the final signed documents.
import datetime
import requests
ZEFORT_APIKEY = "ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"
available_until = datetime.datetime.now() + datetime.timedelta(weeks=2)
response = requests.patch(
f"https://sandbox.zefort.com/api/esigns/{ESIGN_ID}/",
auth=(ZEFORT_APIKEY, ""),
json={"available_until": str(available_until)}
)
response = requests.post(
f"https://sandbox.zefort.com/api/esigns/{ESIGN_ID}/resend_completed/",
auth=(ZEFORT_APIKEY, "")
)
ZEFORT_APIKEY="ySLE1mhh6lfRsYa..."
ESIGN_ID = "esgn_lHVHls1JrjJRTzEM"
curl -X PATCH "https://sandbox.zefort.com/api/esigns/$ESIGN_ID/" \
-u $ZEFORT_APIKEY: \
-H "Content-Type: application/json" \
-d '{"available_until": "2023-12-31 00:00:00"}'
curl -X POST "https://sandbox.zefort.com/api/esigns/$ESIGN_ID/resend_completed/" \
-u $ZEFORT_APIKEY: