The SharePoint REST API lets you read and change SharePoint data with plain HTTP calls to the /_api/ address of any site. This GET request returns five orders from a list:
GET https://spguides.sharepoint.com/sites/SPGuides/_api/web/lists/getbytitle('SPGDemo Orders')/items?$select=ID,Title,OrderTotal&$top=5
Accept: application/json;odata=nometadata
Send it signed in to the site and you get the rows back as JSON, with no SDK or library involved.
In this tutorial, we’ll run every common sharepoint rest api call on a real SharePoint Online site: the endpoint cheat sheet, headers, OData filters, list and item CRUD, folders and files, groups, and the errors you’ll hit.
What is the SharePoint REST API?
It’s a web service built into SharePoint (client.svc) that exposes sites, lists, items, files, users and groups as URLs. You call it with GET, POST and the other HTTP methods, and it answers in JSON or XML using the OData standard.
It covers the same objects as the CSOM client object model and JSOM in SharePoint, so anything that speaks HTTP can use it: browser JavaScript, SPFx, Power Automate, PowerShell, C# or Python.
It works in SharePoint Online and SharePoint Server. More developer guides sit on the SharePoint tutorials hub.
Every URL starts with the site address plus /_api/. From there you walk the object model: _api/site is the site collection and _api/web is the current site.
SharePoint REST API endpoints cheat sheet
Here are the endpoints I use most. Add them after https://spguides.sharepoint.com/sites/SPGuides/_api/; I tested each one on our site for this post:
| What you want | Method | Endpoint |
|---|---|---|
| Site collection | GET | site |
| Current site (title, language, URL) | GET | web?$select=Title,Language |
| All lists and libraries | GET | web/lists |
| One list by title or GUID | GET | web/lists/getbytitle('Orders') or web/lists(guid'...') |
| List columns | GET | web/lists/getbytitle('Orders')/fields |
| List items | GET | web/lists/getbytitle('Orders')/items |
| One item by ID | GET | web/lists/getbytitle('Orders')/items(2) |
| Create a list | POST | web/lists |
| Create / update / delete an item | POST / MERGE / DELETE | .../items, .../items(3) |
| Send an item to the recycle bin | POST | .../items(3)/recycle() |
| Create a folder | POST | web/folders |
| Upload a file | POST | web/GetFolderByServerRelativeUrl('...')/Files/add(url='a.txt',overwrite=true) |
| Download file content | GET | web/GetFileByServerRelativeUrl('...')/$value |
| Current user | GET | web/currentuser |
| Site users / groups | GET | web/siteusers, web/sitegroups |
| Members of a group | GET | web/sitegroups(30)/users |
| Form digest for writes | POST | contextinfo |
HTTP methods and headers the REST API expects
GET reads and POST creates. Updates and deletes are also sent as POST, with an X-HTTP-Method header that says what you really mean:
| Header | When | Value |
|---|---|---|
| Accept | Every call | application/json;odata=nometadata (lean) or odata=verbose (old d.results shape) |
| Content-Type | Calls with a body | Same family as Accept |
| X-RequestDigest | Writes from a browser session | FormDigestValue from /_api/contextinfo |
| X-HTTP-Method | Update or delete | MERGE, PUT or DELETE |
| IF-MATCH | Update or delete | The item’s ETag, or * to overwrite any version |
MERGE changes only the fields you send. PUT replaces the whole object, so optional fields you leave out go back to their defaults. I use MERGE for list items almost every time.
A missing or stale digest is the classic 403. The full fix is in the security validation error guide. Calls signed with an OAuth token, like PnP PowerShell, skip the digest.
How do I test a SharePoint REST API call in the browser?
Sign in to the site, then paste a GET URL in the address bar:
https://spguides.sharepoint.com/sites/SPGuides/_api/web/lists/getbytitle('SPGDemo Orders')/items?$select=ID,Title,CustomerName,OrderTotal&$top=3
The browser doesn’t send a JSON Accept header, so SharePoint answers in Atom XML. Each item comes back as an entry with its fields under m:properties, which is handy for a quick check but hard to read.
For JSON, call it from code. This browser-side fetch asks for nometadata JSON:
const site = "https://spguides.sharepoint.com/sites/SPGuides";
const res = await fetch(site + "/_api/web/lists/getbytitle('SPGDemo Orders')/items?$select=ID,Title,OrderTotal&$top=3", {
headers: { Accept: "application/json;odata=nometadata" }
});
const data = await res.json();
console.table(data.value); // nometadata puts the rows in "value"; verbose puts them in d.results
From PowerShell, Invoke-PnPSPRestMethod reuses your PnP sign-in, so a relative URL is enough. If you haven’t set it up, start with installing PnP PowerShell. This script reads the site, filters the list collection and gets the current user:
$web = Invoke-PnPSPRestMethod -Url "/_api/web?`$select=Title,Language,ServerRelativeUrl"
$web | Select-Object Title, Language, ServerRelativeUrl | Format-List
$filter = "startswith(Title,'SPGDemo O') or startswith(Title,'SPGDemo P')"
$lists = Invoke-PnPSPRestMethod -Url "/_api/web/lists?`$select=Title,ItemCount,BaseTemplate&`$filter=$filter"
$lists.value | Select-Object Title, ItemCount, BaseTemplate | Format-Table -AutoSize
$me = Invoke-PnPSPRestMethod -Url "/_api/web/currentuser?`$select=Id,Title,IsSiteAdmin"
$me | Select-Object Id, Title, IsSiteAdmin | Format-Table -AutoSize

