Skip to content

Latest commit

 

History

History
290 lines (203 loc) · 11 KB

File metadata and controls

290 lines (203 loc) · 11 KB

API reference — publishing HTML to SharePoint / OneDrive

Two interchangeable APIs write the .html file. They share the same driveItem shape; only the base URL and the token audience differ.

Base URL Token audience Typical use
Microsoft Graph (primary) https://graph.microsoft.com/v1.0 https://graph.microsoft.com Any agent with a Graph delegated token.
SharePoint Vroom (alternative) https://{tenant}-my.sharepoint.com/_api/v2.0 (OneDrive) or https://{tenant}.sharepoint.com/{site}/_api/v2.0 (site) https://{tenant}.sharepoint.com Agents already onboarded with a SharePoint OAuth token (SPFx, add-in, on-behalf-of), or targeting a specific SharePoint host/site directly.

Everything below shows Graph paths; for Vroom, swap the base URL and use a SharePoint-audience token — the path suffixes (/me/drive/root:/…:/content, /drives/{id}/items/{id}/content, /createUploadSession) are identical.

graph.microsoft.com routes each request to the SPO farm that holds the caller's OneDrive/site based on the token — including dogfood rings such as *.sharepoint-df.com — so Graph "just works" without choosing a host. Only truly separate national clouds (GCC High, DoD, 21Vianet/China) use different Graph/SharePoint endpoints.


Authentication

All requests are authenticated with an OAuth 2.0 bearer token from Azure AD (Microsoft identity platform, v2.0 endpoint). Requests are made as the signed-in user (delegated).

Authorization: Bearer <access-token>

Scopes (delegated)

Scope Grants Use when
Files.ReadWrite Read/write the user's own OneDrive Publishing to the user's OneDrive (default).
Files.ReadWrite.All Read/write all files the user can access Publishing to a shared library the user can write.
Sites.ReadWrite.All Read/write items in site collections the user can access Publishing into a SharePoint site library.
offline_access Refresh tokens Long-running agents that must refresh without re-prompting.

For Vroom / SharePoint-audience tokens the equivalent delegated scopes are MyFiles.Write (own OneDrive) and AllSites.Write (sites the user can write).

Least privilege: prefer Files.ReadWrite (own OneDrive) unless the user explicitly wants to publish into a shared library/site.

Acquiring a token (do NOT hardcode or invent tokens)

Use MSAL with the user's identity. Register (or reuse) an Azure AD app with the delegated scope(s) above and a redirect URI.

Interactive / device-code (CLI agents):

POST https://login.microsoftonline.com/common/oauth2/v2.0/devicecode
  client_id=<app-id>&scope=Files.ReadWrite offline_access
# → show the returned user_code + verification_uri to the user, then poll:
POST https://login.microsoftonline.com/common/oauth2/v2.0/token
  grant_type=urn:ietf:params:oauth:grant-type:device_code
  &client_id=<app-id>&device_code=<device_code>
# → { "access_token": "...", "refresh_token": "...", "expires_in": 3600 }

Authorization-code (apps with a redirect URI): standard code → token exchange at the same /token endpoint with grant_type=authorization_code.

Refresh: grant_type=refresh_token at the /token endpoint (requires offline_access).

Vroom / SharePoint token: request a token whose resource/audience is the SharePoint host — scope https://{tenant}.sharepoint.com/.default (or AllSites.Write, MyFiles.Write). SPFx/add-in agents typically already hold this via on-behalf-of.

If you don't already have a valid token, run an MSAL flow and prompt the user to sign in. Never fabricate a token, and never print the token onto the published page.


Upload / replace file content (simple upload, ≤ 4 MB)

The simplest publish. Body is the raw HTML bytes; Content-Type: text/html.

Create a new file (by path)

PUT https://graph.microsoft.com/v1.0/me/drive/root:/Agent HTML/<name>.html:/content
Authorization: Bearer <token>
Content-Type: text/html

<!DOCTYPE html> … your document …

Other parents:

PUT /me/drive/items/{parent-id}:/{filename}:/content
PUT /drives/{drive-id}/items/{parent-id}:/{filename}:/content
PUT /sites/{site-id}/drive/items/{parent-id}:/{filename}:/content
PUT /users/{user-id}/drive/items/{parent-id}:/{filename}:/content

Path encoding: in root:/<path>: addressing, percent-encode spaces and special characters in each path segment (e.g. Agent%20HTML, and a name like weird%20%5E!%40.html). To avoid encoding entirely, address by ids instead: PUT /drives/{driveId}/items/{parentId}:/{name}:/content.

Replace an existing file (by item id)

PUT https://graph.microsoft.com/v1.0/me/drive/items/{item-id}/content
Authorization: Bearer <token>
Content-Type: text/html

<!DOCTYPE html> … updated document …

Also: PUT /drives/{drive-id}/items/{item-id}/content, PUT /sites/{site-id}/drive/items/{item-id}/content.

Permissions

Delegated: Files.ReadWrite, Files.ReadWrite.All, or Sites.ReadWrite.All.

Response — driveItem

201 Created (new) or 200 OK (replaced):

{
  "id": "01ABCDEF2345",
  "name": "sales-dashboard.html",
  "size": 84213,
  "webUrl": "https://contoso-my.sharepoint.com/personal/user_contoso_com/Documents/Agent%20HTML/sales-dashboard.html",
  "file": { "mimeType": "text/html", "hashes": { "quickXorHash": "" } },
  "createdDateTime": "2026-07-18T20:15:00Z",
  "lastModifiedDateTime": "2026-07-18T20:15:00Z",
  "parentReference": { "driveId": "b!…", "id": "01ABC…", "path": "/drive/root:/Agent HTML" }
}

