Connecting Your Own Application

If you want to connect your own application or tool to the Documents Partner API, you'll need an application in your tenant's user management. This guide walks you through the setup step by step.

About the screenshots. They come from a test environment. The tenant name and the names of the people involved have been redacted; in your case, you'll see your own tenant instead. The client ID shown belongs to a test application and is only an example of the format.

⚠️ This requires a full license. As a rule, only a full license includes the right to register your own applications. With a partner-bound license, access is tied to the respective partner product, and a self-created application would be rejected with 403 CLIENT_NOT_LICENSED. The difference is explained in Licenses and Permissions.

Your contract is what matters — it may contain different terms in individual cases. If you're unsure whether this option is available for your firm, check with your STP contact beforehand.

What you get — and what you don't

Applies to Your tenant and its sub-tenants only
Display name Freely chosen
Client ID Generated, not assigned by you — you receive a GUID
Authentication method Only Authorization Code is enabled for the Documents Partner API
Permission scopes Only from the selection approved for your tenant
Client secret Required — the application is always confidential

The fact that the client ID is generated is the part that tends to catch people off guard: you can't choose it yourself. Your application must use the generated GUID.

STP administrators can also see the applications you create — useful if you contact support.

Are you a partner looking to offer your product to multiple firms? Then this approach isn't the right one — a tenant-specific application only works for a single tenant. A client that works for all licensed firms is registered centrally by STP; contact STP support for that.

Step one: Go to the application overview

Sign in as an administrator of your tenant and open Identity Admin. In the left sidebar, you'll find the Applications entry.

Identity Admin with the empty application list and the new application button

The list is empty at first ("no applications"). In the top right, "+ new application" lets you create one.

Step two: Basic details and behavior

New application form with display name, toggle list, token lifetimes, and grant type

Start by entering the display name — this will appear on the consent page later, so it should be meaningful to your users. In the example above, it's My Test Client.

Below that are the toggles. These must be enabled:

Toggle Why
enabled Otherwise the application is created but can't be used
requires user consent The user confirms once what the application is allowed to do
requires Proof Key for Code Exchange (PKCE) Required for the Authorization Code flow
allow access tokens via browser Needed when authentication takes place in the browser
include user properties in ID token Provides information about the signed-in user
user Xid property The single most important toggle — see below
allow offline access Issues refresh tokens so the application can keep running without requiring re-authentication every hour

⚠️ Without "user Xid property", every request will fail later. The session in document management is run on behalf of the requesting user, and their identifier comes exclusively from this property. If it's missing, authentication completes without errors — yet every functional call will respond with 403 MISSING_USER_IDENTITY, an error that's nearly impossible to diagnose without this hint.

These toggles should remain off:

Toggle Why not
restrict access to internal host only Would block external access
reuse refresh tokens Only with good reason; the interface itself warns against it
update user properties with refresh tokens Not needed for this purpose
use reference tokens Requires additional validation capabilities on the API side

The "refresh token length in bytes" and "left panel URI" fields should be left empty. For the former, the interface itself warns correctly — too few bytes leads to collisions.

You can leave the token lifetimes below unchanged. The default values are:

Field Default
Identity token lifetime 300 seconds
Access token lifetime 3600 seconds (one hour)
Authorization code lifetime 300 seconds
Device code lifetime 300 seconds
Absolute refresh token lifetime 2592000 seconds (30 days)

Step three: Grant type

Under allowed grant types, select Authorization Code.

For the Documents Partner API, this is the only available option — and that's intentional: the application acts on behalf of a user, and only this flow carries a user identifier. A flow without user authentication would fail at the same point as a missing Xid property.

Step four: Permission scopes

Allowed scopes, the shared secrets table, and the client secret toggle

Under allowed scopes, select what the application is permitted to request. For the Documents Partner API, these are:

Scope in the UI Technical name Purpose
ID / Your user identifier openid Authentication
ID / User profile profile User profile
ID / Your user email address email User's email address
UserManagement.Identity.Account / read my profile um.acc.profile.read Read the signed-in user's profile
Documents Partner API: standalone reach dms.cloud.api.standalone Mandatory — see below
Documents Partner API: read documents (list, download) dms.cloud.api.documents.read Read documents
Documents Partner API: write documents (upload/import) dms.cloud.api.documents.write Write documents
Documents Partner API: stage content via presigned upload URLs dms.cloud.api.uploads Stage files

A read-only application can get by without documents.write and uploads. Anything that needs to store documents requires both.

Why dms.cloud.api.standalone must not be omitted

Every request is checked against two types of scopes: the functional scope and the license scope. If the license scope is missing, the request is rejected before the functional scope is even considered — with 403 LICENSE_MISSING.

For a self-created application, dms.cloud.api.standalone is the only license scope available — the partner-bound variant belongs to the respective partner product.

Without this scope, the application is fully configured yet rejected on every single request. The full validation sequence is described in Licenses and Permissions.

Step five: Client secret

The "requires client secret" toggle is mandatory for tenant-specific applications — leave it enabled. Use it to create a secret under "Pre-shared secrets" by clicking New.

Your application must therefore be able to store it securely. For a purely browser-based application that can't do this, this approach is not suitable — authentication should go through a server on your end that holds the secret.

The secret is only shown in plain text once. After that, the table only shows the hash value in the Value column; Value (unhashed) remains empty. Write down the secret immediately and store it in a secure location — it cannot be retrieved again, only deleted and recreated.

The second table, Certificate secrets, stays empty. It is not needed for the Documents Partner API.

Step six: URLs

Redirect URIs after sign-in and sign-out, CORS origins, and the Save button

Finally, enter the URLs for your application:

Field What to enter Required
Post-login redirect URIs The URL to redirect to after a successful sign-in, e.g. http://localhost:5099/callback yes
Post-logout redirect URIs The URL to redirect to after sign-out, e.g. http://localhost:5099/ recommended
allowed CORS origins Only needed if the application runs in the browser and calls the API directly, e.g. http://localhost:5099 optional

The URLs must match exactly what your application sends during sign-in — including scheme, port, and trailing slash.

A server-side service with no browser interface doesn't need a CORS origin.

Click Save to create the application.

Changes don't take effect immediately. The interface itself notes this: it can take up to 15 minutes for a change to take effect. If something isn't working right after saving, try waiting a moment and trying again before troubleshooting further.

Step seven: Copy the client ID

The created application with its generated client ID and assigned scopes

The application now appears in the list with its generated client ID — a GUID such as 303be097-b510-4436-95ac-f804f8933439. Next to it are the Owner (your tenant) and the Creator (whoever created it). Below that, the overview shows the assigned scopes, for example:

"dms.cloud.api.documents.read" "dms.cloud.api.documents.write"
"dms.cloud.api.standalone" "dms.cloud.api.uploads"
"email" "openid" "profile" "um.acc.profile.read"

This GUID is the client_id your application uses when signing in. Together with the tenant name, you now have everything described in Getting Started for the authentication flow.

Step eight: Complete the authentication

With the client ID in hand, the flow is the same as for any other application. The endpoints are available at your tenant's authentication URL:

https://<tenant>.stp-cloud.de/identity/.well-known/openid-configuration

The subdomain must match the tenant that the acting user account belongs to — for both authorize and token. A request against a different tenant will fail, even if the client ID and secret are correct.

First: Send the user to sign in

GET https://<tenant>.stp-cloud.de/identity/connect/authorize
      ?client_id=303be097-b510-4436-95ac-f804f8933439
      &redirect_uri=http%3A%2F%2Flocalhost%3A5099%2Fcallback
      &response_type=code
      &scope=*
      &state=<your-random-value>
      &code_challenge=<BASE64URL-SHA256-of-verifier>
      &code_challenge_method=S256

