Skip to main content

Data and messaging

This page documents the four service command groups: serverless, pubsub, memorystore, and vectordb. They create, edit, delete, and inspect the platform's managed data and messaging services, and move messages and vectors through them.

How these groups behave

The four groups share rules that differ from the rest of the CLI:

They require a project. There is no server-side default here. If no project resolves from --project, $CAI_PROJECT, or your saved config default, the command fails with:

this command needs a project: pass --project, set $CAI_PROJECT, or run 'platformctl config set-project' (use the id or slug shown by 'platformctl projects list')

A project UUID is used directly with no lookup call (good for CI). A slug or short name costs one GET /v1/projects lookup; if nothing matches:

no project matches "my-proj" among the projects you can access (see 'platformctl projects list')

They do not take their base URL from --api. Each group talks to its own service and has its own endpoint variable. Leave it unset and the group falls back to $CAI_API, and then to the public API — the same default the control-plane commands use. One hostname serves all of them, so in normal use you set nothing.

GroupEndpoint variable
serverlessCAI_SERVERLESS_API
pubsubCAI_PUBSUB_API
memorystoreCAI_MEMORYSTORE_API
vectordbCAI_VECTORDB_API

One caveat: a slug or short-name --project is resolved to its UUID through the core platform API, which is reached via --api or $CAI_API. Pass the project's UUID (from platformctl projects list) to make these commands fully independent of the control plane.

Some commands need the project admin role. Reading a resource, publishing, pulling, and writing points need the member role. Destroying shared state or handing out a credential needs admin. Each command below says which. All list commands follow pagination to the end.

Updates replace, they do not merge. Every update command sends only the flags you passed — but for several settings the server replaces a whole group of fields at once, so a flag you leave out of that group is reset rather than kept. Those cases are called out in place, and the CLI refuses the combinations that would silently break a service.


platformctl serverless

Manage serverless container services: scale-to-zero containers that run on request. See deploy a service and the serverless API reference.

serverless list

platformctl serverless list
platformctl serverless list

You should see a table with the columns NAME, STATE, PUBLISHED, and URL. STATE is the service's one-word summary, always lowercase — pending, ready, not_ready, degraded, or invalid. PUBLISHED is yes/no — whether the service is reachable from the internet. The URL column prefers the service's external URL when it has one. With no services:

no serverless services

serverless get

platformctl serverless get <name>
platformctl serverless get my-service -o json

The default table is the same single row list prints, so a get and a list line up. Use -o json whenever you need fields the table omits.

serverless create

Creates a service from a container image.

platformctl serverless create <name> --image <image> [flags]

Only --image is required. Everything else overrides a platform default: port 8080, a small guaranteed compute envelope (250m CPU / 512Mi), and scaling of 0..10 instances at 80 concurrent requests each. A service is private unless you pass --publish — reachable from inside the project and nowhere else.

FlagDefaultWhat it does
--imagenone — requiredContainer image to run.
--portserver default 8080Port the container listens on.
--port-namenoneName for the container port, e.g. h2c to serve gRPC.
--commandimage entrypointOverrides the image entrypoint. Repeatable, in order.
--argnoneArgument passed to the entrypoint. Repeatable, in order.
--envnoneEnvironment variable KEY=VALUE. Repeatable.
--env-from-secretnoneImport every key of a secret as environment variables. Repeatable.
--env-from-config-mapnoneImport every key of a config map as environment variables. Repeatable.
--cpu250mCPU request, e.g. 250m.
--cpu-limitnoneCPU limit, e.g. 1.
--memory512MiMemory request, e.g. 512Mi.
--memory-limitnoneMemory limit, e.g. 512Mi.
--min-scale0Instances to keep warm. 0 scales to zero when idle.
--max-scale10Instance ceiling. 0 means unbounded, so set it deliberately.
--concurrency80Simultaneous requests one instance handles. 0 means unlimited per instance — omit the flag for the default.
--timeoutserver defaultSeconds a single request may run before it is cut off.
--publishoffExpose the service on the internet.
--publish-hostnoneCustom hostname to publish on. Pass together with --publish.
--runtime-classnoneAsks for a stricter container runtime by name. None is enabled by default.
--service-accountnoneWorkload identity the instances run as.

Omitting --image fails with:

--image is required: a serverless service has nothing to run without one

Example:

platformctl serverless create api \
--image registry.example.com/api:1.4.0 \
--port 8080 --min-scale 1 --max-scale 20 \
--env LOG_LEVEL=info \
--env-from-secret api-credentials \
--publish
Put credentials in a secret, not in --env

A value typed after --env is recorded in your shell history and visible in the process table while the command runs. --env-from-secret names the secret and nothing else, so no value crosses the command line. Use it for anything you would not paste into a chat window.

