Aller au contenu principal

10. Contrats de location

Deux parcours coexistent selon que le contrat est généré via PandaDoc ou importé en PDF déjà signé.

ParcoursQuand l'utiliserMutations principales
Génération PandaDocCréer un contrat à faire signer électroniquementgenerateRentalContract, createPandadocDraftSession, sendGeneratedRentalContract
Import PDF signéAjouter un contrat déjà signé hors UblouploadImportedContractDocumentFile, updateImportedRentalContract
Feature flag

Les deux parcours requièrent le feature flag organisation ENABLE_CONTRACT_V2. Sans ce flag, les mutations V2 renvoient : Contract V2 feature not enabled for this organization.


Partie A — Génération & envoi (PandaDoc)

Observé — génération de contrat, session brouillon PandaDoc, envoi aux signataires.

Pourquoi

Produire un contrat de location à partir d'un dossier, ouvrir l'éditeur / brouillon e-signature, puis envoyer le document généré.

Prérequis

  • Dossier de location (rentalFolderId)
  • Modèle de contrat (contractTemplateId)
  • Signataires (personalProfileId / rôles) et représentant
  • Feature flag ENABLE_CONTRACT_V2 actif

10.1 Générer (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 Session brouillon PandaDoc (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 Envoyer le contrat (sendGeneratedRentalContract)

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

Partie B — Import d'un contrat signé

Reconstruit — workflow import PDF via UploadCare, aligné sur le schéma GraphQL public.

Pourquoi

Ajouter au dossier un contrat de location déjà signé (PDF externe), avec ses dates clés et signataires, sans passer par PandaDoc.

Prérequis

  • Feature flag organisation ENABLE_CONTRACT_V2 actif
  • Dossier locatif existant (rentalFolderId)
  • PDF déjà uploadé sur UploadCare (foreignId retourné par UploadCare)
  • Au moins un signataire dans signatories (profil physique ou moral + rôle)
  • Profils locataires / collaborateurs déjà créés dans le dossier

Workflow

Distinction avec le flux legacy

MutationUsageStatut avec ENABLE_CONTRACT_V2
uploadImportedContractDocumentFileCrée un nouveau contrat importé + document + signataires en une seule opération atomiqueA utiliser pour l'import
uploadContractDocumentFileAttache un document UploadCare à un contrat existant (workflow legacy)Deprecie — renvoie This endpoint is deprecated when Contract V2 is enabled
generateRentalContractGénère un contrat PandaDoc à faire signerParcours génération (partie A)

10.4 Importer (uploadImportedContractDocumentFile)

Arguments

ArgumentTypeObligatoireDescription
rentalFolderIdUUID!OuiDossier locatif cible
inputUploadContractDocumentFileInput!OuiDocument, dates clés, signataires

Input UploadContractDocumentFileInput

ChampTypeObligatoireDescription
foreignIdNonEmptyString!OuiIdentifiant UploadCare du PDF
mimeTypeStringNonEx. application/pdf
nameStringNonNom affiché du document
descriptionStringNonDescription du document
signedAtDate!OuiDate de signature du contrat
isAmendmentBoolean!Ouifalse = contrat, true = avenant
contractStartDateDate!OuiPrise d'effet
contractEndDateDate!OuiFin de validité
durationInMonthsInt!OuiDurée initiale en mois
renewDurationInMonthsInt!OuiDurée d'une reconduction
leaseRenewalOptionLeaseRenewalOptionTypeEnum!Ouiautomatic_renewal, limited_renewal_number, no_renewal
leaseRenewalLimitIntConditionnelRequis si leaseRenewalOption = limited_renewal_number
terminationOptionDates[Date!]NonOptions de congé (bail commercial)
signatories[RentalContractSignatoryInput!]!OuiAu moins un signataire
signatoryContractRepresentativeIdUUIDNonCollaborateur signataire (représentant bailleur)

Input RentalContractSignatoryInput

ChampTypeDescription
personalProfileIdUUIDProfil physique (locataire, garant…)
moralProfileIdUUIDProfil moral
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": "bail-signe.pdf",
"description": "Bail signe hors 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"
}
]
}
}

Valeurs definies automatiquement

L'API renseigne automatiquement les champs suivants sans les exposer en input :

ChampValeurDetail
contractTypeimportedDistinction avec generated (PandaDoc)
contractStatussignedContrat deja signe
documents[].provideruploadcareStockage UploadCare
documents[].statecompletedDocument finalise
documents[].typecontractType document
signatories[].statuscompletedTous les signataires ont signe
signatories[].signedAtegal a signedAt du contratDate de signature commune
referencegeneree automatiquementPrefixe CR, ex. CR00042

Effets de bord

  • Verrouillage des dates cles du contrat
  • Indexation pour la recherche (TypeSense)

Erreurs courantes

MessageCause probable
Contract V2 feature not enabled for this organizationFeature flag ENABLE_CONTRACT_V2 inactif sur l'organisation
This endpoint is deprecated when Contract V2 is enabledUtilisation de uploadContractDocumentFile (legacy) au lieu de uploadImportedContractDocumentFile
Erreur de validation sur signedAtChamp obligatoire manquant dans l'input
Erreur de validation sur signatoriesListe vide ou profils invalides

10.5 Modifier un contrat importe (updateImportedRentalContract)

Utiliser cette mutation pour corriger les dates cles, le type de document (isAmendment), les signataires ou remplacer le fichier PDF.

Arguments

ArgumentTypeObligatoireDescription
idUUID!OuiID du RentalContract importe
inputUpdateImportedRentalContractInput!OuiChamps modifiables

Les champs obligatoires de UpdateImportedRentalContractInput sont les memes que pour l'import (contractStartDate, contractEndDate, durationInMonths, renewDurationInMonths, leaseRenewalOption, isAmendment, signatories). Les champs document (foreignId, mimeType, name, description) et signedAt sont optionnels : les omettre laisse la valeur existante inchangee.

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"
}
]
}
}
attention

updateImportedRentalContract ne s'applique qu'aux contrats de type imported. Les contrats generated (PandaDoc) suivent un autre cycle de vie.


Subscription observee

La capture a egalement souscrit a RentalContractChanged (suivi d'etat du contrat). Pas d'exemple complet ici — voir le schema Subscription.

Suite

Cycle de vie d'une facture · Lire un dossier