Skip to content

feat!: ship the v3 generated SDK surface#230

Merged
gjtorikian merged 49 commits intonext-majorfrom
oagen
Apr 18, 2026
Merged

feat!: ship the v3 generated SDK surface#230
gjtorikian merged 49 commits intonext-majorfrom
oagen

Conversation

@gjtorikian
Copy link
Copy Markdown
Contributor

Summary

  • moves the SDK onto the new v3 / OpenAPI-generated surface, including broad service, method, and option-class renames to standardize on List / Create / Get / Update / Delete
  • renames global configuration from WorkOS to WorkOSConfiguration, targets .NET 8, and centralizes auth-related client_id configuration on WorkOSOptions / WorkOSClient
  • moves path identifiers out of option objects and into explicit method arguments, and updates auth URL / AuthKit helpers to build URLs locally and read required client configuration from the client
  • replaces ad hoc idempotency arguments with RequestOptions, adds automatic retries with exponential backoff plus POST auto-idempotency headers, and exposes per-request retry / API key overrides
  • changes non-2xx handling to throw typed ApiError subclasses instead of relying on best-effort response deserialization
  • improves serialization and generated-model fidelity across oneOf / AnyOf, enums, query and form encoding, and regenerated DTO coverage, while preserving hand-maintained helpers like Passwordless, Vault, Actions, Session, PKCE, auth URL builders, and the public client
  • adds docs/V3_MIGRATION_GUIDE.md to document the breaking changes and upgrade path from v2-style integrations

gjtorikian and others added 30 commits April 9, 2026 13:45
…th URL builders, PublicClient

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The quickstart referenced WorkOS.SetApiKey and WorkOS.WorkOSClient, which
do not exist on the current public API. The correct entry point is the
WorkOSConfiguration static class.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously, SSO and User Management services silently fell back to an
empty string ("") when WorkOSOptions.ClientId was not configured, which
led to a confusing 422 from the API rather than a clear client-side
error. Add WorkOSClient.RequireClientId() that throws
InvalidOperationException with an actionable message, document ClientId
on both WorkOSOptions and WorkOSClient, and update generated services to
call RequireClientId() instead of the silent null-coalesce. README gains
a Client ID configuration section.

Tests: every generated service test constructor now also sets
ClientId = "client_test" so services that require it work out of the box.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ListAuditLogConfiguration and ListOrganizationAuditLogsRetention return
single objects, not collections, so the List prefix is misleading.
Rename them (and their tests) to GetAuditLogConfiguration and
GetOrganizationAuditLogsRetention to keep List* reserved for
list/array/paginated responses.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add XML docs to ApiError and each subclass describing which HTTP status
they map to and when the SDK raises them. Expand the README with an
Error handling section (status-to-exception table plus a try/catch
example) and a Retry behavior section that makes explicit that the SDK
does not auto-retry and that callers are expected to wrap calls in their
own retry policy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Passwordless, Vault, Actions, and Session are fully supported services
that don't map to a single REST endpoint and therefore aren't generated
from the OpenAPI spec. Add a Services section to the README so users can
discover them.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The generated enum types previously carried the stock
StringEnumConverter from Newtonsoft.Json, which throws on any enum value
the SDK does not recognize. This means a new value added server-side
immediately breaks existing clients.

Add WorkOSNewtonsoftStringEnumConverter, a Newtonsoft converter that:

  * respects EnumMember(Value = "...") for both read and write,
  * falls back to the enum's zero member (by convention, Unknown) for
    unrecognized strings,
  * handles Nullable<T> enum fields correctly.

All generated enum files now use this converter instead of
StringEnumConverter. The STJ side already had parity behavior via
WorkOSStringEnumConverterFactory and is unchanged. Adds targeted tests
covering both JSON stacks: known round-trip, unknown-value forward
compat, and write-of-Unknown.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AnyOf<T1,T2> and AnyOf<T1,T2,T3> previously serialized to
{"Value": ...} because the JSON stacks only saw a public Value property.
That produced invalid payloads for endpoints that accept free-form
metadata, notably AuditLogEvent.Metadata and
AuditLogEventActor.Metadata (Dictionary<string, AnyOf<string, double,
bool>>), where the server expects scalar JSON values.

