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.

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.

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.

Open your host console

Or by API, if you prefer

Same job, from your own server, using your management key (step 4):

curl -X POST https://theguybooks.com/v1/hosts/me/domains \
  -H "Authorization: Bearer $THEGUYBOOKS_API_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, for example:

THEGUYBOOKS_API_KEY=…

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.

  1. Create the client with your own customer id — safe to send again.
  2. Register the address of your own site the buyer comes back to — once, in your console.
  3. Ask for a one-use link and redirect the buyer’s browser to it.
  4. The buyer proves their email, chooses a password and comes back to you.
  5. Read the client until it says claimed — that, and only that, means done.

Create the client, with your own customer id

curl -X POST https://theguybooks.com/v1/tenants \
  -H "Authorization: Bearer $THEGUYBOOKS_API_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. Build the body from your own records, so a retry sends exactly the same thing. The answers:

  • 201 — created. Its id is 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 header Idempotent-Replayed: true — that external_id already named a client, so nothing was created and no seat was used. The answer is that client as it stands now.
  • 422 idempotency_mismatch — that external_id already names a client created with a different body. Use a new external_id for a new customer; reuse one only to retry the identical call. details.tenant_id names the client it belongs to.
  • 409 tenant_removed — that external_id names a client you removed. Restore it in your console, or use a new external_id. details.tenant_id names it. (A client deleted for good frees its id.)

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 https://theguybooks.com/v1/tenants/CLIENT_ID/onboarding-links \
  -H "Authorization: Bearer $THEGUYBOOKS_API_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:

  • 409 tenant_claimed — the owner already reached the books. Send them to your sign-in page instead.
  • 409 owner_missing — the client has no owner login yet. Set the owner’s email (below); an address already on file is retried by itself.
  • 409 tenant_removed — you removed the client. 403 tenant_suspended — the client is paused; reinstate it first.
  • 429 onboarding_link_rate_limited — more than 10 links for that client in the last hour. Wait the Retry-After seconds (also in details.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 https://theguybooks.com/v1/tenants/CLIENT_ID \
  -H "Authorization: Bearer $THEGUYBOOKS_API_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 https://theguybooks.com/v1/tenants/CLIENT_ID/owner \
  -H "Authorization: Bearer $THEGUYBOOKS_API_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:

  1. 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.
  2. 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.
  3. The buyer opened an old link: the page offers “Email me a new link” and “Already set up? Sign in”, in your brand.
  4. 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.
  5. 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.
  6. 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).
  7. 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.

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.
  • Every call also counts against your account’s request budget: over it, the answer is 429 rate_limited — wait the Retry-After seconds 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.

import { BooksHost } from '@theguysapp/books-host-sdk';

const books = new BooksHost({ baseUrl: 'https://theguybooks.com', key: process.env.THEGUYBOOKS_API_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);

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.

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 onboarding.state.

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

Two more places worth knowing about once you are signed in. Neither is needed to get live, so come back to them when you are ready.

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. Each is shown once when you mint it, and you can revoke any of them from the same page.

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.

Check that it worked

Three checks, about two minutes, in a browser.

  1. Open your address in a private window. YOUR branded login appears — your name, your logo, your colour. It never says “TheGuyBooks”.
  2. Sign in. Your books open, with your brand in the top bar.
  3. 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.