Microsoft Project Online retires September 30, 2026, migrate to a modern platform before it's too late.Start migration
Back to Blog
Migration

The Complete Guide to Project Online OData Export

The Project Online OData export endpoint closes September 30, 2026. Authentication, entity model, throttling, and PowerShell scripts to get your data out first.

Onplana TeamMay 10, 20269 min read

Here is the test. Open a browser tab and paste this URL:

https://your-tenant.sharepoint.com/sites/pwa/_api/ProjectData/Projects

Replace the domain and PWA path with your own. If you get a JSON or XML response listing your projects, the Project Online OData export endpoint is reachable from your account. If you get a 403 or a redirect to a login page, you need to authenticate first. If you get a 404, your PWA site path is wrong.

That URL is the starting point for extracting the data in Project Online that .mpp files cannot capture: timesheet history, resource pool metadata, cross-project assignment data, custom field values across the entire portfolio, and project-level financial data. When the PWA site retires on September 30, 2026, that endpoint goes dark. Every Power BI report, Excel pivot, or custom script that reads from it breaks at that moment. Getting a clean export before then is not optional.

TL;DR: Project Online OData export in four steps

  1. Authenticate using OAuth with a Microsoft 365 account that has PWA reporting rights.
  2. Enumerate the entities you need (Projects, Tasks, Assignments, Resources, Timesheets) using paginated queries with $top=1000&$skiptoken=....
  3. Transform dates from UTC to your local time zone, handle null values, and filter out soft-deleted records.
  4. Store in a system you control (CSV, SQL database, Azure Storage) before September 30, 2026.

What the Project Online OData API actually exposes

The Project Online OData reporting service sits at /_api/ProjectData and exposes the reporting schema, not the full PWA administrative schema. This distinction matters: the reporting schema is what Microsoft designed for reading and exporting, while the administrative schema (PSI/CSOM) is for read-write operations.

The entities most relevant to migration are shown in the diagram below. Each box is a queryable entity; lines show the relationships you join to reconstruct the full picture. Microsoft's ProjectData OData service reference documents the full entity data model including all navigation properties and association definitions.

Project Online OData entity model: key entities and relationships Projects ProjectId, Name, Start, Finish Tasks TaskId, Name, Start, Finish, % Done Resources ResourceId, Name, Type, Email Assignments TaskId + ResourceId + Work TimesheetLines Actual hours per period Custom Field Values ECF values per entity BaselineTasks Baseline Start, Finish, Work has many has many has many

The key entities and their primary fields:

Entity Primary key What it contains
Projects ProjectId Project-level metadata, start/finish, owner, status
Tasks TaskId, ProjectId Task name, duration, start/finish, percent complete, constraints
Assignments AssignmentId, TaskId, ResourceId Work hours, assignment units, actual work
Resources ResourceId Resource name, type (work/cost/material), email
TimesheetLines TimesheetLineId Actual hours submitted per period per task per resource
BaselineTasks TaskId, ProjectId, BaselineNumber Baseline start/finish/work for each of the 11 baselines

The custom field values (Enterprise Custom Fields, or ECFs) are not a single entity. Each ECF type has its own entity name: TaskCustomFields, ProjectCustomFields, ResourceCustomFields. Query the metadata endpoint at /_api/ProjectData/$metadata to see the full entity list for your tenant.

How to authenticate to the Project Online OData API

The OData endpoint uses Microsoft 365 OAuth authentication. There are three practical approaches depending on your environment.

Option 1: Browser (for exploration)

Navigate to the OData URL in a browser where you are already signed into Microsoft 365. The browser passes your session cookie and returns results directly. This works for ad hoc queries but not for scripted export.

Option 2: PowerShell with PnP.PowerShell (recommended for migration)

# Install once: Install-Module -Name PnP.PowerShell
Connect-PnPOnline -Url "https://your-tenant.sharepoint.com/sites/pwa" -Interactive

# Now make authenticated OData requests in the same session
$headers = @{ "Accept" = "application/json;odata=verbose" }
$url     = "https://your-tenant.sharepoint.com/sites/pwa/_api/ProjectData/Projects"
$result  = Invoke-RestMethod -Uri $url -Headers $headers -UseDefaultCredentials