Introduce AnyOfJsonConverter (Newtonsoft) and AnyOfJsonConverterFactory
(System.Text.Json). Both:

  * write the inner value directly, and
  * read by trying each type argument in declaration order, constructing
    the AnyOf via its implicit conversion operator.

Attach both converters to AnyOf<,> and AnyOf<,,>. Includes tests for
the common AuditLog metadata shape on both JSON stacks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
BuildUri only appended query strings for GET, and CreateHttpRequestMessage
always sent Options as an HTTP body for non-GET methods. That meant a
DELETE endpoint using query params (e.g. cascade_delete=true on
DeleteOrganizationResource) had its options serialized into a body and
was silently dropped by the server, which reads them from the query
string.

Route DELETE through the same query-string path as GET, and skip body
construction on DELETE. Also teach FlattenQueryParameters about bool so
bool query params serialize as `true`/`false` instead of being dropped.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FlattenQueryParameters previously silently dropped values of most types,
only serializing string, IEnumerable, DateTime, Enum, long, and int.
Options that used bool, double, decimal, short, or DateTimeOffset fields
had those fields omitted from the request — there was no warning and no
error, which made the missing data very difficult to diagnose.

Add cases for DateTimeOffset, bool, short, double, float, decimal, and a
final IConvertible fallback. Use CultureInfo.InvariantCulture everywhere
so hosts with non-US locales don't emit "3,14" instead of "3.14".

CreateHttpContent's form-url-encoded path also previously deserialized
directly into IDictionary<string, string>, which threw JsonException on
any non-string field. Route it through the object-based flattener so it
shares behavior (and stays in sync) with the query-string path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ConstructEvent handles signature verification and is the single entry
point customers use to turn an HTTP payload into a typed Webhook event.
It previously had no direct tests — only the primitives (signature
compute, timestamp parse) were exercised. Add tests for the happy path
plus the failure modes that matter for security:

  * valid signature -> Webhook deserializes correctly
  * tampered payload -> throws
  * wrong secret    -> throws
  * expired timestamp (outside tolerance) -> throws
  * malformed signature header -> throws

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WorkOSEntity<T> was an empty generic marker: it carried no fields,
methods, or behavior, and the original reasons for keeping it
(RawResponse, UnmappedFields) are not on the roadmap. It only added
visual noise and a generic type parameter to every generated entity.

Delete the base class and strip `: WorkOSEntity<Foo>` from every
generated entity file. Generated DTOs are now plain POCOs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
System.Text.Json (STJ) is a supported serializer alongside Newtonsoft,
but until now nothing exercised it directly — the suite only used
Newtonsoft via the runtime client. That meant any drift in the
[STJS.JsonPropertyName] / [STJS.JsonConverter] coverage would have
silently slipped through.

Add a SystemTextJsonSupportTest that locks in:
  * snake_case property mapping on Organization
  * nested list and dictionary deserialization
  * round-trip preserving the wire-format keys
  * enum mapping via WorkOSStringEnumConverterFactory on Connection
  * forward-compat fallback to ConnectionState.Unknown on unknown values
  * basic Webhook envelope deserialization

Also: Webhook.cs (a hand-maintained envelope) was missing all
[STJS.JsonPropertyName] attributes, which broke STJ deserialization for
the very payload customers most often pass through ConstructEvent. Add
the missing annotations.

Document the dual-serializer support in the README.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ListOptions, ListMetadata, WorkOSList<T>, and PaginationOrder are part
of every paginated response in the SDK but lacked
[STJS.JsonPropertyName] / [STJS.JsonConverter] attributes. Under STJ,
that meant `data` and `list_metadata` could not be mapped onto their
properties and `order` was serialized as the C# enum name instead of the
wire value. Add the missing STJ annotations so the runtime pagination
shape behaves identically on both serializers.