The response is a 202: the service object exists, but an image still has to be pulled and a revision still has to become ready before it serves. Watch it with platformctl serverless get <name>.

Traffic splits are not settable here — a new service has no revisions to split between. Use serverless set-traffic once it has some.

serverless update

platformctl serverless update <name> [flags]

Takes the same flags as create. A flag you do not pass is left exactly as it is. A flag you do pass replaces its setting — and for the grouped settings that means the whole group.

GroupWhat one flag replaces
--envThe entire environment. Pass every variable you want to keep.
--env-from-secret / --env-from-config-mapThe whole import list — the other flag's entries included. They share one list. Pass every import you want to keep.
--cpu / --memory / --cpu-limit / --memory-limitThe whole compute envelope. Pass the requests and limits you want together.
--publish / --publish-hostPublishing, as a pair.
--port / --port-nameThe port list. --port on its own drops an existing name, h2c for gRPC included.

Scaling is the exception: --min-scale, --max-scale, and --concurrency merge onto the service's current scaling, so naming one leaves the other two alone.

Two of those pairs are guarded, because the half you left out would silently break the service rather than just reset it:

--publish-host needs --publish: publishing is replaced as a pair, so setting a host alone would take the service off the internet (pass --publish, or --publish=false to unpublish deliberately)
--port-name needs --port: the port is replaced as a whole, so naming it without a number would leave the container nothing to listen on

Passing no flags at all:

nothing to update: pass at least one setting flag (see 'platformctl serverless update --help')

Example — changing the image while keeping both imports and the whole environment:

platformctl serverless update api \
--image registry.example.com/api:1.5.0 \
--env LOG_LEVEL=debug --env REGION=eu \
--env-from-secret api-credentials --env-from-config-map api-settings

Each accepted update produces a new revision. Traffic keeps following the latest revision unless a split says otherwise.

serverless delete

platformctl serverless delete <name>

Deletes the service and every revision behind it. There is no undo and no confirmation prompt, and because it takes an endpoint away from whatever is calling it, the API requires the project admin role.

platformctl serverless delete api
deleted api

serverless revisions

platformctl serverless revisions <name>

Lists the immutable revisions behind a service, newest first — one per accepted update. You should see a table with the columns NAME, GEN, READY, TRAFFIC, INSTANCES, and IMAGE. TRAFFIC is the share each revision is actually serving, which is what turns this list into "and which of these is live". INSTANCES shows - when the platform reported no count at all, which is not the same as 0 running.

serverless set-traffic

platformctl serverless set-traffic <name> <revision>=<percent> [<revision>=<percent>...]

The split is replaced, not merged: send the complete intended state, and the percentages must sum to 100. Use a revision name from serverless revisions, or the word latest to keep following whichever revision is newest.

platformctl serverless set-traffic api api-00007=100 # pin a rollback
platformctl serverless set-traffic api latest=90 api-00006=10 # canary

A malformed target fails before anything is sent:

invalid traffic target "api-00007": write REVISION=PERCENT, or latest=PERCENT for the newest revision
invalid percent in "api-00007=most": "most" is not a whole number

The response is a 202: the split is recorded, and traffic moves once the routing is reprogrammed. active_traffic on the service is where the live answer appears — traffic is the split you asked for, active_traffic is the one being served.

serverless spec

platformctl serverless spec <name>

Prints the live definition of a service as YAML. It is sanitized before it leaves the platform: bookkeeping fields are stripped, and the values of environment variables whose names look like credentials are redacted — the names are kept. It never contains a secret value.

The default output is the YAML itself. -o json and -o yaml carry the full response envelope, which also names the service and explains the sanitization.

serverless metrics

platformctl serverless metrics <name>
platformctl serverless metrics api

You should see the live counts, then a per-revision breakdown:

running instances 2
state ready
ready true
scaling 1..20 instances, 80 concurrent requests each

REVISION INSTANCES READY TRAFFIC LATEST
api-00007 2 yes 100% yes
api-00006 0 yes 0% no

state and ready are the same two fields the service object itself carries, repeated here beside the live counts: the lowercase word to show, and the boolean to branch on. A revision's READY is that same boolean per revision — -o json carries each revision's state too, which distinguishes not_ready from unknown, the revision nothing has reported on yet.

A --max-scale of 0 prints as unbounded here rather than as the number 0, which would read as "no instances allowed".

These are live counts read at the moment you ask, not history. Charts over time — request rate, latency percentiles, instances during last night's spike — are not available yet, and the response says so rather than drawing something invented.

serverless logs

platformctl serverless logs <name> [--follow] [--tail <n>]
FlagDefaultWhat it does
-f, --followoffStream new log lines as they arrive.
--tailserver defaultLines of history to print before following.
platformctl serverless logs api --tail 100 -f

An idle service has no instances, which on a scale-to-zero platform is its normal resting state and not a failure: the command says so and exits. Send the service a request and its logs appear.

