Content-Type: text/calendar; method=REQUEST
Your agent can take meetings.
It accepts invites and sends real calendar invitations, by email. Invites from Google Calendar, Outlook, Apple Calendar, Calendly or Zoom become events your agent can read and answer, and the invitations it sends land in people’s calendars. There is no calendar account to connect.
- METHOD:REPLY
- Answers, invitations, changes and cancellations are standard calendar email, so Google Calendar, Outlook and Apple Calendar show them as usual.
- organizer_verified
- Checked with DMARC, so your agent knows whether an invite really comes from its organizer.
- messages:send
- Every answer and invitation is an email from your agent’s inbox, with the same limits and checks as any other send.
BEGIN:VCALENDAR
Three steps, all by email.
Each step is one API call. The examples use the TypeScript and Python SDKs, with the plain HTTP call for the answer; every field is in the API reference and the whole flow in the Meetings guide.
calendar.invite.received
An invite arrives
Someone invites your agent’s address from Google Calendar, Outlook, Apple Calendar, Calendly or Zoom. Once the email is screened, the invite becomes an event: title, time, organizer, guests, and the join link for Zoom, Meet or Teams.
agent.tsimport { Agentboxd } from 'agentboxd'; const mr = new Agentboxd(); // reads AGENTBOXD_API_KEY // Invites waiting for an answer, soonest first. const { data: invites } = await mr.calendar.list(inboxId, { role: 'attendee', response: 'needs_action' }); for (const e of invites) { console.log(e.title, e.start, e.organizer.email, e.organizer_verified); }from agentboxd import Agentboxd mr = Agentboxd() # reads AGENTBOXD_API_KEY # Invites waiting for an answer, soonest first. invites = mr.calendar.list(inbox_id, role="attendee", response="needs_action")["data"] for e in invites: print(e["title"], e["start"], e["organizer"]["email"], e["organizer_verified"]){ "id": "9d7b5f3e-1c0a-4e8d-b6f4-2a0c8e6d4b13", "role": "attendee", "status": "confirmed", "response": "needs_action", "title": "Pricing review", "start": "2026-11-12T15:00:00.000Z", "end": "2026-11-12T15:30:00.000Z", "conference": { "provider": "google_meet", "url": "https://meet.google.com/abc-defg-hij" }, "organizer": { "email": "sam@example.com", "name": "Sam" }, "organizer_verified": true, "attendees": [ { "email": "sam@example.com", "partstat": "accepted" }, { "email": "support-bot@homingbox.net", "partstat": "needs_action" } ], "screening": { "state": "screened", "reason": null, "released_at": null } }POST /v1/calendar/events/:id/respond
Your agent answers
Accept, decline, say maybe or propose another time. The answer goes to the organizer only, never to Reply-To or the other guests, and their calendar updates as usual. Or save it as a draft that a person approves first.
agent.tsimport type { CalendarEvent } from 'agentboxd'; async function answer(e: CalendarEvent) { // Your code decides, not the model: a verified organizer, an invite that isn't held. if (!e.organizer_verified || e.withheld) return 'ask a person'; await mr.calendar.respond(e.id, { response: 'accepted', comment: 'See you then.' }); // Another time instead: { response: 'tentative', propose: { start, end } } // A person approves first: add draft: true, and it waits in Approvals. return 'accepted'; }def answer(e: dict) -> str: # Your code decides, not the model: a verified organizer, an invite that isn't held. if not e["organizer_verified"] or e.get("withheld"): return "ask a person" mr.calendar.respond(e["id"], "accepted", comment="See you then.") # Another time instead: "tentative", propose={"start": ..., "end": ...} # A person approves first: add draft=True, and it waits in Approvals. return "accepted"curl -s -X POST https://api.agentboxd.com/v1/calendar/events/$EVENT_ID/respond \ -H "Authorization: Bearer $AGENTBOXD_API_KEY" \ -H "Content-Type: application/json" \ -d '{"response":"accepted","comment":"See you then."}'POST /v1/inboxes/:id/calendar/events
It sends its own
Your agent sends real invitations, with a video link if you like, and changes or cancels them later. Guests answer from their own calendar, and each answer comes back as calendar.response.received.
agent.tsconst { event } = await mr.calendar.create(inboxId, { title: 'Onboarding call', start: '2026-11-18T15:00:00Z', duration_minutes: 30, time_zone: 'Europe/Paris', // how the email words the time attendees: [{ email: 'dana@example.com', name: 'Dana Lee' }], conference_url: 'https://meet.google.com/abc-defg-hij', }); // The invitation is on its way. Later: mr.calendar.update(event.id, { … }) to change it, // mr.calendar.cancel(event.id) to call it off.event = mr.calendar.create( inbox_id, title="Onboarding call", start="2026-11-18T15:00:00Z", duration_minutes=30, time_zone="Europe/Paris", # how the email words the time attendees=[{"email": "dana@example.com", "name": "Dana Lee"}], conference_url="https://meet.google.com/abc-defg-hij", )["event"] # The invitation is on its way. Later: mr.calendar.update(event["id"], ...) to change it, # mr.calendar.cancel(event["id"]) to call it off.
npx @agentboxd/mcp
Or just ask.
In Claude, Cursor or any MCP client, your agent handles meetings in plain words: the MCP server finds the invite and answers it.
Tools: list_events, get_event, respond_to_invite, create_meeting, update_meeting, cancel_meeting. The four that send can save a draft for a person to approve instead (draft: true). Set up the MCP server.
You: Accept the invite from Sam for Thursday.
Claude: list_events → “Pricing review”, Thu 12 Nov, 15:00–15:30 UTC,
from sam@example.com (organizer verified)
respond_to_invite → accepted
Done. Sam gets the answer; nobody else does.organizer_verified: false
An invite is mail from a stranger.
Anyone can send your agent an invite, and everything in it is text they chose. Meetings treats it that way.
A held invite, as an API key reads it: when and from whom, never what the sender wrote.
{
"title": "[held: injection_risk]",
"description": null,
"location": null,
"conference": null,
"start": "2026-11-12T15:00:00.000Z",
"end": "2026-11-12T15:30:00.000Z",
"organizer": { "email": "it-desk@lookalike.example", "name": null },
"organizer_verified": false,
"withheld": { "state": "held", "reason": "injection_risk" }
}- SCREENED
Invite text is screened first
The title, description, location and links of an invite are checked for prompt injection like the rest of the email, before your agent sees the event.
- HELD
A held invite shows no content
If the check holds the invite, your agent sees only its times, the addresses and why it is held, and can’t answer until a person releases it. On for new workspaces; older ones turn it on in Settings.
- DMARC ✓
The organizer is verified, or not
organizer_verifiedsays whether an authenticated sender vouches for the organizer: the organizer, their own company domain, or a calendar service such as Google Calendar or Calendly. An unverified message can’t change or cancel a verified meeting. - ORGANIZER ONLY
Answers go to the organizer only
Never to Reply-To, never to the other guests. A forged reply address can’t turn your agent’s answer into mail to someone else.
- NO AUTO-REPLY
Nothing is sent because mail arrived
Every answer and invitation is a call your code makes. An invite on its own never makes your agent’s inbox write to anyone.
- draft: true
A person can approve first
With
draft: true, an answer, invitation, change or cancellation waits in Approvals as a draft until a person sends it, and the meeting changes only then. A key withdrafts:writebut notmessages:sendcan propose meetings, never send them. - messages:send
The same rules as any send
Answers and invitations go through your send limits, block lists and suppressions, and count toward your plan’s emails. A key needs
calendar:writeandmessages:sendto send them.
Questions
Questions
Does my agent need a Google or Microsoft account?
No. Meetings work over email: invites arrive in your agent’s inbox and answers go out from it, as standard calendar messages. Nothing connects to anyone’s calendar account.
Will people see a normal invitation?
Yes. The email carries a standard calendar invitation and an invite.ics file, so Google Calendar, Outlook and Apple Calendar show it with their usual buttons to answer.
Can my agent see when I’m free, or let people pick a time?
Not yet. Today it knows the meetings its inbox was invited to and the ones it sent. Free times, “pick a time” emails, booking pages, a calendar feed and reminders are coming next. Roadmap.
What does it cost?
Meetings has no separate price. Each answer, invitation, change or cancellation is an email from your agent’s inbox and counts like any other send. Pricing.
What about recurring meetings and time zones?
A series arrives as one event with its rule, and your agent can send one too. To see each date, list events with expand=occurrences (two years at most per list). Times are in UTC with the invite’s time zone alongside. A series your agent sends with a time zone keeps its local time when the clocks change.
RSVP=TRUE
Put your agent on the invite.
Create an inbox, invite it to a meeting, and answer from your code.