How the API works
The API answers GET requests with JSON. It can read, never approve, release or change anything, so a leaked key exposes status data but can never push software to devices. Everything it returns, every member of your organization already sees in the dashboard.
https://app.reploya.com/api/v1
Two ways in, both sent as Authorization: Bearer: an API key, or an Entra access token of an application in your organization's own Microsoft tenant. Either reads every endpoint, for its own organization only.
API keys
- In the dashboard, open External access, then API access, and choose Create API key.
- Name: say where the key is used, so you know which one to revoke (1 to 80 characters, unique in your organization).
- Expiry: 30 days, 90 days, 1 year or No expiry; 1 year by default. An expired key stays listed as Expired until you revoke it.
- The dialog shows the key once, with a copy button and a ready
curlexample. It starts withrpk_. Store it now; it is not shown again. Reploya keeps only a one way fingerprint of it.
- Who manages keys: owners and admins (the
tenant.managepermission) create and revoke them. Every member sees the list: name, the first characters of the key, created, last used and expiry. - Revoke deletes a key at once; tools using it get
401on their next request. Keys cannot be edited or extended: create a new key, switch the tool over, then revoke the old one. - Keys belong to the organization, not to a person: a key keeps working when the member who created it leaves. At most 10 keys per organization, expired keys included.
Authentication
Send the key in the Authorization header of every request:
Authorization: Bearer rpk_…
curl -H "Authorization: Bearer $REPLOYA_API_KEY" \ "https://app.reploya.com/api/v1/organization"
- Never put a key in a URL, a query string or a browser page. The API sends no CORS headers, so web pages on other sites cannot call it; call it from a server, a script or a tool like n8n.
- The API answers on the app host only,
https://app.reploya.com/api/v1. Another Reploya host answers421instead of redirecting, because a client following a redirect may drop or leak theAuthorizationheader. - Cookies are ignored. A request is its key or token, nothing else.
- An Entra access token goes in the same header; see OAuth2 with Microsoft Entra.
OAuth2 with Microsoft Entra
For companies that prefer their own identity platform over a shared secret: an application or managed identity in your organization's own Microsoft tenant asks Microsoft for a short lived access token (the OAuth2 client credentials flow) and sends it instead of a key. Reploya stores no secret for it; certificates, managed identities and your Entra policies work as usual. You need an Entra admin who can grant admin consent. Keys keep working beside it.
Only tokens from the tenant your organization signs in with count, and only for applications you added in Reploya. Both are needed: the role granted in Entra, and the application added on the API access page.
Set up an app registration
- In the Microsoft Entra admin center, open App registrations and register an application (single tenant is fine). Add a certificate or a client secret under Certificates & secrets.
- Under API permissions, choose Add a permission, then APIs my organization uses, search for Reploya, pick Application permissions and tick
api.read. Then Grant admin consent. - In Reploya, open External access, then API access, and choose Add application. Enter a name and the Application (client) ID of the registration (not its object ID). The dialog shows your tenant ID, the token URL and the exact scope to copy.
Request a token and call the API
curl -X POST "https://login.microsoftonline.com/<your tenant ID>/oauth2/v2.0/token" \ -d grant_type=client_credentials \ -d client_id=<your application ID> \ --data-urlencode client_secret="$CLIENT_SECRET" \ --data-urlencode scope="api://<Reploya application ID>/.default"
The answer holds access_token. Send it like a key:
curl -H "Authorization: Bearer $ACCESS_TOKEN" \ "https://app.reploya.com/api/v1/organization"
The scope is Reploya's Application ID URI followed by /.default, which asks for every role granted to your application. The API access page shows the exact value; here it reads api://<Reploya application ID>/.default.
Use a managed identity
A managed identity has no permissions page, so an admin grants the role in PowerShell (Microsoft Graph module). Use the identity's Client ID (its application ID) when you add it on the API access page.
Connect-MgGraph -Scopes "Application.Read.All","AppRoleAssignment.ReadWrite.All" $reploya = Get-MgServicePrincipal -Filter "appId eq '<Reploya application ID>'" $role = $reploya.AppRoles | Where-Object Value -eq "api.read" $identity = Get-MgServicePrincipal -Filter "displayName eq '<managed identity name>'" New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $identity.Id ` -PrincipalId $identity.Id -ResourceId $reploya.Id -AppRoleId $role.Id
Inside Azure, the identity asks its local endpoint for the token, with the Application ID URI as resource (no /.default). The Azure SDKs do this for you with the scope api://<Reploya application ID>/.default.
curl -H "Metadata: true" \ "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=api://<Reploya application ID>"
With n8n
- Create a credential of type OAuth2 API: grant type Client Credentials, Access Token URL
https://login.microsoftonline.com/<your tenant ID>/oauth2/v2.0/tokenwith your tenant ID, your client ID and secret, scopeapi://<Reploya application ID>/.default, authentication Send credentials in body. - In the HTTP Request node, choose authentication Generic Credential Type, then OAuth2 API with that credential. n8n fetches and renews the token itself.
Revoke
- Remove the application on the API access page: its tokens get
401on the next request. - Removing the role or the application in Entra stops new tokens, but a token already issued stays valid until it expires: about an hour for an app registration, up to 24 hours for a managed identity. Remove it in Reploya too when it must stop at once.
Troubleshooting
401although the token request worked: decode the token (for example on jwt.ms) and look forroles. Ifapi.readis missing, open the application's API permissions and add Reploya from APIs my organization uses (not Microsoft Graph) as an Application permission (not delegated): tickapi.read, confirm with Add permissions, then choose Grant admin consent until the status shows it as granted. A new grant can take a few minutes to reach new tokens.rolesholdsapi.readand the answer is still401: the Application (client) ID is not added on the API access page, or it was removed.
Reploya accepts only application tokens (no tokens acting for a signed in user), signed by Microsoft for Reploya, issued by your organization's tenant, and still valid.
Scopes
Every endpoint needs a scope, a named permission. Keys and Entra applications use the same names; for an application, the scope is the app role granted to it in Entra.
| Scope | Grants |
|---|---|
api.read | Read every endpoint of this page. Every key holds it, and keys created before scopes existed got it. |
Write actions, such as approving a release, will come with their own scopes. A key or application gets them only when you grant them; until then it can only read. A request without the scope an endpoint needs answers 403 insufficient_scope.
Responses
- Field names are camelCase and match the webhook messages:
app.id,app.name,app.platform,version,url. - Times are ISO 8601 strings in UTC.
urlis a link to the matching dashboard page. - A field without a value is left out, never
null. Check whether a field exists rather than comparing it with null. - New fields may appear at any time; ignore the ones you do not know. Removing or renaming a field would come with a new
/api/v2. - The API never returns a person's name or email, an Entra tenant or group id, an Intune app id or a token.
| Header | Value |
|---|---|
X-Request-Id | A new id per request. Quote it if you contact support. |
X-RateLimit-Limit | 60, the requests a key or application may make per minute. |
X-RateLimit-Remaining | Requests left in the current minute. |
Retry-After | On 429 only: whole seconds until the next minute starts. |
Cache-Control | no-store: answers are never cached. |
Paging
Lists answer {"items": [...], "nextCursor": "..."}. Pass nextCursor as cursor to get the next page; the last page has no nextCursor. A cursor is opaque: do not build or change one.
limit sets the page size, 1 to 100. The default is 100 for apps and 20 for approvals and releases. An app renamed between two pages can be skipped or repeated.
/organization
Your organization, and who is asking.
GET /organization
Retrieve your organization
Your organization and the credential that made the request, in caller: its type (key or application), name and scopes. For a key, expiresAt is left out when it never expires, and the same fields also appear under key. Use it to test a credential.
curl -H "Authorization: Bearer $REPLOYA_API_KEY" \ "https://app.reploya.com/api/v1/organization"
{
"organization": {
"id": "019a0b6e-3c11-7e42-8b0a-55e1c9d2f003",
"name": "Contoso"
},
"caller": {
"type": "key",
"name": "n8n weekly report",
"prefix": "rpk_Xk3m9QaZ",
"expiresAt": "2027-10-20T09:00:00.000Z",
"scopes": [
"api.read"
]
},
"key": {
"name": "n8n weekly report",
"prefix": "rpk_Xk3m9QaZ",
"expiresAt": "2027-10-20T09:00:00.000Z"
}
}{
"organization": {
"id": "019a0b6e-3c11-7e42-8b0a-55e1c9d2f003",
"name": "Contoso"
},
"caller": {
"type": "application",
"name": "Reporting job",
"applicationId": "6f1c2a3b-4d5e-4f60-8a7b-9c0d1e2f3a4b",
"scopes": [
"api.read"
]
}
}/apps
The apps your organization manages, with their versions, targets and rollouts.
GET /apps
List apps
Every app your organization manages, sorted by name, as the dashboard's Apps list shows them (removed apps and drafts included).
| Parameter | Meaning |
|---|---|
platform | One of windows, macos. |
patchStatus | One of up_to_date, update_available, updating, failed, not_assigned, not_deployed, removed, draft. |
limit | Items per page, 1 to 100. Default 100. |
cursor | The nextCursor of the previous page. Leave it out for the first page. |
Per app: latestVersion is the newest version Reploya knows, deployedVersion the one live in your tenant, pendingVersion the one a release is rolling out now. targets lists the target group, or the ring names in order. ringStage says where a ring rollout stands; its kind is waiting (with reached, total, next, dueAt), assigning (ring, total), paused (reached, total) or assigned (total).
curl -H "Authorization: Bearer $REPLOYA_API_KEY" \ "https://app.reploya.com/api/v1/apps?platform=windows&patchStatus=updating"
{
"items": [
{
"id": "019a0b6c-4b70-7c18-8d26-e93a1f5b0c77",
"name": "7-Zip",
"vendor": "Igor Pavlov",
"platform": "windows",
"status": "active",
"patchStatus": "updating",
"latestVersion": "24.09",
"deployedVersion": "24.08",
"targets": [
"Pilot",
"Broad"
],
"pendingVersion": "24.09",
"ringStage": {
"kind": "waiting",
"reached": 1,
"total": 2,
"next": "Broad",
"dueAt": "2026-10-22T08:00:00.000Z"
},
"url": "https://app.reploya.com/dashboard/apps/019a0b6c-4b70-7c18-8d26-e93a1f5b0c77"
}
],
"nextCursor": "WyI3LXppcCIsIjAxOWEwYjZjLTRiNzAtN2MxOC04ZDI2LWU5M2ExZjViMGM3NyJd"
}GET /apps/{id}
Retrieve an app
One app: everything of the list item, plus currentRelease (the release rolling out now, with its rings for a ring rollout) and versions (the newest 10 versions Reploya detected, newest first). A ring has position, name, status (pending, waiting, assigning, assigned, failed, canceled) and dueAt. An unknown id answers 404.
curl -H "Authorization: Bearer $REPLOYA_API_KEY" \ "https://app.reploya.com/api/v1/apps/019a0b6c-4b70-7c18-8d26-e93a1f5b0c77"
{
"id": "019a0b6c-4b70-7c18-8d26-e93a1f5b0c77",
"name": "7-Zip",
"vendor": "Igor Pavlov",
"platform": "windows",
"status": "active",
"patchStatus": "updating",
"latestVersion": "24.09",
"deployedVersion": "24.08",
"targets": [
"Pilot",
"Broad"
],
"pendingVersion": "24.09",
"ringStage": {
"kind": "waiting",
"reached": 1,
"total": 2,
"next": "Broad",
"dueAt": "2026-10-22T08:00:00.000Z"
},
"url": "https://app.reploya.com/dashboard/apps/019a0b6c-4b70-7c18-8d26-e93a1f5b0c77",
"currentRelease": {
"id": "019a0b6f-1c82-7a39-9b07-6e4d2f8c0a15",
"status": "waiting",
"version": "24.09",
"deliveryModel": "rings",
"rings": [
{
"position": 1,
"name": "Pilot",
"status": "assigned"
},
{
"position": 2,
"name": "Broad",
"status": "waiting",
"dueAt": "2026-10-22T08:00:00.000Z"
}
]
},
"versions": [
{
"version": "24.09",
"detectedAt": "2026-10-20T06:00:00.000Z",
"releaseNotesUrl": "https://www.7-zip.org/history.txt"
},
{
"version": "24.08",
"detectedAt": "2026-08-11T06:00:00.000Z",
"releaseNotesUrl": "https://www.7-zip.org/history.txt"
}
]
}PATCH /apps/{id} Coming soon
Change an app's approval settings
Set how new versions of an app are approved, as on the app's approval settings in the dashboard.
/approvals
New versions that wait for a person to approve them.
GET /approvals
List pending approvals
Versions waiting for approval, newest first. tenant is the Reploya tenant connection. blocked appears when the version cannot be approved right now, with the reason code the dashboard shows (the full list is in the OpenAPI document).
| Parameter | Meaning |
|---|---|
appId | Only approvals of this app (its id, a uuid). |
limit | Items per page, 1 to 100. Default 20. |
cursor | The nextCursor of the previous page. Leave it out for the first page. |
curl -H "Authorization: Bearer $REPLOYA_API_KEY" \ "https://app.reploya.com/api/v1/approvals"
{
"items": [
{
"id": "019a0b6d-a3f0-7e55-b4c2-8d7e1a6f9b03",
"createdAt": "2026-10-20T09:11:05.000Z",
"app": {
"id": "019a0b6c-6d03-7b9e-a07f-5c2e8d41b9f6",
"name": "Google Chrome",
"platform": "macos"
},
"version": "141.0.7390.66",
"detectedAt": "2026-10-20T09:00:00.000Z",
"tenant": {
"id": "019a0b6b-9f21-7d84-a3c6-1e5f0b8d2a47",
"name": "Contoso"
},
"url": "https://app.reploya.com/dashboard/approvals/019a0b6d-a3f0-7e55-b4c2-8d7e1a6f9b03"
}
]
}POST /approvals/{id}/approve Coming soon
Approve a version
Approve a waiting version so its release starts, as the dashboard's Approve does.
POST /approvals/{id}/decline Coming soon
Decline a version
Decline a waiting version, as the dashboard's Decline does.
/releases
Rollouts of a version to your tenant, running and finished.
GET /releases
List releases
Releases, newest first. target is the group of a single target release (a ring rollout shows its rings on the app). error says in plain English why a failed release failed.
| Parameter | Meaning |
|---|---|
appId | Only releases of this app (its id, a uuid). |
status | One of waiting, canceling, completed, replaced, queued, building, assigning, reconciling, active, retiring, retired, failed, canceled, superseded. |
limit | Items per page, 1 to 100. Default 20. |
cursor | The nextCursor of the previous page. Leave it out for the first page. |
curl -H "Authorization: Bearer $REPLOYA_API_KEY" \ "https://app.reploya.com/api/v1/releases?status=failed"
{
"items": [
{
"id": "019a0b70-5e14-7b62-8c90-3d1a7f4e2b86",
"status": "failed",
"deliveryModel": "single",
"app": {
"id": "019a0b6c-2e95-7a4f-b1d3-7f80c4a9e261",
"name": "Notepad++",
"platform": "windows"
},
"version": "8.7.1",
"tenant": {
"name": "Contoso"
},
"target": "All devices",
"createdAt": "2026-10-19T14:02:11.000Z",
"updatedAt": "2026-10-19T14:03:40.000Z",
"error": "The target group no longer exists in Entra ID.",
"url": "https://app.reploya.com/dashboard/apps/019a0b6c-2e95-7a4f-b1d3-7f80c4a9e261"
}
]
}POST /releases/{id}/retry Coming soon
Retry a failed release
Start a failed release again, as the dashboard's Retry does.
Errors
Every error has the same shape, with a plain English message:
{
"error": {
"code": "invalid_request",
"message": "limit must be a whole number from 1 to 100."
}
}| Status | Code | When |
|---|---|---|
| 400 | invalid_request | A filter value that is not allowed, an appId that is not a uuid, a limit outside 1 to 100, or a cursor this API did not issue. The message says which. |
| 401 | unauthorized | No credential, or one that fails: a key that is malformed, unknown, revoked or expired, or a token that is not a valid Entra application token of an added application. The answer is the same in every case; WWW-Authenticate is Bearer without a credential and Bearer error="invalid_token" with one. |
| 401 | organization_pending_deletion | Your organization is scheduled for deletion, so every key, application and assistant connection is refused until an owner cancels it. |
| 403 | subscription_inactive | Your organization's subscription is not active. |
| 403 | insufficient_scope | The key or application does not grant the scope this endpoint needs. WWW-Authenticate names it, for example Bearer error="insufficient_scope", scope="api.read". |
| 403 | plan_required | Your organization's plan does not include the API. The message names the plans that do. |
| 404 | not_found | No such app in your organization (also for an id of another organization). |
| 405 | method_not_allowed | Anything but GET or HEAD. The API is read only; Allow: GET, HEAD. |
| 421 | wrong_host | The request reached a host other than the app host. Use https://app.reploya.com/api/v1. |
| 429 | rate_limited | More than 60 requests in this minute. Wait the seconds in Retry-After. |
| 500 | internal | Something went wrong on our side. The message holds a request id to quote to support. |
| 503 | auth_unavailable | Entra tokens cannot be checked right now, because Microsoft's signing keys are out of reach. Wait the seconds in Retry-After. API keys are not affected. |
A failed key or token check counts against the address that sent it: too many in a minute answer 429 instead of 401.
Limits
| Limit | Value |
|---|---|
| Requests per key or Entra application | 60 per minute, counted in whole minutes |
| Keys per organization | 10 |
| Entra applications per organization | 10 |
| Items per page | up to 100 |
Need status changes the moment they happen? Use webhooks instead of asking every minute.
OpenAPI document
An OpenAPI 3.1 document describes every endpoint, parameter, response, enum and error. It needs no key. Import it into Postman, Insomnia or a code generator.
Recipe: n8n
An n8n HTTP Request node that reads every app, all pages:
- Create a credential of type Header Auth: name
Authorization, valueBearer rpk_…with your key. - In the HTTP Request node: method
GET, URLhttps://app.reploya.com/api/v1/apps, authentication Generic Credential Type, then Header Auth with that credential. - Under Options, add Pagination:
Pagination Mode: Update a Parameter in Each Request Type: Query, Name: cursor Value: {{ $response.body.nextCursor }} Pagination Complete When: Other Complete Expression: {{ !$response.body.nextCursor }} - Split the result with
{{ $json.items }}and filter, for example, onpatchStatus = update_availablefor a weekly report.
To test the credential, point a node at https://app.reploya.com/api/v1/organization: it answers with your organization and the key's name.
Want Reploya to tell your flow when something happens instead? See the webhook docs.

