Skip to main content

10. Rental contracts

Two paths exist depending on whether the contract is generated via PandaDoc or imported as an already-signed PDF.

PathWhen to useMain mutations
PandaDoc generationCreate a contract for electronic signaturegenerateRentalContract, createPandadocDraftSession, sendGeneratedRentalContract
Signed PDF importAdd an already-signed contract from outside UblouploadImportedContractDocumentFile, updateImportedRentalContract
Feature flag

Both paths require the organization feature flag ENABLE_CONTRACT_V2. Without it, V2 mutations return: Contract V2 feature not enabled for this organization.


Part A — Generation & send (PandaDoc)

Observed — contract generation, PandaDoc draft session, send to signatories.

Why

Produce a rental contract from a folder, open the e-sign draft/editor, then send the generated document.

Prerequisites

  • Rental folder (rentalFolderId)
  • Contract template (contractTemplateId)
  • Signatories (personalProfileId / roles) and representative
  • ENABLE_CONTRACT_V2 feature flag active

10.1 Generate (generateRentalContract)

mutation generateRentalContract($rentalFolderId: UUID!, $input: GenerateRentalContractInput) {
generateRentalContract(rentalFolderId: $rentalFolderId, input: $input) {
id
state
contractStatus
contractType
}
}
{
"rentalFolderId": "00000000-0000-4000-8000-000000000093",
"input": {
"contractTemplateId": "00000000-0000-4000-8000-000000000104",
"isAmendment": false,
"contractStartDate": "2026-08-03",
"contractEndDate": "2029-08-02",
"durationInMonths": 36,
"leaseRenewalOption": "limited_renewal_number",
"renewDurationInMonths": 36,
"leaseRenewalLimit": 1,
"signatoryContractRepresentativeId": "00000000-0000-4000-8000-000000000105",
"signatories": [
{
"role": "tenant",
"personalProfileId": "00000000-0000-4000-8000-000000000099"
}
]
}
}

10.2 PandaDoc draft session (createPandadocDraftSession)

mutation createPandadocDraftSession($rentalContractId: UUID!) {
createPandadocDraftSession(rentalContractId: $rentalContractId) {
eToken
expiresAt
}
}
{
"rentalContractId": "00000000-0000-4000-8000-000000000106"
}
curl -sS -X POST "$UBLO_GRAPHQL_URL" \
-H "Authorization: Bearer $UBLO_API_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"query":"mutation createPandadocDraftSession($rentalContractId: UUID!) { createPandadocDraftSession(rentalContractId: $rentalContractId) { eToken expiresAt } }","variables":{"rentalContractId":"00000000-0000-4000-8000-000000000106"}}'

10.3 Send the contract (sendGeneratedRentalContract)

mutation sendGeneratedRentalContract($rentalContractId: UUID!) {
sendGeneratedRentalContract(rentalContractId: $rentalContractId) {
id
state
contractStatus
}
}
{
"rentalContractId": "00000000-0000-4000-8000-000000000106"
}

Part B — Import a signed contract

Reconstructed — PDF import workflow via UploadCare, aligned with the public GraphQL schema.

Why

Add an already-signed rental contract (external PDF) to a folder with key dates and signatories, without going through PandaDoc.

Prerequisites

  • Organization feature flag ENABLE_CONTRACT_V2 active
  • Existing rental folder (rentalFolderId)
  • PDF already uploaded to UploadCare (foreignId returned by UploadCare)
  • At least one signatory in signatories (personal or moral profile + role)
  • Tenant profiles / collaborators already created in the folder

Workflow

Distinction from the legacy flow

MutationUsageStatus with ENABLE_CONTRACT_V2
uploadImportedContractDocumentFileCreates a new imported contract + document + signatories in one atomic operationUse this for import
uploadContractDocumentFileAttaches an UploadCare document to an existing contract (legacy workflow)Deprecated — returns This endpoint is deprecated when Contract V2 is enabled
generateRentalContractGenerates a PandaDoc contract for signatureGeneration path (part A)

10.4 Import (uploadImportedContractDocumentFile)

Arguments

ArgumentTypeRequiredDescription
rentalFolderIdUUID!YesTarget rental folder
inputUploadContractDocumentFileInput!YesDocument, key dates, signatories

Input UploadContractDocumentFileInput

FieldTypeRequiredDescription
foreignIdNonEmptyString!YesUploadCare file identifier
mimeTypeStringNoe.g. application/pdf
nameStringNoDisplay name of the document
descriptionStringNoDocument description
signedAtDate!YesContract signature date
isAmendmentBoolean!Yesfalse = contract, true = amendment
contractStartDateDate!YesEffective start date
contractEndDateDate!YesEnd date
durationInMonthsInt!YesInitial duration in months
renewDurationInMonthsInt!YesRenewal duration in months
leaseRenewalOptionLeaseRenewalOptionTypeEnum!Yesautomatic_renewal, limited_renewal_number, no_renewal
leaseRenewalLimitIntConditionalRequired when leaseRenewalOption = limited_renewal_number
terminationOptionDates[Date!]NoNotice option dates (commercial lease)
signatories[RentalContractSignatoryInput!]!YesAt least one signatory
signatoryContractRepresentativeIdUUIDNoCollaborator signatory (landlord representative)