After sign-in and consent, the user is redirected to the redirect URI, which carries code and state as query parameters.

scope=* is the recommended approach. STP-IAM responds to a wildcard — as well as an empty scope — with all scopes the application is allowed to request. What actually ends up in the token is always the intersection with the license and the user's roles.

The reason to prefer this over a fixed list: an authentication service will reject the entire request with invalid_scope if even a single named scope is unknown. A hard-coded list is therefore one rename away from breaking authentication entirely. An explicit list only makes sense if an application deliberately needs to request fewer scopes than it's entitled to.

Second: Exchange the code for a token

POST to the token endpoint, with content as application/x-www-form-urlencoded:

Field Value
grant_type authorization_code
code The code from the redirect
client_id The generated GUID of the application
redirect_uri Exactly the same as in the previous request
code_verifier The secret used to generate the code_challenge
client_secret The secret created in step five
curl -sS -X POST \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=$CODE" \
  -d "client_id=303be097-b510-4436-95ac-f804f8933439" \
  -d "redirect_uri=http://localhost:5099/callback" \
  -d "code_verifier=$CODE_VERIFIER" \
  -d "client_secret=$CLIENT_SECRET" \
  "https://<tenant>.stp-cloud.de/identity/connect/token"

Two common reasons this request fails:

  • code_verifier is required whenever PKCE is enabled — and it is here. The authentication service validates it against the previously sent code_challenge. Without it, the exchange is rejected even if the code and secret are correct.
  • client_secret is always required here. Tenant-specific applications are always confidential, so the authentication service expects the secret. (Only with a public client — which doesn't exist in this setup — would the field be omitted entirely; sending it as empty would also be wrong in that case.)

The rest of the flow — renewal via grant_type=refresh_token, token contents — is covered in Getting Started.

Step nine: Your first request

The API is available under the same tenant:

https://<tenant>.stp-cloud.de/documents/dms-cloud-api/api

The version identifier does not belong in the base URL — it goes at the beginning of each path. Your first request should be a search — it validates the entire chain from token through license all the way into document management:

curl -sS \
  -H "Authorization: Bearer $TOKEN" \
  -H "X-Correlation-Id: my-first-request" \
  "https://<tenant>.stp-cloud.de/documents/dms-cloud-api/api/v1/dms/documents/container?pageSize=5"

If you get a list back — even an empty one — the integration is working. To see where to go from there, check out Quick Start.

If you get an error instead

Response Meaning What to do
401 (no error body) Token is missing, expired, or invalid Fetch a new token
403 LICENSE_MISSING The tenant has no license, or the token doesn't carry a license scope Have the licensing checked
403 CLIENT_NOT_LICENSED The license scope in the token belongs to a different application Check that dms.cloud.api.standalone is in the allowed scopes
403 INSUFFICIENT_SCOPE License is present, but the capability scope is missing Check the account's role — and whether the application is allowed to request the scope
403 MISSING_USER_IDENTITY The token doesn't carry a user identifier Enable "user Xid property" on the application (step two)
404 NOT_FOUND Does not exist or is not visible to this user Check the identifier — and the account's permissions in document management
502 CONNECTOR_OFFLINE The firm's connector is unreachable Have the connector's status checked

The full list is available in Error Codes.

What else you'll need

The application alone isn't enough — it only sets the upper limit. You'll still need:

  1. A role for the user accounts that will be acting. Without one, the functional scope won't be present in the token, and requests will end with 403 INSUFFICIENT_SCOPE — see Roles and Groups.
  2. An installed and connected Connector — otherwise every request will respond with 502 CONNECTOR_OFFLINE.

What actually ends up in the token is the intersection of what the application is allowed to request, what the license grants, and what the user's roles provide. A scope that's missing from the application will never make it in — no matter how well licensed or permissioned the user is.

Further reading