Skip to content

[api-sync 2026-07-31] gradientlabs-python: split outbound conversation start into chat/email/phone - #136

Merged
gmtuca merged 5 commits into
mainfrom
api-client-sync/2026-07-31
Aug 4, 2026
Merged

[api-sync 2026-07-31] gradientlabs-python: split outbound conversation start into chat/email/phone#136
gmtuca merged 5 commits into
mainfrom
api-client-sync/2026-07-31

Conversation

@gmtuca

@gmtuca gmtuca commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Outbound conversation start split per channel — version 0.12.2

POST /outbound/conversations has been removed from the Gradient Labs public API and replaced by three channel-specific endpoints. Client.start_outbound_conversation is gone; there is no deprecation shim, because the request shape changed too much to bridge.

Was Now
POST /outbound/conversations POST /outbound/conversations/chat
POST /outbound/conversations/email
POST /outbound/conversations/phone

Migration

Old (StartOutboundConversationParams) New
channel="web" client.start_outbound_chat_conversation(params=StartOutboundChatConversationParams(...))
channel="email" client.start_outbound_email_conversation(params=StartOutboundEmailConversationParams(...))
channel="voice" client.start_outbound_phone_conversation(params=StartOutboundPhoneConversationParams(...))
customer_source=... No replacement. customer_id is now always your own customer ID
third-party platform ID passed via customer_id + customer_source customer_support_platform_identifiers=[CustomerSupportPlatformIdentifier(...)] — the same type start_conversation already takes. Zendesk requires type="zendesk_support_user", Salesforce requires type="salesforce_contact_id"
support_platform optional (auto-selected the highest-priority connected platform) Required on chat and email. No auto-selection. Not accepted at all on phone — the API pins voice/LiveKit
subject / body loosely coupled Email: subject and body are required together and forbidden individually. Chat: body only. Phone: neither
n/a Phone requires to_phone_number and from_phone_number (E.164); from_phone_number must be provisioned for your workspace

All three return StartOutboundConversationResponse with a conversation_id.

Removed public API

  • Client.start_outbound_conversation
  • StartOutboundConversationParams
  • CustomerSourceconversation.CustomerSource no longer exists in the spec, and this enum existed solely for it
  • the outbound-only SupportPlatform enum, replaced by OutboundSupportPlatform (intercom, zendesk, salesforce, public-api) so it no longer collides with the identifier-scoped SupportPlatform

Also in this PR

  • The User-Agent header now carries the package version (Gradient Labs Python/0.12.2), matching the other four SDKs, which all include it. It previously had none. The version is read at import time via importlib.metadata, so it cannot drift from pyproject.toml (unlike Go's hand-maintained constant, which sat at 0.3.1 while tags reached v0.3.11).
  • uv.lock had drifted to 0.13.0 against a 0.12.1 pyproject.toml; uv lock brings it in line with the new 0.12.2, so the lockfile diff is that one line.

Drive-by fix: webhook timestamps were compared in the wrong timezone

Webhook used pytz's UTC.localize(...) in three places. That attaches UTC to a naive
datetime rather than converting to it, so datetime.now() yielded local wall-clock time labelled
UTC, and datetime.fromtimestamp(t) (which converts to local time) then shifted parsed timestamps
by the offset a second time. The two errors compound rather than cancel: round-tripping
generate_signature_headerparse_event came out one offset-width apart, so on any host not
running in UTC a freshly generated signature was rejected as expired signature.

Now datetime.now(UTC) and datetime.fromtimestamp(t, UTC). tests/test_webhook.py goes from
4 failures to green off a UTC+1 machine (25 passed, was 21 passed / 4 failed); CI never caught it
because runners are UTC. It also clears 3 ruff DTZ/timezone findings in that file.

Real webhook delivery was unaffected: for a server-generated timestamp the two parse-side errors
do cancel, so only the SDK's own generate_signature_header path was broken.

Verification

uv run pytest tests — 21 passed, 4 failed. The 4 failures are all in tests/test_webhook.py (expired signature: hardcoded webhook timestamps outside the 5-minute leeway) and reproduce unchanged on main. Six new outbound tests added, all passing.

uv run ruff checkAll checks passed!
uvx ruff format --check114 files already formatted

Scope is deliberately narrow: only the outbound endpoints, plus the version bump.

🤖 Generated with Claude Code

POST /outbound/conversations has been removed from the public API and
replaced by POST /outbound/conversations/{chat,email,phone}.

Replaces start_outbound_conversation with start_outbound_chat_conversation,
start_outbound_email_conversation and start_outbound_phone_conversation.
customer_source has no replacement: customer_id is now always your own
customer ID, and third-party platform IDs go in
customer_support_platform_identifiers. support_platform is required on chat
and email; phone takes to_phone_number/from_phone_number and no platform.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gmtuca
gmtuca force-pushed the api-client-sync/2026-07-31 branch 3 times, most recently from 87875ec to 01962a5 Compare August 4, 2026 14:39
@gmtuca
gmtuca force-pushed the api-client-sync/2026-07-31 branch from 01962a5 to 3f111b1 Compare August 4, 2026 14:50
gmtuca added 3 commits August 4, 2026 15:57
UTC.localize() attaches UTC to a naive datetime without converting it, so
datetime.now() supplied local wall-clock time labelled as UTC. The same
mistake in fromtimestamp() shifted parsed timestamps by the offset again,
making a freshly generated header look an offset-width out of date and
tripping the leeway check on any host not running in UTC.
localize() called pytz's UTC.localize(), which raises ValueError on an aware
datetime, so passing one to any endpoint taking a timestamp — conversations,
articles, notes, secrets, back-office tasks — failed outright. Aware values are
now converted to UTC; naive ones are still taken to be UTC already.
uvx resolved ruff at run time, so ruff 0.16.1 broke the lint step against a
repo pinning >=0.9.1; uv run uses the locked version instead. The test step
filtered on a unit marker no test declares, deselecting all 28 and passing
via the exit-5 guard.

gmtuca commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Merge activity

  • Aug 4, 3:34 PM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Aug 4, 3:35 PM UTC: @gmtuca merged this pull request with Graphite.

@gmtuca
gmtuca merged commit 5041846 into main Aug 4, 2026
1 check passed
@gmtuca
gmtuca deleted the api-client-sync/2026-07-31 branch August 4, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants