Getting started

From zero to a stateful AI character in five steps. About fifteen minutes if you're new to the API. The optional Big Five step takes a couple more.

1 Sign in & grab your API key

Your tenant + first key are auto-provisioned on first sign-in.

  1. Open vilow.dev/dashboard/login.html
  2. Enter your work email → click Email me a link
  3. Click the magic link in your inbox → land in the dashboard
  4. Open the API keys tab — copy the ck_live_… key
Heads up. The key is shown in plain text — copy it now. It's equivalent to a password. Never embed it in browser-side JavaScript or mobile binaries; keep all calls server-side.

From now on every API request needs this header:

X-API-Key: ck_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

2 Big Five onboarding (optional)

Ask your end-user 15 short questions, get a personality vector you can drop straight into the character.

This step is optional — if you skip it, just leave big_five out of the create request and we'll pick reasonable defaults (or generate them in auto-mode). But if your app has a real onboarding flow, this is the cleanest way to ground the bot in real preferences.

Get the question list

# language can be 'en' or 'ru' — falls back to en for unknowns
curl "https://api.vilow.dev/v1/onboarding/big-five/questions?language=en" \
  -H "X-API-Key: ck_live_..."

You'll get back 15 items in this shape:

{
  "language": "en",
  "questions": [
    { "id": "o1", "trait": "openness",
      "text": "I enjoy trying new things and experimenting." },
    { "id": "o2", ... },
    ...
  ]
}

Render each question to your user, collect a 1–5 score per question (1 = strongly disagree, 5 = strongly agree), then submit:

Score the answers

curl -X POST "https://api.vilow.dev/v1/onboarding/big-five/score" \
  -H "X-API-Key: ck_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "answers": {
      "o1": 4, "o2": 5, "o3": 2,
      "c1": 3, "c2": 4, "c3": 2,
      "e1": 4, "e2": 3, "e3": 3,
      "a1": 5, "a2": 5, "a3": 2,
      "n1": 2, "n2": 3, "n3": 4
    }
  }'

You'll get a normalised big_five dict ready to pass to the character creator:

{
  "big_five": {
    "openness": 0.833,
    "conscientiousness": 0.667,
    "extraversion": 0.583,
    "agreeableness": 0.917,
    "neuroticism": 0.333
  }
}

3 Register your end-user

Vilow uses an opaque external_id — pick whatever string you already use internally.

curl -X POST "https://api.vilow.dev/v1/users" \
  -H "X-API-Key: ck_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "external_id": "alice-42",
    "display_name": "Alice"
  }'

Response:

{
  "id": 1,
  "external_id": "alice-42",
  "display_name": "Alice",
  "created_at": "2026-04-26T15:00:00+00:00"
}
Idempotency. The pair (tenant, external_id) is unique. Calling this twice with the same ID returns 409. You can safely call it on every sign-in and ignore conflicts.

4 Create the character

Two modes. Pick one.

Option A — auto-generation

Give us the gender, locale, optional hint and the big_five from step 2. We ask Grok to generate a name, persona, backstory and city in one call.

curl -X POST "https://api.vilow.dev/v1/users/alice-42/characters" \
  -H "X-API-Key: ck_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "generation_mode": "auto",
    "gender": "female",
    "locale": "ru",
    "default_language": "ru",
    "hint": "warm watercolor artist who lives in Tallinn",
    "big_five": {
      "openness": 0.83, "conscientiousness": 0.67,
      "extraversion": 0.58, "agreeableness": 0.92, "neuroticism": 0.33
    }
  }'

Option B — manual

You pass everything; we just persist it. Useful for game studios with a fixed cast.

curl -X POST "https://api.vilow.dev/v1/users/alice-42/characters" \
  -H "X-API-Key: ck_live_..." \
  -d '{
    "name": "Лена",
    "gender": "female",
    "persona": "warm and curious watercolor artist",
    "backstory": "lives in Tallinn, paints mornings, runs a small studio",
    "default_language": "ru",
    "big_five": {"openness": 0.83, "conscientiousness": 0.67, "extraversion": 0.58, "agreeableness": 0.92, "neuroticism": 0.33}
  }'

Either way you get back a Character with an integer id — keep it. You'll need it for every chat call.

Optional fields you can mix in

FieldPurpose
custom_traitsFree-form text from your end-user — quirks, fears, favourite quotes. Injected verbatim into the prompt.
intimate_personaAdult persona block. Only used after the user gives explicit intimate consent. Skipped on tenants with intimate_mode=off.
default_languageauto mirrors the user, ISO codes pin the bot's language.
Plan limit. The Free plan caps you at one character per tenant. Hobby and Pro have no cap.

5 Send your first chat message

One call. One round-trip. Reply, extracted facts, emotions, relationship deltas — all in the response.

curl -X POST "https://api.vilow.dev/v1/chat/alice-42/{character_id}/send" \
  -H "X-API-Key: ck_live_..." \
  -H "Content-Type: application/json" \
  -d '{ "message": "Привет! Как у тебя сегодня настроение?" }'

What you get back

{
  "reply": "Привет! *улыбается, откладывая кисть* Сегодня прекрасное настроение...",
  "conversation_id": 1,
  "message_id": 42,

  // extracted automatically — saved to memory
  "user_facts_saved": 0,
  "self_facts_saved": 1,

  // safety signals
  "user_intimacy_level": 0.0,
  "reply_intimacy_level": 0.0,
  "intimate_blocked": false,

  // relationship dynamics
  "trust": 1.15,
  "friendship": 1.30,
  "relationship_stage": "strangers",
  "relationship_events": [
    { "event": "first_meeting", "timestamp": "..." }
  ],

  // LLM usage (already billed)
  "usage": { "prompt_tokens": 600, "completion_tokens": 80, "total_tokens": 680 }
}

That's the loop. Keep calling /send with the same external_id and character_id — memory, relationship, emotions, life events all carry over automatically.

What if the user replies in another language?

If default_language is auto, the bot mirrors them. Otherwise the bot stays in the configured language. You can override on a per-call basis with "language": "en" in the payload.

What about *actions*?

By default the bot weaves small physical actions (*nods*, *smiles softly*) into 60-70% of replies. To turn this off (e.g. for a therapy bot), set "reply_style": "plain" in your tenant feature flags.

6 Where to go next

Once the basic loop works, layer in the features you need.

FeatureEndpoint
Voice mode (TTS-clean text, optional audio)POST /v1/chat/{ext}/{cid}/send-voice
Browse what the bot remembersGET /v1/users/{ext}/characters/{cid}/memory
Forget a specific memory (right-to-be-forgotten)DELETE …/memory/{id}
Trust / friendship / conflict stateGET …/relationship
Emotions, needs, bio cycle, secretsGET …/personality
Background life — what the bot is up toGET …/life
Diary — "while you were away"GET …/diary
Pending bot promisesGET …/promises
Should the bot reach out unprompted?POST …/proactive/check
Intimate mode consentPOST …/intimate/consent
Token usage / billingGET /v1/usage/summary

Full schema with request/response shapes lives at api.vilow.dev/docs — auto-generated, always in sync with the running service.

Per-tenant configuration

Most behaviour is tunable via feature flags on your tenant — relationship pace, life-event intensity, allowed themes, intimate mode, voice add-on, proactivity quiet hours, etc. Full reference of every flag. Open the Dashboard → Settings tab to edit them as JSON, or hit PATCH /v1/me/feature-flags programmatically.

Quotas & billing

Free covers 200 messages/month. After that you'll get HTTP 402 with one of these codes — your client can use them to drive UX:

codemeaning
monthly_quota_exhaustedFree plan ran out — upgrade or wait for next month.
character_limit_reachedTried to create more than the plan allows.
character_over_plan_limitPlan was downgraded; this character is no longer in the active slot.
balance_too_lowPaid plan in overage — top up the balance.
voice_addon_unavailableAudio rendering needs Pro plan.
subscription_inactiveCard declined and grace period expired.