Privacy Notice
Revision 2026-09-07 (terms-pack v1.3). Effective on publication. Supersedes revision 2026-08-31.
This notice explains how Bilans Solutions LLC ("we", "us") handles personal data across the three Terakota surfaces. Contact for privacy matters and data-subject requests: contact@bilans.io.
We run no trackers, no advertising cookies, and no cross-site profiling on any surface, and we do not sell personal data. The one measurement we run is on the marketing site: cookieless, aggregate analytics with no user identification (§1.1). The Portal and the connect broker run no analytics at all.
1. Marketing site — terakota.io
1.1 What data. The static site sets no cookies and has no forms, so we collect nothing directly from you. It runs cookieless, aggregate analytics (Cloudflare Web Analytics): no cookies, no cross-site tracking, no user identification. It loads fonts from Google Fonts, so your IP address transits to Google when a font loads.
1.2 Why. To serve the page and its fonts, and to count page views in aggregate.
1.3 Lawful basis. Legitimate interest — serving the pages you ask for and counting page views in aggregate, with no cookies and no user identification.
1.4 Where processed. Served by Cloudflare (a Worker at its global edge). Fonts are delivered by Google (global). Analytics are processed by Cloudflare.
1.5 How long. We store nothing ourselves. Cloudflare's retention of aggregate analytics and Google's retention of the font-request IP are governed by them, not us.
2. Portal — app.terakota.io
2.1 What data. For your account we hold: your email, your Auth0 subject identifier, digests of session tokens (never raw tokens), your workspace memberships and entitlements (including whether the control panel is switched on for your account — an operator-set flag; setting or clearing it is an audited grant event), the version of the Account Terms you accepted and when you accepted it, and an append-only audit log of account, grant, and login events. Auth0 holds your email and credentials.
If you connect a production vendor account through the connect service (§3), we also hold a connection record and, while an attempt is in flight, a short-lived attempt row.
Connection record — the complete list. For a production QuickBooks connection made through our connect service, we store exactly these eleven fields and nothing else:
- portal account id
- connection id
- provider (which vendor the connection is for)
- device-key thumbprint
- client id
- keyed HMAC of the normalized realm id
- status
- scope
- created, last-refresh and revocation timestamps
- token-generation HMAC
- revocation reason
Three things are deliberately absent: no raw realm id, no company name, no token material. This is a closed set, not a starting point — adding a field requires amending the architecture decision that fixes it, and the published notices change first.
Connection attempts ("flights") are single-use rows that expire on a short timer (10 minutes by default). They record which vendor the attempt is for, the state of the attempt, a verifier encrypted at rest, the two public keys your install registered for that one attempt, and the loopback address on your machine the result returns to; a flight never holds an authorization code and never holds a token. The audit log gains three connect events: qbo_connect, qbo_revoke, and qbo_refresh_denied.
The control panel — what we record (from portal revision 2026-09-07; from terakota v1.8.0 for the binary). Every change made to a tenant's delivery routing through the panel (and, from the release that ships the binary's change commands, through the binary) — a subscription added or removed, a held delivery replayed, a catch-all replay, and — by an operator, for now — a topic registered or deleted — writes one append-only row in our engine store: when it happened, which account (your account id — an internal identifier, not your email), which tenant, which action, a digest of the routing before and after, and the database login the change ran under. No event content, no inputs beyond the action itself, and — for the binary — never the --intent text you typed. From terakota v1.8.0, each control-plane read and each events-tail poll is recorded too, but separately and more briefly: one line in a read log in our control store — your account id, the tenant, which read, and the time, with no inputs and no results — and nothing at all for the command that only asks who you are signed in as. If a read's line cannot be written, the read still completes and the failure is noted in our application log with the tenant, the command and the kind of failure — never your account id. Like every request to the Portal and its API, a control-plane call also leaves one line in that application log — the route, the outcome, the timing and your account id, with no inputs and no bodies — which lives with our host, Fly, for a bounded period. While a tail runs that is one line every few seconds, which is a record of when your machine was polling. We delete read-log lines older than 90 days, and closing your account deletes yours in the same step that clears your email and display name. The change rows above are different: they are append-only and have no automatic expiry today. For each event delivered on a tenant's hosted spine we also keep one index row — its sequence, topic, entity id, event id, the delivery's message id, and when it was received and delivered; the table has no payload column, so it cannot hold an event's content, and rows older than 90 days are removed on the operator's retention run. Neither the panel nor the binary can see an ingest token, a quarantined delivery's body or signature, a dead letter's payload, or a destination's address or secret; dead-letter error text is shown with addresses masked. Other members of the same tenant can see, on the panel's audit view, that a member account made a change — the action, the time and the digest, not which account.
Auth0 also holds the sign-in grant records for the terakota binary's client once you have used terakota login.
2.2 Why. To authenticate you, control your access, keep the service secure, maintain an integrity record of account and access changes, and — for connections — to notify you of security issues affecting the connect service and to attribute and cut off abuse of our shared vendor application — and, for the control panel, to tie every view and change of a hosted tenant's delivery routing to a member an operator has switched on. Accounts are never joined to any marketing list.
2.3 Lawful basis. Contract necessity for providing the service you signed up for — the account, the Portal, the control panel, and any connection you make through the connect service. Legitimate interest for the rest: keeping the account secure, maintaining the audit record, and attributing and cutting off abuse of our shared vendor application.
2.4 Where processed. Our control store, and the engine store that holds a tenant's delivery routing, its audit rows and its delivery-event index, are Postgres on Fly.io in one US region (Ashburn, Virginia); the Portal reaches the engine store through one database role that sees only the tenant's own rows and none of the secret columns. Identity is handled by Auth0 in the US (tenant dev-bo1prweh.us.auth0.com); from terakota v1.8.0 the terakota binary signs in through dev-bo1prweh.us.auth0.com, the host our sign-in provider (Auth0) runs for us.
2.5 How long. Account data for the life of the account. Closure is operator-run — there is no self-serve close button; ask us at contact@bilans.io, the contact address on the Portal. On that offboarding path access is revoked first (workspace memberships and active sessions are deleted; a control-plane access token already issued to the binary opens nothing from that moment, because every control-plane call checks your membership live (it stays a valid token until it expires, within an hour)), and the same day your identifying data goes with it: your Auth0 user and your sign-in identity are deleted, and on the account record itself your email address, display name, accepted-terms record and verified-email flag are cleared and the connect and control-panel entitlements are withdrawn. What is left that day is a de-identified record that cannot be signed in to and cannot be granted access or a connect entitlement again. Revoking the grant at the vendor is a separate step we perform on request — terakota qbo disconnect --company <id> asks the broker to revoke, and an operator can revoke a connection on our side — and closure on its own stops renewals rather than firing an upstream revocation, which ends the connection within one access-token lifetime. Records of connections that have been revoked are retained for 90 days after revocation — the window that makes abuse attribution on our shared vendor application possible — and are then erased, together with the spent flight rows of those connections. The de-identified account record is removed after that, in a second step: those retained records reference it, and it cannot be removed while they do, which makes the 90-day window a floor on its removal rather than a target. We compute the date it becomes removable from our own records when we act on your request, and give you that date in our response. Our tamper-evident audit log is append-only: it retains pseudonymous entries of account and access changes rather than being rewritten. Audit log retention: 365 days. Business data is not held by us — it stays in your database; only delivery state and receipts metadata are kept. Portal workspace data is synthetic demo data; real business data enters only under a future signed pilot agreement, mandate, and DPA. The connect service is the exception to that: connections made through it are real, not demo — real accounts, real realm HMACs, real production token material in transit.
The control-plane audit rows in the engine store are append-only and have no automatic expiry today; they carry your account id, not your email, and closure does not rewrite them. The control-plane read log is separate and does not persist that way: its lines are deleted after 90 days, and yours go when your account record is de-identified. Delivery-event index rows are removed after 90 days on the operator's retention run.
3. Connect broker — oauth.terakota.io
3.1 What data. Production OAuth credential material in transit — the vendor authorization code, the token pair returned by the vendor, and the sealed capsule delivered to your machine — plus the working data an exchange needs: flight and connection identifiers, the OAuth state, your install's registered public keys and device proof, the requested scope and our client id, the local return address your install registered for the handoff, and the vendor realm identifier, which is keyed-hashed before anything is stored. Credential material is never at rest here — no token, no authorization code, no plaintext realm id — and none of it is written anywhere; what persists is the Section 2.1 connection record and flight metadata, in the portal store.
3.2 Why. Production OAuth at these vendors requires an HTTPS callback and a client secret that a downloadable binary cannot carry, so the code-for-token exchange, each token renewal, and revocation run here instead of on your machine.
3.3 What it is not. The broker is not a data plane. Your business data never traverses it; reads run from your machine to the vendor directly. It never proxies a vendor data API. Its outbound network access is allow-listed to the vendor's pinned token and revocation host and to our own control store — nothing else.
3.4 Posture. TLS-only with HSTS. It reads no cookies (it is a separate origin from the Portal by construction, so Portal cookies cannot reach it), and serves no analytics, no third-party scripts, and no service workers. Its metrics are bounded outcome classes and aggregate counts. No URL queries, request or response bodies, authorization codes, tokens, state values, capsules, or realm ids appear in any log, trace, or edge capture.
3.5 Where processed. Fly.io, one US region (Ashburn, Virginia), on its own deployment identity separate from the Portal.
3.6 How long. Nothing at rest, so nothing to retain. What is recorded about a connection is the connection record in §2.1, held in the Portal control store. Fly platform connection logs (including IPs) are kept by Fly for a bounded period, outside our control.
4. Who else touches the data (sub-processors)
4.1 We use a small set of sub-processors — the third parties that process personal data on our behalf. This is the whole register: which surface and data each touches, its region, and whether a signed data processing agreement is in place.
| Entity | Role | Surfaces | Data touched | Region | DPA status |
|---|---|---|---|---|---|
| Fly.io | Compute host + Postgres at-rest host | portal (incl. the control panel and its API) + connect broker (oauth.terakota.io) | Account + audit data at rest (portal); production OAuth credential material in transit (broker); connection metadata at rest (portal DB); connection logs (IPs); a hosted tenant's delivery routing, control-plane audit rows (account id, tenant, action, digests) and delivery-event index (topic, entity id, event id, times — no payload) at rest (engine DB); the control-plane read log (account id, tenant, which read, time — 90 days) at rest (portal DB) | US (iad, Ashburn VA) | PENDING — blocks the first real connect customer |
| Auth0/Okta | Identity provider | portal (incl. connect ceremony sign-in) + the terakota binary's device-flow sign-in (from terakota v1.8.0) | Email, credentials, login metadata, device-flow sign-in grant records for the binary's client | US | PENDING — blocks the first real connect customer |
| Cloudflare | DNS for all hostnames + serves the static marketing site + cookieless aggregate analytics on it | terakota.io (+ DNS for all) | DNS queries; marketing-site requests incl. visitor IPs; aggregate page-view analytics | Global edge | PENDING — Owner action |
| Google Fonts | Font delivery on the marketing site | terakota.io | Visitor IP at font-request time | Global | PENDING — Owner action |
No signed DPA is in place for any entry yet. For the two that carry connect-service data, that is a named blocker: the connect route stays closed to third parties until the Fly.io and Auth0/Okta posture is confirmed in writing.
4.2 Cloudflare is DNS-only for app.terakota.io and oauth.terakota.io. Neither surface proxies through Cloudflare — their traffic goes straight to Fly.io, and Fly's edge terminates TLS. Cloudflare serves and proxies only the static marketing site, and answers DNS for all three hostnames.
4.3 Intuit is not a sub-processor. Intuit is your own vendor and an independent controller of your QuickBooks data. Your reads run machine-to-Intuit directly; what passes between us and Intuit is the credential exchange itself and nothing else. We record this explicitly rather than leaving Intuit unmentioned.
| Entity | Role | Surfaces | Data touched | Region | DPA status |
|---|---|---|---|---|---|
| Intuit | Independent controller — your own vendor, NOT our sub-processor | connect broker (credential exchange only) | The OAuth authorization code, token pair, and revocation calls for a connection you authorize | US | N/A — not a processing relationship |
4.4 The register above is maintained in the sub-processor register, which is its home and carries the operator notes behind each row. This page and that file move in the same change.
5. Your rights
5.1 Depending on your jurisdiction you may have rights to access, correct, delete, or port your data, and to object to or restrict processing, together with the right to withdraw consent where processing rests on consent and the right to complain to a supervisory authority. We honor these where applicable law gives them to you, and we do not discriminate against you for exercising one.
5.2 To exercise a right, email contact@bilans.io. We verify identity, then handle the request per our DSAR procedure and answer you within the timeline applicable law sets (default: 30 days). An erasure runs in two steps, and we give you the dates for both. The first clears your identifying data — your Auth0 user, your sign-in identity, your sessions and your memberships, and, on the account record, your email address, display name, accepted-terms record and verified-email flag — and runs as one operation once we have verified you, inside that response window. The account record itself is removed in the second step, after the retention window in §2.5 has closed: connection records are kept for 90 days after revocation, and the record cannot be removed while they still reference it, so that step falls after the response deadline rather than inside it. Connection records and flight rows are erased by that same retention sweep. We tell you the date the record becomes removable in the response to your request. The append-only audit log is tombstoned rather than rewritten (§2.5).
6. Changes
6.1 This notice is versioned. The header shows the current version and date. When we change it, we update the version and, for the Portal and the connect service, notify account holders where the change is material.
6.2 Governing law and the responsible entity. This notice and our processing are governed by the laws of Wyoming; the responsible entity is Bilans Solutions LLC, a Wyoming limited liability company, registered agent at 30 N Gould St STE R, Sheridan, Wyoming.
Change log
Revision 2026-09-07 (terms-pack v1.3) — the control panel is covered for the first time. It is part of the Portal, not a fourth surface, so the surface count is unchanged. Section 2.1 names the control-panel entitlement among what we hold, adds the block that states what the panel and the terakota binary record — every routing change as one append-only row in our engine store, each read and each events-tail poll as one line in a 90-day read log, nothing for the command that only asks who you are signed in as, and the delivery-event index with no payload column — and records that Auth0 holds the sign-in grant records for the binary's client. Section 2.2 adds the fourth purpose (tying every view and change of a hosted tenant's routing to a member an operator has switched on) and 2.3 extends contract necessity to the panel. Section 2.4 names the engine store, the tenant-scoped database role the Portal reaches it through, and the host the binary signs in against. Section 2.5 states the two retention answers — append-only change rows with no automatic expiry today, read-log lines deleted after 90 days and at closure, index rows after 90 days — and withdraws the control-panel entitlement at closure alongside the connect one. Section 4's register gains the engine-store and read-log data classes on the Fly.io row and the binary's device-flow sign-in on the Auth0/Okta row; no entity joins or leaves and no DPA status moves. No new retention window applies to anything that existed before, and the 90-day and 365-day windows are unchanged.
Revision 2026-08-31 (terms-pack v1.1) — the anonymous Seam Check at check.terakota.io is retired, and the section that covered it is withdrawn. Section 2 (Seam Check) is removed and the sections after it are renumbered: old Section 3 becomes Section 2 (Portal), 4 becomes 3 (connect broker), 5 becomes 4 (sub-processors), 6 becomes 5 (your rights), and 7 becomes 6 (changes); the entries below this one cite the section numbers published on their own date. The surface count moves four → three — the marketing site, the Portal, and the connect broker. The Cloudflare DNS-only list and the sub-processor register drop the check host and the upload payloads it carried, and Section 5.2 drops its note that the anonymous check stored nothing, because there is no longer such a surface. This withdraws a surface rather than moving it: no processing moves elsewhere, no new data category, no new retention, and the 90-day and 365-day windows are unchanged.
Revision 2026-08-28 (terms-pack v1.1) — the published erasure wording now matches what erasure does. Closure and an erasure request complete in two steps, not one: the day we act on a verified request the identifying data goes (the Auth0 user, the sign-in identity, sessions and memberships, and the email address, display name, accepted-terms record and verified-email flag on the account record), and the account record itself is removed only after the 90-day window for revoked connection records published in Account Terms Section 9.3 has closed, because those retained records reference it and it cannot be removed while they do. Account Terms Section 9.1 and Privacy Notice Section 3.5 now state both steps and the de-identified state between them, and Section 9.1 no longer says connection records are deleted at closure, which Section 9.3 has always contradicted. Privacy Notice Section 6.2 separates the response deadline (default: 30 days), which is unchanged, from the removal of the record, which falls after it, and records that we give you the removable-on date in our response. This corrects a description, not a practice: no new data category, no new retention, and the 90-day and 365-day windows are unchanged.
Revision 2026-08-17 (terms-pack v1.1) — item 9 of the connection-record enumeration now names the revocation timestamp (revoked_at) beside the created and last-refresh timestamps; the field was added so the 90-day erasure of revoked connection records published in Account Terms Section 9.3 is executable. No new data category — a stored-field disclosure clarification, and the enumeration stays closed at eleven items.
Revision 2026-08-13 (terms-pack v1.1) — the connect broker at oauth.terakota.io is added as a fourth surface (Section 4), and the surface count moves three → four throughout. Section 3.1 gains the eleven-field connection record and the flight rows, matched line-for-line to the shipped store and to the terakota Privacy Notice §3a. Section 3.5 states the real closure path — operator-run, no self-serve button — and separates upstream vendor revocation from closure. Section 5 now carries the sub-processor register inline, with the connect-broker rows and the Intuit non-sub-processor row, and names the DPA blocker. The marketing-site entry is corrected to state the cookieless aggregate analytics the site actually runs. The lawful-basis positions in 1.3, 2.3 and 3.3, the rights enumeration in 6.1, and governing law in 7.2 replace open counsel placeholders.
Revision 2026-07-16 — first published version.
Hosted by Terakota. Your data at rest is yours; payloads transit our compute as service-provider processing.