The -Interactive flag opens a browser sign-in window and caches the token. For unattended scripts, replace it with a registered Entra ID app credential using -ClientId and -CertificatePath.

Option 3: Excel Power Query (for non-developers)

In Excel, go to Data > Get Data > From OData Feed, paste the endpoint URL, and authenticate with your Microsoft 365 account. Power Query walks you through table selection and loads data into worksheets. This is the lowest-overhead option for admins who need a one-time export.

The account you authenticate with needs the Manage Users and Groups permission on the PWA site (or higher) to access reporting data for all projects. A standard project manager account will only return data for projects they have access to.

Querying the OData API: pagination and filtering

Every query against the Project Online OData API must handle pagination. The per-entity page limits for Project Online are lower than most developers expect: Projects returns at most 300 per page, Tasks returns at most 300 per page, and Assignments returns at most 1000 per page. For a tenant with 50 active projects and 500 tasks per project, a single request to /Tasks will return at most 300 tasks and silently drop the rest unless you paginate.

The API uses a $skiptoken continuation approach. After the first response, check for a __next link in the JSON body (or odata.nextLink in the verbose format). Follow that link to get the next page, and continue until no __next link is present.

Paginated project export script:

param(
    [Parameter(Mandatory)] [string]$TenantPwaUrl,
    [Parameter(Mandatory)] [string]$OutputPath
)

Connect-PnPOnline -Url $TenantPwaUrl -Interactive
$headers = @{ Accept = "application/json;odata=verbose" }
$baseUrl = "$TenantPwaUrl/_api/ProjectData"

function Get-AllPages {
    param([string]$Url)
    $all = @()
    $next = $Url
    do {
        $response = Invoke-RestMethod -Uri $next -Headers $headers -UseDefaultCredentials
        $all += $response.d.results
        $next = $response.d.__next
        if ($next) { Start-Sleep -Seconds 2 }
    } while ($next)
    return $all
}

# Export core entities
$projects    = Get-AllPages "$baseUrl/Projects"
$tasks       = Get-AllPages "$baseUrl/Tasks"
$resources   = Get-AllPages "$baseUrl/Resources"
$assignments = Get-AllPages "$baseUrl/Assignments"
$timesheets  = Get-AllPages "$baseUrl/TimesheetLines"

# Write to CSV
$projects    | Export-Csv "$OutputPath\projects.csv"    -NoTypeInformation
$tasks       | Export-Csv "$OutputPath\tasks.csv"       -NoTypeInformation
$resources   | Export-Csv "$OutputPath\resources.csv"   -NoTypeInformation
$assignments | Export-Csv "$OutputPath\assignments.csv" -NoTypeInformation
$timesheets  | Export-Csv "$OutputPath\timesheets.csv"  -NoTypeInformation

Write-Host "Export complete: $($projects.Count) projects, $($tasks.Count) tasks"

For baselines, query each numbered baseline entity separately. Project Online exposes them as BaselineTasks (with a BaselineNumber field 0 through 10), BaselineAssignments, and BaselineProjects.

Project Online OData throttling: what to expect

Project Online throttles OData requests the same way it throttles all SharePoint and Project API activity. When you exceed the throttle threshold, the API returns HTTP 429 (Too Many Requests) with a Retry-After header indicating how many seconds to wait before retrying.

The specific thresholds are not published, but in practice:

  • Simple queries (small result sets, no joins) rarely throttle at 1 per second
  • Paginated queries with large result sets (tens of thousands of rows across all pages) can trigger throttling after 200 to 400 requests in a short window
  • Large tenants with many concurrent users are more aggressive throttlers because the tenant-wide quota gets split across all activity

The Start-Sleep -Seconds 2 in the script above is the baseline mitigation. If you see 429 responses, increase the sleep to 5 seconds and retry. A proper retry loop:

function Invoke-WithRetry {
    param([string]$Uri)
    $attempt = 0
    do {
        try {
            return Invoke-RestMethod -Uri $Uri -Headers $headers -UseDefaultCredentials
        }
        catch {
            if ($_.Exception.Response.StatusCode -eq 429) {
                $wait = [int]($_.Exception.Response.Headers["Retry-After"] ?? 30)
                Write-Warning "Throttled. Waiting $wait seconds..."
                Start-Sleep -Seconds $wait
                $attempt++
            }
            else { throw }
        }
    } while ($attempt -lt 5)
}

Run large exports during evenings or weekends when tenant activity is low. This is where having the inventory done in advance pays off: you know exactly how many pages to expect and can estimate the runtime before starting.

Common pitfalls: timezones, deleted projects, and soft-deleted tasks

Timezone mismatch: Project Online stores all dates in UTC and returns them as ISO 8601 strings with a Z suffix. When you write these directly to Excel or a database, tools often interpret them literally and display the wrong date in reports. Apply your tenant's time zone offset during transformation, not after.

Soft-deleted projects: When a PM deletes a project in PWA but the project is still in the recycle bin, its status in OData varies by entity. Some entities continue to return the project with a deleted flag; others exclude it. Cross-reference your OData project count against the number of projects shown in the PWA admin Project Center (which includes an option to show deleted items) before declaring the export complete.

Soft-deleted tasks: Tasks that were deleted inside the desktop client but whose project was published before the delete was committed sometimes survive in the OData reporting database as ghost records. These typically have null start/finish dates and zero duration. Filter them out during transformation with a where TaskIsActive eq true clause.

Null enterprise custom field values: ECF fields that have never been filled in return null rather than an empty string. If your transformation assumes a non-null value and doesn't handle null gracefully, the export will fail or silently drop those records. Explicitly handle null in your transformation pipeline.

Projects in draft state: Projects that exist in PWA but have never been published (never promoted from draft to published) are not visible in the standard OData entities. They only appear through the PSI/CSOM administrative API. The Project Online Inventory Checklist specifically accounts for draft-state projects because they are routinely missed in OData-only exports.

Validating your OData export before relying on it

Before treating the exported CSVs or database tables as your migration source, validate against the live system:

  1. Row counts: Query $count on each major entity in OData and compare against record counts in your exported files. A discrepancy means pagination was interrupted or throttled silently.

  2. Spot-check project totals: Pick three to five projects you know well and verify that their task count, resource count, and custom field values match what you see in PWA.

  3. Verify timesheet coverage: For a project with active timesheets, confirm that the TimesheetLines rows for that project in your export match the hours shown in the PWA Timesheet Center.

  4. Date sanity check: Verify that exported date fields, after timezone conversion, match the dates shown in the PWA Project Center. A systematic one-day offset indicates a timezone conversion error.

The complete migration path, from inventory through export to validation and cutover, is described in the how to migrate from Microsoft Project Online guide. The OData export described here is one step in that broader process.

What to do with the data after export

A clean OData export gives you a data warehouse snapshot of your Project Online tenant: all project schedules, all resource assignments, all timesheet history, all custom field values. From here:

  • If you are migrating to a new PM tool, provide the project-level CSV to your vendor alongside the .mpp files. The OData data fills in what .mpp cannot carry: timesheet history and resource pool metadata.
  • If you are rebuilding Power BI reports against a new data source, the exported CSVs can serve as the historical data layer while the new tool's API feeds current data.
  • If you are archiving for compliance, import the CSVs into a data warehouse or Azure Storage with a retention policy that matches your organization's data-retention requirements.

Store the exported files in a system you own before September 30, 2026. After that date, there is no recovery path for data that was never exported.

Check what your migration covers before you commit Run the free Project Online Inventory Checklist to map every project, resource pool entry, and integration against your export plan. No signup required. Open the Inventory Checklist

Microsoft Project Online™ is a trademark of Microsoft Corporation. Onplana is not affiliated with Microsoft.

Project Online OData exportProject Online ODataPWA OData APIProject Online API exportMigrationMicrosoft Project OnlinePMO

Ready to make the switch?

Start your free Onplana account and import your existing projects in minutes.