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.
| Group | Endpoint variable |
|---|---|
serverless | CAI_SERVERLESS_API |
pubsub | CAI_PUBSUB_API |
memorystore | CAI_MEMORYSTORE_API |
vectordb | CAI_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.
| Flag | Default | What it does |
|---|---|---|
--image | none — required | Container image to run. |
--port | server default 8080 | Port the container listens on. |
--port-name | none | Name for the container port, e.g. h2c to serve gRPC. |
--command | image entrypoint | Overrides the image entrypoint. Repeatable, in order. |
--arg | none | Argument passed to the entrypoint. Repeatable, in order. |
--env | none | Environment variable KEY=VALUE. Repeatable. |
--env-from-secret | none | Import every key of a secret as environment variables. Repeatable. |
--env-from-config-map | none | Import every key of a config map as environment variables. Repeatable. |
--cpu | 250m | CPU request, e.g. 250m. |
--cpu-limit | none | CPU limit, e.g. 1. |
--memory | 512Mi | Memory request, e.g. 512Mi. |
--memory-limit | none | Memory limit, e.g. 512Mi. |
--min-scale | 0 | Instances to keep warm. 0 scales to zero when idle. |
--max-scale | 10 | Instance ceiling. 0 means unbounded, so set it deliberately. |
--concurrency | 80 | Simultaneous requests one instance handles. 0 means unlimited per instance — omit the flag for the default. |
--timeout | server default | Seconds a single request may run before it is cut off. |
--publish | off | Expose the service on the internet. |
--publish-host | none | Custom hostname to publish on. Pass together with --publish. |
--runtime-class | none | Asks for a stricter container runtime by name. None is enabled by default. |
--service-account | none | Workload 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
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.
| Group | What one flag replaces |
|---|---|
--env | The entire environment. Pass every variable you want to keep. |
--env-from-secret / --env-from-config-map | The 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-limit | The whole compute envelope. Pass the requests and limits you want together. |
--publish / --publish-host | Publishing, as a pair. |
--port / --port-name | The 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>]
| Flag | Default | What it does |
|---|---|---|
-f, --follow | off | Stream new log lines as they arrive. |
--tail | server default | Lines 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:
| Flag | Default | What it does |
|---|---|---|
--target | none — required | Service in this project to call when the trigger fires. |
--target-path | none | Path to call on the target, e.g. /invoke. |
--type | none — required | What fires it: schedule, pubsub, or objectstore. |
--suspend | off | Create or leave the trigger suspended, so it does not fire. |
--max-attempts | 5 | Redelivery attempts before giving up, 1–20. |
--backoff | exponential | Redelivery backoff: exponential, linear, or none. |
Schedule flags:
| Flag | Default | What it does |
|---|---|---|
--cron | none | Five-field cron, or a shorthand such as @hourly or @every 30m. |
--time-zone | UTC | Time zone the cron is read in. |
--payload | {} | JSON body delivered on each tick. |
Pub/sub flags:
| Flag | Default | What it does |
|---|---|---|
--topic | none | Topic to consume. |
--subscription | the trigger's own name | Durable subscription to read from. |
Object-store flags:
| Flag | Default | What it does |
|---|---|---|
--bucket | none | Bucket to watch. |
--endpoint | none — required | S3-compatible endpoint. The platform does not host the store. |
--credentials-secret | none | Secret in this project holding the store credentials. |
--after-read | none — required, no default | What happens to a consumed object: move, delete, or none. |
--prefix | none | Only objects under this prefix. |
--event | created | Change that fires it. Repeatable. |
--move-to-bucket | the source bucket | Bucket consumed objects move to. |
--move-to-prefix | none | Prefix consumed objects move to, e.g. processed/. |
--poll-seconds | 60 | How often the bucket is listed. |
--max-messages-per-poll | 10 | Objects one poll may consume. |
--region | us-east-1 | Region. |
--force-path-style | true | Address 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-attemptsand--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.
| Flag | Default | What it does |
|---|---|---|
--max-bytes | 16Mi | Retained-message budget, e.g. 16Mi or 1Gi. |
--max-age | no age limit | Discard messages older than this, e.g. 24h. |
--discard | old | When full, drop old messages or refuse new ones. |
--display-name | none | Human-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 ...]
| Flag | Default | What it does |
|---|---|---|
--message | none — required | The message body: a literal string, @path to read a file, or - to read stdin. |
--attribute | none | A 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 BACKLOG — READY 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.
| Value | What it means |
|---|---|
shared (default) | A queue: messages go round-robin to whichever readers are connected, so adding readers adds throughput. No ordering. |
key-shared | A partitioned queue: several readers, but every message with the same key goes to the same one. Ordering per key. |
exclusive | A stream: exactly one reader, total ordering. A second is refused. |
failover | A 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.
| Flag | Default | What it does |
|---|---|---|
--type | shared | Queueing model: shared, key-shared, exclusive, or failover. Fixed at creation. |
--delivery | pull | pull or push. Implied push by --push-url. |
--push-url | none | HTTP endpoint in this project to POST each message to. |
--push-content-mode | cloudevents-structured | cloudevents-structured or cloudevents-binary. |
--start-from | all | Begin at all retained messages or only new ones. Fixed at creation. |
--ack-deadline-seconds | 30 | Seconds to acknowledge before redelivery, 1–600. |
--max-deliver | 5 | Delivery attempts before a message is dead-lettered. |
--max-ack-pending | 1000 | Unacknowledged messages allowed in flight. |
--dead-letter-topic | none | Existing topic to divert repeatedly-failing messages to. |
--dead-letter-after-attempts | 5 | Attempts before diverting to the dead-letter topic. |
--display-name | none | Human-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]
| Flag | Default | What it does |
|---|---|---|
--max | server default 10, cap 100 | Maximum messages to pull. |
--ack | off | Acknowledge 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)
Pulled messages that are not acknowledged are redelivered after the acknowledgment deadline. Pass --ack when you mean "take these off the queue."
Notes:
--ackissues 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, andDATA, and decodes message payloads for you.-o jsonand-o yamlcarry the exact base64 as returned (in the example above,datadecodes 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 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.
| Flag | Default | What it does |
|---|---|---|
--size-class | small | Instance size: small, medium, or large. |
--version | the platform's current version | Pin the engine version. |
--maxmemory-policy | noeviction | Eviction policy when memory fills: noeviction, allkeys-lru, allkeys-lfu, allkeys-random, volatile-lru, volatile-lfu, volatile-random, or volatile-ttl. |
--persistence | true | Keep data on a disk that survives a restart. |
--persistence-size | 2Gi | Size of that disk. |
--external | off | Also 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
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.
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.
| Flag | Default | What it does |
|---|---|---|
--dimensions | the platform embedding model's width | Vector width; must match your embedding model. Omit it to match the platform's own model. Fixed once the index exists. |
--distance | cosine | Similarity metric: cosine, dot, or euclid. Fixed once the index exists. |
--shards | 1 | Number of shards. Fixed once the index exists. |
--replicas | 1 | Replication factor, 1–8. Editable later. |
--on-disk | off | Store vectors on disk instead of in memory. Fixed once the index exists. |
--payload-on-disk | off | Store payloads on disk instead of in memory. Editable later. |
--quantization | none | Compress vectors: none, scalar, or binary. Fixed once the index exists. |
--quantile | none | Quantile for scalar quantization. |
--always-ram | off | Keep 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.
| Flag | Range | What it does |
|---|---|---|
--replicas | 1–8 | New replication factor. |
--payload-on-disk | — | Store 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|->
| Flag | Default | What it does |
|---|---|---|
--points | none — required | Points 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]
| Flag | Default | What it does |
|---|---|---|
--vector | none — required | Query vector as JSON: a literal array, @path, or - for stdin. Must be exactly as wide as the index. |
--filter | none | Payload filter as a JSON object: a literal, @path, or - for stdin. |
--top-k | server default | How many nearest points to return. |
--offset | 0 | Skip this many of the nearest results. |
--score-threshold | no floor | Drop results scoring below this. |
--with-payload | true | Include each point's payload. |
--with-vector | off | Include 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.
| Flag | Default | What it does |
|---|---|---|
--limit | 50, max 1000 | Points per page. |
--offset | none | Cursor: 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.
| Flag | Default | What it does |
|---|---|---|
--ids | none | Point ids to delete. Repeat the flag or comma-separate them. |
--filter | none | Delete 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.
Related pages
- VectorDB overview — indexes, points, and similarity search.
- MemoryStore overview — managed key-value stores and their size classes.
- Pub/Sub overview — topics, subscriptions, and delivery guarantees.
- VectorDB API, MemoryStore API, and Pub/Sub API — every route these commands call.
- CLI overview — endpoint resolution, credentials, and output formats.