Platform API

The Akka Platform API is the set of gRPC and REST APIs that the Akka CLI and console use to manage organizations, projects, and services. Akka publishes the protobuf and OpenAPI schemas for these APIs, together with a Java client library, in the akka/platform-api GitHub repository, so you can call the same APIs from your own tools and services.

API planes

The Platform API has two planes:

Federation plane

A gRPC API for managing projects, organizations, billing, users, and authentication. Served at api.kalix.io:443.

Control plane

A REST API (OpenAPI) for managing resources within a project, such as services, routes, and secrets. Served at a region-specific endpoint, for example https://api.gcp-us-east1.akka.io.

Authentication

All API calls require a short-lived access token, obtained by one of the following mechanisms.

Refresh token

A refresh token is a long-lived token exchanged for a short-lived access token via the Auth API. There are two kinds:

User token

Grants access to all projects belonging to your user account.

akka auth tokens create --description "My refresh token"
Service token

Grants access to a single project. Use this for CI/CD pipelines and other automation.

akka projects tokens create --description "My service token"

Store the token value securely. It is shown only once.

OAuth token exchange (RFC 8693)

Exchange an existing identity token, such as a GitHub Actions OIDC token or an Akka workload identity token, for an Akka access token using OAuth 2.0 token exchange. No long-lived Akka credential needs to be stored. The audience parameter identifies the configured OpenID Connect identity provider and is required.

To use this mechanism to authenticate an Akka service that calls the Platform API from another Akka service, configure Akka platform workload identity on the calling service.

Java client

The java-client module provides a ready-to-use SDK that handles token acquisition and caching, gRPC channel management, and automatic routing of control plane requests to the correct regional endpoint.

Configuring the client

Refresh token: Environment variable

Set AKKA_TOKEN before starting your application. Optionally set AKKA_API_HOST to override the federation plane host (defaults to api.kalix.io:443).

export AKKA_TOKEN=<your-refresh-token>
AkkaPlatformSdk sdk = AkkaPlatformSdk.create();

Refresh token: Automatic fallback to the Akka CLI

If AKKA_TOKEN is not set and the akka CLI is installed and logged in, the SDK runs akka config get refresh-token (and akka config get api-server-host for non-default installations) automatically. No code changes are needed.

Refresh token: Programmatic

AkkaPlatformSdk sdk = AkkaPlatformSdk.create(
    AkkaPlatformSdkConfig.withRefreshToken("your-refresh-token"));

To also override the federation plane host:

AkkaPlatformSdk sdk = AkkaPlatformSdk.create(
    AkkaPlatformSdkConfig.withRefreshToken("your-refresh-token", "api.kalix.io:443"));

OAuth token exchange: Environment variables

Set AKKA_OAUTH_TOKEN (or AKKA_OAUTH_TOKEN_FILE to point to a file containing the token) and AKKA_OAUTH_TOKEN_AUDIENCE. When either OAuth variable is present, it takes precedence over AKKA_TOKEN. If the audience variable is missing, the SDK throws an exception at startup.

export AKKA_OAUTH_TOKEN="$(cat /var/run/secrets/token)"   # or a static value
export AKKA_OAUTH_TOKEN_AUDIENCE="regions/gcp-us-east1"
# -- or --
export AKKA_OAUTH_TOKEN_FILE=/var/run/secrets/token
export AKKA_OAUTH_TOKEN_AUDIENCE="regions/gcp-us-east1"
AkkaPlatformSdk sdk = AkkaPlatformSdk.create();  // picks up the env vars automatically

When AKKA_OAUTH_TOKEN_FILE is used, the file is re-read on every token exchange, so rotating the file contents is sufficient to rotate the credential without restarting the application.

When Akka platform workload identity is configured for an Akka service, these environment variables are injected automatically into the service at runtime. Calling AkkaPlatformSdk.create() with no further configuration is sufficient.

OAuth token exchange: Programmatic

// Fixed token value
AkkaPlatformSdk sdk = AkkaPlatformSdk.create(
    AkkaPlatformSdkConfig.withOAuthToken(myOidcToken, "regions/gcp-us-east1"));

// File-based (re-read on every exchange)
AkkaPlatformSdk sdk = AkkaPlatformSdk.create(
    AkkaPlatformSdkConfig.withOAuthTokenFile("/var/run/secrets/token", "regions/gcp-us-east1"));

Custom token fetcher

For authentication mechanisms not covered above, implement both TokenConfig and TokenFetcher on the same class. TokenFetcher.getToken() must return a CompletableFuture<TokenWithExpiry> containing a bearer token and its expiry instant. The SDK caches the token until 60 seconds before expiry, then calls getToken() again.

public class MyTokenConfig implements TokenConfig, TokenFetcher {
    @Override
    public CompletableFuture<TokenWithExpiry> getToken() {
        return fetchMyToken().thenApply(t ->
            new TokenWithExpiry(t.value(), t.expiresAt()));
    }
}

