SharePoint Access API
A data-firewalled surface in front of Microsoft Graph that exposes SharePoint Online sites, document libraries, lists, list items, sensitivity labels, and Microsoft Search to AI agents and automation tooling. Every call is gated by a PortEden access token with three-layer enforcement: operation flag, allow/block rules, and field masking.
https://cliv1b.porteden.com/api/access/sharepoint
60 req/min · 300 req/hr per IP
What it covers
- SharePoint Online (Microsoft 365 tenants)
- OneDrive-for-Business document libraries (treated as ordinary SharePoint drives — same endpoints, same DTO shapes)
- Both delegated (per-user OAuth) and
Sites.Selected(app-only, enterprise) connection modes — endpoints are identical from the caller's perspective
Key properties
- Token-gated. Every call requires a PortEden access token (
pe_…) that has explicitly opted into SharePoint access viasharePointAccessEnabled = true. - Opaque, prefixed IDs. All resource IDs ship with a
sharepoint:…prefix. Treat them as opaque strings — never split, parse, or rebuild them client-side. They round-trip verbatim. - DTO reuse from Drive. File-level responses use the same TypeScript shapes as the Drive surface. Only the
idprefix andproviderfield differ. - Three-layer enforcement. Operation bitflag → allow-all/block-all default → explicit allow/block rules.
- Field masking. Responses are post-filtered to drop any field the token did not whitelist via
visibleSharePointFields.
Quick Reference
| Endpoint | Method | Operation | Description |
|---|---|---|---|
| /files | GET | list_files / search_files | Search and list files |
| /files/{fileId} | GET | get_file_metadata | Get file metadata |
| /files/{fileId}/download | GET | download_file | Get short-lived view / download URLs |
| /files/{fileId}/permissions | GET | get_file_metadata | Get sharing permissions |
| /files/upload | POST | upload_file | Upload a new file (≤ 100 MB) |
| /folders | POST | create_folder | Create a folder |
| /files/{fileId}/rename | PATCH | rename_file | Rename a file or folder |
| /files/{fileId}/move | PATCH | move_file | Move a file/folder |
| /files/{fileId} | DELETE | delete_file | Recycle-bin a file/folder |
| /files/{fileId}/share | POST | share_file | Share with user/group/domain |
| /sites | GET | read_sites | List sites |
| /sites/{siteId} | GET | read_sites | Get a single site |
| /sites/{siteId}/lists | GET | read_lists | List lists in a site |
| /sites/{siteId}/lists/{listId} | GET | read_lists | Get a list (with columns) |
| /sites/{siteId}/lists/{listId}/items | GET | read_list_item | List items in a list |
| /sites/{siteId}/lists/{listId}/items/{itemId} | GET | read_list_item | Get a list item |
| /sites/{siteId}/lists/{listId}/items | POST | write_list_item | Create a list item |
| /sites/{siteId}/lists/{listId}/items/{itemId} | PATCH | write_list_item | Update a list item |
| /sites/{siteId}/lists/{listId}/items/{itemId} | DELETE | delete_list_item | Delete a list item |
| /sensitivity-labels | GET | read_sensitivity_labels | List the tenant's label catalogue |
| /files/{fileId}/sensitivity-label | GET | read_sensitivity_labels | Get the label assigned to an item |
| /search | GET | search_content | KQL search across files, list items, lists, sites |
Authentication
All endpoints require a PortEden Access Token via Bearer authentication:
Authorization: Bearer pe_k1_abc123def456... Required token state for any endpoint to return data:
sharePointAccessEnabled === true- The token's operator must have at least one connected SharePoint provider on a resource the token is linked to
allowedSharePointOperationsmust include the operation flag the endpoint demands- The targeted resource must not be blocked by a SharePoint rule attached to the token
Internal vs external base URL
https://mgtv1b.porteden.com for internal management tooling. External integrations (CLI, MCP) should always use cliv1b.porteden.com.Resource ID format
All SharePoint IDs are prefixed and composite. They are opaque to the caller — pass them through verbatim wherever the API asks for an ID.
| Resource | Encoded form | Example |
|---|---|---|
| File / folder | sharepoint:drive:{driveId}:item:{itemId} | sharepoint:drive:b!abc…:item:01ABCDEF… |
| Site | sharepoint:site:{hostname},{spsiteGuid},{spwebGuid} | sharepoint:site:contoso.sharepoint.com,1111…,2222… |
| List | sharepoint:site:{siteId}:list:{listGuid} | sharepoint:site:…:list:33333333-… |
| List item | sharepoint:site:{siteId}:list:{listGuid}:item:{itemId} | sharepoint:site:…:list:…:item:42 |
| Sensitivity label | bare GUID (no prefix) | b8a3a3e4-… |
Don't parse the ID
provider on returned DTOs is always the literal string "SHAREPOINT". Anything starting with something other than sharepoint: on a SharePoint endpoint is a bug — fail closed.Permissions Model
Each request is gated by three layers in order: the operation bitflag → the token's allow-all / block-all default → explicit allow/block rules. Responses are then post-filtered through the field mask.
Layer 1: SharePoint access enabled
The master gate. When sharePointAccessEnabled is false, every SharePoint endpoint returns:
HTTP/1.1 422 Unprocessable Entity { "error": "SHAREPOINT_NOT_ENABLED", "message": "SharePoint access is not enabled for this access token. Enable it at https://my.porteden.com." } Layer 2: Allowed operations
allowedSharePointOperations is a bitflag set serialised as a string array. Each endpoint requires one specific flag — calls without the required flag return 422 OPERATION_NOT_ALLOWED.
Files
| API string | Description |
|---|---|
| list_files | Browse files inside a folder/library |
| search_files | Free-text + filter search across the connected SharePoint surface |
| get_file_metadata | Read a single file's metadata or its sharing permissions |
| download_file | Get short-lived view / download URLs |
| upload_file | Upload a new file (binary body, ≤ 100 MB) |
| create_folder | Create a folder under a library or another folder |
| rename_file | Rename a file or folder |
| move_file | Move a file/folder to a new parent |
| delete_file | Recycle-bin a file/folder (not permanent) |
| share_file | Add a sharing permission |
| update_permissions | Modify or remove an existing sharing permission |
Sites & Lists
| API string | Description |
|---|---|
| read_sites | List and fetch SharePoint sites |
| read_lists | List and fetch SharePoint lists (and their column schema) |
| read_list_item | Read a list item's fields |
| write_list_item | Create or update list items |
| delete_list_item | Delete a list item |
Labels & Search
| API string | Description |
|---|---|
| read_sensitivity_labels | Enumerate the tenant's sensitivity-label catalogue and the label assigned to a specific item |
| search_content | Run a Microsoft Search KQL query across driveItem, listItem, list, site |
Composite Flags
| API string | Equivalent flags |
|---|---|
| read_only | list_files + search_files + get_file_metadata + download_file + read_sites + read_lists + read_list_item + read_sensitivity_labels + search_content |
| write_file | upload_file + create_folder |
| edit_file | rename_file + move_file |
| write_list | write_list_item + delete_list_item |
| all | every flag above |
Note
read_only.Layer 3: Allow-all vs block-all
| sharePointAllowAll | Default behaviour | Rules behave as |
|---|---|---|
| false (default) | All resources blocked | allow list — explicit allow rules unlock specific resources |
| true | All resources allowed | block list — explicit block rules deny specific resources |
Block rules always win. If the token is in block-all mode and performs an upload / create_folder / create_list_item, the backend auto-creates a matching allow rule for the new resource so the token can read it back.
SharePoint rule types
Rules attach to the token (managed via /api/access-tokens/{tokenId}/sharepoint-rules). Each rule has a ruleType, a raw pattern, and an action (allow | block). Matching is performed against the raw identifier — prefixed forms in this documentation are decoded server-side automatically.
| ruleType | Pattern is | Matches |
|---|---|---|
| file_id | a single file/folder ID | exact item match |
| folder | a folder ID | every descendant of the folder |
| drive_id | a drive ID | every item inside that drive (document library) |
| site_id | a composite site ID | everything under the site (drives, lists, items) |
| list_id | a list's GUID | every list item in the list |
| list_item_id | a list item ID | exact item match |
| mime_type | a MIME string with optional * | files matching the MIME type |
| sensitivity_label_id | a label GUID | items carrying the label |
| content_type_id | a SharePoint content-type ID | list items of that content type |
404 vs BLOCKED
404 returned to the caller may be a real "missing resource" or it may be a policy-driven block — when the response code is BLOCKED, treat it as a policy decision, not a missing-resource.Visible fields (post-response field mask)
visibleSharePointFields is the post-response field-mask. Any field the token does not whitelist will be omitted from the JSON payload (or returned as null), regardless of whether the underlying user has permission to see it.
| API string | Applies to | Controls |
|---|---|---|
| name | files, list items, sites, lists | display name |
| mime_type | files | mimeType |
| size | files | byte size |
| created_time | all | createdDateTime / createdTime |
| modified_time | all | lastModifiedDateTime / modifiedTime |
| owners | files | owners[] |
| shared_with | files | sharedWith[] |
| description | files, sites, lists | description |
| web_view_link | all | webUrl / webViewLink |
| download_link | files | downloadLink and the downloadUrl returned by /files/{fileId}/download |
| parent_folder | files | parentFolderId, parentFolderName |
| sensitivity_label | files, list items | embedded label info on driveItem responses + the /sensitivity-label endpoint |
| site_name | list items, lists | site display name on list-scoped responses |
| list_name | list items | parent list's display name |
| content_type | list items | contentType |
| field_values | list items | the fields dictionary — turn off to hide row contents |
| all | — | shorthand for everything above |
field_values is high-blast-radius
all.Endpoint Sections
SharePoint Error Codes
All error bodies are JSON { "error": "<code>", "message": "<human-readable>" } unless noted.
| HTTP | error code | When it fires | Recommended caller behaviour |
|---|---|---|---|
| 401 | — | Missing / malformed Authorization header, or revoked / expired token | re-auth |
| 422 | SHAREPOINT_NOT_ENABLED | sharePointAccessEnabled = false | guide user to enable SharePoint on the token |
| 422 | NO_SHAREPOINT_PROVIDER | Operator has no SharePoint connection on any of the token's resources | guide user to connect SharePoint |
| 422 | OPERATION_NOT_ALLOWED | Operation flag missing from allowedSharePointOperations | name the missing flag in the message |
| 400 | INVALID_ID | Empty or oversize siteId / listId / fileId / itemId | likely caller bug — log and abort |
| 400 | INVALID_QUERY | Search q is empty/whitespace | prompt for a query |
| 403 | READ_ONLY | Connection has only read scopes | guide user to reconnect with write scopes |
| 403 | NO_PROVIDER | No SharePoint connection or all connections excluded by token rules | same as NO_SHAREPOINT_PROVIDER |
| 404 | BLOCKED | A rule (block action, or block-all default with no allow match) matched | surface as policy denial, not 'not found' |
| 404 | — | Real Graph 404 (item missing) | surface as missing-resource |
| 429 | — | Rate limit exceeded (PortEden or Microsoft Graph upstream) | back off using Retry-After |
| 5xx | — | Graph upstream error or unexpected exception | retry with exponential backoff |
Render accessInfo verbatim
accessInfo string returned in successful responses (and on some 4xx bodies) is already user-formatted with https://my.porteden.com deep links. Render it as plain text — do not template over it.Rate Limits
Two IP-bucketed limits cover the SharePoint surface, applied on top of any per-token quota:
| Scope | Limit |
|---|---|
| Per IP, rolling 60 s | 60 requests |
| Per IP, rolling 1 h | 300 requests |
Standard rate-limit headers are emitted on every response:
X-RateLimit-Limit: 60 X-RateLimit-Remaining: 41 X-RateLimit-Reset: 1714330080 Retry-After: 7 # only on 429 Microsoft Graph throttling is also possible upstream — when Graph returns 429, PortEden propagates the same status with the Retry-After header forwarded. Treat 429 as "back off and retry," never as a permanent failure.
Data Types Reference
All TypeScript types below are emitted as camelCase JSON. Optional fields may be absent or null.
Reused from Drive (/api/access/drive)
File-level responses share these shapes with the Drive surface — only the id prefix and provider string differ.
interface DriveFileDto { id: string; // "sharepoint:drive:…:item:…" name?: string; mimeType?: string; size?: number; // bytes createdTime?: string; // ISO 8601 modifiedTime?: string; // ISO 8601 owners?: DriveUserDto[]; sharedWith?: DriveUserDto[]; description?: string; webViewLink?: string; downloadLink?: string; parentFolderId?: string; parentFolderName?: string; labels?: Record<string, string>; isFolder: boolean; provider: string; // always "SHAREPOINT" on this surface } interface DriveUserDto { email: string; displayName?: string; role?: string; // "owner" | "writer" | "reader" | "commenter" } interface DrivePermissionDto { id: string; type: string; // "user" | "group" | "domain" | "anyone" role: string; // "owner" | "writer" | "reader" | "commenter" emailAddress?: string; domain?: string; displayName?: string; } interface DriveFileListResponse { files: DriveFileDto[]; nextPageToken?: string; hasMore: boolean; accessInfo?: string; authWarnings?: string[]; } interface DriveFileResponse { file?: DriveFileDto; accessInfo?: string; } interface DriveFileLinkResponse { success: boolean; webViewLink?: string; downloadUrl?: string; exportLinks?: Record<string, string>; // always null/absent for SharePoint fileName?: string; mimeType?: string; size?: number; isGoogleWorkspaceFile: boolean; // always false for SharePoint errorMessage?: string; errorCode?: string; } interface DrivePermissionsResponse { permissions: DrivePermissionDto[]; accessInfo?: string; } interface DriveOperationResult { success: boolean; fileId?: string; // populated for create/upload responses errorMessage?: string; errorCode?: string; // "READ_ONLY" | "NO_PROVIDER" | "BLOCKED" | "OPERATION_NOT_ALLOWED" | … } Reused request bodies
interface CreateDriveFolderRequest { name: string; parentFolderId?: string; description?: string; } interface RenameDriveFileRequest { newName: string; } interface MoveDriveFileRequest { destinationFolderId: string; } interface ShareDriveFileRequest { type: "user" | "group" | "domain" | "anyone"; role: "reader" | "writer" | "commenter"; emailAddress?: string; domain?: string; sendNotification?: boolean; // default true message?: string; } SharePoint-specific
interface SharePointSiteDto { id: string; // "sharepoint:site:hostname,siteGuid,webGuid" displayName?: string; name?: string; description?: string; webUrl?: string; createdDateTime?: string; // ISO 8601 lastModifiedDateTime?: string; isPersonalSite: boolean; } interface SharePointSiteListResponse { sites: SharePointSiteDto[]; nextPageToken?: string; hasMore: boolean; accessInfo?: string; } interface SharePointListDto { id: string; // "sharepoint:site:…:list:…" displayName?: string; name?: string; description?: string; webUrl?: string; template?: string; // "genericList" | "documentLibrary" | "tasks" | "events" | … hidden: boolean; createdDateTime?: string; lastModifiedDateTime?: string; columns?: SharePointColumnDto[]; // populated on GET /lists/{listId}, omitted on listing } interface SharePointColumnDto { name?: string; // API name — use in $filter / selectFields displayName?: string; // human label columnType?: string; // "text" | "number" | "choice" | "dateTime" | "personOrGroup" | "lookup" | … hidden: boolean; readOnly: boolean; required: boolean; indexed: boolean; } interface SharePointListListResponse { lists: SharePointListDto[]; nextPageToken?: string; hasMore: boolean; accessInfo?: string; } interface SharePointListItemDto { id: string; // "sharepoint:site:…:list:…:item:…" name?: string; webUrl?: string; contentType?: string; createdDateTime?: string; lastModifiedDateTime?: string; fields?: Record<string, unknown>; // null when the token has field_values masked off createdBy?: string; lastModifiedBy?: string; } interface SharePointListItemListResponse { items: SharePointListItemDto[]; nextPageToken?: string; hasMore: boolean; accessInfo?: string; } interface CreateListItemRequest { fields: Record<string, unknown>; // keyed by column API name } interface UpdateListItemRequest { fields: Record<string, unknown>; // only included keys are updated } interface SensitivityLabelDto { id: string; // bare GUID, no prefix displayName?: string; description?: string; color?: string; // "Green" | "Yellow" | "Orange" | "Red" | … sensitivity: number; // priority/severity, higher = more sensitive toolTip?: string; isActive: boolean; isAppliable: boolean; } interface SensitivityLabelListResponse { labels: SensitivityLabelDto[]; accessInfo?: string; } interface ItemSensitivityLabelResponse { success: boolean; labelId?: string; labelDisplayName?: string; assignmentMethod?: "standard" | "privileged" | "auto"; assignedDateTime?: string; errorMessage?: string; } interface SharePointSearchHitDto { entityType: "driveItem" | "listItem" | "list" | "site"; id: string; // already prefixed title?: string; summary?: string; webUrl?: string; lastModifiedDateTime?: string; } interface SharePointSearchResponse { hits: SharePointSearchHitDto[]; from: number; total: number; // unfiltered Graph hit count moreResultsAvailable: boolean; accessInfo?: string; } MCP Tool Authoring Notes
For teams mapping these endpoints onto Claude / ChatGPT tools, a few hard rules:
- Always treat IDs as opaque. Round-trip the prefixed string the API returns. Do not try to construct one from raw Graph IDs — the
:separators look composable but the format is reserved. - Don't ask the model for
selectFieldsblindly. When the model doesn't know the schema, fetch the list once (GET /lists/{listId}) and offer the column API names as enum values in the tool schema. - Render
accessInfoverbatim in the tool's natural-language response. It contains the canonical "go tohttps://my.porteden.com" guidance the user expects. - Surface
BLOCKEDexplicitly. Don't collapse it into a 404 in the tool's UX — users need to know when an item exists but is policy-blocked, vs. when it genuinely doesn't exist. - Cache the sensitivity-label catalogue per session. It rarely changes and the endpoint is cheap, but every call still consumes a tenant-wide quota.
- Re-fetch download URLs every time. They expire in roughly an hour and are unauthenticated — caching one in chat memory is a confidentiality footgun.
- Page list-item queries. Default
limit=50is usually fine; for bulk export tools, raise to 200 (the cap) and followpageToken. - KQL is the search language, not Lucene and not raw SQL. Quote-wrap queries with spaces (
"quarterly review") and use field selectors (Title:"Q1",FileType:docx) for precision.