Tools
Call the Gmail, PDF, and webhook tools over the REST API
Every tool in the MCP surface is also a POST endpoint - same
pure function, same input and output schemas, no behavioral drift between
transports. Requires the services:execute scope.
POST /api/v1/services/{tool_name}user_id is filled in from the authenticated principal, so never send it in the
body.
Tools that change state (gmail_compose, gmail_send, gmail_update_draft,
gmail_archive_thread, the pdf_* and webhook_* writers, and the rest of the
mutating set) require an Idempotency-Key header. The response is replayed
on retries with the same key, and the same key with a different payload is a
422. See Idempotency.
The full request and response schema for every tool is in the generated OpenAPI
document at /openapi.json. The examples below cover the common paths.
gmail_status
Check whether an account is linked.
curl -X POST https://api.daysurface.com/api/v1/services/gmail_status \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{}'Response:
{
"connected": true,
"email": "[email protected]",
"scopes": ["https://www.googleapis.com/auth/gmail.modify"],
"granted_at": "2025-01-15T10:30:00+00:00"
}gmail_curate_inbox
Rank recent inbox threads by a deterministic score.
curl -X POST https://api.daysurface.com/api/v1/services/gmail_curate_inbox \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"limit": 10}'Request body:
| Field | Type | Default | Description |
|---|---|---|---|
query | string | (none) | Gmail search query to narrow the set |
limit | integer | 10 | Threads to rank (1-100) |
The MCP transport renders this as an inbox dashboard; over REST you get the same ranked payload as JSON.
gmail_list_inbox
List recent messages, optionally filtered by a Gmail search query.
curl -X POST https://api.daysurface.com/api/v1/services/gmail_list_inbox \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"query": "from:[email protected] is:unread", "limit": 25}'Request body:
| Field | Type | Default | Description |
|---|---|---|---|
query | string | (none) | Gmail search query |
limit | integer | 25 | Messages to return (1-500) |
gmail_get_thread
Fetch a thread with full message bodies.
curl -X POST https://api.daysurface.com/api/v1/services/gmail_get_thread \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"thread_id": "18f0c...", "strip_quoted_replies": true}'Request body:
| Field | Type | Default | Description |
|---|---|---|---|
thread_id | string | (required) | Thread to fetch |
include_attachment_data | boolean | false | Inline raw attachment bytes |
strip_quoted_replies | boolean | false | Drop quoted history from replies |
Attachments always carry filename, mime_type, size_bytes, and
attachment_id; fetch the bytes on demand with gmail_get_attachment.
gmail_compose
Create a draft. Mutating - send an Idempotency-Key.
curl -X POST https://api.daysurface.com/api/v1/services/gmail_compose \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: 0f1c2b3a-..." \
-H "Content-Type: application/json" \
-d '{
"to": "[email protected]",
"subject": "Contract - Thursday works",
"body": "Thursday at 2pm suits us. Sending an agenda ahead of time."
}'Request body:
| Field | Type | Default | Description |
|---|---|---|---|
to | string | (required) | Comma-separated recipients |
subject | string | (required) | Subject line |
body | string | (required) | Message body |
cc / bcc | string | (none) | Comma-separated address lists |
in_reply_to_thread_id | string | (none) | Thread to attach the draft to |
attachments | array | [] | { filename, mime_type, data_base64 } |
Response: the draft's saved state - draft_id, thread_id, recipients,
subject, a body preview, and the attachment list.
gmail_send
Send a draft that already exists. Mutating - send an Idempotency-Key.
curl -X POST https://api.daysurface.com/api/v1/services/gmail_send \
-H "Authorization: Bearer sk_live_..." \
-H "Idempotency-Key: 7d2e9f01-..." \
-H "Content-Type: application/json" \
-d '{"draft_id": "r-482..."}'Request body:
| Field | Type | Description |
|---|---|---|
draft_id | string | The draft to send |
There is no "compose and send in one call" endpoint. Sending always references a draft, so the content exists and is reviewable before it goes out.
Operational tools
The server also exposes its own maintenance services over the same route shape:
| Tool | Description |
|---|---|
config_show | Show the full configuration |
config_get | Get a single value by dot-separated key |
config_set | Write a config override (mutating) |
doctor | Run environment health checks |
doctor_fix | Run health checks and auto-fix what it can (mutating) |
curl -X POST https://api.daysurface.com/api/v1/services/config_get \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"key": "llm_config.cache_enabled"}'{ "key": "llm_config.cache_enabled", "value": true }