AkkaPlatformSdk sdk = AkkaPlatformSdk.create(
    AkkaPlatformSdkConfig.of(new MyTokenConfig(), "api.kalix.io:443"));

Looking up a project

Projects are identified by a UUID internally. Use resolveProjectId to translate a friendly name to the UUID required by the control plane API:

sdk.resolveProjectId("my-project")
    .thenAccept(projectId -> System.out.println("Project ID: " + projectId));

To list all projects directly:

sdk.projects()
    .listProjects(ListProjectsRequest.newBuilder().build())
    .thenAccept(response ->
        response.getProjectsList().forEach(p ->
            System.out.println(p.getFriendlyName() + " → " + p.getName())));

Getting a service

The control plane API is accessed via sdk.controlPlaneApi(). Each call is automatically routed to the project’s primary region: the project UUID is parsed from the request URI, the region list is fetched and cached, and the request is forwarded to the correct regional endpoint.

AkkaControlPlaneApi api = sdk.controlPlaneApi();

sdk.resolveProjectId("my-project").thenCompose(projectId ->
    api.getService("my-service", projectId)
).thenAccept(service ->
    System.out.println(service.getMetadata().getName()
        + " — " + service.getStatus()));

To list all services in a project:

sdk.resolveProjectId("my-project").thenCompose(projectId ->
    api.listServices(projectId, null)
).thenAccept(list ->
    list.getItems().forEach(s -> System.out.println(s.getMetadata().getName())));

Creating a service

import io.akka.platformapi.controlplane.model.*;

Service service = new Service()
    .metadata(new ObjectMeta()
        .name("my-service"))
    .spec(new ServiceSpec()
        .containers(List.of(new ServiceSpecContainer()
            .name("main")
            .image("my-registry.example.com/my-image:1.0.0"))));

sdk.resolveProjectId("my-project").thenCompose(projectId ->
    api.createService(projectId, service)
).thenAccept(created ->
    System.out.println("Created: " + created.getMetadata().getName()));

Explicit region routing

By default, controlPlaneApi() routes to the primary region. To target a specific region, for example for multi-region projects:

// Region name format: "<region-id>" e.g. "gcp-us-east1"
AkkaControlPlaneApi regionalApi = sdk.controlPlaneApiForRegion("gcp-us-east1");

If the specified region is not associated with the project identified in the request URI, the call fails asynchronously with IllegalArgumentException.

Region information is cached. After a region configuration change, call sdk.clearRegionCache() to force a fresh lookup on the next request.

Closing the client

Close the SDK when your application shuts down, to release the underlying gRPC channels and, if applicable, the managed ActorSystem:

sdk.close();

Using the schemas directly

The federation plane protobuf schemas are in schemas/federation-plane/protobuf/ and the control plane OpenAPI schema is at schemas/control-plane/openapi/api.json, both in the akka/platform-api repository. This section describes the authentication and region discovery flow for clients that use these schemas without the Java client.

Authenticating

All requests require a short-lived access token. Exchange your refresh token for one by calling CreateAccessToken on the Auth service (api.kalix.io:443, TLS):

Service:  kalix.api.auth.v1alpha.Auth
Method:   CreateAccessToken
Request:  CreateAccessTokenRequest {}   (empty body)
Header:   Authorization: Bearer <refresh-token>

The response AccessToken.token is your access token. Send it as Authorization: Bearer <access-token> on all subsequent requests. Tokens are short-lived: cache them and refresh before expire_time.

To authenticate an Akka service that calls the Platform API using its own identity, instead of a stored refresh token, configure Akka platform workload identity on the service.

Region endpoint discovery

Control plane REST requests must be directed to the region-specific endpoint for the project, not to api.kalix.io. Discover the endpoint by calling ListProjects on the Projects service, authenticated with your access token:

Service:  kalix.api.projects.v1alpha.Projects
Method:   ListProjects
Request:  ListProjectsRequest {}
Header:   Authorization: Bearer <access-token>

Each Project in the response contains a regions list. Each Region has:

endpoint

The base URL of the control plane REST requests, for example https://api.gcp-us-east1.akka.io.

primary

true for the region that should be used by default.

name

The fully-qualified resource name, for example projects/<id>/regions/gcp-us-east1.

In most cases, use the region where primary == true. Use its endpoint as the base URL of all control plane REST calls for that project:

GET https://api.gcp-us-east1.akka.io/apis/kalix.io/v1alpha1/namespaces/<project-id>/kalixservices
Authorization: Bearer <access-token>

If you already have the project ID, you can also call ListRegions directly with parent = "projects/<project-id>", instead of iterating through ListProjects.

Page through ListProjects using the next_page_token field in the response until it is empty, since projects are returned in pages.

See also