Endpoint
Call tools act for one Lawtte Studio receptionist. An account that connected
before it had a receptionist (for example, a firm signing up from Claude) is
signed in;
place_call and check_call answer with a tool error naming
set_up_calling until it has run, and the read tools report no calls yet. After
set_up_calling, the call tools act for the account’s trial receptionist, and
after activation for the activated one, without reconnecting.Discovery
The server uses lazy authentication.initialize, tools/list and any public tool
work without a token. Calling a tool that needs an account without a token returns
401 with the OAuth 2.0 Protected Resource Metadata (RFC 9728) pointer in
WWW-Authenticate:
401 on every method, so a client knows to refresh.
That document names the authorization server:
OAuth 2.1
The authorization server publishes standard metadata at/.well-known/oauth-authorization-server and supports:
Because registration is dynamic, a client needs no pre-issued credentials. The flow:
1
Register
POST to the registration_endpoint from the authorization server metadata.
You get a client_id back.2
Authorize
Send the user to the
authorization_endpoint with PKCE. Request
offline_access if you want a refresh token.3
The user picks a receptionist
Lawtte shows its own consent screen: the user signs in, chooses one Studio
calling agent, and approves the call tools. That choice is stored as a grant
against your
client_id and their user — it’s what the server later resolves a
token to.4
Exchange and call
Swap the code at the
token_endpoint and send the access token as
Authorization: Bearer <token> on every MCP request.One grant is one receptionist. A token never spans two Studio accounts, and a user
who wants to switch re-approves. Grants are revocable at any time from
Settings → AI connectors, and revocation takes effect immediately.
Connector-key authentication
For clients that can’t do OAuth — a localmcp-remote setup, a script, CI — the
server also accepts a Lawtte connector key as a bearer token:
calls:read, or calls:write (which includes read).
Receptionist-setup tools need the receptionist:* scopes, which only an OAuth
grant has, so a key gets 403 insufficient_scope for them.
Tools
The call tools and the receptionist setup tools are described in plain language in connector tools. Theinitialize response carries instructions: a short description of Lawtte’s two
kinds of agents (the inbound receptionist and outbound calls) and which tools belong
to each. See inbound and outbound agents. The
schemas, for a client that needs them:
get_calling_setup_status carries readOnlyHint: true. set_up_calling carries
readOnlyHint: false, destructiveHint: false and idempotentHint: true: running it
again updates the settings and never provisions a second number. On an account
without a card it starts a trial — one dedicated calling number, TRIAL_CALLS calls
(3 by default), each capped at the shorter of TRIAL_CALL_MAX_MINUTES (5 by default)
and the calling agent’s own 3-minute limit. Trial accounts need a confirmed email,
get one trial per firm email domain (per address on personal mailboxes), and lose an
unactivated number after TRIAL_NUMBER_DAYS (14 by default). place_call,
check_call and get_call include trial_calls_remaining for a trial account.
Trial calls aren’t metered or billed. Activation on the Lawtte activation page
upgrades the same receptionist and keeps its number.
place_call carries destructiveHint: true and openWorldHint: true; the read
tools carry readOnlyHint: true. A client that surfaces those hints should require
confirmation before place_call — it dials a real person.
name, phone, consent_confirmed and represented_by_counsel are always
required. A call either brings its own objective and recipient_type, or runs a
saved outbound agent with agent_id (plus fields for its placeholders) — not
both: objective with agent_id is an error, and fields without it is too. Both
tools are strict: an unknown
argument is an error, not ignored. place_call and check_call share one schema and
one rule set, so check_call returning allowed: true means place_call with the
same arguments, at the same moment, would dial.
Every call opens with a fixed line the server builds from your firm’s name and your
receptionist’s name: “Hi, this is , a virtual assistant calling on behalf of
. This call is recorded.” It is
sent as the call’s first message and nothing in objective or context can change
or remove it.
Results come back as a text content block holding JSON. Trailing whitespace and key
order aren’t guaranteed; parse it, don’t pattern-match it.
Scopes
Don’t put these scopes in your authorization request — the authorization server
only accepts OIDC scopes and rejects the request. Ask for none of them (or only
openid / offline_access) and the consent screen offers every scope; the user
approves and Lawtte records the grant on its side.
Call a tool without the scope for it and you get 403 with a step-up challenge:
scope (the consent screen then offers
every scope), then retry once — don’t loop.
Clients that don’t use scopes keep full access. A connection that asked for
none of our scopes — including every connector authorized before scopes existed —
is treated as holding all of them. A client that never requested a scope can’t step
up to one, so refusing it would break it for no security gain. Connector keys
created before key permissions existed likewise carry every scope; newer keys
carry only the permission chosen when they were created.
Errors
Business-rule refusals are not HTTP errors. A call blocked by consent, a
represented party, the recipient type, the Do Not Call list, the daily cap, the
two-hour repeat rule, calling hours, an account that isn’t set up for calling (the
text names
set_up_calling) or a trial with no calls left (the text names
create_activation_link) comes back from place_call as a tool error
(isError: true) whose text names the rule that failed and what would change the
answer — surface it to the user. check_call returns the same text as
{ "allowed": false, "reason": "…" }. Bad
input gets the same treatment, naming the field to fix. See
limits and safety.
Connecting a client
MCP Inspector
MCP Inspector
Point it at
https://www.lawtte.ai/api/mcp with an Authorization: Bearer <key>
header, or let it run the OAuth flow. Good for confirming the tool list and
running check_call before you write any code.mcp-remote (stdio clients)
mcp-remote (stdio clients)
For clients that only speak stdio:The header file holds one line:
Authorization: Bearer lawtte_sk_…Your own client
Your own client
Any MCP SDK works. Connect over streamable HTTP to the server URL, attach the
bearer token, and call
tools/list to confirm you see place_call,
check_call, get_call, get_transcript, list_calls and the receptionist
setup tools. The list is the same with or
without a token; the token decides which calls succeed.Before you go to production
Run
check_call first with the exact arguments you’ll send to place_call. It
performs every check — consent, represented party, recipient type, call content,
number validation, Do Not Call, quota, calling hours — and dials, reserves and
records nothing, so you can prove your integration end to end without calling
anyone.
