Developer docs

A Clavocial plugin is a web page you host on your own domain, shown in a sandboxed strip inside the keyboard. It can sign users in with the Clavocial account they already have — so they never make a second account to use what they bought.

Quickstart

  1. Create a developer account in the console (magic link — no password).
  2. Complete your profile. Your name and policy links appear on the consent screen, so they're required.
  3. Verify your domain by publishing a token at /.well-known/clavocial-verification.txt.
  4. Create an app to get a client_id and a client secret (shown once).
  5. Build your plugin page and submit it — it's AI-scanned before listing.

Where plugins live

You host your plugin, on your own domain. Clavocial never serves your code or content. We host only the registry entry that points at you. That means you control releases, your own CDN and caching, and your own backend — and your users' content requests go to you, not us.

Your domain's own Content-Security-Policy applies inside the keyboard. If yours restricts style-src, add your nonce to your inline styles — we won't ask you to weaken your policy.

Plugin manifest

Submitted to POST /plugins/register. Entry-point URLs must be HTTPS on your verified domain.

{
  "pluginId": "yourco-thing",
  "name": "Your Thing",
  "category": "education",
  "description": "What it does, in a sentence.",
  "icon": "https://yourdomain.com/icon.png",
  "publisher": { "name": "Your Co", "domain": "yourdomain.com" },
  "compute": "device",
  "permissions": [],
  "ssoClientId": "cl_...",
  "entryPoints": [
    { "type": "grid-action", "label": "Open", "url": "https://yourdomain.com/plugin/index.html" }
  ],
  "price": { "amount": 499, "currency": "USD", "type": "one-time" },
  "version": "1.0.0"
}

Categories: social, ai-assistant, dictionary, education, game, security, shopping, productivity, other. Entry types: grid-action, compose-addon, text-transform, assistant.

Sign in with Clavocial

Standard OpenID Connect (Authorization Code + PKCE, RS256), so use any OIDC library. Discovery lives at /.well-known/openid-configuration.

SSO modes

autoAfter the first consent, the user is signed in silently on later opens. Best when your plugin needs identity to show anything — e.g. paid content that should just play.
manualNothing happens until the user taps "Sign in with Clavocial".
optionalYou offer Clavocial alongside your own login.

auto never means "no consent" — it means "don't ask again after the first yes."

Scopes

openidA stable pairwise sub. Two publishers can't correlate the same user.
emailemail, email_verified
profilename
entitlementsWhich of your plugins the user owns — so you can gate paid content without running billing.

OIDC reference

GET  /.well-known/openid-configuration
GET  /oauth/jwks                 verify our tokens yourself
POST /oauth/authorize            consent decision or a code
POST /oauth/consent
POST /oauth/token                code -> id_token + access_token (+ refresh)
GET  /oauth/userinfo
POST /oauth/revoke
POST /oauth/logout

An id_token

{
  "iss": "https://clavocial.com",
  "sub": "<pairwise id>",
  "aud": "cl_...",
  "email": "user@example.com",
  "entitlements": ["yourco-thing"],
  "sid": "<session id>",
  "nonce": "...", "iat": 0, "exp": 0
}

Plugin runtime API

Inside the keyboard your page gets window.CPlugin. Every call that touches the user's text is gated on a permission they granted to your plugin — nothing is ambient, and nothing works until you ask.

// The host injects window.CPlugin around page load, so wait for it
// rather than assuming it exists when your script first runs.
async function host(ms = 3000) {
  const t0 = Date.now();
  while (!window.CPlugin && Date.now() - t0 < ms) await new Promise(r => setTimeout(r, 50));
  return !!window.CPlugin;
}

if (await host()) {
  // Field kind + language + what you've been granted. No content, no permission.
  const ctx = await CPlugin.getContext();
  // { language: 'en', fieldType: 'text'|'email'|'password'|..., granted: [...] }

  // Ask before you use a gated call - the HOST draws the prompt, not you.
  if (!ctx.granted.includes('insert-text')) {
    const ok = await CPlugin.requestPermission('insert-text');
    if (!ok) return;
  }
  await CPlugin.insertText('Hello');
}

The full surface

// Text access (needs 'read-selection')
const s = await CPlugin.getSelection();
// { selection, before, after }  - refused on password fields

// Write (needs 'insert-text')
await CPlugin.insertText('appended at the cursor');
await CPlugin.replaceSelection('swaps the selection');

// Clipboard (needs 'read-clipboard')
const { text } = await CPlugin.readClipboard();

// Storage (needs 'storage') - namespaced to your plugin,
// another plugin can never read these keys.
await CPlugin.storage.set('prefs', JSON.stringify({ theme: 'dark' }));
const raw = await CPlugin.storage.get('prefs');

// Sign in with Clavocial
const auth = await CPlugin.signIn({
  clientId: 'cl_...',
  scope: 'openid email entitlements',
});
// { idToken, accessToken, sub, email, entitlements }

CPlugin.onSignOut(() => {
  // the Clavocial session ended - drop local state
});
CallPermissionNotes
getContext()Field kind + language. Carries no content.
getSelection()read-selectionRefused on password fields, without even prompting.
insertText()insert-textTypes at the cursor.
replaceSelection()insert-textSwaps the selection, or the current word.
readClipboard()read-clipboardCurrent clipboard text only.
storage.get/set()storageNamespaced to your plugin. 100 KB per key.
signIn()Returns tokens for your client_id only.

The host draws every consent prompt and runs the OIDC flow, so your page can never fake a permission dialog and never sees the user's Clavocial session — only tokens minted for your client_id. You cannot read what the user types generally, reach another plugin's storage, or touch the keyboard's own bridges. Outside the keyboard window.CPlugin is undefined; fall back to your own sign-in.

Working examples. Four open test plugins exercise the whole API — a live third-party API integration, a selection transformer, a storage-backed snippet store, and a diagnostics plugin that asserts each permission boundary holds. Install API Diagnostics from the plugin store to run the suite against your own build.

Sign-out

When a user signs out of Clavocial we revoke every token from that session and POST a signed logout_token to your registered back-channel logout URI, so you can end your own session server-side. Register it when you create the app.

POST https://yourdomain.com/sso/logout
Content-Type: application/x-www-form-urlencoded

logout_token=<signed JWT: aud=your client_id, sid, backchannel-logout event>

Verify it against our JWKS, then kill the session for that sid.

Security rules

Open the console