webUrl is the link — hand the user <webUrl>?web=1. The ?web=1 opens it in the OneUp HTML viewer; the bare webUrl downloads the .html file. It is stable across content updates.

Conflict behavior

Append ?@microsoft.graph.conflictBehavior=rename|replace|fail to control what happens if the name exists (default on PUT …:/content is replace). Use rename to avoid overwriting.


Large files (upload session)

For content > 4 MB, create an upload session and PUT byte ranges.

POST https://graph.microsoft.com/v1.0/me/drive/root:/Agent HTML/<name>.html:/createUploadSession
Authorization: Bearer <token>
Content-Type: application/json

{ "item": { "@microsoft.graph.conflictBehavior": "replace", "name": "<name>.html" } }

Response:

{ "uploadUrl": "https://…/…", "expirationDateTime": "2026-07-18T20:45:00Z" }

Then PUT the bytes to uploadUrl in ordered ranges (recommended 320 KiB multiples), each with a Content-Range header, e.g. Content-Range: bytes 0-26214399/128000000. The final range returns the completed driveItem (with webUrl). Inlined HTML is almost always < 4 MB, so the simple upload above is normally sufficient.


Update metadata (rename / move)

To rename or move without replacing content, PATCH the item (do not use …/content):

PATCH https://graph.microsoft.com/v1.0/me/drive/items/{item-id}
Authorization: Bearer <token>
Content-Type: application/json

{ "name": "renamed.html", "parentReference": { "id": "{new-parent-id}" } }

Returns the updated driveItem.


Vroom (SharePoint _api/v2.0)

Identical calls, SharePoint base URL, SharePoint-audience token:

PUT https://{tenant}-my.sharepoint.com/_api/v2.0/me/drive/root:/Agent HTML/<name>.html:/content
Authorization: Bearer <sharepoint-token>
Content-Type: text/html

<!DOCTYPE html> … your document …

Site library drive:

PUT https://{tenant}.sharepoint.com/sites/{site}/_api/v2.0/drives/{driveId}/items/{itemId}/content

Response is the same driveItem (read webUrl). Upload sessions (/createUploadSession) and metadata PATCH work the same way against the Vroom base.


Publish by folder URL (Shares API) — fewest calls

When you have the destination folder's URL (a OneDrive/SharePoint folder link), you can resolve it straight to a driveItem and upload into it — without knowing the drive/site/library id or enumerating libraries. This is the fewest-calls path and works uniformly for the user's OneDrive, any SharePoint site, and any (including non-default) document library.

1. Encode the folder URL as a sharing token (u! + unpadded base64url): base64-encode the URL, strip trailing =, replace /_ and +-, then prepend u!. The URL must be a valid URI, so percent-encode spaces (%20).

2. Resolve the folder (1 call) — read id and parentReference.driveId:

GET https://graph.microsoft.com/v1.0/shares/{u!token}/driveItem?$select=id,parentReference,webUrl
Authorization: Bearer {token}

3. Upload into the folder (1 call):

PUT https://graph.microsoft.com/v1.0/drives/{driveId}/items/{folderId}:/<name>.html:/content?@microsoft.graph.conflictBehavior=replace
Authorization: Bearer {token}
Content-Type: text/html

<!DOCTYPE html> … your document …

Returns the new file's driveItem; read webUrl and give the user <webUrl>?web=1.

Vroom equivalent: same paths under {host}/_api/v2.0 (e.g. GET https://{tenant}.sharepoint.com/_api/v2.0/shares/{u!token}/driveItem) with a SharePoint-audience token. Permissions: Files.ReadWrite (own drive) or Sites.ReadWrite.All (sites). See shares_get for the full Shares API. A ready-made example is scripts/publish-to-folder.sh.


Reading a file's content (when designing against it)

To inspect a file while authoring (e.g. to learn a CSV's columns before building a dashboard), download it as the signed-in user:

GET /drives/{driveId}/items/{itemId}/content

This returns a 302 redirect to a short-lived pre-authenticated download URL — follow redirects (fetch follows automatically; curl needs -L) or you get an empty body. (At view time the LiveData flow does this for you server-side; this note is only for authoring-time inspection.)

Blocked file extensions

SharePoint blocks some extensions from upload (e.g. .ashx, .asmx, .json, .soap, .svc, .xamlx). Publish HTML as .html — it is allowed. (Serve JSON data via the LiveData manifest, not as an uploaded .json.)


Errors

Errors return a JSON body:

{ "error": { "code": "invalidRequest", "message": "", "innererror": { "code": "" } } }
HTTP code Cause / fix
401 unauthenticated Missing/expired token or wrong audience (Graph vs SharePoint). Re-acquire.
403 accessDenied Token lacks write scope, or the user can't write that location.
404 itemNotFound Wrong path/id, or parent folder missing (create it).
409 nameAlreadyExists Name conflict with conflictBehavior=fail. Use replace/rename.
413 resourceModified / too large > 4 MB on simple upload → use an upload session.
423 resourceLocked Item checked out / locked.
507 quotaLimitReached Drive is full.

Always confirm with the user before an upload or overwrite — see the SKILL's Confirm before writing.