Lock the behavior in with two tests: a list-page round trip on
WorkOSList<Organization> and a PaginationOrder enum round trip via
ListOptions.

T05 (enums) and T06 (AnyOf) already added STJ parity for the other
advanced shapes called out by the deep review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
POST /user_management/password_reset *creates* a reset request (sends
the email / mints the token). POST /user_management/password_reset/confirm
is the call that actually resets the password using a valid token. The
generated method names had these inverted: ResetPassword targeted the
create endpoint and ConfirmPasswordReset targeted the reset endpoint.

Rename:

  ResetPassword          -> CreatePasswordReset
  ConfirmPasswordReset   -> ResetPassword

Options classes follow the same swap. Update tests accordingly.
workos-dotnet is greenfield, so no Obsolete shims for the old names.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
POST /user_management/authenticate is a oneOf union over eight grant
types. The emitter previously emitted both the typed AuthenticateWith*
wrappers (one per grant) AND a raw CreateAuthenticate that took an empty
UserManagementCreateAuthenticateOptions. There is no input you can
construct that makes the raw call succeed — every invocation 422s — so
the only thing the public method was good for was confusing callers.

Delete the raw method, its empty options class, and the auto-generated
test that verified the trap. Future calls go through the typed
AuthenticateWith{Password,Code,RefreshToken,MagicAuth,EmailVerification,
Totp,OrganizationSelection,DeviceCode} wrappers, which already carry
the correct grant_type and required fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… shape

The eight AuthenticateWith*Options classes had two recurring problems:

  * required fields (Code, RefreshToken, Email, Password,
    PendingAuthenticationToken, OrganizationId, DeviceCode, etc.) were
    declared as nullable string?, so the C# type system gave callers no
    signal that the field was required and the SDK happily sent
    half-formed payloads to the server.
  * the optional fraud/audit fields the spec accepts on every variant
    (ip_address, device_id, user_agent) were dropped entirely.

Update each options class so that:

  * spec-required exposed params are non-nullable `string ... = default!`,
  * ip_address / device_id / user_agent are present as nullable strings,
  * hidden grant_type / client_id / client_secret remain internal.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three SDK methods named GetAuthorizationUrl / GetLogoutUrl returned
Task/void and issued real HTTP requests to the WorkOS authorize/logout
endpoints. The names imply local URL construction, the endpoints expect
the *user's browser* (not the SDK) to redirect there, and most callers
ended up triggering an unwanted server-side state change.

Add WorkOSClient.BuildRequestUri(WorkOSRequest) that constructs the same
URI the SDK would send, without performing any I/O. Rewrite the four
URL-builder methods (UserManagement.GetAuthorizationUrl,
UserManagement.GetLogoutUrl, SSO.GetAuthorizationUrl, SSO.GetLogoutUrl)
to return the URL as a string. Update their tests to assert URL shape
instead of mocking HTTP.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…Async

The eight AuthenticateWith* methods each duplicated the same 8 lines of
WorkOSRequest construction and MakeAPIRequest call. That made adding a
new grant type a copy-paste exercise and made it easy to drift on
shared invariants (path, method, response type).

Extract a private SendAuthenticateAsync helper that owns the request
build and POST. Each wrapper shrinks to: set grant_type, set client_id
via RequireClientId(), conditionally set client_secret, delegate.

Make ClientSecret inclusion explicit and tested:
  * seven wrappers set ClientSecret = client.ApiKey
  * AuthenticateWithDeviceCode does NOT (public-client device flow)
  * the comment on that wrapper explains why
  * a new AuthenticateWrappersTest asserts each wrapper's wire body
    contains/omits client_secret as appropriate

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two systemic naming misses surfaced from the deep review:

  CreateEndpoints  -> CreateEndpoint   (POST /webhooks/endpoints)
  CreateApplications dropped entirely  (POST /connect/applications is a
                                        union split with two typed
                                        wrappers — the raw method took
                                        an empty options class and was
                                        dead weight)