Logs are lines, not a resource, so -o json and -o yaml do not apply here.


platformctl serverless triggers

A trigger calls one service in this project when something happens: a cron schedule ticks, a message lands on a topic, or an object appears in a bucket. A trigger can only name a service by name, and only in its own project — there is no field in which another project could be expressed. See triggers.

serverless triggers list

platformctl serverless triggers list

You should see a table with the columns NAME, TYPE, SOURCE, TARGET, STATE, and LAST RUN. STATE is a lowercase word — pending, ready, not_ready, or suspended — with a ready boolean beside it in -o json, and a message when the answer there is no. With no triggers:

no triggers

LAST RUN is derived from what a firing left behind, so it is a short window rather than a log. It is blank for a trigger that has never fired, and for object-store triggers, which poll continuously and leave no per-firing record.

serverless triggers get

platformctl serverless triggers get <name>

The detail view carries the recent runs the list omits — use -o json to read them, in runs, with last_outcome summarising the most recent. The table shows only that most recent outcome.

serverless triggers create

platformctl serverless triggers create <name> --target <service> --type <schedule|pubsub|objectstore> [flags]

--target and --type are required, plus whatever that source type needs:

--type schedule --cron '0 * * * *' [--time-zone] [--payload]
--type pubsub --topic <topic> [--subscription]
--type objectstore --bucket <bucket> --endpoint <url> --credentials-secret <name>
--after-read move|delete|none [--prefix] [--move-to-prefix]

Common flags:

FlagDefaultWhat it does
--targetnone — requiredService in this project to call when the trigger fires.
--target-pathnonePath to call on the target, e.g. /invoke.
--typenone — requiredWhat fires it: schedule, pubsub, or objectstore.
--suspendoffCreate or leave the trigger suspended, so it does not fire.
--max-attempts5Redelivery attempts before giving up, 1–20.
--backoffexponentialRedelivery backoff: exponential, linear, or none.

Schedule flags:

FlagDefaultWhat it does
--cronnoneFive-field cron, or a shorthand such as @hourly or @every 30m.
--time-zoneUTCTime zone the cron is read in.
--payload{}JSON body delivered on each tick.

Pub/sub flags:

FlagDefaultWhat it does
--topicnoneTopic to consume.
--subscriptionthe trigger's own nameDurable subscription to read from.

Object-store flags:

FlagDefaultWhat it does
--bucketnoneBucket to watch.
--endpointnone — requiredS3-compatible endpoint. The platform does not host the store.
--credentials-secretnoneSecret in this project holding the store credentials.
--after-readnone — required, no defaultWhat happens to a consumed object: move, delete, or none.
--prefixnoneOnly objects under this prefix.
--eventcreatedChange that fires it. Repeatable.
--move-to-bucketthe source bucketBucket consumed objects move to.
--move-to-prefixnonePrefix consumed objects move to, e.g. processed/.
--poll-seconds60How often the bucket is listed.
--max-messages-per-poll10Objects one poll may consume.
--regionus-east-1Region.
--force-path-styletrueAddress buckets as <endpoint>/<bucket> rather than <bucket>.<endpoint>.

--after-read has no default on purpose. An object-store trigger with nothing retiring the objects it has consumed re-reads them on every poll and fires forever. none is a legitimate choice for an idempotent target, but it has to be one somebody made.

Examples:

platformctl serverless triggers create nightly-report \
--type schedule --cron '0 2 * * *' --time-zone Europe/Berlin \
--target reporting --target-path /run

platformctl serverless triggers create order-worker \
--type pubsub --topic orders --target order-handler

The target must already exist, so a typo is caught here rather than becoming a trigger that looks created and quietly never fires. Missing flags fail before anything is sent:

--target is required: name the service in this project the trigger should call
--type is required: schedule, pubsub, or objectstore
--type "queue" is not a trigger source: use schedule, pubsub, or objectstore

serverless triggers update

platformctl serverless triggers update <name> [flags]

Takes the same flags as create, and changes only the blocks you name. Each block is replaced as a whole, which is what these three rules work around:

  • --target — pass it whenever you change --target-path; the pair is replaced together.
  • --type — required whenever you change any source setting, because the source is rebuilt from your flags rather than merged.
  • --max-attempts and --backoff — must be passed together; retry is replaced as a pair, so naming one alone would reset the other to the platform default.
--target is required when changing the target: the service and its path are replaced together
--type is required when changing the source: the whole source block is replaced, so it has to be rebuilt from your flags (see 'platformctl serverless triggers update --help')
--max-attempts and --backoff must be passed together on update: retry is replaced as a whole, so naming one resets the other to the platform default
nothing to update: pass at least one setting flag (see 'platformctl serverless triggers update --help')

