Let’s get technical.
Everything the front page says, written out as the thing it was built from: the real database, how a contact is actually made, and who else touches a call.
You do not need this page to use Rilo, and nothing here contradicts the simpler version. It is the same product, described precisely.
The whole database
Everything our server knows about your family.
Not a summary. These are the seventeen tables the server has, with the columns they have. Four are your family: who they are, who may act for a child, the phones they speak from, and where to ring them. Four hold something for a phone that was switched off, and all four delete themselves. Three are about who may act for a child and which phone stopped being trusted. Two are signed records of an ending: a contact cut, or somebody gone. They are kept so a phone which was offline can check them rather than believe us. One is spent one-time numbers, swept as they expire. One is the subscription, and it holds nothing that names anybody. One is a report a grown-up sent us, and it is the only table here that exists to make something happen. The last is not about your family at all.
- ida hash of the public key, not a name
- public_keythe device key that is the account
- typeadult or child
- type_certthe signature proving it. A child account is signed by a parent, so there is no flag to flip
- created_at
- last_activethe last time any of your phones said hello
- dormant_warned_atempty
- consent_sealedyour sign-up email, sealed to a key that lives on paper with the founder. No server can read it
- uncovered_sinceempty, unless your plan stopped covering this child — then the day we noticed, so the month before anything stops starts from then
- keywhat is being remembered
- valueand what it says
- account_id
- guardian_keya key allowed to act for this child
- added_at
- removed_at
- account_id
- device_keyone phone’s own key. A person can speak from more than one
- agreement_keythe key a message is sealed to, so only that phone can open it
- certthe signature that let this phone in, kept word for word so your phone can check it instead of believing us
- kinda phone, or the printed recovery sheet
- added_at
- removed_at
- account_id
- device_keywhich of your phones this one rings
- platformapns or fcm
- tokenan opaque handle from Apple or Google
- updated_at
- nonce
- expires_at
- edge_hasha hash of the pair, not the two people
- denied_at
- expires_at
- edge_hasha hash of the pair, not the two people
- grant_recordthe permission being decided, and the signatures collected so far
- waiting_keywhose signature is still missing
- requested_bythe phone that asked, so one phone cannot flood a family with questions
- originscanned in person, or a parent acting for their child
- delivered_towhich phones already have it
- declaredthe asking side’s own name, sealed so only the family they scanned can read it
- created_at
- expires_at
- id
- recipient_idrouting needs this one in the clear
- recipient_devicewhich of their phones the box is sealed for. Each phone has its own key, so each gets its own box
- sender_idso the phone knows whose key opens it
- sender_devicewhich of their phones sealed it, so the right copy finds the right lock
- edge_hashthe pair, hashed, so this cannot be read as a map of who talks to whom
- sealedciphertext. There is no text column and there never will be one
- sent_at
- expires_at
- edge_hashthe pair, hashed
- revoked_at
- recordthe signed record, so a phone that was offline can verify the cut itself rather than taking our word for it
- id
- account_idwhose list of phones and grown-ups is changing
- kindjoining, or being removed
- subject_keythe phone or the grown-up it is about
- subject_kindwhich of those two
- agreement_keythe second key a phone needs, empty for paper
- recordthe signature itself, kept word for word so a phone can check it instead of believing us
- signed_bywhich key signed it
- by_authoritywhether a recovery sheet signed it, which is what makes it wait
- issued_atthe moment the signer claims, never our clock
- reasonempty, unless the signer wrote one
- proposed_at
- effective_atwhen it becomes true, two nights after it was proposed
- contested_at
- contested_bywho objected, and the signature they objected with
- contest_record
- resolved_at
- resolutionapplied or cancelled, once it settles
- change_idwhich waiting change
- voter_keywhich grown-up
- decisionlet it through, or stop it
- recordtheir signature
- at
- account_idwhose phone it was
- device_keythe key that was stolen
- revoked_at
- recordthe signed proof, so a phone can check it without asking us
- account_idthe account that is gone
- retired_at
- recordthe signed proof, made with that account’s own key before the key was destroyed
- account_id
- storeapple, google, or on the house
- store_refan opaque handle from the store. It is the only thing that lets us ask it a question later
- store_ref_hasha fingerprint of the handle above. It is what stops one receipt covering two accounts
- environment
- stateactive, grace, lapsed, lapsed_trial or disabled
- seatshow many children it covers. A number, never their names
- expires_at
- acts_at
- updated_at
- device_keythe phone that has not been told yet
- change_idwhich change this is about
- whatproposed, contested, or settled
- account_idwhose list of phones moved. When a grown-up is told about their child, this is the child
- subject_keythe phone that was added or removed
- effective_at
- created_at
- expires_at
- delivered_atwhen we managed to say it, or empty because we have not
- id
- about_keywho the report is about. Readable, so that somebody can act on it
- reasonwhich of six things happened, as the grown-up reporting chose it
- csa
- sealedwhat was written, sealed on the phone that sent it with a key that lies on paper with the founder. No server can read it
- sealed_digesta fingerprint of the sealed part, so that what is opened later is provably what arrived
- received_at
- edge_snapshotwho had been allowed to reach whom, and which grown-up allowed it, as it stood at that moment. This one cannot be reconstructed later
- reporter_keywho sent it, or nothing at all when it was sent without a name
- source
- sealed_key
- receiptthe reference the sender keeps. It is the only way we can answer somebody about a report we cannot open
- acknowledged_at
- decided_at
- actioned_at
- escalated_at
- decided_by
- outcome
Columns that do not exist
- name
- age
- photo
- message text
- call content
- call history
- contact list
- location
- device model
Your email is checked once when you sign up and sealed in the same breath, to a key that lives on paper with the founder and has never touched a server. The law asks us to be able to prove an adult agreed. We could not read it back ourselves. Names and photos are set on each phone and handed to the other phone directly. They never reach us, which is also why you can rename Grandpa to “Opa” and we will never know. We could not show any of this to anyone if we wanted to, and we could not be made to.
This section is generated from the server’s own schema, in accounts.ts and roster-changes.ts, the two files that create the database. If they ever disagree, the files are right and this page is a bug.
Contacts
How a contact is actually made.
Somebody’s phone makes a one-time invite. The other phone reads it, scanned from a screen in the same room or opened from a link that grown-up sent. Reading it starts the introduction and, in the same moment, records the other phone’s public key, so both phones know exactly which key belongs to which person from then on. That is the part that makes a later call impossible to sit in the middle of, including for us. None of it is connected until a grown-up on the other side has said yes.
Still not a contact. A responsible adult has to sign the contact on their own phone, behind a biometric check made at that moment. What they sign is the whole thing. Who, with whom, and in which direction. Their signature is what the server checks before it will route anything. The server holds no key that can sign, so it cannot create a contact, and neither can anyone who steals the database.
When both phones hold the finished contact, the row is deleted and the contact exists only on the two devices. Cutting it later writes a signed revocation, which is the one record kept indefinitely. It is stored as a hash of the pair, so it says that a contact ended without saying who knew whom.
Who else is involved
Three companies touch a call. None of them can hear it.
Six other companies touch the service, and they are all listed in the privacy policy. Three of them are involved in getting a call to ring, and this is what each of those three can and cannot see.
-
Apple & Google
Push notifications, so a call rings a phone that is asleep. Unavoidable on both platforms. They carry an opaque identifier and nothing else. No name, no number, no content.
-
Cloudflare
Roughly one call in six cannot connect the two phones directly, on hotel Wi-Fi or some mobile networks, and is bounced through a relay. The relay forwards encrypted packets it cannot open, but it does see that two anonymous ids were connected, when, for how long, and from which addresses. That is metadata we do not keep, held by somebody else.
-
Us
The table above. One small server in the Netherlands that introduces two phones and then gets out of the way.