Rename WebhooksCreateEndpointsOptions to WebhooksCreateEndpointOptions
to match. Delete ConnectCreateApplicationsOptions and the corresponding
TestCreateApplications case, since the raw CreateApplications method is
gone (CreateOAuthApplication / CreateM2MApplication remain).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add EntityRoundTripTest that loads a fixture, deserializes, re-serializes,
and structurally compares the result for representative entities under
both Newtonsoft and System.Text.Json. Catches drift in property naming,
enum mapping, date handling, and nested-object shape that key-only
checks would miss.

The structural compare is permissive about extra keys in the output (the
SDK may include explicit nulls the fixture omits) and treats date
strings as instants so "...000Z" and "...+00:00" are considered equal,
which avoids false positives from serializer-specific date formatting.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HttpMock previously only let tests check that a request was *made* on a
given method+path. The body and query string went uninspected, so a
serializer regression that produced a totally wrong payload would have
passed the suite as long as the URL was right.

Add two HttpMock helpers:
  * AssertRequestBodyJsonContainsAsync(expectedJson) — structural JSON
    subset assertion against the captured request body, with a clear
    JSON-path on failure.
  * AssertQueryParam(key, value) — URL-decoded query-param assertion.

Add RequestPayloadShapeTest demonstrating both helpers against
Organizations.Create and Organizations.List. Used as the pattern
generated service tests should adopt next.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lpers on Service

Every generated service method previously had its own ~8-line WorkOSRequest
build + MakeAPIRequest call: the same boilerplate repeated across ~156
operations. Add the missing transport methods to the Service base so an
emitter can collapse a typical operation to a one-liner like:

    return this.PostAsync<Foo>($"/foo/{id}", options, requestOptions, cancellationToken);

ListAutoPagingAsync is also exposed at the base so paginated wrappers can
delegate without rebuilding the request.

Existing generated methods still work unchanged — switching the emitter
to delegate through the helpers is a follow-up that requires a regen
pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gjtorikian and others added 12 commits April 13, 2026 18:17
ListAutoPagingAsync mutated the caller's own ListOptions instance in
place to advance the `after` cursor, and it never cleared `before`
when doing so. Two concrete bugs:

1. Caller-visible state drift — after iterating, the caller's options
   object now carried a stale `after` cursor from the last page.
2. Page 2 could send both `before` and `after` simultaneously, which
   the API rejects.

Clone the options via a new `BaseOptions.Clone()` virtual (MemberwiseClone
on the concrete subclass) and build a fresh WorkOSRequest so pagination
mutates only our copy. Clear `before` whenever we set `after`.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous flattener serialized options with Newtonsoft, deserialized
back into IDictionary<string, object>, then walked the dict. That
round-trip erased int/short/decimal/float type info (all numbers came
back as long or double), let nested objects fall through to ToString()
soup, and reintroduced bool casing ambiguity every time the spec
evolved.

Walk the options object graph directly via reflection instead. Respect
[JsonProperty] for wire names and DefaultValueHandling.Ignore, respect
[JsonIgnore] for full exclusion, and flatten nested objects with
bracket notation. Dates still serialize in ISO 8601 UTC, enums still
render as their member name, booleans are lowercase, floats use the
round-trip format.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extend HttpMock with two opt-in primitives:

- AssertQueryParams(IDictionary<string,string>): assert every expected
  key/value pair appears on the last captured request's query string
  with an exact value match. Extra keys are tolerated.
- MockResponseExact(method, path, query, bodyPredicate, status, body):
  strict variant of MockResponse that only matches when both the query
  params and (optionally) the request body match. Paired with
  MockBehavior.Strict, a mismatched query or body now fails the test
  on SendAsync instead of returning silently.