Use --suspend=true to stop a trigger firing without deleting it, and --suspend=false to resume it:

platformctl serverless triggers update nightly-report --suspend=true

serverless triggers delete

platformctl serverless triggers delete <name>

Deletes the trigger. The service it fires is left alone.

deleted nightly-report

To stop a trigger temporarily without losing its configuration, use --suspend=true on update instead.


platformctl pubsub topics

A topic is a named channel that producers publish messages to; subscriptions deliver those messages to consumers. See topics and subscriptions.

pubsub topics list

platformctl pubsub topics list

You should see a table with the columns NAME, READY, STATE, and PUBLISHED — the last being the count of messages published to that topic, read from the topic's stats. STATE is a lowercase word (pending, ready, degraded, deleting), and READY is the boolean answer to "can I publish to this right now". With no topics:

no topics

pubsub topics get

platformctl pubsub topics get <topic>
platformctl pubsub topics get orders -o json

You should see the full topic object as JSON.

pubsub topics create

platformctl pubsub topics create <topic> [flags]

Only the flags you pass are sent, so an omitted flag takes the platform's own default rather than a value the CLI invented. Requires the project admin role.

FlagDefaultWhat it does
--max-bytes16MiRetained-message budget, e.g. 16Mi or 1Gi.
--max-ageno age limitDiscard messages older than this, e.g. 24h.
--discardoldWhen full, drop old messages or refuse new ones.
--display-namenoneHuman-readable label for the topic.

--max-bytes is the topic's share of the project's storage budget, and it is claimed the moment the topic exists. Creating a topic can therefore be refused for lack of budget before a single message is published; pubsub quota shows what is left.

platformctl pubsub topics create orders --max-bytes 1Gi --max-age 24h --discard new

pubsub topics update

platformctl pubsub topics update <topic> [flags]

Takes the same flags as create, with no defaults applied — only what you pass is sent, so raising the size limit cannot silently revert an age limit someone else just set. Requires the project admin role.

The topic's name is not changeable: it is the object's identity and its broker address. Everything else is re-applied to the live topic on the next reconcile, so a change takes effect without recreating anything.

Raising --max-bytes re-checks the project's storage budget exactly as creating a topic does, and is refused if the higher ceiling does not fit. Lowering it is always allowed. Pass --max-age "" to remove an age limit entirely.

Passing no flags is an error rather than a request that changes nothing:

nothing to change: pass --display-name, --max-bytes, --max-age or --discard

pubsub topics delete

platformctl pubsub topics delete <topic>

Its subscriptions go with it: a subscription on a topic that no longer exists would be unrepairable, so the platform removes them together. Retained messages are destroyed with the topic and are not recoverable. Requires the project admin role.

deleting topic orders and its subscriptions

"Deleting", not "deleted": the API answers 202 because the topic is only marked, and the platform still has to tear the stream down.

pubsub topics publish

platformctl pubsub topics publish <topic> --message <text|@file|-> [--attribute KEY=VALUE ...]
FlagDefaultWhat it does
--messagenone — requiredThe message body: a literal string, @path to read a file, or - to read stdin.
--attributenoneA KEY=VALUE metadata pair attached to the message. Repeat the flag for more attributes.

Missing --message fails with:

--message is required (a literal string, @file, or - for stdin)

A malformed attribute fails with:

--attribute "region" must be KEY=VALUE

Example:

platformctl pubsub topics publish orders --message '{"order_id": 42}' --attribute region=eu -o json

You should see the id of each stored message:

{"message_ids": ["..."]}

If the server stores only some of the batch, the CLI reports:

partial publish: 1 of 2 stored (<error>)

Publishing from a file or stdin

platformctl pubsub topics publish orders --message @order.json
echo '{"order_id": 43}' | platformctl pubsub topics publish orders --message -

platformctl pubsub subscriptions

Subscriptions are nested under a topic, so every command in this group takes the topic through --topic — never as a positional argument:

--topic is required (subscriptions are nested under a topic)

pubsub subscriptions list

platformctl pubsub subscriptions list --topic <topic>
platformctl pubsub subscriptions list --topic orders

You should see a table with the columns NAME, TOPIC, TYPE, READY, and BACKLOGREADY the boolean, BACKLOG the unread count from the subscription's stats. -o json carries the lowercase state word beside them, and a message while the answer to ready is no.

pubsub subscriptions get

platformctl pubsub subscriptions get <sub> --topic <topic>
platformctl pubsub subscriptions get orders-worker --topic orders -o json

You should see the full subscription object as JSON.

pubsub subscriptions create

platformctl pubsub subscriptions create <sub> --topic <topic> [flags]

Requires the project admin role.

There are two independent choices here, and they are easy to confuse.

--type is the queueing model, and therefore the ordering guarantee.

