deep.rent Portal API
Public reference for creating and managing portal documents and links via API keys.
Base URL: https://api.deep.rent (replace with your environment, e.g. https://api-dev.deep.rent).
Overview
Use the Portal API when your system wants to publish documents or links into a customer's deep.rent portal.
Typical examples are operating cost statements, handover documents, inspection reports, contracts, certificates, or links to an external workflow.
The core idea is simple: you create a portal entry, decide who should see it, optionally upload a file, and then notify the recipient.
What you can publish
| Use case | How it works |
|---|---|
| Provide a PDF or file to a tenant, owner, customer, or partner | Create a document entry, request an upload URL, upload the file, and mark the upload as complete. |
| Share a link instead of a file | Create a link entry with metadata.url. No file upload is needed. |
| Publish into a specific portal folder | Create the entry with folderId. |
| Make content available to a group of recipients | Create the entry with accessGroupIds. |
| Give access to several email recipients directly | Create the entry with accessEmails. |
| Keep recipient data in sync with your CRM | Create or upsert contacts via /external/contacts, then target entries with contactId. |
Choosing recipients
Every entry needs at least one recipient or access scope. You can combine these options when needed.
| Target | Field | Best for |
|---|---|---|
| One existing CRM contact | contactId |
Clean CRM sync, named recipient in the Protocol App, automatic portal identity linking. |
| One email address | email |
Quick delivery when you do not need a full CRM contact. |
| Multiple email addresses | accessEmails |
Sharing one document with several external recipients. |
| A portal folder | folderId |
Publishing content into a known object, address, or customer folder. |
| A consumer group | accessGroupIds |
Sharing with a predefined group, for example all tenants or all owners for an object. |
Typical document flow
1. Optional: create or upsert the recipient contact
PUT /external/contacts/:id
2. Create the portal entry and define who can access it
POST /external/portal/entries
3. For a file: request a presigned upload URL
POST /external/portal/files/upload-url
4. Upload the file bytes directly to storage
PUT <uploadUrl>
5. Mark the upload as complete
PATCH /external/portal/files/:fileId/upload-status
6. Optional: trigger the "new document" notification
POST /external/portal/notifications/new-entry
Minimal example
This creates a portal entry for an existing contact. Add the file upload steps when the entry should contain a document.
POST /external/portal/entries
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"title": "Operating cost statement 2025",
"description": "Available in your customer portal",
"contactId": "CONTACT_ID",
"folderId": "FOLDER_ID"
}
Main endpoint groups
| Area | Endpoint |
|---|---|
| Entries | /external/portal/entries |
| Files | /external/portal/files/* |
| Folders | /external/portal/folders |
| Groups | /external/portal/consumer-groups |
| Contacts | /external/contacts |
| Notifications | /external/portal/notifications/new-entry |
Authentication
API key (/external/*)
Send your API key on every external request:
x-api-key: YOUR_API_KEY
Or:
Authorization: Bearer YOUR_API_KEY
- Each key is bound to one organization. You cannot override the organization with
x-org-id. - The key owner must be a member of that organization.
- Keys with
permissions: nullare legacy unrestricted keys. - Restricted keys must include the required portal permission for each endpoint.
Permissions
| Permission | Allows |
|---|---|
portal:entries:read |
List/read entries, files, folders, consumer groups, and create download URLs |
portal:entries:write |
Create, archive, unarchive, delete, upload, update upload status, and notify |
contacts:read |
List and read contacts |
contacts:write |
Create, upsert, and archive contacts |
Missing permission returns:
{
"status": 403,
"message": "Insufficient API key permissions",
"required": "portal:entries:write"
}
Entries
Portal entries can be documents or links. A new entry must have a title and at least one recipient or access scope:email, contactId, folderId, accessGroupIds, or accessEmails.
For link entries, include metadata.url.
Create entry
POST /external/portal/entries
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"title": "Rental handover documents",
"description": "Documents for the tenant portal",
"entryType": "DOCUMENT",
"contentType": "DOCUMENT",
"email": "[email protected]",
"folderId": "folder-id",
"accessGroupIds": ["group-id"],
"accessEmails": ["[email protected]"]
}
Response 201: the created portal entry.
Create link entry
POST /external/portal/entries
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"title": "External checklist",
"entryType": "LINK",
"contentType": "LINK",
"email": "[email protected]",
"metadata": {
"url": "https://example.com/checklist"
}
}
List entries
GET /external/portal/entries?folderId=folder-id&objectId=object-id&includeDeleted=false
x-api-key: YOUR_API_KEY
Query parameters:
| Name | Type | Notes |
|---|---|---|
folderId |
string | Optional folder filter. Folder must belong to the key organization. |
objectId |
string | Optional object filter. |
includeDeleted |
boolean | Include archived/deleted entries when true. |
Get entry detail
GET /external/portal/entries/:entryId
x-api-key: YOUR_API_KEY
Returns 404 when the entry does not belong to the API key organization.
Archive entry
POST /external/portal/entries/:entryId/archive
x-api-key: YOUR_API_KEY
Unarchive entry
POST /external/portal/entries/:entryId/unarchive
x-api-key: YOUR_API_KEY
Delete entry
DELETE /external/portal/entries/:entryId
x-api-key: YOUR_API_KEY
Hard-deletes the entry and its storage objects. Returns 204 with no body.
Files
Files use a presigned upload/download flow. API keys create the file records and URLs;
the actual bytes are uploaded directly to storage with the returned URL.
List entry files
GET /external/portal/entries/:entryId/files?fileRole=ORIGINAL&includePending=true
x-api-key: YOUR_API_KEY
Query parameters:
| Name | Type | Notes |
|---|---|---|
fileRole |
string | Optional role filter. |
includePending |
boolean | Include files that have not yet completed upload. |
Create upload URL
POST /external/portal/files/upload-url
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"entryId": "entry-id",
"fileName": "handover.pdf",
"mimeType": "application/pdf",
"fileRole": "ORIGINAL"
}
Response 200:
{
"fileId": "file-id",
"bucket": "bucket-name",
"objectKey": "portal/...",
"uploadStatus": "PENDING",
"uploadUrl": "https://...",
"expiresIn": 900
}
Upload bytes
PUT https://presigned-upload-url
Content-Type: application/pdf
<file bytes>
Do not send the deep.rent API key to the presigned storage URL.
Update upload status
PATCH /external/portal/files/:fileId/upload-status
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"status": "UPLOADED"
}
Allowed status values are UPLOADED and FAILED. When set to UPLOADED, the backend verifies the object and makes the file visible.
Create download URL
POST /external/portal/files/download-url
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"fileId": "file-id",
"disposition": "inline"
}
disposition can be inline or attachment; it defaults to inline.
Response 200:
{
"fileId": "file-id",
"entryId": "entry-id",
"fileName": "handover.pdf",
"mimeType": "application/pdf",
"fileRole": "ORIGINAL",
"uploadStatus": "UPLOADED",
"downloadUrl": "https://...",
"expiresIn": 900
}
Folders and groups
List folders
GET /external/portal/folders
x-api-key: YOUR_API_KEY
With no query parameters, this returns organization root folders.
Optional filters:
GET /external/portal/folders?objectId=object-id
GET /external/portal/folders?addressId=address-id
List consumer groups
GET /external/portal/consumer-groups?objectId=object-id
x-api-key: YOUR_API_KEY
Send exactly one context filter:
| Name | Required | Notes |
|---|---|---|
objectId |
one of objectId or addressId |
Lists groups for an object. |
addressId |
one of objectId or addressId |
Lists groups for an address. |
Missing both filters returns 400 { "error": "object_id_or_address_id_required" }.
Notifications
Trigger new entry notification
POST /external/portal/notifications/new-entry
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"email": "[email protected]"
}
Or:
{
"identityId": "portal-identity-id"
}
The organization must own at least one portal entry for the identity. Otherwise the endpoint returns 403.
Contacts API
Manage the contacts your organization shares portal content with. Contacts created here appear in the Protocol App and are automatically linked to portal recipients by email.
Base path: /external/contacts — same authentication as the other external endpoints (x-api-key or Bearer).
Permissions: contacts:read for reads, contacts:write for writes. Keys with permissions: null are unrestricted.
Endpoints
| Method | Path | Permission | Purpose |
|---|---|---|---|
POST |
/external/contacts |
write | Create a contact. The server generates the id. |
PUT |
/external/contacts/:id |
write | Upsert with your id for idempotent CRM sync. Returns 201 when created, 200 when updated. |
GET |
/external/contacts |
read | List contacts. Supports includeArchived and integrationProvider query filters. |
GET |
/external/contacts/:id |
read | Read one contact. |
DELETE |
/external/contacts/:id |
write | Archive a contact with a soft delete. |
Create a contact
POST /external/contacts
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"contact": {
"salutation": "MR",
"firstName": "Max",
"lastName": "Mustermann",
"email": "[email protected]",
"phoneNumber": "+49 30 1234567",
"companyName": null
},
"details": {
"language": "de",
"role": "RENTER",
"type": "CONTACT"
},
"address": {},
"integration": {
"provider": "my-crm",
"externalId": "crm-4711"
}
}
All fields are optional. The body shape matches the session API (PUT /api/contacts/:id).
Response 201: the full contact object including the generated id.
Prefer PUT /external/contacts/:id with a stable id from your system when syncing. Repeated calls update instead of duplicating.
Use with portal entries
Create the contact first, then target portal entries with contactId or the contact's email.
POST /external/portal/entries
Content-Type: application/json
x-api-key: YOUR_API_KEY
{
"title": "Operating cost statement 2025",
"contactId": "CONTACT_ID"
}
Targeting by contactId uses the contact's email. Targeting by bare email also works and auto-links to a CRM contact with the same address when one exists.
Email-only vs CRM contact
| Approach | How | Result |
|---|---|---|
| Email only | Create an entry with email or accessEmails |
Creates or reuses a lightweight portal identity only. The recipient can access content, but has no name, phone, or role in the Protocol App Contacts workspace. |
| CRM contact | Create a contact through /external/contacts, then create an entry with contactId or matching email |
Links portal delivery to full contact data in the app. |
Portal identity auto-linking also works retroactively when a CRM contact with the same email exists later.
| Status | Meaning |
|---|---|
400 |
Validation failed, for example invalid field types or missing_id |
401 |
Missing or invalid API key |
403 |
Key lacks the required contacts permission |
404 |
Contact not found in your organization |
409 |
Conflict, for example duplicate integration external id |
Error handling
Errors are returned as JSON. Common responses:
| Status | Meaning |
|---|---|
400 |
Required field or query parameter missing/invalid |
401 |
API key missing or invalid |
403 |
Missing permission or forbidden organization access |
404 |
Entry, file, folder, or identity not found |
409 |
Notification could not be sent in the current state |
500 |
Internal server error |
Example:
{
"error": "entry_id_required"
}