Keeps the existing MockResponse untouched so generated tests can
migrate in place without a mass rewrite.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cumulative regen from the emitter-side work (P0.1, P0.3, P0.4, P1.8–10,
P2.11–14, P3.15–16) plus the IR-level enum synthesis (P0.2). Notable
user-visible changes:

- Required enum fields skip serializing the Unknown sentinel so unset
  required enums produce a clean missing-field API error instead of
  `"unknown"` on the wire.
- Service methods collapse to one-line GetAsync/PostAsync/… calls on
  the Service base; hundreds of inlined WorkOSRequest blocks removed.
- Duplicate enum classes collapse to canonical names; parent classes
  that only differed by aliased children dedupe further.
- Literal / single-value enum fields ("object": "event", …) emit as
  string with an internal setter and const initializer.
- Dictionary<string, object> fields get typed Get<Field>Attribute<T>
  accessors that work under Newtonsoft and STJ.
- oneOf-of-string-consts (e.g. UserIdentitiesGetItem.provider) now
  materialize as a real enum (UserIdentitiesGetItemProvider) instead
  of collapsing to `object`.
- Generated tests seed required string body/query fields and assert
  against the exact fixture values, catching snake_case regressions.
- URL-builder ops (GetAuthorizationUrl, GetLogoutUrl) get sync URL-
  structure tests instead of awaited HttpMock calls.
- XML doc summaries preserve acronyms (SSO, API, …) and pick the
  right indefinite article.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Make WorkOSClient a partial class and move generated service accessor
properties (backing fields + lazy-init properties) from the hand-
maintained file to WorkOSClient.Generated.cs. The generated partial
class adds XML <summary> docs to each service accessor property.

Hand-maintained services (Passwordless, Vault, Actions, Session) remain
in the hand-maintained file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ect>

Regenerated output from emitter changes that generate synthetic models
and enums for inline definitions in oneOf branches:

- ConnectApplication.RedirectUris: List<object> -> List<ConnectApplicationRedirectUri>
  with typed Uri (string) and Default (bool) properties
- DataIntegrationAccessTokenResponse.AccessToken: object? ->
  DataIntegrationAccessTokenResponseAccessToken with typed fields
  (AccessToken, ExpiresAt, Scopes, MissingScopes)
- DataIntegrationAccessTokenResponse.Error: string? ->
  DataIntegrationAccessTokenResponseError enum
  (NeedsReauthorization, NotInstalled)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Alias classes now emit the model's actual schema description in their
XML <summary> tag instead of "structurally identical to X". The
structural note is preserved in <remarks> with a <see cref> link.

Also adds synthetic models for EventSchema inline objects
(EventSchemaContext, EventSchemaContextActor, EventSchemaData).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Regenerated options classes now emit [System.Obsolete] attributes for
deprecated body fields, matching the existing behavior for deprecated
query parameters.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the sdk-runtime-contract §2 requirements:

- Retry 429 and 5xx responses with exponential backoff + full jitter
- Honor Retry-After header as a minimum delay floor
- Auto-generate Idempotency-Key for POST requests when not provided
- Configurable via WorkOSOptions.MaxRetries (default 2) and
  per-request override via RequestOptions.MaxRetries

Adds test coverage for contract §2, §3, §4, §6:

- Error-path tests for 401, 404, 422, 429, 500, 400
- Pagination tests for empty page and multi-page auto-pagination
- Retry behavior tests (backoff, Retry-After, per-request override)
- Auto-idempotency key generation for POST requests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gjtorikian gjtorikian requested review from a team as code owners April 14, 2026 14:53
@gjtorikian gjtorikian requested review from nicknisi and removed request for a team April 14, 2026 14:53
}
}

// Forward-compat: unknown API enum values deserialize to the zero
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why I believe the unknown enum values need to be 0

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is correct. WorkOSNewtonsoftStringEnumConverter (and the STJ counterpart) fall back to Enum.ToObject(underlying, 0) for unrecognized values — which lands on Unknown because it's the zero member. This gives us forward-compatibility: if the API adds a new enum value the SDK doesn't know about yet, it deserializes cleanly instead of throwing.