ValueWhat it means
shared (default)A queue: messages go round-robin to whichever readers are connected, so adding readers adds throughput. No ordering.
key-sharedA partitioned queue: several readers, but every message with the same key goes to the same one. Ordering per key.
exclusiveA stream: exactly one reader, total ordering. A second is refused.
failoverA stream with standbys: one active reader, the rest wait.

--delivery is who moves the messages. pull (the default) means you call pubsub subscriptions pull when you are ready. push means the platform POSTs each message to --push-url.

They interact: an exclusive or failover subscription admits one reader at a time, so it cannot be read through the pull API, and the server refuses that combination at create time rather than at first read. Passing --push-url implies --delivery push, since that is the only mode in which it means anything.

FlagDefaultWhat it does
--typesharedQueueing model: shared, key-shared, exclusive, or failover. Fixed at creation.
--deliverypullpull or push. Implied push by --push-url.
--push-urlnoneHTTP endpoint in this project to POST each message to.
--push-content-modecloudevents-structuredcloudevents-structured or cloudevents-binary.
--start-fromallBegin at all retained messages or only new ones. Fixed at creation.
--ack-deadline-seconds30Seconds to acknowledge before redelivery, 1–600.
--max-deliver5Delivery attempts before a message is dead-lettered.
--max-ack-pending1000Unacknowledged messages allowed in flight.
--dead-letter-topicnoneExisting topic to divert repeatedly-failing messages to.
--dead-letter-after-attempts5Attempts before diverting to the dead-letter topic.
--display-namenoneHuman-readable label for the subscription.

A push target must be a service in this project's own network space — that boundary is what stops a subscription from being pointed at another tenant, or off the platform entirely. --dead-letter-topic must already exist, and must differ from this subscription's own topic, or a poison message would be republished into the topic it came from.

Flags that would mean nothing in the mode you chose are refused rather than ignored:

--push-content-mode only means something with --push-url or --delivery push
--dead-letter-after-attempts only means something with --dead-letter-topic

Examples:

platformctl pubsub subscriptions create orders-worker --topic orders \
--type shared --ack-deadline-seconds 60 --max-deliver 3 \
--dead-letter-topic orders-dlq

platformctl pubsub subscriptions create orders-push --topic orders \
--push-url http://<private-hostname>/events

--type and --start-from are fixed at creation. To change either, delete the subscription and create it again.

pubsub subscriptions update

platformctl pubsub subscriptions update <sub> --topic <topic> [flags]

Takes the same flags as create minus --type and --start-from. Only the flags you pass are sent; everything else keeps its current value. Requires the project admin role.

Everything here is behaviour the platform applies from the live subscription on every read, so a change takes effect on the next message without recreating anything or disturbing the cursor.

