Trust, stated as rows you can check.

No testimonials and no badge wall: the record of how hiy is governed and built. Each row links to the doc or policy that backs it — or says plainly that it is enforced in code.

01 · Identity & review

Identity, and who reviews it.

The identity policy is the rule the whole product rests on, and it is enforced by machinery — not stated in a FAQ.

Where a twin can be

StateA stranger getsMoved there by
DraftNot foundWhere every twin starts
Awaiting reviewNot foundYou, by publishing
LiveThe twinThe screen, or a person
DeclinedNot foundOnly a person, with a reason
Create · Address

I confirm this hiy represents me, or a product or organisation I own — and that I have the rights to the content I’ll add.

Unticked, the API refuses“You must confirm this twin represents you, or a product or organisation you own.”

Overview

This hiy wasn’t approved

A reviewer couldn’t confirm this twin represents you or something you own.

You may only twin yourself, or a product you own.

The same rule in the terms, the acceptable-use policy, the API and the confirmation checkbox. There is deliberately no “with their permission” carve-out: permission is something we cannot verify, and a rule with an unverifiable exception has no teeth.

The identity screen can approve; it can never reject.

Publishing runs a deterministic screen — no model call, so every decision is explainable. It auto-approves only when the twin’s name matches the account’s own identity and the sources actually talk about that person; everything else waits for a human. Turning someone down stays a human act, with a written reason the owner can act on.

By design, enforced in code

A decision is made about the exact name the reviewer read.

Approval records the name the human was looking at, and refuses to land if the twin was renamed while they read it — so a cleared name can never quietly become a different person’s.

By design, enforced in code

The Verified badge is derived, never stored.

It follows from the name that was cleared: rename an approved twin away from that name and the badge goes; rename back and it returns. There is nothing a rename can launder.

On failure:

When the screen cannot clear a twin, the twin waits.

There is no timeout that publishes by default. The twin sits at awaiting review — a 404 to every stranger — until a human decides, because REVIEW_ENFORCED in lib/twin-access.ts is the flag publicState is defined to agree with. The queue it waits in is an admin-only route; it is not a public status surface and we do not describe it as one.

By design, enforced in code

The states a twin moves through — draft, awaiting review, live, and the two ways a decision can turn it down — are drawn in what happens when you publish.

02 · What no plan removes

What no plan can remove.

De-branding is the only trust-adjacent thing paying changes, and it is marketing credit — never honesty. These surfaces are never removable, on any plan.

yoursite.comExample
Yoursite support

Can you move me to yearly billing?

Yes. Yearly billing starts at your next renewal, and I’ve asked the billing team to switch your plan.

Billing FAQdocs.yoursite.com/billing
  • Billing change requestSent to the team.
From the site you're on:plan

Do you take bank transfers?

Not in the source material

Nothing I’ve been given covers bank transfers, so I won’t guess. Want me to pass your question to a person?

I looked 3 ways before answering· show

AI support · may make mistakes

Report · Processed by hiy.ai · Privacy

On every plan, on every answer

  1. CitationsThe source behind a grounded answer
  2. The action receiptWhich action ran: “Sent to the team.”
  3. What the site told this answer“From the site you're on:” and the keys, never the values
  4. The honest gap“Not in the source material”, and how many ways it looked
  5. The AI label“AI support · may make mistakes”
  6. The report linkBeside who processes the conversation
1on the picture.

Citations behind grounded answers.

No plan can remove them. The creator chooses how they render — a quiet source line, open excerpts, or not shown to visitors — but that is the creator’s editorial call over their own material; paying cannot make an answer look better-sourced than it is.

2on the picture.

The action receipt.

An answer that ran a Notify or a Call your endpoint carries a short line naming which action ran and how it went. Hiding the wording of your own material is a creator’s right; hiding that an answer left the building is not — so that line is not plan-gated, and the setting that hides sources does not reach it.

3on the picture.

What the site told this answer.

When a host’s signed token attests facts about the person asking, the answer carries a line naming which keys arrived — the names, never the values. A visitor is not entitled to be kept ignorant that an answer used facts about them they never typed, and a visitor who is not told concludes either that hiy is tracking them or that the agent is guessing well. Both are wrong. No plan and no setting removes it.