[STJS.JsonConverter(typeof(WorkOSStringEnumConverterFactory))]
public enum ApplicationsOrder
{
[EnumMember(Value = "unknown")]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be EnumMember(Value = 0)]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the JSON string that gets sent to/from the API, not the numeric index — so Value = 0 wouldn't compile. The numeric 0 is already implicit (it's the first member). Value = "unknown" just ensures that if this member is ever serialized to JSON, it writes lowercase "unknown" rather than PascalCase "Unknown".

{

/// <summary>Unique identifier for the event.</summary>
[JsonProperty("id")]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sucks that we need to have both attributes on all of these.

Could we go with a convention based approach? Would we ever actually use a different JSON key then the camelcase version of the property name?

// System.Text.Json
var options = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};

// Newtonsoft
var settings = new JsonSerializerSettings
{
    ContractResolver = new CamelCasePropertyNamesContractResolver()
};

Comment thread src/WorkOS.net/Entities/ApiKeyOwner.cs Outdated
@@ -0,0 +1,8 @@
// This file is auto-generated by oagen. Do not edit.

namespace WorkOS
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this for?

@@ -0,0 +1,52 @@
// @oagen-ignore-file
namespace WorkOS
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we consider:

OneOf library
The OneOf NuGet package is the community standard for discriminated unions in C#:
csharppublic Dictionary<string, OneOf<string, double, bool>>? Metadata { get; set; }

Comment thread src/WorkOS.net/Errors/ApiError.cs Outdated
/// status code (e.g. 400, 403, 409) surfaces as an <see cref="ApiError"/> directly.
/// The <see cref="Exception.Message"/> contains the raw response body.
/// </summary>
public class ApiError : Exception
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably call this ApiException instead of ApiError

Comment thread src/WorkOS.net/Errors/ApiError.cs Outdated
}

/// <summary>Thrown when the API rejects the provided credentials (HTTP 401).</summary>
public class AuthenticationError : ApiError
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here... in C# the terminology is Exception

gjtorikian and others added 2 commits April 16, 2026 15:05
…ed JSON, OneOf

- Rename *Error → *Exception (ApiError→ApiException, AuthenticationError→
  AuthenticationException, etc.) to follow C# naming conventions
- Remove per-property [JsonProperty]/[JsonPropertyName] attributes in favor
  of global SnakeCaseNamingStrategy on both Newtonsoft and STJ serializers
- Replace custom AnyOf<T1,T2> with OneOf NuGet package (community standard
  for discriminated unions, supports up to 9 type params)
- Regenerate all entities, options, and tests from updated emitter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Model references are now rewritten to the canonical name via mapTypeRef,
so the empty `class Foo : Bar { }` alias files are no longer needed.
Removes 93 dead entity files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
gjtorikian and others added 5 commits April 16, 2026 20:15
…re catch

- Fix README retry docs (was incorrectly saying SDK doesn't retry)
- Add Async suffix to VaultService and SessionService methods with compat wrappers
- Refactor VaultService to use Service base class helpers (GetAsync/PostAsync/etc.)
- Have SessionService extend Service base class
- Add XML docs to SessionAuthResult and SessionRefreshResult properties
- Fix bare catch in RequestUtilities.FlattenObject → catch (TargetInvocationException)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…refs

- Add Code, ErrorMessage, Errors, and RawBody properties to ApiException,
  parsed best-effort from the JSON response body
- Remove internal spec references (H04-H07, H13) from SessionService doc
- Add tests for structured error parsing (JSON, non-JSON, errors array)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@gjtorikian gjtorikian merged commit f7f8087 into next-major Apr 18, 2026
9 checks passed
@gjtorikian gjtorikian deleted the oagen branch April 18, 2026 22:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants