API keys and OAuth clients

Automation shouldn’t sign in as a person. Streamtime has two kinds of machine credential:

  API key OAuth client
For Scripts, CI, Terraform, MCP clients and the CLI Applications that sign in with OAuth client credentials, for example a Kafka producer
Scope One organization, one role One organization, optionally limited to some Kafka clusters
Available Always With Streamtime identity as the sign-in
Sent as X-Streamtime-Key header A token from the OAuth token endpoint

The key or secret is shown once, when it is created or rotated. Store it in your secret manager straight away.

API key roles

Give each key the least it needs. A key has one of these roles: Agent (the least privileged), MCP Agent (required to connect an MCP client), Cluster Administrator, Infrastructure Administrator, Billing Administrator or Administrator (everything in the organization). The roles are the same ones people have; see User Management.

The API examples below share this setup:

export STREAMTIME_TOKEN=<your-access-token>
export API=https://api.example.com/organizations/<org>
import os
from streamtime_sdk.client import AuthenticatedClient

client = AuthenticatedClient(
    base_url="https://api.example.com",
    token=os.environ["STREAMTIME_TOKEN"],
)
ORG = "<org>"

Uses the generated client in streamtime-sdk/typescript/client.

import { client } from "./client/client.gen";

client.setConfig({
  baseUrl: "https://api.example.com",
  headers: { Authorization: `Bearer ${process.env.STREAMTIME_TOKEN}` },
});
const org = "<org>";
package main

import (
    "bytes"
    "crypto/rand"
    "encoding/hex"
    "encoding/json"
    "net/http"
    "os"
)

const api = "https://api.example.com/organizations/<org>"

// newKey returns a random idempotency key.
func newKey() string {
    b := make([]byte, 16)
    rand.Read(b)
    return hex.EncodeToString(b)
}

// call sends one request with the access token and a JSON body (or none).
// An optional idempotency key is sent as the Idempotency-Key header.
func call(method, path string, body any, idempotencyKey ...string) (*http.Response, error) {
    var buf bytes.Buffer
    if body != nil {
        if err := json.NewEncoder(&buf).Encode(body); err != nil {
            return nil, err
        }
    }
    req, err := http.NewRequest(method, api+path, &buf)
    if err != nil {
        return nil, err
    }
    req.Header.Set("Authorization", "Bearer "+os.Getenv("STREAMTIME_TOKEN"))
    req.Header.Set("Content-Type", "application/json")
    if len(idempotencyKey) > 0 {
        req.Header.Set("Idempotency-Key", idempotencyKey[0])
    }
    return http.DefaultClient.Do(req)
}

Create an API key

  1. Open the API Keys page from the account menu (organization administrators).
  2. Select Create API Key, enter a Name and choose the Role.
  3. Copy the key now; it isn’t shown again.
curl -X POST "$API/api-keys/" \
  -H "Authorization: Bearer $STREAMTIME_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "ci", "role": "Cluster Administrator"}'

The response’s key is the only time the key is returned. List keys with GET $API/api-keys/; revoke one with DELETE $API/api-keys/<id>/.

from streamtime_sdk.api.api_keys import organizations_api_keys_create
from streamtime_sdk.models import APIKEY

created = organizations_api_keys_create.sync(
    ORG, client=client, body=APIKEY.from_dict({"name": "ci", "role": "Cluster Administrator"})
)
print(created.key)  # shown once
import { organizationsApiKeysCreate } from "./client";

const { data } = await organizationsApiKeysCreate({
  path: { org_identifier: org },
  body: { name: "ci", role: "Cluster Administrator" },
});
console.log(data?.key); // shown once
resp, err := call("POST", "/api-keys/", map[string]any{
    "name": "ci",
    "role": "Cluster Administrator",
})
if err != nil {
    return err
}
defer resp.Body.Close()
streamtime api-key create --name ci --role "Cluster Administrator"
streamtime api-key list
streamtime api-key rotate <key-id>
streamtime api-key delete <key-id>

Always pass --role; the CLI’s default role isn’t one of the roles above.

Not available in MCP.

resource "streamtime_api_key" "ci" {
  name = "ci"
  role = "Cluster Administrator"
}

The key value is sensitive and kept in Terraform state after creation; an import can’t recover it, so protect the state storage. Destroying the resource revokes the key.

terraform import streamtime_api_key.ci '<key-id>'

See streamtime_api_key.

Create an OAuth client

  1. Go to Settings → OAuth Clients (shown when the organization signs in with Streamtime identity) and select Create OAuth Client.
  2. Enter an Identifier and a Name, and optionally limit it to some Kafka clusters.
  3. Select Create and copy the client secret; it isn’t shown again. Rotate secret issues a new one.
curl -X POST "$API/oauth-clients/" \
  -H "Authorization: Bearer $STREAMTIME_TOKEN" -H "Content-Type: application/json" \
  -d '{"identifier": "producer", "name": "Order producer", "kafka_clusters": ["orders"]}'

# New secret
curl -X POST "$API/oauth-clients/producer/rotate/" -H "Authorization: Bearer $STREAMTIME_TOKEN"
from streamtime_sdk.api.o_auth_clients import organizations_oauth_clients_create
from streamtime_sdk.models import OAuthClientWrite

organizations_oauth_clients_create.sync(
    ORG,
    client=client,
    body=OAuthClientWrite.from_dict(
        {"identifier": "producer", "name": "Order producer", "kafka_clusters": ["orders"]}
    ),
)
import { organizationsOauthClientsCreate } from "./client";

await organizationsOauthClientsCreate({
  path: { org_identifier: org },
  body: { identifier: "producer", name: "Order producer", kafka_clusters: ["orders"] },
});
resp, err := call("POST", "/oauth-clients/", map[string]any{
    "identifier":     "producer",
    "name":           "Order producer",
    "kafka_clusters": []string{"orders"},
})
if err != nil {
    return err
}
defer resp.Body.Close()

Not available in the CLI yet. Use the console, the API or Terraform.

Not available in MCP.

resource "streamtime_oauth_client" "producer" {
  identifier     = "producer"
  name           = "Order producer"
  kafka_clusters = ["orders"]
}

Its access settings can’t be changed in place, so a change replaces the client. The secret is sensitive and kept in Terraform state; destroying the resource revokes the client.

terraform import streamtime_oauth_client.producer producer

See streamtime_oauth_client.