Upwork 0.0.0-dev.5

This is a prerelease version of Upwork.
There is a newer version of this package available.
See the version list below for details.
dotnet add package Upwork --version 0.0.0-dev.5
                    
NuGet\Install-Package Upwork -Version 0.0.0-dev.5
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Upwork" Version="0.0.0-dev.5" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Upwork" Version="0.0.0-dev.5" />
                    
Directory.Packages.props
<PackageReference Include="Upwork" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Upwork --version 0.0.0-dev.5
                    
#r "nuget: Upwork, 0.0.0-dev.5"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Upwork@0.0.0-dev.5
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Upwork&version=0.0.0-dev.5&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Upwork&version=0.0.0-dev.5&prerelease
                    
Install as a Cake Tool

Upwork

Nuget package dotnet License: MIT

C# SDK for the official Upwork GraphQL API, focused on OAuth2 authorization and read-only marketplace job search.

Features

  • OAuth2 Authorization Code Grant and Refresh Token Grant
  • Authorization URL builder for https://www.upwork.com/ab/account-security/oauth2/authorize
  • Token exchange against https://www.upwork.com/api/v3/oauth2/token
  • GraphQL client for https://api.upwork.com/graphql using Authorization: Bearer
  • Optional X-Upwork-API-TenantId header support
  • companySelector helper for organization context discovery
  • marketplaceJobPostingsSearch wrapper with cursor pagination and sort attributes
  • marketplaceJobPostingsContents wrapper for job detail/content lookup
  • Typed DTOs for marketplace job IDs, title, description, published date, ciphertext, annotations, skills, budget/hourly/client/proposal fields when returned by Upwork
  • GraphQL missing-scope exception handling and HTTP 429 rate-limit hooks
  • Source-generated JSON serialization for trimming and NativeAOT compatibility

No mutation helpers are exposed for this OAuth/job-search workflow.

Required Scopes

Select these scopes in the Upwork app/key settings for this read-only job-search use case:

  • Common Entities - Read-Only Access
  • Read marketplace Job Postings

OAuth

Build an authorization URL from environment variables:

using Upwork;

var clientId =
    Environment.GetEnvironmentVariable("UPWORK_CLIENT_ID") is { Length: > 0 } clientIdValue
        ? clientIdValue
        : throw new InvalidOperationException("UPWORK_CLIENT_ID is required.");
var clientSecret =
    Environment.GetEnvironmentVariable("UPWORK_CLIENT_SECRET") is { Length: > 0 } clientSecretValue
        ? clientSecretValue
        : throw new InvalidOperationException("UPWORK_CLIENT_SECRET is required.");
var redirectUri =
    Environment.GetEnvironmentVariable("UPWORK_REDIRECT_URI") is { Length: > 0 } redirectValue
        ? new Uri(redirectValue)
        : throw new InvalidOperationException("UPWORK_REDIRECT_URI is required.");

var oauthConfig = new UpworkOAuthConfig(clientId, clientSecret, redirectUri);
using var oauth = new UpworkOAuthClient(oauthConfig);

var authorizationUrl = oauth.CreateAuthorizationUri(
    state: Environment.GetEnvironmentVariable("UPWORK_OAUTH_STATE"));

Exchange an authorization code:

var authorizationCode =
    Environment.GetEnvironmentVariable("UPWORK_AUTHORIZATION_CODE") is { Length: > 0 } codeValue
        ? codeValue
        : throw new InvalidOperationException("UPWORK_AUTHORIZATION_CODE is required.");

var token = await oauth.ExchangeAuthorizationCodeAsync(authorizationCode);

Refresh an access token:

var refreshToken =
    Environment.GetEnvironmentVariable("UPWORK_REFRESH_TOKEN") is { Length: > 0 } refreshValue
        ? refreshValue
        : throw new InvalidOperationException("UPWORK_REFRESH_TOKEN is required.");

var refreshedToken = await oauth.RefreshTokenAsync(refreshToken);

The SDK returns tokens but does not persist them. Store accessToken, refreshToken, and expiry metadata in your application.

Use a bearer token from your own token store:

using Upwork;

var accessToken =
    Environment.GetEnvironmentVariable("UPWORK_ACCESS_TOKEN") is { Length: > 0 } accessTokenValue
        ? accessTokenValue
        : throw new InvalidOperationException("UPWORK_ACCESS_TOKEN is required.");
var tenantId = Environment.GetEnvironmentVariable("UPWORK_TENANT_ID");

using var client = new UpworkClient(
    new UpworkClientOptions
    {
        AccessToken = accessToken,
        TenantId = string.IsNullOrWhiteSpace(tenantId) ? null : tenantId,
    });

var selector = await client.GetCompanySelectorAsync();

var jobs = await client.SearchMarketplaceJobPostingsAsync(
    UpworkMarketplaceJobFilters.Keywords("dotnet graphql", first: 10) with
    {
        VerifiedPaymentOnly = true,
    },
    sortAttributes:
    [
        new UpworkMarketplaceJobPostingSearchSort(
            UpworkMarketplaceJobPostingSearchSortFields.Recency),
    ]);

var firstJobId = jobs.Edges?
    .Select(edge => edge.Node?.Id)
    .FirstOrDefault(id => id is { Length: > 0 });

if (firstJobId is { Length: > 0 })
{
    var contents = await client.GetMarketplaceJobPostingsContentsAsync([firstJobId]);
}

For backend-owned token storage, implement IUpworkAccessTokenProvider and pass it through UpworkClientOptions.AccessTokenProvider.

Rate Limits

HTTP 429 responses throw UpworkRateLimitException with RetryAfter when Upwork sends that header. To retry, provide an IUpworkRateLimitHandler and set MaxRateLimitRetries.

Migration Notes

  • Replace direct REST/OAuth code with UpworkOAuthConfig and UpworkOAuthClient.
  • Store tokens in your backend database or secret store; the SDK does not persist secrets.
  • Refresh tokens with RefreshTokenAsync before access-token expiry and update stored tokens atomically.
  • Use GetCompanySelectorAsync to discover organization context, then set TenantId when your workflow requires X-Upwork-API-TenantId.
  • Use SearchMarketplaceJobPostingsAsync; do not use deprecated marketplaceJobPostings.
  • Use GetMarketplaceJobPostingsContentsAsync for job content/detail lookup.
  • Handle UpworkMissingScopeException by asking the user/admin to grant Common Entities - Read-Only Access and Read marketplace Job Postings.
  • Handle UpworkRateLimitException or configure IUpworkRateLimitHandler for retry/backoff.
  • Do not log or persist authorization codes, access tokens, refresh tokens, client secrets, or full Authorization headers.

Support

Priority place for bugs: https://github.com/tryAGI/Upwork/issues
Priority place for ideas and general questions: https://github.com/tryAGI/Upwork/discussions

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 112 6/23/2026
0.0.0-dev.7 122 6/23/2026
0.0.0-dev.6 49 6/23/2026
0.0.0-dev.5 52 6/23/2026
0.0.0-dev.4 56 6/23/2026
0.0.0-dev.3 60 6/22/2026
0.0.0-dev.2 66 6/21/2026
0.0.0-dev.1 61 6/21/2026
0.0.0-dev 101 6/21/2026