4on the picture.

The honest gap, and its receipt.

When the material runs out it says so instead of inventing, and shows how many searches it ran before giving up. The count never hides, on any plan or setting — without it, “looked and found nothing” and “never looked” would be the same observation.

5on the picture.

The AI label.

Every twin and agent is labelled as an AI wherever it answers, and says so itself when asked. De-branding swaps hiy’s name for the category noun — “Sam’s AI twin”, which discloses more, not less.

6on the picture.

The report link.

Every public twin carries one. Reports land in an operator queue with real dispositions — not a mailbox nobody reads.

By design, enforced in code

Cannot:

A hidden citation cannot be recovered from the page.

When a creator chooses not to show sources, the citations are removed from the response before it is sent — citationsForVisitor in lib/rag/tools.ts filters them out and drops the source id with them. Hiding them in the view would leave them in the wire for anyone who opened a network tab, so “hidden” is made to mean absent.

By design, enforced in code

03 · Token security

Token security. No token, no agent.

A Support or Team Agent has no public URL. By default your users reach it through a short-lived token your own server mints — your site vouches for its visitors, so they never need hiy accounts.

A visitor token

Signed with
HMAC-SHA256, and nothing else
Lasts
15 minutes by default
Refused beyond
60 minutes, however well it is signed
Opens
One agent, never another in the same organisation
Signing secret
Derived per organisation, stored nowhere
May carry
Up to 6 facts the owner declared, written nowhere
Without one
A 404, the same as a made-up address

Signed with HMAC-SHA256 — deliberately not JWT.

There is exactly one algorithm and it is not negotiable: no algorithm header to confuse, no “alg: none”, no key discovery. A bearer credential for one agent for a few minutes does not need a framework built for federated identity.

Lifetime is capped, and the cap is ours.

Tokens default to fifteen minutes and are refused beyond MAX_TOKEN_LIFETIME_SECONDS = 3600 s (one hour) — even when signed correctly. Lifetime is our policy, not the signer’s.

By design, enforced in code

Audience-bound.

A token minted for one agent never opens another, even inside the same organisation. A valid signature alone is not enough.

By design, enforced in code

Signing secrets are derived, never stored.

Each organisation’s token-signing secret derives one-way from a single master, so no per-organisation signing secret is written down anywhere, and holding one organisation’s tells you nothing about any other’s. That is a claim about tokens specifically. Credentials you give an agent for an action are stored — encrypted, and described below.

By design, enforced in code

A token may attest a few declared facts, and hiy writes none of them down.

Your server may include up to six facts about the person asking — only the ones the agent’s owner declared, by the keys they chose. readAttested in lib/attested.ts reads them for the length of one answer, hands them to the action that needs them, and writes them to no row: not the conversation, not the follow-up record, not an event, not the search index. There is nothing for deletion to reach, because nothing was kept. A value your own endpoint sends back is a different thing: it is read only from a field the action declares (one added in the dashboard declares none), and an answer that uses it is kept with the conversation like any other.

Verification fails closed, and explains nothing.

The signature is checked in constant time before the payload is even parsed, and a caller holding a guess is never told which check failed — “bad signature” and “expired” together would tell an attacker which half to fix. Only a token really minted for that agent is told it expired, or came from the wrong site.

By design, enforced in code

Without a token, the agent does not exist.

Asking for it returns the same “not found” a made-up address does — a 404, not a login wall that confirms there is something worth attacking. Team Agents answer outsiders the same way. The owner’s server mints the token — or, for a Support Agent its owner has opened on their websites, hiy mints it for requests that present one of those listed sites as their Referer. That is a check browsers honour and programs do not, so an open agent’s limits are on cost, not on who can ask. A Team Agent can never be opened; the database refuses it.

A token hiy mints carries no facts, and can only look things up.

hiy’s own minter has no way to attest anything about a visitor, so an open visitor is always anonymous. For them, the only action offered is a look-up that needs no stored key: nothing that uses the owner’s key or their site’s facts, sends data, or posts a notification — decided per answer, so an action added later is covered too.

The three parts of a token, and the order the server checks them in, are drawn in how a token is built and checked.

04 · Limits that fail closed

Limits that fail closed.

Wherever metering or entitlement can fail, it fails toward not serving — never toward serving unmetered, and never toward quietly honouring a plan that lapsed.

Where each failure lands

Ifhiy
Two conversations race for the last messageOne gets it. The cap is checked and counted in one step.
The message counter errorsThe answer is declined, never served unmetered.
Reading the plan failsThe Free plan’s limits apply, not the paid ones.
The nightly billing job never runsAn ended trial still resolves to Free.
The month’s messages run outThe visitor is told so, never shown a dead widget.

The message cap is checked and counted in one atomic step.

Concurrent conversations cannot race past it, and if the counter itself errors, the answer is declined rather than served unmetered.

By design, enforced in code

Entitlement is re-derived on every read.

The stored plan is treated as a cache of a decision. An expired trial serves the free plan even if the job that should have recorded the expiry never ran; a failed read serves the free plan rather than the paid feature set.

By design, enforced in code

Hitting the cap is stated, never disguised.

A twin out of monthly messages tells visitors so and stops; an embedded Support Agent at its cap states the limit and offers the contact form — never a dead widget on your site, never an auth-shaped error.

On failure:

When the billing cron fails, the plan degrades to Free.

The nightly job only tidies the plan label; it is never what entitles anyone. lib/billing/entitlement.ts derives the answer from the reason columns instead — a trial past its end date, or a Stripe status outside the entitled set, resolves to the default plan whether or not the job ever ran. A missed run therefore gives nobody a free paid plan, which is the failure the derivation exists to prevent.

By design, enforced in code

Availability, honestly.

No status page yet, and no availability percentages — we won’t publish figures we aren’t measuring against a public record. The service is provided as-is.

05 · Who processes what

Who processes what. And for how long.

The data posture in the GDPR sense of the words — who answers for which data, who touches it, and for how long.

Who answers for which data

  • Your agents' conversations with your customers or colleagues

    You · controllerhiy · processor

  • Your account: email, sources, settings

    hiy · controller

  • A fact your server attests in a token

    You · controllerhiy · keeps nothing

Every service that touches it

Vercel
Serves the application. Its AI Gateway reaches DeepSeek and Google models on zero-data-retention hosts only.
Supabase
Database and sign-in, in the EU (eu-west-1).
Anthropic
Runs models, called directly.
OpenAI
Runs models, called directly, and makes the search embeddings.
Resend
Notification email.
Stripe
Payments, if you subscribe.
Settings · Your data

Your data

Your content is yours — take it or remove it whenever you like.

Export my data (JSON)
{
  "twins": [ … ],
  "sources": [ … ],
  "conversations": [ … ],
  "messages": [ … ],
  "held_handles": [ … ],
  "org_credentials": [ … ],
  "credential_audit": [ … ]
}

Processor for agent conversations; controller for account data.

When your Support or Team Agent answers your customers or colleagues, those conversations are your data: you are the controller, and hiy processes them on your behalf. For your own account — email, sources, settings — hiy is the controller. A fact your own server attests about one of your own users in a token is your data about your own user, so the answer is not ambiguous there either: you are the controller, hiy never asks for it and never keeps it, and it exists here only for as long as the answer it was sent for.

Every service that touches your data, named.

Vercel serves the application, and through Vercel AI Gateway reaches some models — DeepSeek and Google today — only on hosts with a zero-data-retention agreement. Supabase hosts the database and authentication in the EU (eu-west-1). Anthropic and OpenAI run the models they are called for directly, and OpenAI also generates the search embeddings. Resend delivers notification email. Stripe processes payments, if you subscribe. Each processes data only to provide its service to us.

Kept while your account exists, then really gone.