Language 1033 is English (United States). The same web endpoint with $select=Language answers the “get site language” question.
Filter, sort and expand list items with OData query options
Query options go after the endpoint: $select picks columns, $filter picks rows, $orderby sorts, $top limits and $expand pulls lookup and person fields. Here’s one call with all five:
$url = "/_api/web/lists/getbytitle('SPGDemo Orders')/items" +
"?`$select=ID,Title,CustomerName,OrderTotal,SalesRep/Title,Product/Title" +
"&`$expand=SalesRep,Product" +
"&`$filter=OrderStatus eq 'Shipped'" +
"&`$orderby=OrderTotal desc&`$top=5"
$orders = Invoke-PnPSPRestMethod -Url $url
$orders.value | Select-Object ID, Title, CustomerName, OrderTotal,
@{n='SalesRep';e={$_.SalesRep.Title}}, @{n='Product';e={$_.Product.Title}} | Format-Table -AutoSize

Only three orders are shipped, so $top=5 returned three, highest total first. SalesRep and Product came back as names because both are in $expand.
Three things that caught me in testing:
- Without
$top, the items endpoint returns only the first 100 rows plus anodata.nextLink. Follow that link to get the next page. $skipis ignored for list items.$skip=10&$top=2still gave me IDs 1 and 2, so page with the$skiptokeninside nextLink.- Use internal names.
$select=Customer Namefails;CustomerNameworks.
This is the nextLink our 210-row list returned:
.../items?%24skiptoken=Paged%3dTRUE%26p_ID%3d100&%24select=ID
Supported filter functions include eq, ne, gt, lt, startswith() and substringof(). endswith and tolower aren’t. For huge lists, a CAML query gives you more control, and Power Automate users can follow REST API pagination in Power Automate.
Create a list and add items with POST
Creating a list is a POST to the lists collection with a title and a template (100 is a custom list, 101 a document library). Then each item is a POST to its items endpoint:
$body = @{ Title = "SPGDemo Vendors"; BaseTemplate = 100; Description = "Vendors for the REST API demo" }
$list = Invoke-PnPSPRestMethod -Method Post -Url "/_api/web/lists" -Content $body
"Created list: $($list.Title) (template $($list.BaseTemplate))"
foreach ($v in "Northwind Traders", "Fabrikam Inc.", "Litware Supplies") {
$item = Invoke-PnPSPRestMethod -Method Post -Url "/_api/web/lists/getbytitle('SPGDemo Vendors')/items" -Content @{ Title = $v }
"Added item $($item.Id): $($item.Title)"
}

The new list showed up in Site contents with the three vendors:

In browser JavaScript, get a digest first and send it with the POST:
const digest = (await (await fetch(site + "/_api/contextinfo", {
method: "POST", headers: { Accept: "application/json;odata=nometadata" }
})).json()).FormDigestValue;
const res = await fetch(site + "/_api/web/lists/getbytitle('SPGDemo Vendors')/items", {
method: "POST",
headers: {
"Accept": "application/json;odata=nometadata",
"Content-Type": "application/json;odata=nometadata",
"X-RequestDigest": digest
},
body: JSON.stringify({ Title: "Northwind Traders" })
});
console.log(res.status); // 201 Created
With odata=verbose, the body also needs the list’s ListItemEntityTypeFullName as the type. Spaces in the list name become _x0020_:
{
"__metadata": { "type": "SP.Data.SPGDemo_x0020_VendorsListItem" },
"Title": "Northwind Traders"
}
For bulk loads, adding list items with PnP PowerShell is simpler than a REST loop.
Update and delete list items (MERGE, DELETE and recycle)
To update, send MERGE to the item’s URL. To remove an item but keep it recoverable, call recycle(). Here are both:
$items = "/_api/web/lists/getbytitle('SPGDemo Vendors')/items"
Invoke-PnPSPRestMethod -Method Merge -Url "$items(3)" -Content @{ Title = "Litware Supplies LLC" }
$extra = Invoke-PnPSPRestMethod -Method Post -Url $items -Content @{ Title = "Old Vendor Entry" }
Invoke-PnPSPRestMethod -Method Post -Url "$items($($extra.Id))/recycle()" -Content @{} | Out-Null
$bin = Invoke-PnPSPRestMethod -Url "/_api/web/recyclebin?`$filter=Title eq 'Old Vendor Entry'"
$bin.value | Select-Object Title, ItemType, DeletedDate | Format-Table -AutoSize

Item 3 now reads Litware Supplies LLC, and Old Vendor Entry sits in the site recycle bin.
Watch out with DELETE. When I sent a plain DELETE to a test item, it never showed up in the recycle bin. If someone might want it back, use recycle(). Restores work as described in the SharePoint recycle bin guide.
The same calls from browser JavaScript use POST plus the method header:
await fetch(site + "/_api/web/lists/getbytitle('SPGDemo Vendors')/items(3)", {
method: "POST",
headers: {
"Accept": "application/json;odata=nometadata",
"Content-Type": "application/json;odata=nometadata",
"X-RequestDigest": digest,
"X-HTTP-Method": "MERGE",
"IF-MATCH": "*"
},
body: JSON.stringify({ Title: "Litware Supplies LLC" })
}); // 204 No Content on success
// delete: same URL, "X-HTTP-Method": "DELETE", no body
// recycle instead: POST .../items(3)/recycle()
IF-MATCH: * overwrites any version. Send the item’s real ETag instead and SharePoint refuses changes made on top of someone else’s edit. A wrong ETag returned this 412:
412 {"odata.error":{"code":"-1, Microsoft.SharePoint.Client.ClientServiceException","message":{"lang":"en-US",
"value":"The request ETag value '\"99\"' does not match the object's ETag value '\"1211e783-...-634ed16f8d4f,5\"'."}}}
Deleting items also leaves gaps in the IDs. If that bothers you, see how to reset the list item ID.
Folders and files with the REST API
Files live in libraries, so I created one first, with template 101:
$lib = Invoke-PnPSPRestMethod -Method Post -Url "/_api/web/lists" -Content @{ Title = "SPGDemo Vendor Files"; BaseTemplate = 101 }
"Library: $($lib.Title)" # Library: SPGDemo Vendor Files
Then a folder, a small text file, the folder’s file list and the file’s content:
$lib = "/sites/SPGuides/SPGDemo Vendor Files"
$folder = Invoke-PnPSPRestMethod -Method Post -Url "/_api/web/folders" -Content @{ ServerRelativeUrl = "$lib/Insurance" }
$text = "Certificate of insurance received 10/24/2026"
$file = Invoke-PnPSPRestMethod -Method Post -ContentType "text/plain" -Content $text `
-Url "/_api/web/GetFolderByServerRelativeUrl('$lib/Insurance')/Files/add(url='coi-northwind.txt',overwrite=true)"
$files = Invoke-PnPSPRestMethod -Url "/_api/web/GetFolderByServerRelativeUrl('$lib/Insurance')/Files?`$select=Name,TimeCreated"
$files.value | Format-Table Name, TimeCreated -AutoSize
Invoke-PnPSPRestMethod -Raw -Url "/_api/web/GetFileByServerRelativeUrl('$lib/Insurance/coi-northwind.txt')/`$value"