Input RentalContractSignatoryInput

FieldTypeDescription
personalProfileIdUUIDPersonal profile (tenant, guarantor…)
moralProfileIdUUIDMoral profile
roleRentalContractSignatoryRoleEnumtenant, guarantor, proxy
mutation uploadImportedContractDocumentFile(
$rentalFolderId: UUID!
$input: UploadContractDocumentFileInput!
) {
uploadImportedContractDocumentFile(rentalFolderId: $rentalFolderId, input: $input) {
id
reference
contractType
contractStatus
isAmendment
signedAt
contractStartDate
contractEndDate
durationInMonths
renewDurationInMonths
leaseRenewalOption
leaseRenewalLimit
terminationOptionDates
signatoryContractRepresentativeId
documents {
... on UploadcareFile {
id
foreignId
provider
state
type
mimeType
name
}
}
signatories {
id
role
status
signedAt
personalProfileId
moralProfileId
}
}
}
{
"rentalFolderId": "00000000-0000-4000-8000-000000000093",
"input": {
"foreignId": "UPLOADCARE_FILE_ID_PLACEHOLDER",
"mimeType": "application/pdf",
"name": "signed-lease.pdf",
"description": "Lease signed outside Ublo",
"signedAt": "2026-01-15",
"isAmendment": false,
"contractStartDate": "2026-02-01",
"contractEndDate": "2029-01-31",
"durationInMonths": 36,
"renewDurationInMonths": 36,
"leaseRenewalOption": "limited_renewal_number",
"leaseRenewalLimit": 1,
"terminationOptionDates": ["2028-07-31"],
"signatoryContractRepresentativeId": "00000000-0000-4000-8000-000000000105",
"signatories": [
{
"role": "tenant",
"personalProfileId": "00000000-0000-4000-8000-000000000099"
},
{
"role": "guarantor",
"personalProfileId": "00000000-0000-4000-8000-000000000110"
}
]
}
}

Automatically set values

The API automatically sets the following fields without exposing them as input:

FieldValueDetail
contractTypeimportedDistinct from generated (PandaDoc)
contractStatussignedAlready signed contract
documents[].provideruploadcareUploadCare storage
documents[].statecompletedFinalized document
documents[].typecontractDocument type
signatories[].statuscompletedAll signatories have signed
signatories[].signedAtequal to contract signedAtShared signature date
referenceauto-generatedCR prefix, e.g. CR00042

Side effects

  • Contract key dates locked
  • Search indexing (TypeSense)

Common errors

MessageLikely cause
Contract V2 feature not enabled for this organizationENABLE_CONTRACT_V2 feature flag inactive on the organization
This endpoint is deprecated when Contract V2 is enabledUsing legacy uploadContractDocumentFile instead of uploadImportedContractDocumentFile
Validation error on signedAtRequired field missing from input
Validation error on signatoriesEmpty list or invalid profiles

10.5 Update an imported contract (updateImportedRentalContract)

Use this mutation to correct key dates, document type (isAmendment), signatories, or replace the PDF file.

Arguments

ArgumentTypeRequiredDescription
idUUID!YesImported RentalContract ID
inputUpdateImportedRentalContractInput!YesEditable fields

Required fields in UpdateImportedRentalContractInput match the import (contractStartDate, contractEndDate, durationInMonths, renewDurationInMonths, leaseRenewalOption, isAmendment, signatories). Document fields (foreignId, mimeType, name, description) and signedAt are optional: omitting them leaves the existing value unchanged.

mutation updateImportedRentalContract(
$id: UUID!
$input: UpdateImportedRentalContractInput!
) {
updateImportedRentalContract(id: $id, input: $input) {
id
contractType
contractStatus
isAmendment
signedAt
contractStartDate
contractEndDate
durationInMonths
renewDurationInMonths
leaseRenewalOption
leaseRenewalLimit
terminationOptionDates
signatoryContractRepresentativeId
documents {
... on UploadcareFile {
id
foreignId
name
}
}
signatories {
id
role
personalProfileId
moralProfileId
}
}
}
{
"id": "00000000-0000-4000-8000-000000000106",
"input": {
"contractStartDate": "2026-03-01",
"contractEndDate": "2029-02-28",
"durationInMonths": 36,
"renewDurationInMonths": 36,
"leaseRenewalOption": "automatic_renewal",
"isAmendment": true,
"signedAt": "2026-02-20",
"signatoryContractRepresentativeId": "00000000-0000-4000-8000-000000000105",
"signatories": [
{
"role": "tenant",
"personalProfileId": "00000000-0000-4000-8000-000000000099"
}
]
}
}
caution

updateImportedRentalContract only applies to imported contracts. generated contracts (PandaDoc) follow a different lifecycle.


Observed subscription

The capture also subscribed to RentalContractChanged (contract state tracking). No full example here — see the Subscription schema.

Next

Invoice lifecycle · Read a rental folder