mero.admin (AdminApi) is the node’s /admin-api REST surface — the full set
(~110 methods) across applications, contexts, namespaces, groups, blobs, aliases,
capabilities, metadata, upgrades, accounts/devices, and TEE. Every method is a suspend fun over
the shared HttpClient ; most reads unwrap the node’s
{ data } envelope for you. Endpoints below are relative to /admin-api.
List methods return the node’s full result set — the admin API does not paginate,
so there are no limit/offset/cursor parameters.
Method
Endpoint
healthCheck(): HealthStatus
GET /health
isReady(): HealthStatus
GET /ready
isAuthed(): AdminAuthStatus
GET /is-authed
getNodeIdentity(): NodeIdentity
GET /identity
Method
Endpoint
installApplication(request): InstallApplicationResponseData
POST /install-application
installFromRegistry(packageName, version): InstallApplicationResponseData
POST /install-application
getRegistryVersions(registryUrl, packageName): List<String>
off-node registry read
installDevApplication(request): InstallApplicationResponseData
POST /install-dev-application
uninstallApplication(appId): UninstallApplicationResponseData
DELETE /applications/{appId}
listApplications(): ListApplicationsResponseData
GET /applications
getApplication(appId): GetApplicationResponseData
GET /applications/{appId}
getApplicationAbi(appId, serviceName?): JsonElement?
GET /applications/{appId}/abi
listApplicationVersions(applicationId): List<ApplicationVersionEntry>
GET /applications/{applicationId}/versions
Installs are registry-only since core 0.11.0-rc.31
InstallApplicationRequest is {package, version} and nothing else — the node
fetches from the registry it is configured with. The request carries
deny_unknown_fields, so a body still naming url is refused, not ignored:
400 unknown field \url`, expected `package` or `version`. installDevApplicationtakes only apath, and that path must be an .mpkbundle — a raw.wasm` is
refused with “not a signed application bundle” .
Method
Endpoint
listPackages(): ListPackagesResponseData
GET /packages
listPackageVersions(packageName): ListVersionsResponseData
GET /packages/{packageName}/versions
getLatestPackageVersion(packageName): GetLatestVersionResponseData
GET /packages/{packageName}/latest
Method
Endpoint
createContext(request): CreateContextResponseData
POST /contexts
deleteContext(contextId, request? = null): DeleteContextResponseData
DELETE /contexts/{contextId}
getContexts(): GetContextsResponseData
GET /contexts
getContext(contextId): Context
GET /contexts/{contextId}
getContextsForApplication(applicationId): GetContextsResponseData
GET /contexts/for-application/{applicationId}
generateContextIdentity(): GenerateContextIdentityResponseData
POST /identity/context
getContextIdentities(contextId): GetContextIdentitiesResponseData
GET /contexts/{contextId}/identities
getContextIdentitiesOwned(contextId): GetContextIdentitiesResponseData
GET /contexts/{contextId}/identities-owned
joinContext(contextId): JoinContextResponseData
POST /contexts/{contextId}/join
getContextGroup(contextId): ContextGroupResponseData
GET /contexts/{contextId}/group
getContextStorage(contextId): ContextStorageResponseData
GET /contexts/{contextId}/storage
syncContext(contextId? = null)
POST /contexts/sync/{contextId?}
resyncContext(contextId, request = ResyncContextRequest()): ResyncContextResponseData
POST /contexts/{contextId}/resync
performIntent(contextId, request): PerformIntentResponseData
POST /contexts/{contextId}/intents
updateContextApplication(contextId, request)
POST /contexts/{contextId}/application
getContextsWithExecutorsForApplication(applicationId): ContextsWithExecutorsResponseData
GET /contexts/with-executors/for-application/{applicationId}
Method
Endpoint
uploadBlob(request): UploadBlobResponseData
PUT /blobs
deleteBlob(blobId): DeleteBlobResponseData
DELETE /blobs/{blobId}
listBlobs(): ListBlobsResponseData
GET /blobs
getBlob(blobId): ByteArray
GET /blobs/{blobId} (raw bytes)
getBlobInfo(blobId): GetBlobInfoResponseData
HEAD /blobs/{blobId}
Method
Endpoint
createContextAlias(request): CreateAliasResponseData
POST /alias/create/context
createApplicationAlias(request): CreateAliasResponseData
POST /alias/create/application
lookupContextAlias(name): LookupAliasResponseData
POST /alias/lookup/context/{name}
lookupApplicationAlias(name): LookupAliasResponseData
POST /alias/lookup/application/{name}
deleteContextAlias(name): DeleteAliasResponseData
POST /alias/delete/context/{name}
deleteApplicationAlias(name): DeleteAliasResponseData
POST /alias/delete/application/{name}
listContextAliases(): ListAliasesResponseData
GET /alias/list/context
listApplicationAliases(): ListAliasesResponseData
GET /alias/list/application
createDeviceAlias(request): CreateAliasResponseData
POST /alias/create/device
lookupDeviceAlias(name): LookupAliasResponseData
POST /alias/lookup/device/{name}
deleteDeviceAlias(name): DeleteAliasResponseData
POST /alias/delete/device/{name}
listDeviceAliases(): ListAliasesResponseData
GET /alias/list/device
Three scopes: context, application, device
There is no identity/{contextId} scope — every route under it answers 404 .
The SDK used to offer four methods there; they are gone, replaced by the device
scope core actually serves.
The list routes answer a map — {"data": {"<alias>": "<value>"}}, and
{"data": {}} when empty — so ListAliasesResponseData is a
List<AliasEntry> the SDK builds from it. Modeled as {aliases: […]} it threw
MissingFieldException on every call, empty or not.
Method
Endpoint
listNamespaces(): ListNamespacesResponseData
GET /namespaces
getNamespace(namespaceId): Namespace
GET /namespaces/{namespaceId}
admitJoin(namespaceId, request): AdmitJoinResponseData
POST /namespaces/{namespaceId}/admit
revokeAccountDevice(namespaceId, request): RevokeDeviceResponseData
POST /namespaces/{namespaceId}/account/revoke
listNamespacesForApplication(applicationId): ListNamespacesResponseData
GET /namespaces/for-application/{applicationId}
createNamespace(request): CreateNamespaceResponseData
POST /namespaces
deleteNamespace(namespaceId, request? = null): DeleteNamespaceResponseData
DELETE /namespaces/{namespaceId}
createNamespaceInvitation(namespaceId, request? = null): CreateNamespaceInvitationResult
POST /namespaces/{namespaceId}/invite
joinNamespace(namespaceId, request): JoinNamespaceResponseData
POST /namespaces/{namespaceId}/join
leaveNamespace(namespaceId, request? = null)
POST /namespaces/{namespaceId}/leave
createGroupInNamespace(namespaceId, request? = null): CreateGroupInNamespaceResponseData
POST /namespaces/{namespaceId}/groups
listNamespaceGroups(namespaceId): List<SubgroupEntry>
GET /namespaces/{namespaceId}/groups
Method
Endpoint
createGroup(request): CreateGroupResponseData
POST /groups
getGroupInfo(groupId): GroupInfoResponseData
GET /groups/{groupId}
deleteGroup(groupId, request? = null): DeleteGroupResponseData
DELETE /groups/{groupId}
listGroupMembers(groupId): ListGroupMembersResponseData
GET /groups/{groupId}/members
listGroupContexts(groupId): ListGroupContextsResponseData
GET /groups/{groupId}/contexts
addGroupMembers(groupId, request)
POST /groups/{groupId}/members
removeGroupMembers(groupId, request)
POST /groups/{groupId}/members/remove
updateMemberRole(groupId, identity, request)
PUT /groups/{groupId}/members/{identity}/role
setMemberAutoFollow(groupId, identity, request)
PUT /groups/{groupId}/members/{identity}/auto-follow
listSubgroups(groupId): List<SubgroupEntry>
GET /groups/{groupId}/subgroups
reparentGroup(childGroupId, request): ReparentGroupResponseData
POST /groups/{childGroupId}/reparent
detachContextFromGroup(groupId, contextId, request? = null)
POST /groups/{groupId}/contexts/{contextId}/remove
leaveGroup(groupId, request? = null)
POST /groups/{groupId}/leave
leaveContext(contextId, request? = null)
POST /contexts/{contextId}/leave
Method
Endpoint
createGroupInvitation(groupId, request? = null): CreateGroupInvitationResult
POST /groups/{groupId}/invite
joinGroup(request): JoinGroupResponseData
POST /groups/join
joinSubgroupInheritance(groupId): JoinSubgroupInheritanceResponseData
POST /groups/{groupId}/join-via-inheritance
issueOwnershipProof(groupId, request? = null): JsonElement
POST /groups/{groupId}/issue-ownership-proof
issueNamespaceOwnershipProof(groupId, request? = null): JsonElement
POST /groups/{groupId}/issue-namespace-ownership-proof
Method
Endpoint
getMemberCapabilities(groupId, identity): MemberCapabilities
GET /groups/{groupId}/members/{identity}/capabilities
setMemberCapabilities(groupId, identity, request)
PUT /groups/{groupId}/members/{identity}/capabilities
getDefaultCapabilities(groupId): Int
(reads getGroupInfo)
setDefaultCapabilities(groupId, request)
PUT /groups/{groupId}/settings/default-capabilities
getSubgroupVisibility(groupId): String
(reads getGroupInfo)
setSubgroupVisibility(groupId, request)
PUT /groups/{groupId}/settings/subgroup-visibility
getTeeAdmissionPolicy(groupId): GetTeeAdmissionPolicyResponseData
GET /groups/{groupId}/settings/tee-admission-policy
setTeeAdmissionPolicy(groupId, request)
PUT /groups/{groupId}/settings/tee-admission-policy
Method
Endpoint
setGroupMetadata(groupId, request)
PUT /groups/{groupId}/metadata
getGroupMetadata(groupId): MetadataRecord?
GET /groups/{groupId}/metadata
setMemberMetadata(groupId, identity, request)
PUT /groups/{groupId}/members/{identity}/metadata
getMemberMetadata(groupId, identity): MetadataRecord?
GET /groups/{groupId}/members/{identity}/metadata
setContextMetadata(groupId, contextId, request)
PUT /groups/{groupId}/contexts/{contextId}/metadata
getContextMetadata(groupId, contextId): MetadataRecord?
GET /groups/{groupId}/contexts/{contextId}/metadata
Method
Endpoint
syncGroup(groupId, request? = null): SyncGroupResponseData
POST /groups/{groupId}/sync
syncGroupContexts(groupId): ListGroupContextsResponseData
composite — see below
listMemberDevices(groupId): MemberDevices
GET /groups/{groupId}/member-devices
upgradeGroup(groupId, request): UpgradeGroupResponseData
POST /groups/{groupId}/upgrade
getGroupUpgradeStatus(groupId): GroupUpgradeStatusResponseData
GET /groups/{groupId}/upgrade/status
retryGroupUpgrade(groupId, request? = null): RetryGroupUpgradeResponseData
POST /groups/{groupId}/upgrade/retry
getMigrationStatus(namespaceId): MigrationStatus
GET /groups/{namespaceId}/migration-status
getCascadeStatus(namespaceId): List<CascadeStatusEntry>
GET /groups/{namespaceId}/cascade-status
abortMigration(namespaceId, request? = null): JsonElement
POST /groups/{namespaceId}/migration/abort
Joining a group does not initialize its contexts on your node: cross-node sync is
asynchronous, and until a context is joined and state-pulled it sits on the
all-ones (1111…) uninitialized hash, so every read looks empty.
syncGroupContexts does the whole dance for you:
// sync the group, then join + state-pull every context in it
val contexts = mero.admin. syncGroupContexts (groupId)
It runs syncGroup, lists the group’s contexts, then joinContext +
syncContext for each, tolerating per-context failures (sync is async — call it
again). The sample app’s chat calls it in a short retry loop right after
joinNamespace, which is what makes an invited space’s channels appear.
A device pairs to an account once (core 0.11.0-rc.28 moved this off the
namespace), then links into namespaces. Revocation stayed per-namespace, because
that is where the group key rotates.
Method
Endpoint
listAccountDevices(): AccountDevices
GET /account/devices
listAccountApplications(): AccountApplications
GET /account/applications
pairInit(request): PairInitResponseData
POST /account/pair-init
pairComplete(request): PairCompleteResponseData
POST /account/pair-complete
relinkDevice(deviceId, request): RelinkDeviceResponseData
POST /account/devices/{deviceId}/relink
rescopeDevice(deviceId, request): RescopeDeviceResponseData
PUT /account/devices/{deviceId}/scope
labelDevice(deviceId, request): LabelDeviceResponseData
PUT /account/devices/{deviceId}/label
`relink` adds, `scope` replaces (core 0.11.0-rc.41)
relinkDevice is add-only, so it can widen a device’s reach but never narrow it.
rescopeDevice is the other direction, and a PUT for that reason: it replaces
the scope outright.
The body is {"scope":"all"} or {"scope":{"only":["<application id>", …]}} —
serde’s externally-tagged enum, so DeviceScope.All is the bare string
"all", not an object. Tagged rather than a list whose emptiness means
everything (which is what relink uses): here an empty only is refused with a
400, instead of the narrowest slip becoming the widest possible grant.
Both routes must run on the node holding the account root — only its key can
sign the change. The device is not consulted and need not be online.
labelDevice names a device so a listing renders as something other than 64 hex
characters. The name replicates: it comes back on AccountDevice.label for every
device of the account, and labelEpoch orders it against a rename made elsewhere
at the same moment.
Three of these answer flat, not enveloped
GET /account/devices, GET /account/applications and
GET /groups/{id}/member-devices return {devices:[…]} / {applications:[…]} /
{members:[…]} with no data wrapper , unlike every route around them.
Decoding one as an envelope yields null and reads as “empty”.
Method
Endpoint
getTeeInfo(): TeeInfoResponseData
GET /tee/info
teeAttest(request): TeeAttestResponseData
POST /tee/attest
teeVerifyQuote(request): TeeVerifyQuoteResponseData
POST /tee/verify-quote
Method
Endpoint
getPeersCount(): PeersCountResponseData
GET /peers
getNetworkStatus(): JsonElement
GET /network/status
getUsage(): JsonElement
GET /usage
getCertificate(): String
GET /certificate
The file also exports a top-level helper compareSemver(a: String, b: String): Int
for ordering dotted version strings.
val applicationId: String ,
val serviceName: String ? = null ,
val contextStateHash: String ,
val dagHeads: List < List < Int >>,
val applicationVersion: String ? = null ,
// The element type of getContexts() / getContextsForApplication().
data class ContextWithGroup ( /* Context fields … */ val groupId: String ? = null )
val namespaceId: String , val appKey: String , val targetApplicationId: String ,
val createdAt: Long , val name: String ? = null ,
val memberCount: Int , val contextCount: Int , val subgroupCount: Int ,
val appVersion: String ? = null ,
val groupId: String , val appKey: String , val targetApplicationId: String ,
val memberCount: Int , val contextCount: Int ,
val activeUpgrade: GroupUpgradeStatus ? = null ,
val defaultCapabilities: Int , val subgroupVisibility: String ,
val metadata: MetadataRecord ? = null , val groupStateHash: String ? = null ,
data class BlobInfo ( val blobId: String , val size: Long ) // GetBlobInfoResponseData adds hash?, mimeType?
data class MetadataRecord (
val name: String ? = null , val data : Map < String , String >,
val updatedAt: Long , val updatedBy: String , // MILLIS, and u64 in core
val accountId: String , val deviceId: String ? = null , val publicKey: String ,
val accountRootPublicKey: String , val deviceAgreementKey: String ? = null ,
val holdsAccountRoot: Boolean ? = null ,
data class MemberCapabilities ( val capabilities: Int ) // see /reference/capabilities/
upgradePolicy is gone
core#3485 removed the concept — it always held "LazyOnAccess" — so neither
Namespace nor GroupInfo carries it, and neither CreateNamespaceRequest nor
CreateGroupRequest sends it. A model that still declares it required throws
MissingFieldException on every read; ignoreUnknownKeys does not help, because
the problem is a missing field, not an unknown one.
Timestamps are Long, not Int: MetadataRecord.updatedAt is wall-clock
milliseconds and overflows a 32-bit Int today.
See AdminTypes.kt
for the complete set of request and response types.