@tallion/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,189 @@
1
+ # @tallion/sdk
2
+
3
+ TypeScript SDK for [Tallion](https://tallion.ai) — AI agent spend control.
4
+
5
+ Give your AI agents the ability to make purchases with built-in guardrails: spend limits, approval flows, and full transaction context.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @tallion/sdk
11
+ ```
12
+
13
+ ## Quick start
14
+
15
+ ```typescript
16
+ import { Tally } from "@tallion/sdk";
17
+
18
+ const tally = new Tally({ apiKey: process.env.TALLION_API_KEY! });
19
+ // sk_sandbox_* → sandbox, sk_live_* → production (auto-detected)
20
+ ```
21
+
22
+ ## Authorization
23
+
24
+ Before an agent can spend on behalf of a customer, the customer must authorize it through Tallion's hosted OAuth flow with PKCE.
25
+
26
+ ### 1. Create an authorization URL (server-side)
27
+
28
+ ```typescript
29
+ const { url, state, codeVerifier } = await tally.authorize.createUrl({
30
+ customerIdentifier: "+1234567890", // phone or email
31
+ redirectUrl: "https://yourapp.com/callback",
32
+ scopes: ["purchase", "balance:read"],
33
+ suggestedLimits: {
34
+ maxPerTransaction: 10000, // $100.00 (cents)
35
+ maxPerDay: 50000,
36
+ requireApprovalAbove: 25000,
37
+ },
38
+ });
39
+
40
+ // Store state + codeVerifier in your session, then open `url` as a popup
41
+ ```
42
+
43
+ ### 2. Exchange the code (callback handler)
44
+
45
+ ```typescript
46
+ const { accessToken, refreshToken, customerId, installationId } =
47
+ await tally.authorize.exchangeCode({
48
+ code: req.query.code,
49
+ codeVerifier: storedCodeVerifier,
50
+ });
51
+
52
+ // Store accessToken securely — this is what you pass to purchase/balance calls
53
+ ```
54
+
55
+ ### 3. Refresh tokens
56
+
57
+ ```typescript
58
+ const tokens = await tally.authorize.refreshToken({
59
+ refreshToken: storedRefreshToken,
60
+ });
61
+ ```
62
+
63
+ ### 4. Revoke access
64
+
65
+ ```typescript
66
+ await tally.authorize.revoke(accessToken);
67
+ ```
68
+
69
+ ## Purchases
70
+
71
+ ```typescript
72
+ const tx = await tally.purchase({
73
+ customerToken: accessToken,
74
+ amount: 2500, // $25.00 in cents
75
+ merchant: {
76
+ name: "Delta Airlines",
77
+ mcc: "3000",
78
+ country: "US",
79
+ },
80
+ context: {
81
+ description: "Round-trip flight NYC → LAX, March 15",
82
+ category: "travel",
83
+ lineItems: [{ name: "Economy seat", quantity: 1, price: 2500 }],
84
+ externalReference: "booking-abc-123",
85
+ refundPolicy: "Non-refundable",
86
+ metadata: { flightNumber: "DL 1234" },
87
+ },
88
+ });
89
+
90
+ // tx.status: "approved" | "pending_approval" | "declined"
91
+ console.log(tx.transactionId, tx.status, tx.decisionReason);
92
+ ```
93
+
94
+ ## Balance
95
+
96
+ ```typescript
97
+ const balance = await tally.balance(accessToken);
98
+
99
+ console.log(balance.remaining); // cents remaining in wallet
100
+ console.log(balance.fundingAmount); // total funded
101
+ console.log(balance.spentAmount); // total spent
102
+ ```
103
+
104
+ ## Webhooks
105
+
106
+ Verify incoming webhook signatures using HMAC-SHA256. Works in Node 18+, Deno, Bun, and Cloudflare Workers (uses Web Crypto API).
107
+
108
+ ```typescript
109
+ import { Tally } from "@tallion/sdk";
110
+
111
+ const tally = new Tally({ apiKey: process.env.TALLION_API_KEY! });
112
+
113
+ // In your webhook handler
114
+ app.post("/webhooks/tallion", async (req, res) => {
115
+ const event = await tally.webhooks.verify(
116
+ req.body, // raw body string
117
+ req.headers["x-tally-signature"],
118
+ );
119
+
120
+ switch (event.event) {
121
+ case "transaction.approved":
122
+ // Handle approved transaction
123
+ break;
124
+ case "transaction.declined":
125
+ // Handle declined transaction
126
+ break;
127
+ case "approval.approved":
128
+ // Customer approved a pending transaction
129
+ break;
130
+ case "dispute.created":
131
+ // Customer filed a dispute
132
+ break;
133
+ }
134
+
135
+ res.json({ received: true });
136
+ });
137
+ ```
138
+
139
+ ### Webhook events
140
+
141
+ | Event | Description |
142
+ |---|---|
143
+ | `transaction.approved` | Transaction was approved |
144
+ | `transaction.declined` | Transaction was declined by policy |
145
+ | `transaction.pending_approval` | Transaction requires customer approval |
146
+ | `approval.approved` | Customer approved a pending transaction |
147
+ | `approval.declined` | Customer declined a pending transaction |
148
+ | `approval.expired` | Approval request expired |
149
+ | `dispute.created` | Customer opened a dispute |
150
+ | `dispute.resolved` | Dispute was resolved |
151
+ | `authorization.completed` | Customer completed the OAuth flow |
152
+ | `authorization.revoked` | Customer revoked agent access |
153
+
154
+ ## Error handling
155
+
156
+ ```typescript
157
+ import { Tally, TallionError } from "@tallion/sdk";
158
+
159
+ try {
160
+ const tx = await tally.purchase({ ... });
161
+ } catch (err) {
162
+ if (err instanceof TallionError) {
163
+ console.error(err.status); // HTTP status code
164
+ console.error(err.message); // Error description
165
+ console.error(err.code); // Error code (e.g. "insufficient_funds")
166
+ }
167
+ }
168
+ ```
169
+
170
+ ## Sandbox vs production
171
+
172
+ | | Sandbox | Production |
173
+ |---|---|---|
174
+ | API key prefix | `sk_sandbox_*` | `sk_live_*` |
175
+ | Base URL | `api.sandbox.tallion.ai` | `api.tallion.ai` |
176
+ | SMS verification | Code `123456` always works | Real SMS via Twilio |
177
+ | Transactions | Simulated | Real card network |
178
+ | Funding | Plaid Sandbox institutions | Real bank accounts |
179
+
180
+ The SDK auto-detects the environment from your API key — no configuration needed.
181
+
182
+ ## Requirements
183
+
184
+ - Node.js 18+ (or any runtime with `fetch` and `crypto.subtle`)
185
+ - Zero runtime dependencies
186
+
187
+ ## License
188
+
189
+ MIT
@@ -0,0 +1,193 @@
1
+ interface TallyConfig {
2
+ apiKey: string;
3
+ baseUrl?: string;
4
+ }
5
+ interface CreateAuthUrlOptions {
6
+ customerIdentifier: string;
7
+ redirectUrl: string;
8
+ scopes?: string[];
9
+ codeChallengeMethod?: "S256" | "plain";
10
+ suggestedLimits?: SpendLimits;
11
+ }
12
+ interface AuthUrlResult {
13
+ url: string;
14
+ state: string;
15
+ codeVerifier: string;
16
+ authorizationId: string;
17
+ }
18
+ interface ExchangeCodeOptions {
19
+ code: string;
20
+ codeVerifier: string;
21
+ }
22
+ interface TokenResult {
23
+ accessToken: string;
24
+ refreshToken: string;
25
+ tokenType: string;
26
+ expiresIn: number;
27
+ customerId: string;
28
+ installationId: string;
29
+ }
30
+ interface RefreshTokenOptions {
31
+ refreshToken: string;
32
+ }
33
+ interface PurchaseOptions {
34
+ customerToken: string;
35
+ amount: number;
36
+ currency?: string;
37
+ merchant: MerchantInfo;
38
+ context: TransactionContext;
39
+ walletId?: string;
40
+ }
41
+ interface LegacyPurchaseOptions {
42
+ amount: number;
43
+ merchantName: string;
44
+ merchantMcc?: string;
45
+ merchantCountry?: string;
46
+ currency?: string;
47
+ walletId?: string;
48
+ reasoning?: string;
49
+ installationId: string;
50
+ }
51
+ interface MerchantInfo {
52
+ name: string;
53
+ mcc?: string;
54
+ country?: string;
55
+ }
56
+ interface TransactionContext {
57
+ description: string;
58
+ category: string;
59
+ lineItems?: LineItem[];
60
+ externalReference?: string;
61
+ refundPolicy?: string;
62
+ metadata?: Record<string, unknown>;
63
+ }
64
+ interface LineItem {
65
+ name: string;
66
+ quantity: number;
67
+ price: number;
68
+ }
69
+ interface PurchaseResult {
70
+ transactionId: string;
71
+ status: "approved" | "pending_approval" | "declined";
72
+ decision: string;
73
+ decisionReason: string;
74
+ amount: number;
75
+ merchantName: string;
76
+ approvalDeadline?: string;
77
+ }
78
+ interface BalanceResult {
79
+ walletId: string;
80
+ fundingAmount: number;
81
+ spentAmount: number;
82
+ remaining: number;
83
+ }
84
+ interface SpendLimits {
85
+ maxPerTransaction?: number;
86
+ maxPerDay?: number;
87
+ maxPerMonth?: number;
88
+ requireApprovalAbove?: number;
89
+ }
90
+ interface WebhookEvent {
91
+ event: string;
92
+ timestamp: string;
93
+ data: Record<string, unknown>;
94
+ }
95
+
96
+ declare class AuthorizeModule {
97
+ private baseUrl;
98
+ private apiKey;
99
+ constructor(baseUrl: string, apiKey: string);
100
+ /**
101
+ * Create an authorization URL for customer consent.
102
+ * Returns the URL to open in a popup/browser plus the PKCE code verifier.
103
+ */
104
+ createUrl(options: CreateAuthUrlOptions): Promise<AuthUrlResult>;
105
+ /**
106
+ * Exchange an authorization code for access + refresh tokens.
107
+ */
108
+ exchangeCode(options: ExchangeCodeOptions): Promise<TokenResult>;
109
+ /**
110
+ * Refresh an access token using a refresh token.
111
+ */
112
+ refreshToken(options: RefreshTokenOptions): Promise<TokenResult>;
113
+ /**
114
+ * Revoke an access or refresh token.
115
+ */
116
+ revoke(token: string): Promise<void>;
117
+ }
118
+
119
+ declare class BalanceModule {
120
+ private baseUrl;
121
+ private apiKey;
122
+ constructor(baseUrl: string, apiKey: string);
123
+ /**
124
+ * Get wallet balance for a customer (using OAuth token).
125
+ */
126
+ get(customerToken: string, walletId?: string): Promise<BalanceResult>;
127
+ }
128
+
129
+ declare class PurchaseModule {
130
+ private baseUrl;
131
+ private apiKey;
132
+ constructor(baseUrl: string, apiKey: string);
133
+ /**
134
+ * Make a purchase using an OAuth customer token.
135
+ */
136
+ create(options: PurchaseOptions): Promise<PurchaseResult>;
137
+ /**
138
+ * Make a purchase using the legacy API key + installation ID auth.
139
+ */
140
+ legacyCreate(options: LegacyPurchaseOptions): Promise<PurchaseResult>;
141
+ }
142
+
143
+ declare class WebhooksModule {
144
+ private secret?;
145
+ constructor(secret?: string | undefined);
146
+ /**
147
+ * Verify a webhook signature and parse the event.
148
+ * Uses Web Crypto API (works in Node 18+, Deno, Bun, Cloudflare Workers, etc.)
149
+ *
150
+ * @param body - Raw request body string
151
+ * @param signature - Value of X-Tally-Signature header
152
+ * @param tolerance - Max age in seconds (default: 300 = 5 minutes)
153
+ */
154
+ verify(body: string, signature: string, tolerance?: number): Promise<WebhookEvent>;
155
+ }
156
+
157
+ declare class Tally {
158
+ private baseUrl;
159
+ private apiKey;
160
+ /** OAuth authorization flow */
161
+ authorize: AuthorizeModule;
162
+ /** Purchase operations */
163
+ purchases: PurchaseModule;
164
+ /** Balance operations */
165
+ balances: BalanceModule;
166
+ /** Webhook signature verification */
167
+ webhooks: WebhooksModule;
168
+ constructor(config: TallyConfig);
169
+ /**
170
+ * Convenience method: Make a purchase (delegates to purchases.create).
171
+ */
172
+ purchase(options: PurchaseOptions): Promise<PurchaseResult>;
173
+ /**
174
+ * Convenience method: Get balance for a customer.
175
+ */
176
+ balance(customerToken: string, walletId?: string): Promise<BalanceResult>;
177
+ /**
178
+ * Check if this is a sandbox instance.
179
+ */
180
+ get isSandbox(): boolean;
181
+ /**
182
+ * Get the resolved base URL.
183
+ */
184
+ get url(): string;
185
+ }
186
+
187
+ declare class TallionError extends Error {
188
+ readonly status: number;
189
+ readonly code?: string;
190
+ constructor(status: number, message: string, code?: string);
191
+ }
192
+
193
+ export { type AuthUrlResult, AuthorizeModule, BalanceModule, type BalanceResult, type CreateAuthUrlOptions, type ExchangeCodeOptions, type LegacyPurchaseOptions, type LineItem, type MerchantInfo, PurchaseModule, type PurchaseOptions, type PurchaseResult, type RefreshTokenOptions, type SpendLimits, TallionError, Tally, type TallyConfig, type TokenResult, type TransactionContext, type WebhookEvent, WebhooksModule };
@@ -0,0 +1,193 @@
1
+ interface TallyConfig {
2
+ apiKey: string;
3
+ baseUrl?: string;
4
+ }
5
+ interface CreateAuthUrlOptions {
6
+ customerIdentifier: string;
7
+ redirectUrl: string;
8
+ scopes?: string[];
9
+ codeChallengeMethod?: "S256" | "plain";
10
+ suggestedLimits?: SpendLimits;
11
+ }
12
+ interface AuthUrlResult {
13
+ url: string;
14
+ state: string;
15
+ codeVerifier: string;
16
+ authorizationId: string;
17
+ }
18
+ interface ExchangeCodeOptions {
19
+ code: string;
20
+ codeVerifier: string;
21
+ }
22
+ interface TokenResult {
23
+ accessToken: string;
24
+ refreshToken: string;
25
+ tokenType: string;
26
+ expiresIn: number;
27
+ customerId: string;
28
+ installationId: string;
29
+ }
30
+ interface RefreshTokenOptions {
31
+ refreshToken: string;
32
+ }
33
+ interface PurchaseOptions {
34
+ customerToken: string;
35
+ amount: number;
36
+ currency?: string;
37
+ merchant: MerchantInfo;
38
+ context: TransactionContext;
39
+ walletId?: string;
40
+ }
41
+ interface LegacyPurchaseOptions {
42
+ amount: number;
43
+ merchantName: string;
44
+ merchantMcc?: string;
45
+ merchantCountry?: string;
46
+ currency?: string;
47
+ walletId?: string;
48
+ reasoning?: string;
49
+ installationId: string;
50
+ }
51
+ interface MerchantInfo {
52
+ name: string;
53
+ mcc?: string;
54
+ country?: string;
55
+ }
56
+ interface TransactionContext {
57
+ description: string;
58
+ category: string;
59
+ lineItems?: LineItem[];
60
+ externalReference?: string;
61
+ refundPolicy?: string;
62
+ metadata?: Record<string, unknown>;
63
+ }
64
+ interface LineItem {
65
+ name: string;
66
+ quantity: number;
67
+ price: number;
68
+ }
69
+ interface PurchaseResult {
70
+ transactionId: string;
71
+ status: "approved" | "pending_approval" | "declined";
72
+ decision: string;
73
+ decisionReason: string;
74
+ amount: number;
75
+ merchantName: string;
76
+ approvalDeadline?: string;
77
+ }
78
+ interface BalanceResult {
79
+ walletId: string;
80
+ fundingAmount: number;
81
+ spentAmount: number;
82
+ remaining: number;
83
+ }
84
+ interface SpendLimits {
85
+ maxPerTransaction?: number;
86
+ maxPerDay?: number;
87
+ maxPerMonth?: number;
88
+ requireApprovalAbove?: number;
89
+ }
90
+ interface WebhookEvent {
91
+ event: string;
92
+ timestamp: string;
93
+ data: Record<string, unknown>;
94
+ }
95
+
96
+ declare class AuthorizeModule {
97
+ private baseUrl;
98
+ private apiKey;
99
+ constructor(baseUrl: string, apiKey: string);
100
+ /**
101
+ * Create an authorization URL for customer consent.
102
+ * Returns the URL to open in a popup/browser plus the PKCE code verifier.
103
+ */
104
+ createUrl(options: CreateAuthUrlOptions): Promise<AuthUrlResult>;
105
+ /**
106
+ * Exchange an authorization code for access + refresh tokens.
107
+ */
108
+ exchangeCode(options: ExchangeCodeOptions): Promise<TokenResult>;
109
+ /**
110
+ * Refresh an access token using a refresh token.
111
+ */
112
+ refreshToken(options: RefreshTokenOptions): Promise<TokenResult>;
113
+ /**
114
+ * Revoke an access or refresh token.
115
+ */
116
+ revoke(token: string): Promise<void>;
117
+ }
118
+
119
+ declare class BalanceModule {
120
+ private baseUrl;
121
+ private apiKey;
122
+ constructor(baseUrl: string, apiKey: string);
123
+ /**
124
+ * Get wallet balance for a customer (using OAuth token).
125
+ */
126
+ get(customerToken: string, walletId?: string): Promise<BalanceResult>;
127
+ }
128
+
129
+ declare class PurchaseModule {
130
+ private baseUrl;
131
+ private apiKey;
132
+ constructor(baseUrl: string, apiKey: string);
133
+ /**
134
+ * Make a purchase using an OAuth customer token.
135
+ */
136
+ create(options: PurchaseOptions): Promise<PurchaseResult>;
137
+ /**
138
+ * Make a purchase using the legacy API key + installation ID auth.
139
+ */
140
+ legacyCreate(options: LegacyPurchaseOptions): Promise<PurchaseResult>;
141
+ }
142
+
143
+ declare class WebhooksModule {
144
+ private secret?;
145
+ constructor(secret?: string | undefined);
146
+ /**
147
+ * Verify a webhook signature and parse the event.
148
+ * Uses Web Crypto API (works in Node 18+, Deno, Bun, Cloudflare Workers, etc.)
149
+ *
150
+ * @param body - Raw request body string
151
+ * @param signature - Value of X-Tally-Signature header
152
+ * @param tolerance - Max age in seconds (default: 300 = 5 minutes)
153
+ */
154
+ verify(body: string, signature: string, tolerance?: number): Promise<WebhookEvent>;
155
+ }
156
+
157
+ declare class Tally {
158
+ private baseUrl;
159
+ private apiKey;
160
+ /** OAuth authorization flow */
161
+ authorize: AuthorizeModule;
162
+ /** Purchase operations */
163
+ purchases: PurchaseModule;
164
+ /** Balance operations */
165
+ balances: BalanceModule;
166
+ /** Webhook signature verification */
167
+ webhooks: WebhooksModule;
168
+ constructor(config: TallyConfig);
169
+ /**
170
+ * Convenience method: Make a purchase (delegates to purchases.create).
171
+ */
172
+ purchase(options: PurchaseOptions): Promise<PurchaseResult>;
173
+ /**
174
+ * Convenience method: Get balance for a customer.
175
+ */
176
+ balance(customerToken: string, walletId?: string): Promise<BalanceResult>;
177
+ /**
178
+ * Check if this is a sandbox instance.
179
+ */
180
+ get isSandbox(): boolean;
181
+ /**
182
+ * Get the resolved base URL.
183
+ */
184
+ get url(): string;
185
+ }
186
+
187
+ declare class TallionError extends Error {
188
+ readonly status: number;
189
+ readonly code?: string;
190
+ constructor(status: number, message: string, code?: string);
191
+ }
192
+
193
+ export { type AuthUrlResult, AuthorizeModule, BalanceModule, type BalanceResult, type CreateAuthUrlOptions, type ExchangeCodeOptions, type LegacyPurchaseOptions, type LineItem, type MerchantInfo, PurchaseModule, type PurchaseOptions, type PurchaseResult, type RefreshTokenOptions, type SpendLimits, TallionError, Tally, type TallyConfig, type TokenResult, type TransactionContext, type WebhookEvent, WebhooksModule };