Publishing a capability
Build a capability with your coding agent, publish it to your organization, then submit it to the marketplace.
A capability goes through three states. It is private to your organization the moment you sync it, pending review once you submit it, and published on the marketplace after a Keiki reviewer approves it. Publishing outside your own organization always goes through a person.
This page gives you a prompt to paste into your coding agent (Claude Code, Cursor, Codex) that does the private part end to end, and the manual steps for the rest. The manifest format itself is on Capabilities.
Before you start
-
A Keiki account at onkeiki.com. Signups are invite-only while we are in early access; join the waitlist or ask your organization's owner for an invite.
-
An API key: Settings → API Keys → Create key. The value is shown once, so export it right away and keep it out of git:
Shellexport KEIKI_API_KEY=sk_live_... -
Bun or Node, and a TypeScript project (an empty directory is fine).
The SDK talks to https://onkeiki.com by default. Set KEIKI_PLATFORM_URL only if you run
your own Keiki instance.
1. Build and publish privately with your coding agent
Paste this from the repository that holds your agent, or an empty one. Put your spec for the
capability under # What to build first. The agent works with or without an existing API
key: if you have one, set KEIKI_API_KEY in the environment before you start.
You are building a Keiki capability: a versioned JSON manifest that gives any Keiki agent
new tools, prompt guidance, schedules and stored state. Goal: publish it privately to my
Keiki organization and install it on my agent, so I can try it before submitting it to the
marketplace. Derive every value from my spec and this repository. Never invent endpoints,
secrets or hosts. Stop and ask me if the spec leaves a choice open.
# What to build
<describe the capability: what the agent should be able to do, which API or service it
talks to, and what the tools should be called>
# 1. Read the format
Read https://onkeiki.com/docs/capabilities.md in full before writing anything. It defines
the manifest, the runtimes (render-template, openapi, codemode, mcp), the `uses`
namespaces (entities, sandbox, http) and how secrets are declared. Pick the runtime that
matches my spec; prefer `codemode` with `uses: ["http"]` for wrapping an HTTP API.
# 2. Get access
- If KEIKI_API_KEY is set in the environment, use it and skip to step 3.
- Otherwise tell me to sign in at https://onkeiki.com (signups are invite-only: accept an
invite or join the waitlist), create a key under Settings → API Keys, and export it as
KEIKI_API_KEY. Wait for me. Do not create an account on my behalf.
- Treat the key as a secret: read it from the environment, never write it into a file
that gets committed.
# 3. Write the manifest
Install the SDK if it is not already a dependency (`bun add @onkeiki/agents` or
`npm install @onkeiki/agents`), then write ONE manifest as a TypeScript object in this
repository, next to my agent config if there is one. Type it strictly — do not widen or
cast. Shape, for a capability that wraps an HTTP API:
import type { CapabilityManifest } from '@onkeiki/agents'
export const myCapabilityManifest = {
slug: 'open-meteo-weather',
version: '1.0.0',
title: 'Open-Meteo weather',
description: 'Current weather and short-range forecast for any coordinates.',
runtime: 'codemode',
uses: ['http'],
http: { allow: ['api.open-meteo.com'] },
// only when the upstream API needs a credential:
// secrets: [{ name: 'EXAMPLE_API_KEY', required: true, description: '...' }],
prompt:
'When the user asks about the weather, call GET https://api.open-meteo.com/v1/forecast' +
'?latitude=<lat>&longitude=<lon>¤t_weather=true (no auth) and summarize current_weather.',
} satisfies CapabilityManifest
Run the TypeScript compiler; a manifest that does not type-check is not done. Rules:
- `slug` is [a-z0-9-], unique to my organization; `version` is semver and starts at 1.0.0.
- List in `uses` only the namespaces the tools need, and in `http.allow` only the hosts
they call. HTTPS only; a leading "*." matches subdomains.
- Declare every credential the capability needs in `secrets`, and reference it as
`{{SECRET_NAME}}` in headers or bodies. The installer supplies the value. Never hard-code
a key.
- Give every tool a precise `description` and a strict JSON Schema `inputSchema`. The
agent that installs this only sees those two things.
- `prompt` tells the installing agent when to use the tools, and for `codemode` documents
the API it should call. Keep it short and specific.
# 4. Publish privately and install
Add the manifest to my agent's `capabilities` array and sync with the SDK. If my
repository already has an AgentKit config, add to it rather than creating a second one;
otherwise create `publish.ts`:
import { AgentKit } from '@onkeiki/agents'
import { myCapabilityManifest } from './capability'
const kit = AgentKit.create({
apiKey: process.env.KEIKI_API_KEY!,
name: 'My Agent',
agent: {
model: 'openai/gpt-4o-mini', // any OpenRouter model id
maxSteps: 10,
historyLimit: 40,
systemPrompt: () => 'You are a helpful assistant.',
tools: [],
capabilities: [myCapabilityManifest],
},
})
await kit.sync()
Run it with `bun run publish.ts` (or `npx tsx publish.ts`). `sync` validates the manifest,
publishes it under my organization (private) and installs that exact version on the agent.
If the platform rejects the manifest, fix the manifest, not the platform.
# 5. Verify and report
Success looks like:
[agents] Syncing to platform at https://onkeiki.com
[agents] Sync complete: agent="My Agent" id=<uuid> tools=0
A `[agents] Sync failed` warning with the platform's error is not success. Then tell me:
the slug and version, the tools added, which secrets I have to set as developer secrets
before the tools work, and the next step (submitting for review, which I do myself). Do
not submit it for review.When the agent reports success, open Marketplace → Your capabilities at onkeiki.com: the new version is listed as Private, and the agent it was installed on is under Agents.
Bumping version and syncing again publishes a new private version. Installs pin an exact
version, so the agent keeps the old one until the manifest in your config changes.
2. Set the secrets
Skip this if the manifest declares no secrets. Otherwise every name in it has to exist
as a developer secret before the tools can run. Set them from the dashboard, or over REST:
curl -X PUT -H "Authorization: Bearer $KEIKI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"value": "..."}' \
https://onkeiki.com/api/v1/developer/secrets/EXAMPLE_API_KEYThey are resolved on the host per call and never reach code the model wrote.
3. Submit for review
Submission needs the owner or admin role and a payment method on file for your organization
(Settings → Billing). In the dashboard, open Marketplace: everything
your organization has synced is listed under Your capabilities with its status. Click
Submit for review on the version, add notes for the reviewer (including the test agent
and conversation that show it working) and, optionally, a .zip bundle (5 MB or smaller)
with source, tests or a demo. Read the requirements first.
The same call from a script, using your dashboard session rather than an API key:
HOST=https://onkeiki.com
# Sign in and keep the session cookie
curl -c jar -H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "..."}' \
$HOST/api/webapp/auth/login
# Your organization's capabilities, with their id and status
curl -b jar $HOST/api/webapp/marketplace/mine
# Submit one version
curl -b jar -F notes="What it does. Test agent: <agent name>, conversation: <conversation>" -F bundle=@my-capability.zip \
$HOST/api/webapp/marketplace/mine/<capability-id>/submitThe version moves to pending_review, and the manifest lands in the review inbox. A
reviewer approves it to published or rejects it with a note; either way the status shows
under Your capabilities, and a rejected version can be resubmitted from there. A
published version appears in the marketplace for every organization to install, pinned to
that version. Ship a fix by syncing a new version and submitting it; published versions never
change.
Requirements
A capability has to meet every rule in this section before it can be published. Keiki checks the first three groups automatically. Reviewers check the last two by reading the manifest and the traces of your own test agent.
Checked when you sync
The platform refuses a manifest that breaks any of these rules, and the error lists every
problem. kit.sync only logs that error as a warning ([agents] Sync failed: ...) and then
carries on, so check its output.
slugis lowercase letters, digits and hyphens, starting with a letter or digit, up to 64 characters.versionis an exactmajor.minor.patch, with no range or pre-release tag.titleanddescriptionare present.runtimeis one ofrender-template,openapi,codemode,mcporbuiltin.- Every
usesentry names a known namespace. - A tool
nameis lowercase snake case, starting with a letter, up to 64 characters. Every tool has adescriptionand aninputSchema, and itsrun.kindmatches the manifest'sruntime. - A
render-templatetool names a template declared intemplates, andtemplatesis only allowed on that runtime. - A
codemodecapability exposes onlycodemode_runandcodemode_types, with norun.code. The agent writes the code, so document your API in theprompt. loopsrequiresuses: ["loops"]. Each loop has aname, aprompt, ascheduleTypeofonce,cronorinterval, and ascheduleExpression. Any trigger other thanalwaysneeds a probe.entitiesrequiresuses: ["entities"]. An entity name is lowercase snake case, starting with a letter.secretsis a list of{ name, required?, description? }.uses: ["http"]andhttp.allowcome together. Each host is a lowercase hostname, optionally starting with*.and optionally ending in:port.- A version that is
pending_revieworpublishedcan't be changed. Syncing a different manifest under that version fails, so bump the version. Aprivateorrejectedversion can be synced again, and it goes back toprivate.
Checked when you submit
- You have the owner or admin role, your organization has a payment method on file, and it has an active API key.
- The version isn't already
pending_revieworpublished. - A bundle, if you attach one, is a
.zipfile of 5 MB or less.
Enforced while it runs
http.requestgoes over HTTPS only, and only to hosts inhttp.allow. Port 443 is allowed unless an entry names a different port. A host that resolves to a private address is refused, and redirects are not followed.- A
{{SECRET}}placeholder resolves only if the manifest declares that secret and the installer has set it. A published capability can't fall back to Keiki's own keys. Secret values are redacted from errors. - The agent sees at most 8,000 characters of a response. A payload written with
savecan be up to 25 MB. Withsave.base64Field, the rest of the JSON response comes back in full, with the saved field's value replaced by"[saved to sandbox]". - A
codemode_rungets 180 seconds, and one request inside it gets up to 170 seconds.
What reviewers check
- The manifest asks only for what it needs: no
usesentry orhttp.allowhost without a tool that uses it. - Credentials arrive through
secretsand{{PLACEHOLDER}}substitution, never inline. - Tool descriptions and schemas are specific enough that an agent picks the right tool and passes valid input on the first try.
- The
promptexplains when to use the capability, not just what it is. - It works on a small model. Reviewers read the traces of your own test agent, and a
capability has to behave on a model like
openai/gpt-4o-mini, not only on a frontier model. In your submission notes, say which agent and conversation to look at.
Capabilities that spend money or act as the person
Stricter rules apply when a tool books, buys, sends, cancels or signs in somewhere on the
person's behalf. Rules that live only in the prompt don't count here. A model will sometimes
skip them, so the API behind the capability has to enforce them.
- Nothing happens without a yes. Before a booking, purchase or any other irreversible call, the agent reads back exactly what will happen (item, date and time, party size, total price, cancellation terms) and the person agrees to it. Your API should refuse a request that doesn't match a choice it actually offered, for example a slot or datetime that never appeared in an availability response.
- Dates and times are the person's local time. Work out "today" and "tomorrow" in the person's or the venue's timezone, not in UTC, and say the date back in words ("Friday, Oct 2"). If your test run books the wrong day, the submission is rejected.
- Report only what happened. "Booked" or "paid" appears only after your API says so. A
pending,
requires_actionor failed state has to be described as exactly that. - Retries are safe. Every call with side effects is idempotent, keyed so that a retried turn can't book or charge twice.
- No credentials in chat. Card numbers and passwords never pass through the conversation: collect cards on your own payment page, and don't have the model choose a password. If your service creates an account for the person, it sends them a link to set their own password.
- Tell the person before relaying a code. If the flow needs a verification code sent by a third party (OpenTable, StubHub and so on), say who sent it and what it lets you do before asking the person to paste it. Use it once, for that action only.