Three settings are deliberately absent because they cannot be changed on a live subscription, and accepting them silently would be worse than refusing them: the topic (it is the subscription's identity), the type (the broker records it on the connected reader, not on the subscription), and start-from (it does not move a reader that has already begun). To change any of them, delete and recreate.

Pass --dead-letter-topic "" to remove a dead-letter path. --dead-letter-after-attempts may be passed on its own to retune a path that already exists — but not to create one, and not alongside a removal:

orders-worker has no dead-letter topic, so there is nothing for --dead-letter-after-attempts to tune: pass --dead-letter-topic as well
--dead-letter-topic "" removes the dead-letter path outright, so --dead-letter-after-attempts has nothing left to tune

Retuning the content mode of a subscription that is already pushing works; setting it on a pull subscription does not:

--push-content-mode only means something with --push-url or --delivery push (orders-worker does not push today)

Passing no flags:

nothing to change: pass --display-name, --ack-deadline-seconds, --max-deliver, --max-ack-pending, --delivery, --push-url, --push-content-mode, --dead-letter-topic or --dead-letter-after-attempts

pubsub subscriptions delete

platformctl pubsub subscriptions delete <sub> --topic <topic>

Requires the project admin role. The topic and its retained messages are untouched. What is destroyed is this subscription's cursor, so anything it had not yet acknowledged is not redelivered to a subscription created later under the same name — unless that one starts from the beginning of the topic.

deleting subscription orders-worker on topic orders

pubsub subscriptions pull

Fetches waiting messages from a subscription.

platformctl pubsub subscriptions pull <sub> --topic <topic> [--max <n>] [--ack]
FlagDefaultWhat it does
--maxserver default 10, cap 100Maximum messages to pull.
--ackoffAcknowledge the pulled messages, so they are not delivered again.

Example:

platformctl pubsub subscriptions pull orders-worker --topic orders --max 10 --ack -o json

You should see the pull response on stdout:

{"messages": [{"ack_id": "...", "id": "...", "data": "eyJvcmRlcl9pZCI6IDQyfQ==", "key": ""}]}

and, because of --ack, a confirmation on stderr:

acknowledged 1 message(s)
Pull without --ack does not consume

Pulled messages that are not acknowledged are redelivered after the acknowledgment deadline. Pass --ack when you mean "take these off the queue."

Notes:

  • --ack issues a separate acknowledge call after the pull. Its confirmation goes to stderr so stdout stays the exact pull response for scripting.
  • If acknowledging fails after a successful pull:
pulled 1 message(s) but acknowledging them failed (they will redeliver): ...
  • The default table output has the columns ACK_ID, ID, KEY, and DATA, and decodes message payloads for you. -o json and -o yaml carry the exact base64 as returned (in the example above, data decodes to {"order_id": 42}).

pubsub subscriptions ack

platformctl pubsub subscriptions ack <sub> <ack-id>... --topic <topic>

The second half of a pull that was not run with --ack. pull leaves messages unacknowledged so they redeliver if the reader dies, and prints an ack id for each one in its ACK_ID column. Pass those ids here once the work they represent is actually done.

platformctl pubsub subscriptions ack orders-worker --topic orders ack-abc123 ack-def456

An ack id is valid only until the subscription's ack deadline expires, after which the message is redelivered with a new one. This command needs only the member role, not admin.


platformctl pubsub credentials, usage, and quota

These three are project-level reports; none of them takes a topic.

pubsub credentials

platformctl pubsub credentials

Prints the endpoint and token a client uses to talk to the message broker directly, without going through this API. Connecting directly is a supported path, not a workaround — this API exists so that you do not have to, not so that you cannot. Use it for a long-lived consumer, for a client library that already speaks the protocol, or for throughput a request-per-pull cannot reach.

The output contains a credential

The token authenticates as this project and nothing else: it can produce and consume inside this project and cannot see another project's topics. It does not expire, so treat it as a long-lived secret. Because it is minted from a shared signing key, the platform can only revoke it by rotating that key for every project.

Requires the project admin role, since it returns that credential.

pubsub usage

platformctl pubsub usage

Shows what each topic in this project has carried. You should see a table with the columns TOPIC, PUBLISHED, DELIVERED, IN, OUT, STORED, BACKLOG, and SUBSCRIPTIONS, followed by a totals row and the server's note. With no topics:

no topics, so nothing has been metered

The figures come from the broker itself — what it actually handled, not what a client believed it sent. IN, OUT, STORED, and BACKLOG are shown in binary units; the exact byte counts are in -o json and -o yaml.

They are cumulative since each topic was last loaded by a broker and reset when one restarts, so they are a usage signal and a rate source rather than a ledger. Member role is enough.

pubsub quota

platformctl pubsub quota
platformctl pubsub quota

You should see the project's message-storage budget:

budget 1.0 GiB
claimed by topics 272.0 MiB
available 752.0 MiB
stored 3.4 MiB
unacknowledged 0 B
topics 4

A topic claims its whole --max-bytes the moment it exists, whether or not anything has been published to it. So "claimed by topics" is what decides whether the next topic can be created, and it is normally much larger than "stored". Creating a topic, or raising an existing topic's --max-bytes, is refused when it would not fit in what is available. Member role is enough.

See also: publish and consume and the pub/sub API reference.


platformctl memorystore

Manage memory store instances — in-memory key-value stores, used as a cache and as the session store behind agents. See the memory store quickstart.

memorystore list

platformctl memorystore list

You should see a table with the columns NAME, SIZE_CLASS, STATE, and READY. STATE is a lowercase word — unknown, provisioning, ready, degraded, or deleting — and READY is the yes/no answer to "can I connect to this right now". With none:

no memorystores

memorystore get

platformctl memorystore get <name>
platformctl memorystore get session-cache -o json

You should see the full memory store object as JSON. It names the Secret holding the instance's password and never the value.

memorystore create

platformctl memorystore create <name> [flags]

Only the name is required — every other field has a server-side default, so platformctl memorystore create sessions is a complete request. Member role is enough.

FlagDefaultWhat it does
--size-classsmallInstance size: small, medium, or large.
--versionthe platform's current versionPin the engine version.
--maxmemory-policynoevictionEviction policy when memory fills: noeviction, allkeys-lru, allkeys-lfu, allkeys-random, volatile-lru, volatile-lfu, volatile-random, or volatile-ttl.
--persistencetrueKeep data on a disk that survives a restart.
--persistence-size2GiSize of that disk.
--externaloffAlso expose a TLS endpoint reachable from the internet, not only from inside the platform. The password is the only boundary in front of it.

The default policy is noeviction, deliberately not the cache-shaped one: this store backs agent sessions as often as it backs a cache, and an evicted session is a conversation that forgets its first turn with no error anywhere.

platformctl memorystore create sessions --size-class medium --persistence-size 8Gi
This is the only response that contains the password

Every later read returns the name of the Secret holding the password and never the value. Capture it now, or plan to consume it the intended way — a workload in the same project mounts that Secret with a secretKeyRef and never handles the value at all.

The output puts the password on its own line so it can be selected cleanly:

name: sessions
state: provisioning
host: <private-hostname>
port: 6379
username: default
credential secret: memorystore-sessions-credential

password (shown once):
2f9d4c1b8a6e0357

uri: ...

The credential Secret is named right above the value, because mounting that Secret with a secretKeyRef is the intended way to consume the password and you should not have to run get to find its name.

The instance is still provisioning when this returns (201, not 202: it exists and is addressable immediately). Poll platformctl memorystore get <name> until it reports ready before connecting.

memorystore stats

platformctl memorystore stats <name>
platformctl memorystore stats session-cache

You should see a live statistics snapshot read from the store itself: used memory, operation counts, and similar runtime figures. The response never discloses the store's credential — see connect from workloads for how workloads get access.

memorystore rotate-credential

platformctl memorystore rotate-credential <name>

Issues a new password and returns it, in the same one-time format create uses. Requires the project admin role, like every endpoint that returns a credential.

What this breaks

The instance reads its password once, at start, so the new one takes effect only when it restarts — which the platform triggers immediately. The old password stays in force until that rollout finishes and stops working the moment it does. Any workload still holding the old value then fails to authenticate: a client that hard-coded the password, one that read it from the credential Secret at startup and cached it, or a connection pool that reconnects after the restart. Workloads that mount the Secret still have to be restarted to re-read it, because the Secret changing does not re-authenticate a live client.

memorystore delete

platformctl memorystore delete <name>

Destroys the data volume as well as the instance, and it is not reversible. Requires the project admin role.

deleting memorystore session-cache

The API answers 202 rather than 204 because the deletion is asynchronous: the platform still has to stop the instance and reclaim the volume, and reporting "done" would claim the storage is already gone.


platformctl vectordb

Manage vector indexes — searchable stores of embeddings, lists of numbers that represent meaning, so similar text lands near similar text — and the points inside them. See the vector database quickstart, Indexes and points, and search.

vectordb list

platformctl vectordb list

You should see a table with the columns NAME, DIMENSIONS, DISTANCE, STATE, READY, and POINTS. With none:

no indexes

vectordb get

platformctl vectordb get <name>
platformctl vectordb get product-embeddings -o json

You should see the full index object as JSON.

vectordb create

platformctl vectordb create <name> [flags]

Only the name is required. --dimensions is the field you most likely want to set anyway, because it must match the width of your embedding model. Member role is enough.

FlagDefaultWhat it does
--dimensionsthe platform embedding model's widthVector width; must match your embedding model. Omit it to match the platform's own model. Fixed once the index exists.
--distancecosineSimilarity metric: cosine, dot, or euclid. Fixed once the index exists.
--shards1Number of shards. Fixed once the index exists.
--replicas1Replication factor, 1–8. Editable later.
--on-diskoffStore vectors on disk instead of in memory. Fixed once the index exists.
--payload-on-diskoffStore payloads on disk instead of in memory. Editable later.
--quantizationnoneCompress vectors: none, scalar, or binary. Fixed once the index exists.
--quantilenoneQuantile for scalar quantization.
--always-ramoffKeep the quantized vectors in memory.
platformctl vectordb create product-embeddings --dimensions 1024 --distance cosine --replicas 2

The two quantization tuning flags are refused without a mode, because quantization is fixed at creation and the only way back from a silent success is deleting the index and re-upserting every vector:

--quantile and --always-ram only apply to a quantization mode: pass --quantization scalar (or binary) as well

The index is created before its collection exists: the response is a 201 carrying a not-yet-ready object, and state and ready are read back from the engine by the controller rather than asserted at creation.

vectordb update

platformctl vectordb update <name> [--replicas <n>] [--payload-on-disk]

Only two fields are editable, and that is a property of the engine rather than a gap in this command: a live collection can change its replication factor and whether payloads live on disk, and nothing else.

FlagRangeWhat it does
--replicas1–8New replication factor.
--payload-on-diskStore payloads on disk instead of in memory.

Dimensions, distance, shards, on-disk vectors, and quantization are baked into the index the points were built into, so "changing" one would mean dropping the collection and its vectors. The API rejects those outright rather than answering 200 to a change the collection would never take, which is why this command exposes no flags for them. Sending one through the API directly returns, for example:

dimensions is immutable: existing points cannot be re-embedded, so a different width is a new index, not an edit
quantization is immutable: it is fixed when the collection is built

Only the flags you pass are sent, so changing the replica count cannot silently revert a payload placement someone else just set. Passing neither is an error rather than a no-op request:

nothing to change: pass --replicas, --payload-on-disk, or both (the other fields of an index are fixed when it is created)

Requires the project admin role: it reconfigures shared state, the same line delete draws.

vectordb delete

platformctl vectordb delete <name>

Every vector in the index goes with it and there is no undo, which is why the API gates this on project admin rather than the member role that writing points needs.

deleted index product-embeddings

vectordb upsert

Writes (inserts or overwrites) points into an index.

platformctl vectordb upsert <index> --points <json|@file|->
FlagDefaultWhat it does
--pointsnone — requiredPoints as JSON: a literal, @path to read a file, or - for stdin.

Either shape works — a bare array of points, or the full request object with a points key — so a file written for the REST API can be piped straight in. Each point is {"id": ..., "vector": [...], "payload": {...}}. The id is optional and, when given, must be an unsigned integer or a UUID string; omit it and the server mints a UUID, which is usually what you want for embeddings that have no natural key.

platformctl vectordb upsert product-embeddings --points @points.json
platformctl vectordb upsert product-embeddings --points '[{"vector": [0.1, 0.2], "payload": {"sku": "A-42"}}]'

Every vector must be exactly as wide as the index. A mismatch is rejected naming the point and both widths, rather than by the engine in terms of its own deserializer.

--points is required (a JSON literal, @file, or - for stdin)
--points is not valid JSON (pass a literal, @file, or - for stdin)

vectordb query

Searches an index for the points nearest a query vector.

platformctl vectordb query <index> --vector <json|@file|-> [flags]
FlagDefaultWhat it does
--vectornone — requiredQuery vector as JSON: a literal array, @path, or - for stdin. Must be exactly as wide as the index.
--filternonePayload filter as a JSON object: a literal, @path, or - for stdin.
--top-kserver defaultHow many nearest points to return.
--offset0Skip this many of the nearest results.
--score-thresholdno floorDrop results scoring below this.
--with-payloadtrueInclude each point's payload.
--with-vectoroffInclude each point's stored vector.

Payloads come back by default, because a search that returns bare ids is almost never what the caller wanted; pass --with-payload=false to drop them.

platformctl vectordb query product-embeddings --vector @query.json --top-k 5

You should see a table with the columns ID, SCORE, and PAYLOAD, with payloads truncated to fit. With no hits:

no matches

--filter takes the engine's own payload-filter syntax and narrows the search to matching points; it can only reference payload keys inside this index. Use -o json for every field the server sent, including the vector when you asked for it.

--vector is required (a JSON array literal, @file, or - for stdin)

vectordb scroll

platformctl vectordb scroll <index> [--limit <n>] [--offset <cursor>]

Lists the points in an index in id order, with no query vector. This is the "what is in this index" read, as opposed to query's "what is near this vector" — so a stray point can be found and removed without first knowing a vector that retrieves it.

FlagDefaultWhat it does
--limit50, max 1000Points per page.
--offsetnoneCursor: the next_offset a previous page returned.

You should see a table with the columns ID and PAYLOAD. Paging is by cursor, and the CLI prints the next call for you:

more points: --offset 4f1c9f2e-1c1a-4a63-9f83-0b0f0a1b2c3d

That line appears only when there is another page, so its absence is the signal that the walk is complete rather than something to look for and not find. Scroll returns no vectors by design — a page of raw vectors is large and is not what a browser shows. With no points:

no points

vectordb delete-points

platformctl vectordb delete-points <index> (--ids <id>... | --filter <json|@file|->)

Deletes points from an index, leaving the index itself in place.

FlagDefaultWhat it does
--idsnonePoint ids to delete. Repeat the flag or comma-separate them.
--filternoneDelete every point matching this JSON payload filter: a literal, @path, or - for stdin.

Pass exactly one of the two. Both together is ambiguous about which selects, and neither would be a request to delete nothing:

pass exactly one of --ids or --filter
platformctl vectordb delete-points product-embeddings --ids 41,42
platformctl vectordb delete-points product-embeddings --filter @stale.json

An id that is an unsigned integer is sent as a number, and anything else as a string, which the server then requires to be a UUID:

a point id cannot be empty; ids are unsigned integers or UUID strings

An empty filter object is refused rather than honoured: it matches every point, so a template that rendered no conditions would quietly empty the index. Delete the index if that is the intent.

vectordb credentials

platformctl vectordb credentials

Shows the connection details for talking to the vector database directly, for a client that would rather speak its native protocol than go through this API. Requires the project admin role, like every endpoint that returns a credential.

Read the response's note before relying on it. The token, when one is issued, is scoped to exactly this project's collections by name — the engine's token scoping cannot express a prefix — so it is a snapshot: an index created after you fetch this is not covered until you fetch credentials again. A token is issued only when the platform has both a signing key and per-project token enforcement turned on; when either is missing the response says which, and the shared master key is never handed out as a fallback.

See also: the vector database API reference.