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
- Create a developer account in the console (magic link — no password).
- Complete your profile. Your name and policy links appear on the consent screen, so they're required.
- Verify your domain by publishing a token at
/.well-known/clavocial-verification.txt. - Create an app to get a
client_idand a client secret (shown once). - 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.
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
auto | After 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. |
manual | Nothing happens until the user taps "Sign in with Clavocial". |
optional | You offer Clavocial alongside your own login. |
auto never means "no consent" — it means "don't ask again after the first yes."
Scopes
openid | A stable pairwise sub. Two publishers can't correlate the same user. |
email | email, email_verified |
profile | name |
entitlements | Which 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
}); | Call | Permission | Notes |
|---|---|---|
getContext() | — | Field kind + language. Carries no content. |
getSelection() | read-selection | Refused on password fields, without even prompting. |
insertText() | insert-text | Types at the cursor. |
replaceSelection() | insert-text | Swaps the selection, or the current word. |
readClipboard() | read-clipboard | Current clipboard text only. |
storage.get/set() | storage | Namespaced 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
- Redirect URIs are exact-match, HTTPS, and on your verified domain. No wildcards.
- PKCE (S256) is required. So are
stateandnonce. - Codes are single-use and live 60 seconds. A failed redemption burns the code.
- Client secrets are shown once and stored hashed. Rotate them in the console.
- Plugins are AI-scanned before listing; only a clean verdict gets listed.
- Least privilege: request the narrowest scopes and permissions that work.