Works with Muse — connect Earl to your Muse and ask about your business from there.Connect →

Connect to Earl

Earl has a small, plain API for outside assistants: ask him anything in words, or read a business's calendar, jobs, customers, and invoices as records. One key per assistant, made by the business owner, acting as them.

Base URLhttps://api.earl.solutions/v1
OpenAPIhttps://api.earl.solutions/v1/openapi.json
AuthAuthorization: Bearer earl_sk_…
Rate60 requests a minute per key; 30 asks a minute

How to speak for Earl

Earl is a business's office manager, not a database. When your assistant relays him, keep it in character: say what Earl reports, in his words, and never invent a number, a date, or a name that didn't come back from a call. Anything that touches money or a customer (sending an invoice, texting someone, approving what's waiting) happens on the owner's say-so: confirm with them before you call it, and when a reply comes back waiting_for_owner, tell them Earl has it drafted and is waiting for their OK in the app. Earl answers in the language he's written to, English or Spanish, so pass the owner's words through as they wrote them. Employees never see rates, pay, invoices, or quotes; a key acts as the owner, so what you show is for the owner's eyes.

Connect Earl (OAuth)

The clean way in: a "Connect Earl" button. Standard OAuth 2.0 authorization code, with PKCE for public clients. Register your app with us (earlsoftwarellc@gmail.com) and you get a client id, and a secret if you can keep one.

1. Send the owner to  https://api.earl.solutions/oauth/authorize
     ?response_type=code&client_id=…&redirect_uri=…&state=…&scope=business
     &code_challenge=<S256 of your verifier>&code_challenge_method=S256
   They sign in to Earl, pick the business, and press Allow.
2. We send them back to your redirect_uri with ?code=…&state=…
3. POST https://api.earl.solutions/oauth/token   (form-encoded)
     grant_type=authorization_code&code=…&redirect_uri=…&client_id=…
     &code_verifier=…            (or client_secret=… for a confidential client)
   → { "access_token": "earl_sk_…", "token_type": "Bearer", "scope": "business" }
4. Use it: Authorization: Bearer earl_sk_…   on every /v1 call.
   It doesn't expire; the owner revokes it under Office → Connections, or you POST https://api.earl.solutions/oauth/revoke with token=…

One token is one business. An owner with several businesses connects each one separately. Codes last five minutes and work once.

Getting a key by hand

The business owner opens Earl, goes to Office → Connections → API keys, names the key after the assistant, and copies it. It is shown once. The key acts as that owner on that one business, so it can do what they can do in the app. They can revoke it on the same screen at any time.

Access requirements. A business on Earl with an active plan or trial. The key holder is the business owner. Nothing about the plan changes what the key can do; a business over its monthly usage allowance gets a 402 until the period resets.

Ask Earl

The one call that does most things. Say it the way the owner would. Earl books, quotes, logs hours, moves stock, files things, and answers questions. He confirms what he did, and when he can't verify something he says so instead of guessing. Anything that can't be undone waits for the owner's OK in the app, and the reply says so.

POST https://api.earl.solutions/v1/ask
Authorization: Bearer earl_sk_…
Content-Type: application/json

{ "message": "Book Mike and Danny on the Miller kitchen Tuesday through Thursday, 7 to 3" }

→ { "runId": "…", "conversationId": "…", "status": "done",
    "reply": "Booked: Mike and Danny, Miller kitchen, Tue 9/23 to Thu 9/25, 7:00 to 3:00." }

The call waits up to 90 seconds for the reply. Pass "wait": false to get the run back at once and poll GET /v1/runs/{id}. Pass the conversationId from a reply on the next ask to keep the thread, so "make it Friday too" means what it should.

Read the records

Plain JSON, for when the assistant wants to show something rather than ask. All GET, all with the same key.

GET /v1/me                       the business, its plan, its bots, today's date
GET /v1/today                    who is where today, and what waits for the owner
GET /v1/calendar?from=&to=       calendar entries (default: the next two weeks)
GET /v1/jobs?status=open&q=      jobs
GET /v1/customers?q=             customers
GET /v1/invoices?unpaid=1&q=     invoices, with balances
GET /v1/brief                    the whole day in one call: calendar, crew free, to-do with Earl's proposals,
                                 what waits for the owner, unpaid invoices, what the crew is owed, yesterday
GET /v1/search?q=                find anything across customers, jobs, invoices, quotes, calendar, crew
GET /v1/approvals                what Earl is waiting on the owner for

Dates are YYYY-MM-DD in the business's own time zone. Money is in dollars. Every list is capped at 200 rows; use q to narrow.

Do things

Named actions with plain schemas, for assistants that pick tools by description. Each one becomes a clear instruction to Earl, so his checks and the owner's limits still apply: anything over a limit comes back waiting_for_owner instead of happening.

POST /v1/actions/book       { job, date, customer?, address?, start?, end?, crew?[], notes? }
POST /v1/actions/text       { to, text }            a crew member or customer, by name or number
POST /v1/actions/call       { to, name?, about? }   Earl rings the owner, then dials from the business line
POST /v1/actions/invoice    { customer, lines[{description, amount}], job?, due?, send? }
POST /v1/actions/remember   { text }                a fact Earl keeps and uses from then on
POST /v1/approvals/{id}/decide  { decision: "approve" | "reject", note? }

Texting and calling need the business line or texting to be on for the business (Office → Phone & text). Every action replies like /v1/ask: a status, Earl's one-line report, and the run id.

Hear from Earl

Register a webhook and Earl posts when something happens, so the assistant can start the conversation instead of only answering.

POST /v1/webhooks   { url, events?: ["approval.waiting", "booking.requested", "call.taken", "invoice.paid", "approval.decided"] }
→ { id, secret }   the secret is shown once

Each delivery: POST { id, type, businessId, createdAt, data }
  X-Earl-Event: booking.requested
  X-Earl-Signature: sha256=HMAC-SHA256(secret, raw body)
Retried with backoff for about four hours; twenty straight failures switch the webhook off.
POST /v1/webhooks/{id}/test sends a test.ping.

Errors

401  no key, or a revoked one          { "error": "…", "code": "api_key_invalid" }
402  plan lapsed or usage allowance hit  { "error": "…", "code": "payment_required" | "usage_cap" }
429  over the rate limit
503  the bots are offline; try again in a minute

What a key can't do

Make or revoke keys, change billing, close the business, or sign in to the app. Those stay with the owner, behind a passkey or a fresh code. Every ask and every change a key causes shows on the business's activity feed with the key's name, so the owner always sees what an assistant did.

Questions, or a connector you'd like to build: earlsoftwarellc@gmail.com. The rules for using the service are in the terms; what we do with data is in the privacy policy.