Perslis Accessibility
02 / THE API

Put it inside your app in an afternoon.

One state machine, reached over JSON. Your app calls it with the key we issue — from Swift, Kotlin, Python, a browser or a Node backend — and keeps its own symbols, voice and interface.

PATH ONE · EMBED

Node, Electron or a backend.

Import the factory and own the rendering. Nothing fetches or draws your artwork for you.

import { createTinkySpeak } from 'tinkyspeak-runtime';

const session = createTinkySpeak({
  provider: { kind: 'tinkymind' },
  language: 'en', partnerLanguage: 'en',
  profile: { age: 7, level: 'sentence', choices: 6 },
  symbols: choice => yourArtworkFor(choice),
});
const state = await session.hear({ text: 'Would you like a drink?' });
renderYourTiles(state.choices);

// In YOUR user-selection handler:
const selected = session.select({ choiceId: chosenTileId });
const pending  = await session.speak({ draftId: selected.draft.id });
// Your app delivers pending.speech.text in pending.speech.language.
session.confirmSpeech({ speechId: pending.speech.id, outcome: 'spoken' });
PATH TWO · HTTP

Any language, any platform.

Run tinkyspeak serve and the whole engine is a local JSON API on 127.0.0.1:7437. The included fetch client has no Node imports, so it bundles straight into a web app.

import { createTinkySpeakClient } from 'tinkyspeak-runtime/client';

const api     = createTinkySpeakClient({ url: 'http://127.0.0.1:7437' });
const session = await api.createSession({ language: 'en', partnerLanguage: 'en' });
const state   = await session.hear({ text: 'Are you hungry?' });
renderYourTiles(state.choices);

const selected = await session.select({ choiceId: chosenTileId });
const pending  = await session.speak({ draftId: selected.draft.id });
await yourSpeechEngine(pending.speech.text, pending.speech.language);
await session.confirmSpeech({ speechId: pending.speech.id, outcome: 'spoken' });
Method and routeWhat it does
GET /v1/capabilitiesModel language metadata, session-memory limits and output ownership.
POST /v1/sessionsOptional language, partnerLanguage, profile, context; returns state with an id.
POST /v1/sessions/ID/hearWhat the partner said; returns the state with choices.
POST /v1/sessions/ID/selectA chosen choiceId, optionally edited text; creates a draft.
POST /v1/sessions/ID/composeThe user's own words; creates a draft.
POST /v1/sessions/ID/speakA draftId; returns an app-owned delivery request in speech.
POST /v1/sessions/ID/confirm-speechspoken, displayed, failed or cancelled.
POST /v1/sessions/ID/scanBase64 images and a mode; returns observations and selectable tiles.
POST /v1/sessions/ID/languagesPartner and user language, validated against the model.
DELETE /v1/sessions/IDCloses the session and cancels its work.

Selection, draft and speech IDs prevent stale or repeated actions. Concurrent actions return 409 session_busy. Errors come back as {"error":{"code":"...","message":"..."}}. No conversation request bodies are logged.

WHAT YOUR APP STILL OWNS

The boundary is deliberate.

01

Delivery

The runtime returns the exact text, its language and a delivery ID. Your app speaks or displays it, then confirms the outcome. The HTTP server refuses a server-side speaker outright.

02

Artwork

Each choice carries a sentence, label, emoji and ID. Supply your own symbol or photo through symbols(choice), or map exact sentences in config. Emoji remain the fallback.

03

Identity and access

This is one trusted app's API. Your backend owns accounts, authorization and session ownership when serving many users.

PROVIDERS

Bring the model you already use.

The runtime ships adapters, not weights.

{ kind: 'tinkymind' }                       // installed local child model; English only
{ kind: 'gemini',   model: 'YOUR_MODEL', apiKeyEnv: 'GEMINI_API_KEY' }
{ kind: 'ollama',   model: 'YOUR_INSTALLED_MODEL' }
{ kind: 'compatible', endpoint: 'https://your-model.example/v1', model: 'YOUR_MODEL' }
{ kind: 'board',    tiles: yourAuthoredTiles }  // no generative model at all
ContinueTry it