The library now holds Insurance plus Contracts, a folder I made the same way in an earlier test:

Files/add takes the raw file bytes as the body, and $value returns the content. Microsoft caps a file created this way at 2 GB. Names with % or # need the ResourcePath versions of these endpoints.
To remove a file but keep it recoverable, recycle it:
POST https://spguides.sharepoint.com/sites/SPGuides/_api/web/GetFileByServerRelativeUrl('/sites/SPGuides/SPGDemo Vendor Files/Insurance/coi-northwind.txt')/recycle()
X-RequestDigest: <digest>
For real uploads, uploading files with PnP PowerShell and uploading files with metadata in SPFx wrap these same endpoints.
SharePoint groups and users with the REST API
siteusers lists the users the site knows (its User Information List), and sitegroups lists its groups. Creating a group is a POST with a title:
$group = Invoke-PnPSPRestMethod -Method Post -Url "/_api/web/sitegroups" `
-Content @{ Title = "SPGDemo Vendor Reviewers"; Description = "REST API demo group" }
"Group $($group.Id): $($group.Title)" # Group 30: SPGDemo Vendor Reviewers
Adding a member is a POST of the user’s login name to the group’s users collection. Removing one is removebyid() or removeByLoginName():
$group = Invoke-PnPSPRestMethod -Url "/_api/web/sitegroups/getbyname('SPGDemo Vendor Reviewers')"
$login = (Invoke-PnPSPRestMethod -Url "/_api/web/getuserbyid(26)").LoginName
Invoke-PnPSPRestMethod -Method Post -Url "/_api/web/sitegroups($($group.Id))/users" -Content @{ LoginName = $login } | Out-Null
(Invoke-PnPSPRestMethod -Url "/_api/web/sitegroups($($group.Id))/users?`$select=Id,Title").value |
Format-Table Id, Title -AutoSize
Invoke-PnPSPRestMethod -Method Post -Url "/_api/web/sitegroups($($group.Id))/users/removebyid(26)" -Content @{} | Out-Null