Delete an agent, a person, or your account and the corresponding data goes with it — deletion is deletion, not a hidden flag. Two things are named rather than buried, because both are exceptions a reader would otherwise find out the hard way. Deleting one agent leaves material that another of your agents draws on — it moves to your Knowledge Hub instead of being destroyed, because taking it would empty an agent nobody asked to delete. And the address each agent answered at is held for 90 days, the name and the date and nothing else, so that a link somebody saved cannot be claimed by a stranger the moment you leave. A fact your site attested about the person asking is outside this promise rather than covered by it, and in the direction that favours them: it is never written down at all, so there is nothing for a deletion to reach.

Action credentials are stored encrypted, and every use by an action is recorded.

An action that writes into your help desk needs your key, so unlike a signing secret it has to be kept and handed back. It goes into Supabase Vault: the value in the database is ciphertext, and the key that opens it is read from the server at startup rather than held in any table — so a database dump, a backup or a replication stream is ciphertext hiy cannot read from the dump alone. Inside the application every read goes through one function, read_org_credential in supabase/migrations/20260831090000_org_credentials.sql, which checks the credential belongs to your organisation and has not been revoked, and writes a row saying which action asked and when; that log comes out in your account export. Replacing or removing a credential destroys the old ciphertext rather than superseding it. What that does not protect you from: hiy’s own service role can call that function — and anything holding the service-role key and a direct SQL connection reads the values straight out of the vault without passing it, in which case nothing is written to that log. It is a record of what your actions did, not proof of everything that could have happened. Encrypting at rest defends the database, not the application — no scheme that lets an agent use your key can also keep it from the software running the agent. Give an agent a key scoped to what the action needs, and remove it when you are done with it.

The privacy pointer survives de-branding.

Every agent conversation carries a “processed by hiy.ai · privacy” link in its chat chrome, in the same never-removable class as the AI label. Paying removes hiy’s marketing credit from an embed; it never removes the pointer to who processes the conversation.

Cannot:

A Team Agent cannot tell an admin who asked.

There is no per-person attribution surface to turn off, because there is none to begin with: api/twin/conversation answers 404 for any twin whose variant is internal, so a transcript for a team agent cannot be opened by anyone, owner included — and the People view drops attribution for those same twins. It cannot ask, either, which is the half that used to depend on a setting: allowCapture: false on the team-agent policy is a ceiling over the creator’s own follow-up switch, and mayCapture in lib/agent-kinds.ts composes the two everywhere a card could appear — so a colleague is never shown the field they would type their own name into, whatever the switch says. It cannot be told, either: allowAttested: false sits on the same policy, so a host that signs facts about the colleague asking into its token gets an agent that never has them — nothing is constructed for an internal agent to read. An admin sees the topics and the gaps and not who raised them. A colleague asking their team’s agent a question is not building a record against themselves.

By design, enforced in code

Cannot:

hiy cannot join your two visits.

Your token carries your own site’s id for the person asking. hiy compares it once, for one purpose — recognising an owner previewing their own agent — and never writes it. The conversation row has no column for it: the field that once existed was dropped, along with the index that had been built for exactly that question. So “show me everything this person ever asked” is not a report we have turned off. It has no answer to give, on any plan and for any operator.

By design, enforced in code

No formal data-processing agreement document yet.

The posture on this page is the commitment, and the privacy policy is the governing text. A signable DPA is the written-down follow-up — if you need one to evaluate, say so and it moves up the list.

06 · How it’s engineered

How it’s engineered. Read from the code.

The honest version of an AI-engine page. No diagrams of machinery that doesn’t exist, and no benchmark charts. When numbers appear here they are ours, read from the code that enforces them.

Three prompt layers

  1. Layer oneEvery twin and agent

    Say you’re an AI. Never invent. Admit the uncovered half.

    No setting reaches it

  2. Layer twoEach kind

    A Support Agent never sells. A Team Agent reports, never decides.

    No setting reaches it

  3. Layer threeYours

    Voice, scope, off-limits topics.

    You write it

Which model answers, today

DeepSeek V4 Flash
Expert Twin chat, Team Agent answers, wiki synthesis
Claude Haiku 4.5
Support answers, suggested follow-ups, gap questions

Routed by task. No plan buys a smarter model.

