Skip to documentation
Documentation

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:

    Shell
    export 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.

text
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>&current_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:

Shell
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_KEY

They 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:

Shell
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>/submit

The 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.

  • slug is lowercase letters, digits and hyphens, starting with a letter or digit, up to 64 characters. version is an exact major.minor.patch, with no range or pre-release tag.
  • title and description are present. runtime is one of render-template, openapi, codemode, mcp or builtin.
  • Every uses entry names a known namespace.
  • A tool name is lowercase snake case, starting with a letter, up to 64 characters. Every tool has a description and an inputSchema, and its run.kind matches the manifest's runtime.
  • A render-template tool names a template declared in templates, and templates is only allowed on that runtime.
  • A codemode capability exposes only codemode_run and codemode_types, with no run.code. The agent writes the code, so document your API in the prompt.
  • loops requires uses: ["loops"]. Each loop has a name, a prompt, a scheduleType of once, cron or interval, and a scheduleExpression. Any trigger other than always needs a probe.
  • entities requires uses: ["entities"]. An entity name is lowercase snake case, starting with a letter.
  • secrets is a list of { name, required?, description? }.
  • uses: ["http"] and http.allow come together. Each host is a lowercase hostname, optionally starting with *. and optionally ending in :port.
  • A version that is pending_review or published can't be changed. Syncing a different manifest under that version fails, so bump the version. A private or rejected version can be synced again, and it goes back to private.

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_review or published.
  • A bundle, if you attach one, is a .zip file of 5 MB or less.

Enforced while it runs

  • http.request goes over HTTPS only, and only to hosts in http.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 save can be up to 25 MB. With save.base64Field, the rest of the JSON response comes back in full, with the saved field's value replaced by "[saved to sandbox]".
  • A codemode_run gets 180 seconds, and one request inside it gets up to 170 seconds.

What reviewers check

  • The manifest asks only for what it needs: no uses entry or http.allow host without a tool that uses it.
  • Credentials arrive through secrets and {{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 prompt explains 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_action or 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.