Skip to content

Commit 22494d9

Browse files
Mlaz-codeclaude
andcommitted
feat(auth): support Bearer token alongside X-API-Key
Add an optional `authMethod` field to `SharpAPIConfig` accepting `'x-api-key'` (default) or `'bearer'`. When `'bearer'` is selected the HTTP client sends `Authorization: Bearer <apiKey>` on REST requests instead of the historical `X-API-Key: <apiKey>` header. Use cases: - Customers fronting the API behind IAM / SSO layers that expect a standard `Authorization` header. - Corporate proxies and API gateways that strip non-standard `X-*` headers but pass `Authorization` through untouched. Back-compat: the `apiKey` constructor argument is unchanged and the default auth method remains `'x-api-key'`, so existing callers see no behavioural change. SSE and WebSocket transports are intentionally unaffected — they already pass the key as the `?api_key=` query parameter because browsers cannot set custom headers on `EventSource` or `WebSocket` connections. Bumps version 0.2.4 → 0.2.5 (additive public API surface). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 63cb959 commit 22494d9

3 files changed

Lines changed: 63 additions & 2 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@sharp-api/client",
3-
"version": "0.2.4",
3+
"version": "0.2.5",
44
"description": "Official TypeScript/JavaScript client for the SharpAPI real-time sports betting odds API",
55
"type": "module",
66
"main": "./dist/index.cjs",

src/index.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,18 +27,46 @@
2727
// Configuration
2828
// =============================================================================
2929

30+
/**
31+
* How the SDK presents the API key on REST requests.
32+
*
33+
* - `'x-api-key'` (default): sends `X-API-Key: <apiKey>`. Matches the
34+
* historical behaviour of the SDK and is the recommended choice for
35+
* most users.
36+
* - `'bearer'`: sends `Authorization: Bearer <apiKey>` instead. Use this
37+
* when the request travels through an IAM / SSO layer or an API
38+
* gateway / corporate proxy that strips custom headers (`X-*`) but
39+
* preserves the standard `Authorization` header.
40+
*
41+
* SSE and WebSocket auth are unaffected by this setting — those
42+
* transports always pass the key as the `?api_key=` query parameter
43+
* because browsers cannot set custom headers on `EventSource` or
44+
* `WebSocket` connections.
45+
*/
46+
export type AuthMethod = 'x-api-key' | 'bearer'
47+
3048
export interface SharpAPIConfig {
3149
/** API key (sk_xxx format) */
3250
apiKey: string
3351
/** Base URL (default: https://api.sharpapi.io) */
3452
baseUrl?: string
3553
/** Request timeout in ms (default: 30000) */
3654
timeout?: number
55+
/**
56+
* Auth header to use on REST requests (default: `'x-api-key'`).
57+
*
58+
* Set to `'bearer'` to send `Authorization: Bearer <apiKey>` instead
59+
* of `X-API-Key: <apiKey>` — useful behind IAM / SSO layers and API
60+
* gateways that strip non-standard headers. SSE and WebSocket
61+
* transports are unaffected (they always use `?api_key=` query).
62+
*/
63+
authMethod?: AuthMethod
3764
}
3865

3966
const DEFAULT_CONFIG = {
4067
baseUrl: 'https://api.sharpapi.io',
4168
timeout: 30000,
69+
authMethod: 'x-api-key' as AuthMethod,
4270
}
4371

4472
// =============================================================================
@@ -536,11 +564,13 @@ class HttpClient {
536564
private _apiKey: string
537565
private baseUrl: string
538566
private timeout: number
567+
private authMethod: AuthMethod
539568

540569
constructor(config: SharpAPIConfig) {
541570
this._apiKey = config.apiKey
542571
this.baseUrl = config.baseUrl || DEFAULT_CONFIG.baseUrl
543572
this.timeout = config.timeout || DEFAULT_CONFIG.timeout
573+
this.authMethod = config.authMethod || DEFAULT_CONFIG.authMethod
544574
}
545575

546576
/** Exposed for StreamResource — SSE URL requires the key as a query param. */
@@ -573,10 +603,14 @@ class HttpClient {
573603
params?: Record<string, unknown>,
574604
): Promise<T> {
575605
const url = this.buildUrl(path, params)
606+
const authHeaders: Record<string, string> =
607+
this.authMethod === 'bearer'
608+
? { Authorization: `Bearer ${this.apiKey}` }
609+
: { 'X-API-Key': this.apiKey }
576610
const init: RequestInit = {
577611
method,
578612
headers: {
579-
'X-API-Key': this.apiKey,
613+
...authHeaders,
580614
'Content-Type': 'application/json',
581615
},
582616
}

tests/client.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,33 @@ describe('OddsResource', () => {
306306
const calledOptions = (globalThis.fetch as ReturnType<typeof vi.fn>).mock
307307
.calls[0][1] as RequestInit
308308
expect(calledOptions.headers).toHaveProperty('X-API-Key', 'sk_test_MY_KEY')
309+
expect(calledOptions.headers).not.toHaveProperty('Authorization')
310+
})
311+
312+
it('sends Bearer token when authMethod is "bearer"', async () => {
313+
globalThis.fetch = mockFetch(ODDS_RESPONSE)
314+
const api = new SharpAPI('sk_test_MY_KEY', { authMethod: 'bearer' })
315+
316+
await api.odds.get()
317+
318+
const calledOptions = (globalThis.fetch as ReturnType<typeof vi.fn>).mock
319+
.calls[0][1] as RequestInit
320+
expect(calledOptions.headers).toHaveProperty(
321+
'Authorization',
322+
'Bearer sk_test_MY_KEY',
323+
)
324+
expect(calledOptions.headers).not.toHaveProperty('X-API-Key')
325+
})
326+
327+
it('defaults to X-API-Key when authMethod omitted', async () => {
328+
globalThis.fetch = mockFetch(ODDS_RESPONSE)
329+
const api = new SharpAPI('sk_test_DEFAULT')
330+
331+
await api.odds.get()
332+
333+
const calledOptions = (globalThis.fetch as ReturnType<typeof vi.fn>).mock
334+
.calls[0][1] as RequestInit
335+
expect(calledOptions.headers).toHaveProperty('X-API-Key', 'sk_test_DEFAULT')
309336
})
310337

311338
it('fetches best odds', async () => {

0 commit comments

Comments
 (0)