# Graph Surfaces

Microsoft Graph endpoints used by each tool, with the response shape and the fields selected.

## outlook-mail-list

**Endpoint (Inbox):** `GET /me/mailFolders/Inbox/messages?$top={N}&$orderby=receivedDateTime DESC&$select=id,subject,from,receivedDateTime,bodyPreview,isRead`

**Endpoint (other folder):** `GET /me/mailFolders/{folderId-or-displayName}/messages?...`

**Returned shape:**
```typescript
Array<{
  id: string;
  subject: string | null;
  from: string | null;          // emailAddress.address
  receivedDateTime: string;     // ISO 8601
  bodyPreview: string;          // truncated to 1024 chars
  isRead: boolean;
}>
```

## outlook-mail-search

**Endpoint:** `GET /me/messages?$search="{query}"&$top={N}&$select=id,subject,from,receivedDateTime,bodyPreview,isRead`

**Required header:** `ConsistencyLevel: eventual` (Graph requirement for `$search`).

**Returned shape:** identical to `outlook-mail-list`.

## outlook-calendar-list

**Endpoint:** `GET /me/calendarView?startDateTime={now}&endDateTime={now+rangeDays}&$top={N}&$orderby=start/dateTime ASC&$select=id,subject,start,end,location,attendees,isAllDay,organizer`

**Returned shape:**
```typescript
Array<{
  id: string;
  subject: string | null;
  start: { dateTime: string; timeZone: string };
  end:   { dateTime: string; timeZone: string };
  location: string | null;
  attendees: Array<{ email: string; name: string | null; response: string }>;
  isAllDay: boolean;
  organizer: string | null;
}>
```

## outlook-calendar-event

**Endpoint:** `GET /me/events/{eventId}?$select=id,subject,start,end,location,attendees,isAllDay,organizer,bodyPreview,body,webLink`

**Returned shape:** as `outlook-calendar-list` plus:
```typescript
{
  bodyPreview: string;       // 1024 chars
  body: string;              // up to 16384 chars (HTML or text per content type)
  webLink: string | null;
}
```

## outlook-contacts-list

**Endpoint:** `GET /me/contacts?$top={N}&$select=id,displayName,emailAddresses,businessPhones,homePhones,mobilePhone,jobTitle,companyName`

**Returned shape:**
```typescript
Array<{
  id: string;
  displayName: string | null;
  emailAddresses: string[];   // .address values flattened
  phones: string[];           // business + home + mobile concatenated
  jobTitle: string | null;
  companyName: string | null;
}>
```

## outlook-mailbox-info

**Endpoint:** `GET /me/mailFolders?$top=50&$select=id` (best-effort — folder count is null if Graph is unreachable)

**Returned shape:**
```typescript
{
  registered: boolean;
  graphUserId: string | null;
  scopes: string[];
  tokenExpSec: number | null;
  refreshTokenExpSec: number | null;
  tokenWithinRefreshWindow: boolean;
  folderCount: number | null;
}
```

## outlook-account-register

No Graph endpoint of its own. The flow exchanges an authorization code at:

- `POST https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token`

After the token exchange, the tool fetches `GET /me?$select=id,mail,userPrincipalName` to record `graphUserId`.

## Error classification

All tool failures route through `lib/graph-client.ts:classifyGraphError`. Decisions key off **status code + Graph `error.code` field + headers**, never the error `message` body.

| HTTP status | Graph `error.code` | Class | Action |
|---|---|---|---|
| 401 | (any) | `auth` | Refresh once, retry; if still 401 → terminal `auth-required` |
| 429 | (any) — header `Retry-After` present | `rate-limit-recoverable` | Backoff per header, retry once |
| 429 | (any) — header `Retry-After` absent | `rate-limit-terminal` | Terminal abort |
| 503 | `MailboxNotEnabledForRESTAPI` | `on-prem` | Terminal: route operator to IMAP plugin |
| 5xx | (any) | `5xx` | Retry once with 500 ms; then terminal |
| any | (any) | `other` | Re-throw |
