Skip to content

feat: one-key client for the new private key - #155

Merged
sydneycollins-cio merged 7 commits into
mainfrom
new-api-keys
Oct 20, 2026
Merged

sydneycollins-cio merged 7 commits into
mainfrom
new-api-keys

Conversation

@sydneycollins-cio

@sydneycollins-cio sydneycollins-cio commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Adds a one-key client for the new secret sk_ API keys.

  • One sk_ key covers Track and the App API, sent as a Bearer token.
  • The region comes from the key (sk_us_… / sk_eu_…).
  • Public pk_ keys are refused for App API calls, since they are for tracking only.
  • Legacy keys work as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sydneycollins-cio

Copy link
Copy Markdown
Contributor Author

Tested locally at this PR's head (5077ea962c) against services 199c4a789c, CDP aa721dbec and edge af983b44, all at main. Keys were made through services' UI API and synced to CDP and edge the normal way. A pass-through proxy recorded the auth scheme and status. CDP ran in strict mode, so a bad key gets an error instead of 200.

Key Call Result
ak_ identify Bearer, 200
ak_ track event Bearer, 200
ak_ send email Bearer, 200 on /v1/send/email
wk_ identify Bearer, 200
wk_ send email refused by the SDK (no transactional access), no request sent
legacy App API key send email Bearer, 200
legacy site ID + API key old Track client identify Basic, 200
revoked ak_ identify 401, SDK reports the key can't use Track

No key showed up in the SDK's output or logs. All passed.

@sydneycollins-cio
sydneycollins-cio marked this pull request as ready for review October 13, 2026 16:35

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread lib/customerio/unified_client.rb
A padded ak_eu_ key passed the empty check, missed the prefix match,
defaulted to US, and went on the wire with the spaces.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@Shahroz16 Shahroz16 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approving from my side. Two small wording bits:

Could we avoid saying every 401/403 means the key can't do something? An IP restriction or a disabled workspace can return those too. The original error is still there, so a more general message should be enough here.

The README's “Legacy App API keys also work here” could use the Track limitation too: legacy keys work for transactional calls, but Bearer Track calls on Pipelines workspaces need a source-bound new key.

That's my take. Let's also get Bernard or the NF team to review this.

sydneycollins-cio and others added 2 commits October 15, 2026 18:12
Read the region from any key shaped {xx}_{us|eu}_ and treat pk_ as the
public key.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd qualify legacy keys in the README

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@sydneycollins-cio

Copy link
Copy Markdown
Contributor Author

The 401/403 message now says the server refused the request and points to the key type, IP restrictions and workspace status, with the original error kept as cause. The README now says legacy keys work for transactional calls and that Bearer Track calls on Pipelines workspaces need a source-bound pk_ or sk_ key (5df2d83).

@Shahroz16 Shahroz16 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good. A few small things, none blocking.

Comment thread lib/customerio/unified_client.rb Outdated
capability,
"The server refused this request to #{CAPABILITY_ACTIONS[capability]} (#{e.code}): #{e.message}. " \
"Check the key type, its IP restrictions and the workspace status.",
e.code

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

code comes through as a String here ("401"), since that's what InvalidResponse holds, while Node and Python give an integer. Could we pass e.code.to_i?

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.

Done in 5865af8. KeyCapabilityError#code is now an Integer (e.code.to_i), and the specs check 401/403 as numbers.

Comment thread lib/customerio/unified_client.rb Outdated
end

def trigger_broadcast(req)
guard(:transactional) { api_client.trigger_broadcast(req) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

A refused broadcast reads …to send transactional messages, since this goes through guard(:transactional). Could broadcasts get their own capability or wording? Node and Python don't have trigger_broadcast on the one-key client, so it might also be worth lining those up.

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.

Done in 5865af8. trigger_broadcast now has its own :broadcast capability, so a refusal reads "trigger broadcasts" for both a pk_ key and a server 401/403. I kept the method rather than dropping it, since that was the smaller change; README and specs are updated.

Comment thread lib/customerio/unified_client.rb Outdated

match = KEY_PREFIX.match(api_key)
@key_kind = match ? match[1].to_sym : :legacy
@region = options[:region] || (match && match[2] == "eu" ? Regions::EU : Regions::US)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If region: disagrees with the key, say an sk_eu_ key with Regions::US, the App API answers with a 301 to beta-api-eu.customer.io and this raises it as an InvalidResponse, while Track answers 401 this key belongs to the eu region. A prefixed key only belongs to one region, so could we raise here when they disagree?

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.

Done in 5865af8. A prefixed key with a different region: now raises ArgumentError in the constructor (region us does not match the eu region of this key, same wording as Python). Legacy keys still take any region, and the README no longer says an explicit region overrides the key.

…use a region that disagrees with the key

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… show the server's reason, and only read pk_/sk_ prefixes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 0f11b4e. Configure here.

Comment thread lib/customerio/unified_client.rb
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@Shahroz16 Shahroz16 left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good to me. Custom regions are accepted, every URL has to be https, and refusals show the server's reason now.

@sydneycollins-cio
sydneycollins-cio merged commit 57799de into main Oct 20, 2026
9 checks passed
@sydneycollins-cio
sydneycollins-cio deleted the new-api-keys branch October 20, 2026 15:57
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