Read from the code

  • 400output tokens

    The longest a Support Agent’s answer can run

  • 3searches

    The most one answer runs before it answers with what it found

  • 10minutes

    A rebuild’s lease, after which the next run proceeds

Three prompt layers, and only one belongs to the creator.

Layer one is identical for every twin and agent on the platform — the honesty rules: say you’re an AI, never invent, admit the uncovered half of a half-covered question. Layer two is identical per kind — a Support Agent never sells; a Team Agent reports and never decides. Layer three is the creator’s: voice, scope, off-limits topics. No setting reaches layers one or two.

By design, enforced in code

Retrieval over a synthesized wiki — not a fine-tune.

Sources are rewritten into a structured wiki and indexed for search. Every answer is grounded in passages retrieved for that question, and a grounded answer can show the passage it came from.

Sources feed retrieval, never training.

Your material becomes an index to be searched at answer time. It trains no model — not ours, not a shared one, and never another creator’s twin or agent.

Models are tiered by task — never by plan.

Where a person’s voice is the product — an Expert Twin’s chat, a Team Agent’s answers, and the wiki synthesis behind them — the model is the one that passed our answer evaluation, not the priciest one: today DeepSeek V4 Flash, reached through Vercel AI Gateway on zero-data-retention hosts, after it matched Claude Sonnet on faithfulness. Support answers, suggested follow-ups and gap questions run a lighter tier — today Claude Haiku — because a password-reset answer needs speed and precision, not a voice. The promise is the routing, not the vendor; no plan buys a smarter model.

By design, enforced in code

Cost discipline is fail-closed too.

Answer length is capped per kind of agent — a Support Agent’s answer is capped at 400 output tokens by its entry in lib/agent-kinds.ts — and an answer that searches its own knowledge gets a hard ceiling of MAX_SEARCHES_PER_ANSWER = 3 rounds. It answers with what it found rather than running an open-ended loop.

By design, enforced in code

Cannot:

hiy’s own prose cannot be quoted to a visitor as a source.

Every indexed passage stores where it came from. buildIndexChunks in lib/rag/chunk.ts stamps origin as it writes — source for a creator’s own material, summary for hiy’s synthesis of it — and every summary passage is dropped before the citations reach a stranger. The accepted consequence is real: an answer leaning on the wiki shows a visitor fewer citations, sometimes none. Showing more would mean presenting our sentences as the creator’s.

By design, enforced in code

Cannot:

A Support Agent cannot offer you services.

Not an instruction it is asked to follow — a catalogue it is never given. The support variant carries allowServices: false in lib/agent-kinds.ts, usableCatalogue filters the services category out of both the prompt and the tool list before the answer starts, and hiddenBookingField in lib/records/lookup.ts withholds the sign-up link on any other bookable category — from the tool result as well as the card. The bound is worth stating exactly: what it cannot reach is the services catalogue and every booking link, not every list its owner chose to attach.

By design, enforced in code

Cannot:

The search receipt counts queries; it cannot claim results.

What the receipt reports is the list of searches the twin chose to run, in order — the queries, not a verdict on what was there. hiy never tells you a search “found nothing”, because the honest observation is that it looked and this is what it looked for.

On failure:

When a reindex fails, the lease expires and the next run proceeds.

A rebuild takes a database-backed lease of REINDEX_LEASE_SECONDS = 600 s (ten minutes) rather than a lock it must remember to release. A process that dies mid-rebuild therefore blocks nothing: the lease runs out and the next attempt takes it. The failure mode is a stale index that is visibly stale, never a knowledge base wedged shut by a crash.

By design, enforced in code

Where a passage came from is a stored column with three states, and only one of them can be quoted to a stranger — drawn in why only your words are quoted.

Check every row yourself. Free for 14 days.

Start free
  • 14-day free trial
  • No card needed
  • Paste a URL to start

All of it is checkable in the live product too: ask our own agent whether it’s an AI, ask it something its material doesn’t cover, open a citation.

Missing the row you came for? Ask us. If the honest answer is “not yet”, that is the answer you will get — in writing, so you can hold us to it.