Host setup
Your books, under your name.
We run the books and the login for them, wearing your brand. For almost every host there is nothing to build: your branded login is live the moment your account is, and putting it on your own web address takes one form and one DNS record. Work through the steps below in order — the whole thing is a coffee, not an afternoon.
Who this page is for
Hosts and resellers — you offer bookkeeping to your own clients, and their books run under your name instead of ours. If that is you, you are in the right place.
Just keeping the books for your own business? You do not need any of this. Choose the One business plan on the sign-up form and sign in when your welcome email arrives.
Reselling also means your own Terms of Service and Privacy Policy carry a few sentences about the bookkeeping provider. The Host Legal Integration Pack has the steps and ready-to-paste model text.
Your login link works today
Nothing to switch on. Your branded sign-in page is already live at your day-one address:
https://theguybooks.com/login?host=YOUR-SLUG
Swap YOUR-SLUG for the short name in your welcome email — your slug is just the nickname we file your account under, like acme-books.
Put that link wherever your Sign in or Books button lives on your website. Your people land on your branded login, and once they are in, on their books. Nothing gets installed on your site.
This address stays on our domain, so it reads as your brand hosted by us. Step 3 moves it onto a web address of your own, which is the full white-label finish.
Signing in, and the extra check
We own the login — passwords, resets and sign-in security — all under your brand. You run no login and store nobody’s password.
The first owner of a set of books is invited by email: a “set your password” link that carries your name and logo. That owner then invites their own staff or accountant from the settings menu inside the books, under Manage users. You never touch their passwords.
Signing in has two layers:
- Always on. The first time someone signs in from a browser we do not recognise, we email them a one-time 6-digit code. They type it in and we remember that browser for 30 days. This layer cannot be switched off.
- Optional. Anyone can switch on an authenticator app from their account screen, under Sign-in security. While it is on, the app’s code takes the place of the emailed one — nobody is ever asked for both.
An owner coming back. Once an owner has reached their books, they sign in at your branded sign-in page every time. There is no new link for them: asking for an onboarding link for that client answers 409 tenant_claimed, so send the owner to your sign-in page instead. The emailed code is asked only on a browser we do not recognise. Once they type it, we remember that browser for 30 days, and after that it asks once more.
Your own console sign-in works the same way. Its controls are in the console sidebar under Account & Security.
Put it on your own domain
This is the step that makes it fully yours: your books served from an address like books.yourcompany.com, with your name in the browser bar the whole way through. Your clients never see ours. There is still no code to add — just one entry at your domain registrar.
The easy way — from your console
Sign in, open Set up in the sidebar, and find Option B — Connect your own domain. Type just your domain, for example yourcompany.com. The books. part in front is fixed, so your books land at books.yourcompany.com. Press Connect a domain and the records you need appear right there.
Or by API, if you prefer
Same job, from your own server, using your management key (step 4):
curl -X POST $BOOKS_BASE_URL/v1/hosts/me/domains \
-H "Authorization: Bearer $BOOKS_HOST_KEY" \
-H "Content-Type: application/json" \
-d '{"domain": "books.yourcompany.com"}'The answer comes back with status set to pending and a dns_records list. Each record has three fields — type, name and value — which are exactly the three boxes you fill in next.
Adding the DNS record
A DNS record is the little pointer entry you add where you bought your domain — GoDaddy, Namecheap, Cloudflare, whoever. It tells the internet where that address should go.
Sign in there, find the DNS settings for your domain, and add each record we showed you. Copy Type, Name and Value exactly as given. Most registrars add the rest of your domain to the Name box for you, so enter it precisely as shown and do not add your domain on the end again.
What “verified” looks like
Nothing else to press. We watch for the record and switch the domain on by ourselves, usually within the hour — DNS changes take a little while to travel, so a wait is normal, not a fault.
On the Set up page your domain carries a little status tag. It reads verifying while it waits, and flips to active once the record is found and the security certificate is issued. Active means your login and books are being served from your address. Your records stay on screen afterwards on purpose — if the entry is ever deleted at your registrar, you need them again.
One domain per account. You can hold 1 connected domain at a time, and that limit is enforced on our side — the API refuses an extra one just as the console does. Once yours is connected the console replaces the entry box with the domain you have. Typed it wrong, or moving to a different domain? Email support@theguys.app and we will switch it for you.
Once your domain is live, go back and point the Sign in link on your website at it instead of the day-one address from step 1.
Keeping your key safe
Your API keys are created in your console — Set up → Advanced: API & automation — and each one is shown to you exactly once; we never email a key. A workspace key is the full-access one — anything the API can do, it can do — so treat it like the key to the shop. Keep it in your server’s environment, beside the address it talks to, for example:
BOOKS_BASE_URL=https://theguybooks.com BOOKS_HOST_KEY=…
Every sample on this page reads those two. BOOKS_BASE_URL is https://theguybooks.com on production; the sandbox address, and which key works on which, are in the inventory guide’s ring table.
Every host key has the same shape, whatever its scope. A live key starts with tgb_ and has 32 random letters, digits, hyphens and underscores after it, 36 characters in all; a test key starts with tgb_test_ (41 characters). If your code checks a key, check how it starts, never its length — the start will not change, the length may grow.
Three rules for the key, and they are short:
- Keep it on your server only, in your environment settings.
- Never put it in a page your visitors load, and never in your code repository.
- Think it has leaked? Revoke it on the same console page and create a new one — or use Roll, which makes the new key and keeps the old one working for the time you pick while you switch over.
You never have to use the key at all. Everything on this page can be done from the console with a mouse.
Connecting your clients
Each client business gets its own private set of books, under your brand. One seat is one client business. There are two ways to add a client, and both do exactly the same thing — use whichever suits you.
From your console
Sign in, and on the console’s home page find Invite a client business. Type the business name and the owner’s email, then press Create books & send the invite.
From your own software, by API
If your own app signs up the businesses you serve, it can create their books and walk the buyer straight in, the moment they pay — no phone call and no waiting on an email. Your server makes every call below with a workspace key — the full-access key from step 4. Every field is listed in the API description at https://theguybooks.com/v1/openapi.json — no key needed to read it.
- Create the client with your own customer id — safe to send again.
- Register the address of your own site the buyer comes back to — once, in your console.
- Ask for a one-use link and redirect the buyer’s browser to it.
- The buyer proves their email, chooses a password and comes back to you.
- Read the client until it says claimed — that, and only that, means done.
Create the client, with your own customer id
curl -X POST $BOOKS_BASE_URL/v1/tenants \
-H "Authorization: Bearer $BOOKS_HOST_KEY" \
-H "Content-Type: application/json" \
-d '{"external_id": "cust_1042", "name": "Smith Plumbing", "owner_email": "owner@yourclient.com", "owner_invite": "link"}'external_id is required: it is your id for that customer — the id in your own database is ideal. name is the client’s business name and owner_email is the person who will own those books; send both (without a name the books take your own business name, and without an email nobody is invited until you set one). There are optional branding fields too; leave them out and the client’s books wear your own logo and colour.
A retry is always safe. Your external_id stays tied to that client for as long as the client exists, so if your call times out or your server crashes halfway, send the same call again — minutes or days later — and you get the same client back, never a second set of books. Save the exact body you sent, and retry with that same body. A retry with any field changed answers 422 idempotency_mismatch, and its details.tenant_id is the client that external_id already names. The answers:
201— created. Itsidis the client’s id: store it next to the customer in your own system, because every later call about that client uses it.200, with the headerIdempotent-Replayed: true— thatexternal_idalready named a client, so nothing was created and no seat was used. The answer is that client as it stands now.422idempotency_mismatch— thatexternal_idalready names a client created with a different body. Use a newexternal_idfor a new customer; reuse one only to retry the identical call.details.tenant_idnames the client it belongs to.409tenant_removed— thatexternal_idnames a client you removed. Within 24 hours, restore it in your console or by API (POST /v1/tenants/{id}/restore), or use a newexternal_id; after 24 hours, e-mail support — we restore it, with its team’s access, within the retention window (90 days from the removal). Its books stay exportable for those 90 days (GET /v1/tenants/{id}/export, below).details.tenant_idnames it. (A client deleted for good frees its id.)409external_id_bound— thatexternal_idis set on a client you made in the console (see below). The client is yours already:details.tenant_idnames it, so read it withGET /v1/tenants/{id}instead of creating it.
A client you made in the console. It has no external_id, because the console never asks for one. Set yours on it once with PUT /v1/tenants/{id}/external-id and the body { "external_id": "your-id" } — the answer is the client. After that the id is frozen: sending the same id again changes nothing, a different one answers 409 external_id_already_set, and an id another of your clients already carries answers 409 external_id_taken, with that client in details.tenant_id. From then on, a create that sends that external_id answers 409 external_id_bound naming the client. In the package: tenants.bind(id, externalId).
When you sent owner_email, the answer also carries owner.status: invited (the owner was given a way in, whether or not that address already had a login with us), invalid (not an email address — nobody was invited) or error (the owner could not be set up yet; we retry it by ourselves). Whatever the status, the client’s books exist and hold a seat — read where it stands, below.
Pick how the owner gets in: owner_invite
"email"— the default. We email the owner at once, under your brand. Pick this when the person who pays is not sitting in front of your app — you add clients from your back office, or one person buys for another."link"— you send the buyer in yourself, with the link below. We hold the email, so they are not sent two competing ways in, and send it only if the client is still not claimed 15 minutes after you created it. Pick this when the buyer has just paid inside your app and is still on your page.
Register your return address, once
In your console, open Set up → Advanced: API & automation → Return addresses, and add the address of your own site the buyer should come back to — just the start of it, like https://app.yourcompany.com, with nothing after the domain. It must be https://, and it is matched exactly: scheme, domain and port. You can hold up to 10. Only a person signed in to your console can add one — never a key — so a leaked key can never send your buyers somewhere else.
Send the buyer in: get a link, then redirect
curl -X POST $BOOKS_BASE_URL/v1/tenants/CLIENT_ID/onboarding-links \
-H "Authorization: Bearer $BOOKS_HOST_KEY" \
-H "Content-Type: application/json" \
-d '{"return_url": "https://app.yourcompany.com/welcome"}'The answer is 201 with url and expires_at. Answer the buyer’s browser with a redirect to url, right then. The link works once and lasts 10 minutes, and asking again makes a fresh one and ends every older one — so never store it, and when you need one, ask for a new one. return_url is optional: any address on your site that starts with a return address you registered; without it the buyer ends in their books. Any other address is refused 400 return_url_not_allowed.
Redirect only. Never email the link, never paste it in a chat or a text message, never show it on a page. Whoever opens it first is walked toward that client’s books, so it belongs in one place only: your redirect, in the browser of the buyer who just paid.
The link is refused, and nothing is made, when:
409tenant_claimed— the owner already reached the books. Send them to your sign-in page instead.409owner_missing— the client has no owner login yet. Set the owner’s email (below); an address already on file is retried by itself.409tenant_removed— you removed the client. Within 24 hours, restore it in your console or by API (POST /v1/tenants/{id}/restore), or create the client again with a newexternal_id; after 24 hours, e-mail support — we restore it, with its team’s access, within the retention window (90 days from the removal). Its books stay exportable for those 90 days (GET /v1/tenants/{id}/export, below).403tenant_suspended— the client is paused; reinstate it first.429onboarding_link_rate_limited— more than 10 links for that client in the last hour. Wait theRetry-Afterseconds (also indetails.retry_after).
What the buyer sees
A page on your address, in your brand, showing the client’s name, the owner’s email and one button. Opening the page uses nothing up — a link scanner, a chat preview or a double tap cannot spend it; only the button does. Then:
- A new owner gets a one-time 6-digit code at that email address, types it in, chooses a password, and lands on your
return_url— or in their books, without one. - An owner who already has a login with us is sent to your branded sign-in page instead, signs in the way they always do, and then lands on your
return_url. The new books are on their login. - A link that is no longer good — used, out of date, or replaced by a newer one — shows a page in your brand with two ways forward: Email me a new link (it goes to the owner’s address on file and lasts 7 days) and Already set up? Sign in.
Know when they are in: read the client
curl $BOOKS_BASE_URL/v1/tenants/CLIENT_ID \ -H "Authorization: Bearer $BOOKS_HOST_KEY"
The answer is the client — id, name, status, your external_id — and its onboarding block: state, owner_email, invited_at, link_issued_at and claimed_at. onboarding.state is one of:
owner_pending— the books exist, but nobody can sign in to them yet: we are still setting up the owner (we retry by ourselves), or you sent no owner email — set one.invited— the owner can get in, and nobody has used a link yet.started— the owner is part-way: a link was used or a password was chosen, but they have not reached the books yet.claimed— the owner reached the books with nothing left to do. The only state that means done.
Coming back is not the same as done. A buyer reaching your return_url only means they came back to your site. Mark the client set up only when its onboarding.state reads claimed. There is no webhook: read the client when the buyer lands on your return address, then every few seconds for a while, and now and then after that.
Correcting an owner’s email
Typed the address wrong, or the buyer wants a different one? Until the client is claimed you can replace it. Every link already sent stops working, and the new owner is emailed. Sending the same address again changes nothing. Once the client is claimed it is refused 409 tenant_claimed — the owner manages their own login from then on. In the console it is Correct the owner’s email on the client’s row (Add the owner’s email when none is on file); by API:
curl -X PUT $BOOKS_BASE_URL/v1/tenants/CLIENT_ID/owner \
-H "Authorization: Bearer $BOOKS_HOST_KEY" \
-H "Content-Type: application/json" \
-d '{"owner_email": "right@yourclient.com"}'The answer is the client, exactly as reading it answers.
When something goes wrong
Each step catches what the one before it missed. Work down the list:
- Check the status page first: https://theguybooks-status.betteruptime.com says whether books, sign-in or the API are degraded on our side — a 503 rate_limiter_unavailable or a 500 internal_error names it too.
- Your create call failed or timed out: send the same call again, with the same external_id. A 200 means the first one had worked.
- The buyer did not finish, or the link ran out: ask for a new link and redirect them again. Each new link ends the older ones.
- The buyer opened an old link: the page offers “Email me a new link” and “Already set up? Sign in”, in your brand.
- The buyer never came back: with owner_invite "email" the owner was emailed at create; with "link" we email them ourselves if the client is still not claimed 15 minutes after you created it.
- The owner lost the email or forgot their password: “Forgot password” on your branded sign-in page works for them, even before they ever chose one.
- Your console: each client’s row on the home page has “Resend invite” and “Correct the owner’s email” — or “Add the owner’s email” when none is on file (by API, the correction is PUT /v1/tenants/{tenantId}/owner, above).
- A paused client gets no new link and no resent invite (tenant_suspended): reinstate it first, then try again.
Still stuck? Write to support@theguys.app with the client’s id — and check https://theguybooks-status.betteruptime.com for anything degraded on our side.
The limits
- Links: 10 per client in any hour. Ask for one only when the buyer is about to be redirected.
- Emails to one client’s owner: one every 60 seconds and 5 in any 24 hours, whoever asks for them — your create call, your console, the buyer’s Email me a new link, or our own follow-up. So a stuck retry loop can never bury anyone’s inbox.
- Return addresses: up to 10 per account.
- Requests: each key may make 120 calls every 60 seconds, and each IP address 240. On top of that, your whole account may make 300 calls every 60 seconds, shared by all of your keys — two keys never buy two budgets. The
RateLimit-Limit,RateLimit-RemainingandRateLimit-Resetheaders on each answer report the per-key budget. Over any budget, the answer is429rate_limited— wait theRetry-Afterseconds and send the same call again.
With our TypeScript package
Everything above, with typed answers, typed refusals and safe retries built in: npm install @theguysapp/books-host-sdk.
The package is ESM-only: load it with import, never require. It needs Node.js 20.19 or newer (>=20.19).
import { BooksHost } from '@theguysapp/books-host-sdk';
const books = new BooksHost({ baseUrl: process.env.BOOKS_BASE_URL!, key: process.env.BOOKS_HOST_KEY! });
// When the buyer has paid — safe to run again for the same customer:
const { tenant } = await books.tenants.create({
external_id: customer.id,
name: customer.businessName,
owner_email: customer.email,
owner_invite: 'link',
});
await saveClientId(customer.id, tenant.id);
const link = await books.tenants.createOnboardingLink(tenant.id, {
return_url: 'https://app.yourcompany.com/welcome',
});
return Response.redirect(link.url, 303); // the buyer's browser, right now
// Later, in a background job — never inside the buyer's request:
const result = await books.tenants.waitForClaimed(tenant.id);
if (result.claimed) await markSetUp(customer.id);A client made with new BooksHost({ baseUrl, key }) has these calls:
books.items—availablebooks.mappings—propose,get,listbooks.usage—post,voidbooks.purchases—post,voidbooks.posts—list,followbooks.tenants—create,list,listAll,get,createOnboardingLink,updateOwner,bind,waitForClaimed,remove,restore,suspend,reinstate,exportbooks.hosts—usage,subscription- Straight on the client —
books.spec(),books.request(),books.rateLimit
Any route the spec at https://theguybooks.com/v1/openapi.json documents that has no method of its own is sent with books.request(method, path, body?) — the same retries, refusals and rate-limit handling as every call above. For example, await books.request('PATCH', '/v1/tenants/CLIENT_ID/config', { company_name: 'Smith Plumbing' }) changes a client’s business name.
The package’s books-host-sdk conformance --mode onboarding --base-url … --owner-email … --return-url … certifies your create → link → landing road on a throwaway client it removes at the end. It needs the full key from step 4, and it does not certify started or claimed — both need your buyer’s button press. Run it against production, where the landing page is public: on the sandbox the links open on our login-walled preview, so its landing check stops at that wall.
What your client’s owner receives
From the console, or with owner_invite left as "email", a new owner gets an email carrying your name and logo that says their books are ready, with a button that opens the same branded page your redirect would — good for 7 days. Someone who already has a login with us gets a short note instead, saying the new books were added to their login, with a link to your sign-in page. Both come from our address, noreply@theguybooks.com, with your business name as the sender’s name. The pages open on your own domain once it is active (step 3); until then they open on our address with your slug on the end, ?host=YOUR-SLUG.
Seats
Each client uses one seat from the moment its books are created; a retry that answers 200 uses none. When every seat is taken, nothing is created: the console holds the invite and offers you another block, and the API answers 402 with the error seat_limit_reached. A paused client still holds its seat; a removed one frees it. Your software can check first with GET /v1/hosts/me/usage, which answers seats_used and seat_limit.
Removing a client
The console’s Remove and Undo buttons have API twins. DELETE /v1/tenants/{id} removes a client: its sign-in stops at once, its seat frees at once, its books are kept under the retention policy, and you have 24 hours to undo it. POST /v1/tenants/{id}/restore is that undo. It answers 200 with the client; 409 undo_expired after 24 hours; and 402 seat_limit_reached when every seat is taken — add a block, then restore again.
Both need the full key from step 4; a narrow automation key is refused 403 insufficient_scope. Removing a removed client, or restoring one that is not removed, answers 200 and changes nothing — so a retry is safe.
After 24 hours the undo is gone, but the client is not: e-mail support@theguys.app and we restore it within the retention window — status back to active, its team’s old logins re-seated, and the owner told by e-mail unless you run your own sign-in door (then you tell them). A restore needs a free seat.
Exporting a client’s books
GET /v1/tenants/{id}/export answers ONE JSON document with the client’s complete books: the client and every table of its books — journal entries with their lines, chart of accounts, customers, vendors, items with quantities on hand, classes, invoices, bills, payments, the receipts list without the files, bank transactions, rules, CRM, mileage, reports and settings. Never our internal logs, secrets or receipt files.
It works while the client is active, paused, or removed for 90 days after the removal; after that it answers 409 export_window_closed. It needs the full key from step 4.
It streams and can be tens of megabytes: read it to the end, and trust it only when the LAST key is complete: true — a document that stops early carries an error key instead. A paused or removed client’s owner cannot export from inside the books, so this is how you pull their books for them. In the package: tenants.export(id).
Since schema_version 2 the document also carries team — who had access to the books: each person’s user id, role, e-mail and the date they were given access, and whether the row is live or the snapshot we took when the client was removed — and the client’s saved reports, as custom_reports and management_reports. Receipt images are the one thing it does not include.
Finding your client ids later
In the console: Set up → Advanced: API & automation → Your client ids, with a copy button beside each client. By API: GET /v1/tenants lists every client with its id, name, status — active, suspended (paused) or removed — your external_id and its whole onboarding block, the same one GET /v1/tenants/{id} answers. To find one client in one call, filter it: ?owner_email= finds a client you made in the console by its owner’s address, and ?external_id= finds one your software made.
A long list comes in pages. Leave ?limit= off and you get the whole list, as before, up to 1,000 clients. Send ?limit= (1 to 100) and you get that many. Either way the answer ends with next_cursor: when it is not null there are more clients, so send it back as ?cursor= to get the next ones. The filters work with the pages. In the package, tenants.listAll() walks every page for you and hands back one list.
Optional — books at a path on a site you already run
Skip this unless you specifically want the books at something like yourcompany.com/books rather than on a domain of their own. This is the only setup that touches your code, and it is four lines: answer that route with a 302 redirect — a “moved, go here instead” answer — to your white-label address.
// app/books/page.tsx — ONLY for putting the books at a path on a site you
// already run. A dedicated domain needs no code at all.
import { redirect } from 'next/navigation';
export default function Books() {
redirect('https://theguybooks.com/login?host=YOUR-SLUG');
// once your own domain is live, point this at it instead:
// redirect('https://books.yourcompany.com');
}The stack does not matter; the rule is the same everywhere. There is still nothing to gate and nothing to install — we own the login and the books either way.
The rest of your console
A few more places worth knowing about once you are signed in. None of them is needed to get live — your login link works without them — but inventory connectivity has to be on before anyone can mint an inventory key (below).
Branding. The Branding page in the sidebar is where your logo and accent colour live. Upload a PNG, JPEG or WebP up to 2 MB — SVG is not supported. Your name and logo then appear on the sign-in page and in the top bar of every client’s books, and new client books pick them up automatically. Your business name is shown there but locked; email us to change it.
Automation keys (white-label seat-block plans). Under Set up → Advanced: API & automation you can mint narrow keys — up to 5 live at a time — that can do exactly two things: pause a client and reinstate one. They cannot read books, create clients or change settings. Use one of those in your own billing system instead of the full key from step 4. Removing and restoring a client needs the full key. Each is shown once when you mint it, and you can revoke any of them from the same page.
Every key, and what it reaches. A key has one scope, chosen when you mint it:
| Scope | Name in your console | What it reaches | Most live at a time |
|---|---|---|---|
full | Workspace key | every call the API has, the inventory calls included | 2 |
tenant-lifecycle | Automation key | pause and reinstate a client, nothing else | 5 |
inventory | Inventory key | the 7 inventory calls, nothing else | 2 |
There is also an embed integration, for showing the books inside a page you already run in an iframe. It is not written up here — email support@theguys.app and we will send you the notes.
Inventory sync. If your own app tracks the materials a job uses or the stock you buy, it can send that straight into each client’s books with an inventory key. The inventory sync guide is written for your developer.
Turn inventory connectivity on first
One step comes before any code, and only an owner of your host account can take it: turning inventory connectivity on in your host console. Until it is on, no inventory key can be minted. The console shows no inventory key tools, and the server refuses the mint itself, so there is no way around it. It is free, and it is done once for your whole account.
- Who: an owner of your host account. A developer seat sees the same place without the button: “Your software can send stock movements straight into each client’s books. It’s off for now — an owner turns it on here, and then inventory keys unlock in this section.”
- Where: your host console, Set up → Advanced: API & automation, the card titled “Inventory connectivity”.
- Tick the box “I accept the Terms of Service, including section 2.4 (Host API), version terms-of-service-2026-10-18”.
- Press “Turn it on”. It stays off until the box is ticked.
What you see after: the card gives way to the Inventory keys section, with one line under its heading — “Inventory connectivity is on since <the day>. Your install guide was sent to <your email> on <the day>.” — and a “Send it again” button. If the email has not gone out yet, the line ends “Your install guide has not been sent yet.” instead.
The install email: pressing “Turn it on” emails an install checklist to the owner who pressed it — a link to the inventory sync guide, the sandbox offer, the conformance check, the list each client’s owner works through in their books, and where to mint the key and find your client ids. It never carries a key. “Send it again” re-sends it, to whoever presses that button.
Who can have it: hosts on the white-label seat-block plan, the plan whose console has Set up → Advanced: API & automation. For any other plan the server refuses it: “Inventory connectivity is part of the white-label plan.”
Once it is on, the Inventory keys section mints inventory keys — up to 2 live at a time, each shown once.
Your team
More than one person can sign in to your console, each with their own login. There are two roles:
Owner — Runs the business side: billing, clients, inviting people, the brand.
Developer — Runs the technical side: API keys, return addresses, your domain; can see the client list but not change it.
Invite someone from the Team page in your console: enter their email address, pick the role, and press Send invite. They get an email with a link that works for 7 days.
Invite your developer instead of sharing a key. A developer creates the API keys and registers the return addresses themselves, so no key is ever handed from one person to another.
Keep at least two owners, so your team can always get in if one person loses access.
One owner is the primary contact: billing emails go to that address. Any owner can make another owner the primary contact from the Team page.
Check that it worked
Three checks, about two minutes, in a browser.
- Open your branded sign-in link in a private window. Your login appears with your name, your logo and your colour — your brand, not ours. The web address stays ours until your own domain is live.
- Sign in. Your books open, with your brand in the top bar.
- Click the Sign in / Books link on your own website. It lands on that same branded login.
If you connected your own domain, check one more thing: its status tag on the Set up page reads active, not verifying. Still verifying after an hour or two is nearly always a DNS record that did not save, or saved with the domain name typed on the end twice — open the record at your registrar and compare it to the one we show you, character for character.
Stuck on any step? Reply to your welcome email — that thread is your support channel — or write to support@theguys.app. A person reads every one. We answer within one business day.
