# Keep credentials on the server: the runtime model for connecting a business system

Module credentials, Launch Grant exchange, short-lived instance tokens, member convergence, fail-closed scopes and idempotency ownership: the boundaries the server side must hold when a remote business system connects to Runlume.

> Runlume provides platform capabilities, independent business apps, and professional services that can be combined as needed. Features, allowances, and delivery scope follow the selected plan or service agreement. Business apps can be selected independently; collaboration follows configured integrations and authorization boundaries.

Original page: https://runlume.app/en/blog/platform-integration-runtime-model

Developer guides · App integration / Credentials and sessions / API contract

Published：2026-09-19 · Author：Runlume Team

When a remote business system connects to the platform, the hard part is rarely the API call itself. It is deciding who holds which credential, how long each one stays valid, and how quickly a revoked membership takes effect. These three questions decide whether the system still behaves correctly months later. This is what we hold on that path, and where the boundaries sit.

## Two levels of credential, and the long-lived one never leaves the server

The platform issues two kinds of identity. The module service identity is long-lived, lives only on the business system's server, and is used to exchange a Launch Grant (`POST /integration/v1/sso/launch-grants/exchange`) and to request an instance service token (`POST /integration/v1/service-tokens`). The instance service token is short-lived: at most five minutes, fixed audience, and scopes narrowed to the request at hand, returned with `no-store`.

The browser therefore never receives module credentials. The frontend only hands the Launch Code it was given to its own backend, and every exchange happens server-side. For short-lived tokens the SDK caches by instance plus the requested scope set, for at most 300 seconds, with no refresh: it requests a new token once the remaining lifetime falls below a safety window, a concurrent burst triggers a single exchange, and tokens are never reused across instances.

## Launch is a one-time exchange that ends in your own session

A user arriving from the platform carries a Launch Code, not a long-lived credential. The backend exchanges and consumes the Grant using the module service identity, then establishes the business system's own local session. That session holds derived identity only: no platform token, no Launch Code, no client secret and no full claims.

Both the exchange and later session checks verify signatures: the platform runtime JWKS is used to check the algorithm, issuer, audience, validity window, `token_use`, account and app instance, and scopes, with a key source that refuses redirects and caps response size. One point is easy to get wrong: a login page reporting "connected" only proves the backend can parse the platform's public keys. It does not prove the visitor is signed in, and it does not prove the Launch path works. Keep those three statements separate.

## When a membership is revoked, local sessions have to converge

A valid platform session does not mean a local session may run forever. Over a long-lived session, a removed member, a changed role, or a suspended instance all have to reach the business system within a bounded window. The mechanism is a batch check under the instance service identity (`POST /integration/v1/session-checks`): `active` is true only when the instance, the instance membership, the account and the account membership are all valid, while non-members and unknown users both return `active=false` so that membership existence does not leak. Each business system converges on the window it declares, and in practice write requests can use a shorter window than reads.

## If the scopes do not cover it, fail locally

Every runtime request carries scopes. The SDK only hands back usable credentials when the scopes the platform actually granted cover that request; otherwise it fails locally and sends no business request. The value of this rule is that "the platform never authorized this" stays separate from "the platform call failed": the first is a configuration problem that should be visible immediately, instead of turning into an opaque upstream error.

## Idempotency and retries belong to the caller

The SDK never retries a request on its own. Write operations must carry a stable idempotency key supplied by the caller, and a failed request is not resent — because "unknown outcome" and "failed" are different states, and only the business system knows whether to resend, query, or tell the user. On errors, platform Problem Details map to stable error classes (unauthenticated, forbidden, conflict, rate limited, server error, and so on) with a retryable marker, and credentials and response bodies never reach logs or error serialization.

## Two artifacts, two ways to obtain them

The two SDKs are not equally obtainable, so confirm which one applies before you start. On TypeScript and Node the package is `@td-runlume/platform-integration-sdk` on public npm, Apache-2.0, for server-side use. On Java the coordinate is `app.runlume.platform:platform-integration-sdk-java`, served from the platform's Nexus repository and **not on Maven Central**; it may only be used under credentials and a licence granted by the platform, and the artifacts must not be redistributed. Java integrations typically pair it with the Spring Boot starter, which wires the SDK components and the local session validity pipeline, leaving the business system to provide two extension points: a session boundary and a workspace lookup.

Endpoint paths, scope names and available capabilities follow the contract and platform documentation agreed at onboarding. This article only covers the boundaries the server side has to hold; the capabilities themselves start at [platform capabilities](/en/platform#capability-identity).

## Related pages

- [Platform](https://runlume.app/en/platform)
- [Business apps](https://runlume.app/en/apps)
- [Developers](https://runlume.app/en/developers)
- [About Runlume](https://runlume.app/en/about)
