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
marketplaceJobPosting wrapper for expanded job details
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:
1 2 3 4 5 6 7 8 91011121314151617181920
usingUpwork;varclientId=Environment.GetEnvironmentVariable("UPWORK_CLIENT_ID")is{Length:>0}clientIdValue?clientIdValue:thrownewInvalidOperationException("UPWORK_CLIENT_ID is required.");varclientSecret=Environment.GetEnvironmentVariable("UPWORK_CLIENT_SECRET")is{Length:>0}clientSecretValue?clientSecretValue:thrownewInvalidOperationException("UPWORK_CLIENT_SECRET is required.");varredirectUri=Environment.GetEnvironmentVariable("UPWORK_REDIRECT_URI")is{Length:>0}redirectValue?newUri(redirectValue):thrownewInvalidOperationException("UPWORK_REDIRECT_URI is required.");varoauthConfig=newUpworkOAuthConfig(clientId,clientSecret,redirectUri);usingvaroauth=newUpworkOAuthClient(oauthConfig);varauthorizationUrl=oauth.CreateAuthorizationUri(state:Environment.GetEnvironmentVariable("UPWORK_OAUTH_STATE"));
Exchange an authorization code:
123456
varauthorizationCode=Environment.GetEnvironmentVariable("UPWORK_AUTHORIZATION_CODE")is{Length:>0}codeValue?codeValue:thrownewInvalidOperationException("UPWORK_AUTHORIZATION_CODE is required.");vartoken=awaitoauth.ExchangeAuthorizationCodeAsync(authorizationCode);
Refresh an access token:
123456
varrefreshToken=Environment.GetEnvironmentVariable("UPWORK_REFRESH_TOKEN")is{Length:>0}refreshValue?refreshValue:thrownewInvalidOperationException("UPWORK_REFRESH_TOKEN is required.");varrefreshedToken=awaitoauth.RefreshTokenAsync(refreshToken);
The SDK returns tokens but does not persist them. Store accessToken, refreshToken, and expiry metadata in your application.
usingUpwork;varaccessToken=Environment.GetEnvironmentVariable("UPWORK_ACCESS_TOKEN")is{Length:>0}accessTokenValue?accessTokenValue:thrownewInvalidOperationException("UPWORK_ACCESS_TOKEN is required.");vartenantId=Environment.GetEnvironmentVariable("UPWORK_TENANT_ID");usingvarclient=newUpworkClient(newUpworkClientOptions{AccessToken=accessToken,TenantId=string.IsNullOrWhiteSpace(tenantId)?null:tenantId,});varselector=awaitclient.GetCompanySelectorAsync();varjobs=awaitclient.SearchMarketplaceJobPostingsAsync(UpworkMarketplaceJobFilters.Keywords("dotnet graphql",first:10)with{VerifiedPaymentOnly=true,},sortAttributes:[ new UpworkMarketplaceJobPostingSearchSort( UpworkMarketplaceJobPostingSearchSortFields.Recency), ]);varfirstJobId=jobs.Edges?.Select(edge=>edge.Node?.Id).FirstOrDefault(id=>idis{Length:>0});if(firstJobIdis{Length:>0}){varcontents=awaitclient.GetMarketplaceJobPostingsContentsAsync([firstJobId]);}
Read expanded job fields from search results or direct detail lookup:
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 GetMarketplaceJobPostingAsync for expanded job details, or GetMarketplaceJobPostingsContentsAsync for bulk content 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.