Protocol

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: null are 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"
}