# SharePoint REST API - Working with Sites

`SP.Web` represents a SharePoint site — it is the entry point for everything the REST API exposes at the site level: lists, fields, content types, users, groups, folders, and navigation. You access it at `/_api/web`, where the URL prefix determines which site you're working with.

* * *

## Introduction

SharePoint distinguishes between a *site collection* (`SP.Site`, accessible at `/_api/site`) and a *site* or *web* within a collection (`SP.Web`, accessible at `/_api/web`). A site collection is the top-level container with its own security boundary, storage quota, and administration settings. Every site collection contains at least one site — the root site — and may contain subsites. This post covers `SP.Web`.

`/_api/web` always resolves to the site matching the URL prefix. To work with a subsite, prefix `/_api/web` with that subsite's URL:

```plaintext
https://contoso.sharepoint.com/sites/marketing/_api/web      ← marketing site
https://contoso.sharepoint.com/sites/marketing/team/_api/web ← team subsite
```

If you know only a web's GUID rather than its URL, `SP.Site.OpenWebById` provides an ID-based lookup through `/_api/site/openWebById(...)`. The exact OData v4 REST syntax for this method has not been live-validated in this series.

> **Authentication note:** The raw HTTP examples use a SharePoint Bearer token. With delegated authentication, the token represents a signed-in user and the operation must be permitted by both the delegated scope and that user's SharePoint permissions. With application (app-only) authentication, the token represents the Entra application and SharePoint evaluates its application permissions. For Entra app-only access to SharePoint REST, Microsoft documents certificate-based authentication; client-secret app-only tokens are not accepted by SharePoint REST. The legacy SharePoint ACS app-only model was retired on November 27, 2023 and stopped working on April 2, 2026. In SPFx, use `SPHttpClient` instead — it supplies the current user's SharePoint authentication context and manages request digests for write operations.

* * *

## Before You Start: Headers and JSON Format

SharePoint's REST API defaults to **OData v3 in most cases** when the `OData-Version` header is not supplied. To use OData v4 — which is what SPFx's `SPHttpClient` uses by default — include the `OData-Version: 4.0` header:

```http
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Content-Type: application/json;odata.metadata=none  (JSON-body requests only)
```