Miller showed up as member 26, and the group was empty again after removebyid(26). New groups get no permissions until you assign a role, so this test changed no access.
One PnP gotcha: a POST with no body, like removebyid(), failed with “Cannot handle the data at position 0” until I added -Content @{}.
To get the current user’s groups, call:
GET https://spguides.sharepoint.com/sites/SPGuides/_api/web/currentuser/groups?$select=Id,Title
It returns only groups the user belongs to directly. I own the site through its Microsoft 365 group, so SPGuides Owners didn’t appear for me. Showing the user’s name instead? See displaying the current user in SPFx.
Call the REST API from SPFx, Power Automate and PowerShell
In an SPFx web part, use SPHttpClient. It handles the sign-in and adds the digest to writes:
const url = `${this.context.pageContext.web.absoluteUrl}/_api/web/lists/getbytitle('SPGDemo Orders')/items?$select=ID,Title&$top=5`;
const response = await this.context.spHttpClient.get(url, SPHttpClient.configurations.v1);
const orders = (await response.json()).value;
I use this pattern in retrieving list items in SPFx. For mail, Teams or OneDrive data, Microsoft Graph in SPFx is the better API.
In Power Automate, the Send an HTTP request to SharePoint action takes the same endpoint as a relative URI:
Site Address: https://spguides.sharepoint.com/sites/SPGuides
Method: GET
Uri: _api/web/lists/getbytitle('SPGDemo Orders')/items?$select=ID,Title&$top=5
Headers: Accept = application/json;odata=nometadata
More flow examples are in REST API calls in Power Automate. In PowerShell, use Invoke-PnPSPRestMethod as shown throughout this post.
SPServices and SendEmail: what’s retired
SPServices was a jQuery library that wrapped SharePoint’s old SOAP web services (the .asmx files) for classic pages. It isn’t maintained, and its Script Editor approach doesn’t run on modern pages.
Every SPServices task has a REST call: GetListItems becomes /items, UpdateListItems becomes POST or MERGE, and GetUserProfileByName becomes /currentuser or the user profile endpoints. Move that code into SPFx with SPHttpClient.
The SP.Utilities.Utility.SendEmail endpoint is also gone. Microsoft retired it on October 31, 2025. Send mail with Microsoft Graph’s sendMail, or the Outlook connector’s Send an email action in Power Automate.
Common SharePoint REST API errors
These are the real error messages our site returned, and what each one means:
- 403 “The security validation for this page is invalid”: a browser write without a valid X-RequestDigest.
- “List ‘SPGDemo Vendor’ does not exist at site with URL …”: a typo in getbytitle. Titles must match the display name exactly.
- “The expression “Customer Name” is not valid.”: you used a display name in $select or $filter. Use the internal name.
- “The query to field ‘SalesRep’ is not valid … the $expand query string must contains SalesRep.”: a person or lookup field in $select without $expand.
- 412 ETag mismatch: someone changed the item after you read it. Re-read it, or send
IF-MATCH: *. - 429 Too Many Requests: SharePoint throttled you. Wait for the Retry-After time, then batch or slow down.
If you’re building on SharePoint data, these guides pick up where this one ends:
Frequently Asked Questions
What is the REST API in SharePoint?
It’s SharePoint’s built-in web service at /_api/. You read and change sites, lists, items, files, users and groups with HTTP requests and get JSON or XML back, from any language that can send HTTP.
Where is the SharePoint REST API documentation?
Microsoft keeps it on Learn, starting with Get to know the SharePoint REST service. It links to the list, file, OData query and batch pages.
How do I get more than 100 or 5,000 items with the SharePoint REST API?
Follow the odata.nextLink in each response. It carries a $skiptoken for the next page. $skip doesn’t work on list items, and Microsoft’s OData query operations page lists the supported options.
Should I use the SharePoint REST API or Microsoft Graph?
Use the SharePoint REST API for SharePoint-only features like site groups, list settings and recycle(). Use Microsoft Graph when you also need mail, Teams or OneDrive, or one API across Microsoft 365.
Do SharePoint REST API calls need X-RequestDigest?
Only writes (POST, MERGE, PUT, DELETE) from a browser session. Calls with an OAuth token, like PnP PowerShell, SPHttpClient or Power Automate, don’t need you to add it.

Hey! I’m Bijay Kumar, founder of SPGuides.com and a Microsoft Business Applications MVP (Power Automate, Power Apps). I launched this site in 2020 because I truly enjoy working with SharePoint, Power Platform, and SharePoint Framework (SPFx), and wanted to share that passion through step-by-step tutorials, guides, and training videos. My mission is to help you learn these technologies so you can utilize SharePoint, enhance productivity, and potentially build business solutions along the way.
Hi bijay what is cost for Learning API connection with flutter/Mobile access sharepoint List
HOW CAN I UPLOAD METADATA FROM JSON TO A SHAREPOINT LIBRARY?