For a full explanation of OData versions, JSON format options, and how these headers interact, see [Understanding SharePoint REST JSON Formats](https://robwindsor.hashnode.dev/understanding-sharepoint-rest-json-formats).

* * *

## The SP.Web Entity

At the time of testing, `SP.Web` exposed 120 properties in SharePoint Online's schema. With `odata.metadata=none`, a plain `GET /_api/web` returns 71 of them. The properties you'll use most often:

| Property | Type | Description |
| --- | --- | --- |
| `Id` | `Guid` | The site's GUID. Stable across renames and moves. |
| `Title` | `string` | The site's display name shown in navigation and the browser tab. |
| `Description` | `string` | The site description. |
| `Url` | `string` | The full absolute URL of the site, e.g. `https://contoso.sharepoint.com/sites/marketing`. |
| `ServerRelativeUrl` | `string` | The server-relative URL, e.g. `/sites/marketing`. |
| `WebTemplate` | `string` | The base template identifier, e.g. `STS` for the Team Site template family or `GROUP` for a Microsoft 365 Group-connected site. Does not include the configuration number. `STS#0` is the classic team-site configuration; `STS#3` is the modern, non-group-connected team-site configuration. |
| `WebTemplateConfiguration` | `string` | The full template identifier combining the base template name and configuration number, e.g. `STS#3` or `GROUP#0` (validated against SharePoint Online). This is a preview property and may require `$select` to appear in the response. |
| `Configuration` | `int` | The numeric configuration identifier, e.g. `3` for `STS#3`, `0` for `GROUP`. |
| `Language` | `int` | The site's locale identifier (LCID), e.g. `1033` for English (US). |
| `Created` | `DateTime` | When the site was provisioned. |
| `LastItemModifiedDate` | `DateTime` | When any item in the site was last modified. |
| `WelcomePage` | `string` | Site-relative path to the home page, e.g. `SitePages/Home.aspx`. |
| `QuickLaunchEnabled` | `bool` | Whether the left-side quick launch navigation is enabled. |
| `MegaMenuEnabled` | `bool` | Whether the mega menu navigation is enabled. |
| `RecycleBinEnabled` | `bool` | Whether the recycle bin is enabled for this site. |
| `NoCrawl` | `bool` | Whether this site is excluded from search indexing. |
| `MembersCanShare` | `bool` | Whether members can share the site and its contents. |
| `IsMultilingual` | `bool` | Whether multilingual page publishing is enabled. |
| `FooterEnabled` | `bool` | Whether the site footer is enabled. |

Navigation properties (accessed via `$expand` or as sub-resources):

| Property | Type | Description |
| --- | --- | --- |
| `CurrentUser` | `SP.User` | The user making the request (or the app identity for app-only tokens). |
| `SiteUsers` | `SP.UserCollection` | SharePoint user/principal collection for the site collection containing this web. |
| `SiteGroups` | `SP.GroupCollection` | SharePoint groups defined for the site collection. |
| `AssociatedOwnerGroup` | `SP.Group` | The site's Owners group. |
| `AssociatedMemberGroup` | `SP.Group` | The site's Members group. |
| `AssociatedVisitorGroup` | `SP.Group` | The site's Visitors group. |
| `Webs` | `SP.WebCollection` | The immediate subsites of this site. Does not recurse. |
| `RootFolder` | `SP.Folder` | The site's root folder. |
| `Lists` | `SP.ListCollection` | All lists and libraries in the site. |
| `Fields` | `SP.FieldCollection` | Site columns defined on the site. |
| `ContentTypes` | `SP.ContentTypeCollection` | Content types available to the site. |
| `Navigation` | `SP.Navigation` | Navigation configuration for the site. |
| `RegionalSettings` | `SP.RegionalSettings` | Locale, time zone, and calendar settings. |
| `Features` | `SP.FeatureCollection` | Features activated on the site. |
| `RecycleBin` | `SP.RecycleBinItemCollection` | Items in the recycle bin. |

* * *

## API Operations

### Get the Current Site

Returns the `SP.Web` entity for the current site with the 71 default properties:

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
```

**Response — 200 OK** (subset of returned properties shown)

```json
{
  "AllowRssFeeds": true,
  "CommentsOnSitePagesDisabled": false,
  "Configuration": 0,
  "Created": "2025-01-15T09:00:00Z",
  "Description": "Marketing team site",
  "EnableMinimalDownload": false,
  "FooterEnabled": true,
  "HideTitleInHeader": false,
  "Id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "IsMultilingual": false,
  "Language": 1033,
  "LastItemModifiedDate": "2026-08-10T14:30:00Z",
  "MegaMenuEnabled": true,
  "MembersCanShare": true,
  "NoCrawl": false,
  "QuickLaunchEnabled": true,
  "RecycleBinEnabled": true,
  "ServerRelativeUrl": "/sites/marketing",
  "Title": "Marketing",
  "TreeViewEnabled": false,
  "Url": "https://contoso.sharepoint.com/sites/marketing",
  "WebTemplate": "GROUP",
  "WelcomePage": "SitePages/Home.aspx"
}
```

`WebTemplate` reflects the base template only — it does not include the configuration number. For a Microsoft 365 Group-connected site this is `GROUP`; for a site created from `STS#3` (the modern non-group-connected team-site configuration), this is `STS`.

* * *

### Get Selected Properties

Use `$select` to limit the response to only the named properties:

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web?$select=Id,Title,Url,WebTemplate,Language,Created
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
```

**Response — 200 OK**

```json
{
  "Created": "2025-01-15T09:00:00Z",
  "Id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "Language": 1033,
  "Title": "Marketing",
  "Url": "https://contoso.sharepoint.com/sites/marketing",
  "WebTemplate": "GROUP"
}
```

Properties outside the default 71 — such as `EffectiveBasePermissions` — require an explicit `$select` to appear in the response.

* * *

### Update Site Properties

To update writable site properties, send a `MERGE` request with only the properties to change:

```http
POST https://contoso.sharepoint.com/sites/marketing/_api/web
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Content-Type: application/json;odata.metadata=none
Authorization: Bearer <token>
X-HTTP-Method: MERGE

{
  "Title": "Marketing Department",
  "Description": "Marketing department site",
  "MembersCanShare": false
}
```

> This has the highest permission requirement of any operation in this post. `AllSites.Manage` / `Sites.Manage.All` returns 403, and `AllSites.FullControl` with a Member-level user returns 403 — Owner group membership is required (validated against SharePoint Online). The `Sites.Manage.All` restriction applies regardless of which properties are being changed — even a `Title`\-only MERGE returns 403 (validated against SharePoint Online).

**Response — 204 No Content**

Only the properties included in the body are changed.

* * *

### Get the Current User

Returns the `SP.User` entity for the caller:

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web/currentuser
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
```

**Response — 200 OK**

```json
{
  "Email": "adele@contoso.com",
  "Id": 14,
  "IsHiddenInUI": false,
  "IsSiteAdmin": false,
  "LoginName": "i:0#.f|membership|adele@contoso.com",
  "PrincipalType": 1,
  "Title": "Adele Vance",
  "UserId": {
    "NameId": "10032000a1b2c3d4",
    "NameIdIssuer": "urn:federation:microsoftonline"
  },
  "UserPrincipalName": "adele@contoso.com"
}
```

Three properties identify the user in different contexts: `UserPrincipalName` is the Entra ID UPN — the value you would use in Microsoft Graph queries. `LoginName` is SharePoint's claims-format identifier, which can be passed to `ensureuser`. `Email` is the user's email address, which typically matches `UserPrincipalName` for internal users but can differ for guests and users with proxy addresses.

With an app-only token, this endpoint returns the application's identity rather than a user: `Title` is `"SharePoint App"`, `Id` is `1073741822` (the reserved integer SharePoint assigns to all app-only callers), `LoginName` is `"i:0i.t|00000003-0000-0ff1-ce00-000000000000|app@sharepoint"`, `UserPrincipalName` is `null`, and `UserId.NameIdIssuer` is `"TrustedProvider:00000003-0000-0ff1-ce00-000000000000"` rather than `"urn:federation:microsoftonline"`.

* * *

### Get Site Users

Returns the site collection's SharePoint user/principal collection:

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web/siteusers
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
```

**Response — 200 OK** (abridged)

```json
{
  "value": [
    {
      "Email": "adele@contoso.com",
      "Id": 14,
      "IsHiddenInUI": false,
      "IsSiteAdmin": false,
      "LoginName": "i:0#.f|membership|adele@contoso.com",
      "PrincipalType": 1,
      "Title": "Adele Vance",
      "UserId": {
        "NameId": "10032000a1b2c3d4",
        "NameIdIssuer": "urn:federation:microsoftonline"
      },
      "UserPrincipalName": "adele@contoso.com"
    },
    {
      "Email": "",
      "Id": 3,
      "IsHiddenInUI": false,
      "IsSiteAdmin": false,
      "LoginName": "c:0o.c|federateddirectoryclaimprovider|e5f6a7b8-c9d0-1234-ef01-234567890123",
      "PrincipalType": 4,
      "Title": "Marketing Members",
      "UserId": null,
      "UserPrincipalName": null
    }
  ]
}
```

`PrincipalType` identifies the entry type: `1` = individual user, `4` = `SecurityGroup` enum value (SharePoint Online also uses this value for Microsoft 365 group principals), `8` = SharePoint group. `SiteUsers` is the site collection's user/principal collection — it is not a report of who currently has permission to the site. Entries can remain after a principal's effective access changes, so use the permissions APIs when you need to determine current access. For large sites, normal REST paging applies — use `$top` to limit the number of results returned.

* * *

### Get a User by ID

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web/siteusers/getbyid(14)
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
```

**Response — 200 OK**

```json
{
  "Email": "adele@contoso.com",
  "Id": 14,
  "IsHiddenInUI": false,
  "IsSiteAdmin": false,
  "LoginName": "i:0#.f|membership|adele@contoso.com",
  "PrincipalType": 1,
  "Title": "Adele Vance",
  "UserId": {
    "NameId": "10032000a1b2c3d4",
    "NameIdIssuer": "urn:federation:microsoftonline"
  },
  "UserPrincipalName": "adele@contoso.com"
}
```

If the ID does not exist, SharePoint returns `404` with `"code": "-1, Microsoft.SharePoint.Client.ResourceNotFoundException"` and `"message": "User cannot be found."` (validated against SharePoint Online).

* * *

### Get a User by Email

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web/siteusers/getByEmail('adele@contoso.com')
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
```

**Response — 200 OK** — same shape as Get a User by ID. If the email is not found, returns the same `404` shape as `getbyid`.

* * *

### Ensure a User

`ensureuser` resolves a user by their claims login name and returns the `SP.User` entity, provisioning the user in the site collection's user information list if needed:

```http
POST https://contoso.sharepoint.com/sites/marketing/_api/web/ensureuser
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Content-Type: application/json;odata.metadata=none
Authorization: Bearer <token>

{
  "logonName": "i:0#.f|membership|adele@contoso.com"
}
```

> This endpoint works at `AllSites.Read` / `Sites.Read.All` level regardless of whether the user is already provisioned in the site — provisioning a new user also succeeds at Read scope (validated against SharePoint Online for both cases).

**Response — 200 OK** — same shape as Get a User by ID.

The `logonName` accepts either the claims-format login name (`i:0#.f|membership|adele@contoso.com`) or a raw UPN (`adele@contoso.com`) — both are resolved correctly (validated against SharePoint Online). This endpoint is useful when you need the integer SharePoint user ID for writing person/group field values.

* * *

### Get Site Groups

Returns all SharePoint groups defined for the site collection:

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web/sitegroups
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
```

**Response — 200 OK** (abridged)

```json
{
  "value": [
    {
      "Description": "",
      "Id": 5,
      "IsHiddenInUI": false,
      "LoginName": "Marketing Owners",
      "OwnerTitle": "Marketing Owners",
      "PrincipalType": 8,
      "Title": "Marketing Owners"
    },
    {
      "Description": "",
      "Id": 6,
      "IsHiddenInUI": false,
      "LoginName": "Marketing Members",
      "OwnerTitle": "Marketing Owners",
      "PrincipalType": 8,
      "Title": "Marketing Members"
    },
    {
      "Description": "",
      "Id": 7,
      "IsHiddenInUI": false,
      "LoginName": "Marketing Visitors",
      "OwnerTitle": "Marketing Owners",
      "PrincipalType": 8,
      "Title": "Marketing Visitors"
    }
  ]
}
```

`PrincipalType: 8` identifies a SharePoint group.

* * *

### Get Associated Groups

SharePoint sites have three built-in associated groups: Owners, Members, and Visitors. Each can be retrieved individually:

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web/associatedownergroup
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
```

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web/associatedmembergroup
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
```

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web/associatedvisitorgroup
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
```

**Response — 200 OK** — a single `SP.Group` entity with the same shape as an item in the `sitegroups` collection.

These endpoints are useful when you need the ID or title of a specific associated group without retrieving the full groups collection.

* * *

### Get Navigation Settings

Returns high-level navigation configuration for the site:

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web/navigation
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
```

**Response — 200 OK**

```json
{
  "UseShared": false
}
```

Only `UseShared` is returned by default — it indicates whether this site inherits navigation from the parent site. The quick launch and top navigation bar node collections are available as sub-resources:

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web/navigation/quicklaunch
GET https://contoso.sharepoint.com/sites/marketing/_api/web/navigation/topnavigationbar
```

**Response — 200 OK** (quicklaunch, abridged)

```json
{
  "value": [
    {
      "Id": 1031,
      "IsExternal": false,
      "IsVisible": true,
      "Title": "Home",
      "Url": "/sites/marketing"
    },
    {
      "Id": 2003,
      "IsExternal": false,
      "IsVisible": true,
      "Title": "Documents",
      "Url": "/sites/marketing/Shared Documents/Forms/AllItems.aspx"
    }
  ]
}
```

`topnavigationbar` returns the same shape — an empty collection if no top navigation nodes are configured.

* * *

### Get Regional Settings

Returns locale, time zone, and calendar settings for the site:

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web/regionalSettings
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
```

**Response — 200 OK** (subset of 29 returned properties shown)

```json
{
  "AdjustHijriDays": 0,
  "AlternateCalendarType": 0,
  "CalendarType": 1,
  "DateFormat": 0,
  "DateSeparator": "/",
  "DecimalSeparator": ".",
  "DigitGrouping": ",",
  "FirstDayOfWeek": 0,
  "FirstWeekOfYear": 0,
  "IsEastAsia": false,
  "IsRightToLeft": false,
  "IsUIRightToLeft": false,
  "ListSeparator": ",",
  "LocaleId": 1033,
  "NegNumberMode": 1,
  "NegativeSign": "-",
  "ThousandSeparator": ",",
  "Time24": false,
  "TimeSeparator": ":"
}
```

`LocaleId` is the site's LCID (`1033` = English US). `Time24` indicates whether the site uses 24-hour time.

* * *

### Get Subsites

Returns the immediate subsites of the current site. Each entry is a `SP.WebInformation`/WebInfo summary — not a full `SP.Web` entity:

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web/webs
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
```

> Unlike most `GET /_api/web` requests, this endpoint requires Write-level permissions. `AllSites.Read` / `Sites.Read.All` returns 403 (validated against SharePoint Online).

**Response — 200 OK** (abridged)

```json
{
  "value": [
    {
      "Created": "2026-08-01T11:00:00Z",
      "Id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
      "Language": 1033,
      "ServerRelativeUrl": "/sites/marketing/team",
      "Title": "Team",
      "Url": "https://contoso.sharepoint.com/sites/marketing/team",
      "WebTemplate": "STS"
    }
  ]
}
```

This endpoint returns only immediate children — it does not recurse into nested subsites. `WebTemplate` in the response shows the base template only — the configuration suffix is stripped. A subsite created with `STS#3` returns `STS` here.

* * *

### Get the Root Folder

Returns the `SP.Folder` entity for the site's root:

```http
GET https://contoso.sharepoint.com/sites/marketing/_api/web/rootfolder
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
```

> Like `GET /web/webs`, this endpoint requires Write-level permissions even though it is a read operation. `AllSites.Read` / `Sites.Read.All` returns 403 (validated against SharePoint Online).

**Response — 200 OK**

```json
{
  "Exists": true,
  "ItemCount": 0,
  "Name": "marketing",
  "ServerRelativeUrl": "/sites/marketing/",
  "TimeCreated": "2025-01-15T09:00:00Z",
  "TimeLastModified": "2026-08-10T14:30:00Z",
  "UniqueId": "c3d4e5f6-a7b8-9012-cdef-123456789012",
  "WelcomePage": "SitePages/Home.aspx"
}
```

`ServerRelativeUrl` for the root folder includes a trailing slash. `WelcomePage` is the same value as `SP.Web.WelcomePage` — the site-relative path to the home page.

* * *

### Create a Subsite

> **Prerequisite:** Subsite creation must be enabled in the tenant and site configuration. SharePoint administrators can disable subsite creation, in which case creation is blocked through both the UI and the API.

Creates a new subsite under the current site using `SP.WebCreationInformation`:

```http
POST https://contoso.sharepoint.com/sites/marketing/_api/web/webs/add
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Content-Type: application/json;odata.metadata=none
Authorization: Bearer <token>

{
  "parameters": {
    "Title": "Team",
    "Url": "team",
    "WebTemplate": "STS#3",
    "Language": 1033,
    "UseSamePermissionsAsParentSite": true
  }
}
```

> `AllSites.Manage` / `Sites.Manage.All` returns 403, and `AllSites.FullControl` with a Member-level user returns 403 — Owner group membership is required (validated against SharePoint Online).

**Response — 200 OK** (not 201) — full `SP.Web` entity of the created subsite (subset shown):

```json
{
  "Id": "d4e5f6a7-b8c9-0123-def0-123456789012",
  "Language": 1033,
  "ServerRelativeUrl": "/sites/marketing/team",
  "Title": "Team",
  "Url": "https://contoso.sharepoint.com/sites/marketing/team",
  "WebTemplate": "STS",
  "WelcomePage": "SitePages/Home.aspx"
}
```

`Url` in the request body is the site-relative path segment, not a full URL — SharePoint appends it to the parent site's URL. `WebTemplate` in the response strips the configuration suffix: `STS#3` in the request returns `STS` in the response (validated against SharePoint Online).

`SP.WebCreationInformation` properties:

| Property | Type | Description |
| --- | --- | --- |
| `Title` | `string` | Display name for the new site. Optional — defaults to `'Team Site'` if omitted (validated against SharePoint Online). |
| `Url` | `string` | URL segment appended to the parent site URL. Required. |
| `WebTemplate` | `string` | Template identifier including configuration, e.g. `STS#3`. Optional — defaults to `'GROUP'` if omitted (validated against SharePoint Online). |
| `Language` | `int` | LCID for the site's language. Optional — omitting it defaulted to `1033` (English US) in the SharePoint Online environment tested; the actual default may reflect the tenant's configured language. |
| `UseSamePermissionsAsParentSite` | `bool` | Controls permission inheritance. Optional — when omitted, SharePoint gives the new site unique permissions rather than inheriting from the parent (`HasUniqueRoleAssignments: true`; validated against SharePoint Online). Supply `true` to inherit parent permissions. |
| `Description` | `string` | Site description. Optional. |

> **Note:** Microsoft documentation also shows an older creation route using `/_api/web/webinfos/add` with `SP.WebInfoCreationInformation`. The `webs/add` route shown here maps to `WebCollection.Add` and was validated against SharePoint Online.

* * *

### Delete a Subsite

There is no `DELETE /_api/web/webs/{url}` endpoint. To delete a subsite, send a tunneled DELETE to the subsite's own `/_api/web` endpoint:

```http
POST https://contoso.sharepoint.com/sites/marketing/team/_api/web
OData-Version: 4.0
Accept: application/json;odata.metadata=none
Authorization: Bearer <token>
X-HTTP-Method: DELETE
Content-Length: 0
```

> `AllSites.Manage` / `Sites.Manage.All` returns 403, and `AllSites.FullControl` with a Member-level user returns 403 — Owner group membership is required (validated against SharePoint Online).

**Response — 204 No Content**

The request must target the subsite's own `/_api/web` endpoint, not the parent site's. Deleting a subsite is not reversible through the SharePoint recycle bin — both the end-user and site collection recycle bins are bypassed. Verify you are targeting the correct subsite URL before sending the delete request.

* * *

## Quick Reference

### Headers

| Header | Notes |
| --- | --- |
| `Authorization: Bearer <token>` | Required for the raw HTTP OAuth examples in this post. In SPFx, `SPHttpClient` handles authentication automatically. |
| `Accept: application/json;odata.metadata=none` | Required for a JSON response. Omitting it causes SharePoint to return Atom XML. |
| `Content-Type: application/json;odata.metadata=none` | Required on requests with a body (POST, MERGE). |
| `OData-Version: 4.0` | Activates OData v4 behavior. Used throughout the examples in this post. |
| `X-HTTP-Method: MERGE` | Tunnels an update over POST. |
| `X-HTTP-Method: DELETE` | Tunnels a delete over POST. |
| `If-Match: *` | Not required for `SP.Web` MERGE or subsite DELETE — SharePoint Online returns 204 without it (validated). Lists and list items use `If-Match` for ETag concurrency control. |
| `Content-Length: 0` | Required on bodyless DELETE tunnel requests — SharePoint Online returns 411 Length Required without it. Most HTTP clients add it automatically. |

### Response Status Codes

| Operation | Status | Body |
| --- | --- | --- |
| GET site / sub-resources | 200 OK | JSON |
| Update site properties (MERGE) | 204 No Content | Empty |
| Ensure user | 200 OK | `SP.User` entity |
| Create subsite | 200 OK | Full `SP.Web` entity of new site |
| Delete subsite | 204 No Content | Empty |
| Insufficient permission (elevated read/write operations) | 403 Forbidden | Error JSON |
| `siteusers/getbyid` or `getByEmail` — unknown principal | 404 Not Found | Error JSON |
| Bodyless subsite DELETE without `Content-Length: 0` | 411 Length Required | Error text |

### Permission Requirements

The table below lists the minimum delegated and application permission scopes. Delegated scopes apply when calling with a Bearer token on behalf of a signed-in user; the minimum user permission level column shows what access the signed-in user needs when using classic SharePoint permissions and modern SharePoint group permissions. Application permissions apply when calling without a signed-in user context (app-only). The application permissions in this table are permissions on the Office 365 SharePoint Online API, not the same-named Microsoft Graph permissions. An SPFx solution using `SPHttpClient` does not require you to grant these delegated or application scopes to the solution; it calls SharePoint as the current user, whose SharePoint permissions govern the operation. Delegated permission scope requirements were validated against SharePoint Online using users with site Member and Owner group access. The classic permission requirements in the table come from Microsoft's documentation.

| Operation | Minimum delegated scope | Minimum user permission level | Minimum tenant-wide application permission |
| --- | --- | --- | --- |
| Get site (`GET /web`) | `AllSites.Read` | Classic: Read; Group: Visitor | `Sites.Read.All` |
| Get site users and groups | `AllSites.Read` | Classic: Read; Group: Visitor | `Sites.Read.All` |
| Get current user | `AllSites.Read` | Classic: Read; Group: Visitor | `Sites.Read.All` |
| Ensure a user | `AllSites.Read` | Classic: Read; Group: Visitor | `Sites.Read.All` |
| Get navigation settings | `AllSites.Read` | Classic: Read; Group: Visitor | `Sites.Read.All` |
| Get regional settings | `AllSites.Read` | Classic: Read; Group: Visitor | `Sites.Read.All` |
| Get subsites (`GET /web/webs`) | `AllSites.Write` | Classic: Contribute; Group: Member | `Sites.ReadWrite.All` |
| Get root folder (`GET /web/rootfolder`) | `AllSites.Write` | Classic: Contribute; Group: Member | `Sites.ReadWrite.All` |
| Update site properties (MERGE) | `AllSites.FullControl` | Classic: Full Control; Group: Owner | `Sites.FullControl.All` |
| Create a subsite | `AllSites.FullControl` | Classic: Full Control; Group: Owner | `Sites.FullControl.All` |
| Delete a subsite | `AllSites.FullControl` | Classic: Full Control; Group: Owner | `Sites.FullControl.All` |

> **Note:** The application permissions above are tenant-wide grants. For least-privilege access to specific site collections, the SharePoint **Sites.Selected** permission can be used for both application and delegated tokens. Sites.Selected consent alone does not grant access to any site — an explicit Read, Write, Manage, or FullControl role must be assigned on each target site collection via the Microsoft Graph `/sites/{id}/permissions` endpoint. For delegated tokens, access is the intersection of the signed-in user's own permissions and the role granted to the application for that site.

> **Note:** MERGE, subsite create, and subsite delete all require `Sites.FullControl.All` for app-only access — `Sites.Manage.All` is not sufficient (validated). For delegated access, Owner group membership is required in addition to `AllSites.FullControl` — Member group membership with the same API scope returns 403 (validated).

* * *

## Wrapping Up

`SP.Web` is the anchor of the SharePoint REST API — every list, field, content type, user, and folder in this series is reachable from `/_api/web`. A few things to keep in mind as you integrate this into your solutions:

*   `/_api/web` always resolves to the site matching the URL prefix. To work with a subsite, prefix `/_api/web` with the subsite's URL. If you know only the web's GUID, `SP.Site.OpenWebById` provides an ID-based lookup through `/_api/site/openWebById(...)`.
    
*   `GET /web/webs` and `GET /web/rootfolder` require Write-level permissions even though they are read operations — `Sites.ReadWrite.All` for app-only, `AllSites.Write` + Member group permission for delegated. Plan for this when designing least-privilege solutions.
    
*   MERGE, subsite create, and subsite delete all require `Sites.FullControl.All` (app-only) or `AllSites.FullControl + Owner` (delegated) — the highest scope in the SharePoint REST permission hierarchy. This is a notably higher bar than the equivalent operations on lists and list items.
    
*   When you create a subsite, the response returns `200` rather than `201`, and `WebTemplate` in the response drops the configuration suffix (`STS#3` → `STS`).
    
*   `ensureuser` works at `Sites.Read.All` / `AllSites.Read` level regardless of whether the user is already provisioned — provisioning a new user also succeeds at Read scope (validated). The `logonName` accepts either claims-format (`i:0#.f|membership|<upn>`) or a raw UPN — both work. Use it to resolve a user to the integer SharePoint user ID needed for person/group field values.
    
*   Deleting a subsite bypasses the recycle bin — there is no user-accessible recovery path once the delete completes.
    
*   `GET /web/currentuser` returns the app identity when called with an app-only token: `Title` is `"SharePoint App"`, `Id` is `1073741822`.
    
*   A plain `GET /_api/web` returns 71 of the 120 schema properties at the time of testing. Use `$select` when you only need a few, or to reach properties outside the default set.
    

Happy coding!
