@ovra/ts-sdk 0.5.0 → 0.5.2
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 +84 -426
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,496 +1,154 @@
|
|
|
1
|
-
# Ovra TypeScript
|
|
1
|
+
# Ovra SDK for TypeScript
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@ovra/ts-sdk)
|
|
4
|
+
[](https://www.npmjs.com/package/@ovra/ts-sdk)
|
|
5
|
+
[](https://www.npmjs.com/package/@ovra/ts-sdk)
|
|
5
6
|
|
|
6
|
-
The
|
|
7
|
-
infrastructure for AI agents
|
|
8
|
-
spending intents, settle payments under signed mandates, and reconcile from
|
|
9
|
-
the same typed surface.
|
|
7
|
+
The Ovra SDK for TypeScript provides access to the [Ovra API](https://getovra.com)
|
|
8
|
+
— payment infrastructure for AI agents — from server-side TypeScript or JavaScript.
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
from `apps/api/openapi.json`; releases ship manually for now (see
|
|
13
|
-
[Publishing](#publishing) below — auto-publish is wired but pending
|
|
14
|
-
on GH Actions billing). See the repo
|
|
15
|
-
[CHANGELOG](https://github.com/ovra/ovra/blob/main/CHANGELOG.md) for the
|
|
16
|
-
v0 → v1 migration notes.
|
|
10
|
+
## Documentation
|
|
17
11
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
`ovra_pay`); the AP2 agentic-commerce buy is now the canonical payment
|
|
21
|
-
flow. The `ovra.credentials` namespace is gone — use
|
|
22
|
-
`ovra.agenticCommerce.{search, buy, getOrder, listOrders, verifyOrder,
|
|
23
|
-
refundOrder}` instead. See "Agentic Commerce surface" below.
|
|
12
|
+
Full documentation lives at **[app.getovra.com/docs/quickstart](https://app.getovra.com/docs/quickstart)**.
|
|
13
|
+
Error reference at **[app.getovra.com/docs/errors](https://app.getovra.com/docs/errors)**.
|
|
24
14
|
|
|
25
|
-
##
|
|
15
|
+
## Installation
|
|
26
16
|
|
|
27
17
|
```sh
|
|
28
18
|
npm install @ovra/ts-sdk
|
|
29
19
|
```
|
|
30
20
|
|
|
31
|
-
|
|
32
|
-
Cloudflare Workers, Vercel Edge).
|
|
33
|
-
|
|
34
|
-
## Quickstart
|
|
35
|
-
|
|
36
|
-
Grab an API key at [getovra.com/dashboard](https://getovra.com/dashboard) →
|
|
37
|
-
Settings → API keys, then drop straight into the canonical **one-call buy**:
|
|
21
|
+
## Getting started
|
|
38
22
|
|
|
39
23
|
```ts
|
|
40
24
|
import { Ovra } from "@ovra/ts-sdk";
|
|
41
25
|
|
|
42
|
-
const ovra = new Ovra({
|
|
26
|
+
const ovra = new Ovra({
|
|
27
|
+
apiKey: process.env.OVRA_API_KEY!, // sk_test_… in sandbox, sk_live_… in prod
|
|
28
|
+
});
|
|
43
29
|
|
|
44
|
-
// `ovra.pay()` wraps the AP2 agentic-commerce buy — DPAN mint + the
|
|
45
|
-
// 4-mandate AP2 chain + capture + receipt in one round-trip. Amount is
|
|
46
|
-
// EUR (decimal); the SDK converts to cents on the wire.
|
|
47
30
|
const result = await ovra.pay({
|
|
48
|
-
agentId: "agt_…",
|
|
49
|
-
offerId: "
|
|
50
|
-
merchant: "
|
|
51
|
-
amount:
|
|
52
|
-
purpose: "
|
|
31
|
+
agentId: "agt_…",
|
|
32
|
+
offerId: "aic_openai-1000",
|
|
33
|
+
merchant: "OpenAI",
|
|
34
|
+
amount: 10.0,
|
|
35
|
+
purpose: "Top up OpenAI credits",
|
|
53
36
|
});
|
|
54
37
|
|
|
55
38
|
if (result.status === "completed") {
|
|
56
|
-
console.log(result.orderId
|
|
57
|
-
|
|
58
|
-
console.
|
|
39
|
+
console.log(result.orderId); // cmo_…
|
|
40
|
+
console.log(result.order.amount); // { amount: 1000, currency: "eur" }
|
|
41
|
+
console.log(result.order.authorization.network_auth_code); // "000123"
|
|
59
42
|
}
|
|
60
43
|
```
|
|
61
44
|
|
|
62
|
-
|
|
45
|
+
`ovra.pay()` declares the intent, runs the policy gate, debits the wallet,
|
|
46
|
+
mints the DPAN, signs the 4-mandate AP2 chain, captures with the merchant,
|
|
47
|
+
and returns the order envelope in a single round-trip.
|
|
63
48
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
body: {
|
|
67
|
-
agent_id: "agt_…",
|
|
68
|
-
offer_id: "off_…",
|
|
69
|
-
purpose: "Monthly Notion Team Plan renewal",
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
console.log(order.order_id, order.status);
|
|
73
|
-
```
|
|
49
|
+
Need the lower-level primitives (custom approval UI, manual intent, raw
|
|
50
|
+
generated operations)? See [app.getovra.com/docs/quickstart](https://app.getovra.com/docs/quickstart).
|
|
74
51
|
|
|
75
|
-
|
|
76
|
-
out-of-band capture)? The primitives are still here:
|
|
52
|
+
## Requirements
|
|
77
53
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
// `ovra.policies.list()`. `profile.purpose` is required.
|
|
81
|
-
const { data: agent } = await ovra.agents.create({
|
|
82
|
-
body: {
|
|
83
|
-
name: "Notion Buyer",
|
|
84
|
-
policy: "po_…",
|
|
85
|
-
profile: { purpose: "Monthly Notion Team Plan renewal" },
|
|
86
|
-
},
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
// 2. Declare a spending intent (`agent`, not `agent_id`).
|
|
90
|
-
const { data: intent } = await ovra.intents.create({
|
|
91
|
-
body: {
|
|
92
|
-
agent: agent.id,
|
|
93
|
-
merchant: "Notion",
|
|
94
|
-
amount: { amount: 7900, currency: "eur" }, // €79.00
|
|
95
|
-
purpose: "Monthly Notion Team Plan renewal",
|
|
96
|
-
},
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// 3. Approve the intent (production: this comes from the human
|
|
100
|
-
// approving in the dashboard or a passkey assertion).
|
|
101
|
-
await ovra.intents.approve({ path: { id: intent.id } });
|
|
102
|
-
|
|
103
|
-
// 4. Execute the checkout — `intent` + `target_url` (the merchant URL
|
|
104
|
-
// the DPAN will be submitted to) are both required.
|
|
105
|
-
const { data: result } = await ovra.checkout.execute({
|
|
106
|
-
body: {
|
|
107
|
-
intent: intent.id,
|
|
108
|
-
target_url: "https://www.notion.so/checkout",
|
|
109
|
-
},
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
console.log(result.transaction_id, result.status);
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
Amounts on the wire are integers in the currency's minor units
|
|
116
|
-
(`amount: 7900` = €79.00). Card PAN / CVV are **never** returned — only
|
|
117
|
-
`last4` and a tokenized DPAN reach your code.
|
|
54
|
+
Node.js 20+, or any runtime with global `fetch` (Bun, Deno, Cloudflare
|
|
55
|
+
Workers, Vercel Edge).
|
|
118
56
|
|
|
119
57
|
## Authentication
|
|
120
58
|
|
|
121
|
-
The constructor accepts any Ovra credential prefix. Pick the smallest
|
|
122
|
-
that fits the call site.
|
|
59
|
+
The constructor accepts any Ovra credential prefix. Pick the smallest
|
|
60
|
+
scope that fits the call site.
|
|
123
61
|
|
|
124
|
-
| Prefix
|
|
125
|
-
|
|
|
126
|
-
| `sk_live_…`
|
|
127
|
-
| `sk_test_…`
|
|
128
|
-
| `rk_…`
|
|
129
|
-
| `at_…`
|
|
62
|
+
| Prefix | What | Use when |
|
|
63
|
+
| -------------- | --------------------- | ------------------------------------------------------------------------------ |
|
|
64
|
+
| `sk_live_…` | Full live key | Server-side prod code that mints / rotates / manages. |
|
|
65
|
+
| `sk_test_…` | Full sandbox key | Local dev + CI. Every envelope returns `livemode: false`. |
|
|
66
|
+
| `rk_…` | Restricted key | Read + scoped writes. Blocked from keys / webhooks / billing / policies. |
|
|
67
|
+
| `at_…` | Agent token | Bound to one agent with a hard spend cap. Safe to ship to an agent runtime. |
|
|
130
68
|
|
|
131
69
|
```ts
|
|
132
70
|
const ovra = new Ovra({
|
|
133
71
|
apiKey: process.env.OVRA_API_KEY!,
|
|
134
|
-
|
|
135
|
-
// analytics and to help support trace issues back to your integration.
|
|
136
|
-
appInfo: { name: "AcmeCRM", version: "1.4.2" },
|
|
72
|
+
appInfo: { name: "AcmeCRM", version: "1.4.2" }, // X-Ovra-Client-App
|
|
137
73
|
});
|
|
138
74
|
|
|
139
|
-
//
|
|
140
|
-
//
|
|
141
|
-
// sk_* → "full" | rk_* → "restricted" | at_* → "agent"
|
|
142
|
-
console.log(ovra.keyScope);
|
|
75
|
+
// Dev-time scope hint (not enforced — server is the source of truth):
|
|
76
|
+
console.log(ovra.keyScope); // "full" | "restricted" | "agent" | "unknown"
|
|
143
77
|
```
|
|
144
78
|
|
|
145
|
-
Test-mode keys produce objects with `livemode: false`; live keys produce
|
|
146
|
-
`livemode: true`. Whenever the API is deployed against the sandbox card
|
|
147
|
-
issuer, **every** response is `livemode: false` regardless of key — the
|
|
148
|
-
whole workspace is sandbox until the real issuer ships.
|
|
149
|
-
|
|
150
|
-
## Money
|
|
151
|
-
|
|
152
|
-
Money is `{ amount, currency }` everywhere on the wire. `amount` is an
|
|
153
|
-
integer in minor units; `currency` is a three-letter ISO code lowercased.
|
|
154
|
-
|
|
155
|
-
| Display | Wire value |
|
|
156
|
-
| ------------ | ----------------------------------------- |
|
|
157
|
-
| €50.00 | `{ amount: 5000, currency: "eur" }` |
|
|
158
|
-
| €0.99 | `{ amount: 99, currency: "eur" }` |
|
|
159
|
-
| £1,234.56 | `{ amount: 123456, currency: "gbp" }` |
|
|
160
|
-
| ¥1,000 (JPY) | `{ amount: 1000, currency: "jpy" }` (JPY has no minor units; treat the integer as the whole-yen amount.) |
|
|
161
|
-
|
|
162
|
-
Floats and decimal strings are rejected at the validation layer. There is
|
|
163
|
-
no implicit unit conversion — what you send is what gets compared against
|
|
164
|
-
the policy.
|
|
165
|
-
|
|
166
|
-
## Timestamps
|
|
167
|
-
|
|
168
|
-
Every timestamp is an ISO 8601 string in the `created` field
|
|
169
|
-
(`"2026-05-25T18:04:16Z"`). The legacy `created_at` / `createdAt` / epoch
|
|
170
|
-
millis variants are gone.
|
|
171
|
-
|
|
172
79
|
## Errors
|
|
173
80
|
|
|
174
|
-
|
|
175
|
-
|
|
81
|
+
Errors are typed exceptions per category — branch on `e.code` (machine-
|
|
82
|
+
readable), show `e.message` to humans, link to `e.docUrl` from your own UI.
|
|
176
83
|
|
|
177
84
|
```ts
|
|
178
|
-
import {
|
|
179
|
-
Ovra,
|
|
180
|
-
OvraError,
|
|
181
|
-
OvraNotFoundError,
|
|
182
|
-
OvraRateLimitError,
|
|
183
|
-
} from "@ovra/ts-sdk";
|
|
85
|
+
import { OvraError } from "@ovra/ts-sdk";
|
|
184
86
|
|
|
185
87
|
try {
|
|
186
|
-
await ovra.
|
|
187
|
-
} catch (
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
} else {
|
|
196
|
-
throw err;
|
|
88
|
+
await ovra.pay({ agentId, offerId, merchant, amount });
|
|
89
|
+
} catch (e) {
|
|
90
|
+
if (!(e instanceof OvraError)) throw e;
|
|
91
|
+
switch (e.code) {
|
|
92
|
+
case "E_INSUFFICIENT_FUNDS": return topUpAndRetry();
|
|
93
|
+
case "E_POLICY_LIMIT_EXCEEDED": return showLimits(e.message);
|
|
94
|
+
case "E_OFFER_EXPIRED": return refreshOffer();
|
|
95
|
+
case "E_FSM_INVALID_TRANSITION": return refetchOrder();
|
|
96
|
+
default: return reportError(e.message, e.docUrl);
|
|
197
97
|
}
|
|
198
98
|
}
|
|
199
99
|
```
|
|
200
100
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
```json
|
|
204
|
-
{
|
|
205
|
-
"error": {
|
|
206
|
-
"type": "policy_error",
|
|
207
|
-
"code": "E_POLICY_DENIED",
|
|
208
|
-
"message": "Spend exceeds the configured policy limit.",
|
|
209
|
-
"param": "amount",
|
|
210
|
-
"doc_url": "https://docs.getovra.com/errors/E_POLICY_DENIED",
|
|
211
|
-
"request_id": "req_…",
|
|
212
|
-
"status": 403
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
The thrown `OvraError` flattens that envelope onto the instance —
|
|
218
|
-
`err.type`, `err.code`, `err.message`, `err.param`, `err.docUrl`,
|
|
219
|
-
`err.requestId`, `err.status`. Branch on `err.code` — it's typed,
|
|
220
|
-
~50 canonical values, stable across releases. Never parse `err.message`
|
|
221
|
-
— it's a human string and may change.
|
|
101
|
+
Every envelope carries `code`, `type`, `message`, `param`, `docUrl`,
|
|
102
|
+
`requestId`, `status`. Full reference: [app.getovra.com/docs/errors](https://app.getovra.com/docs/errors).
|
|
222
103
|
|
|
223
104
|
## Idempotency
|
|
224
105
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
106
|
+
Every mutating call is idempotent by default — the SDK mints a UUID
|
|
107
|
+
`Idempotency-Key` per logical operation and reuses it across retries.
|
|
108
|
+
Replays inside the 24h dedup window return the original response with
|
|
109
|
+
`X-Idempotent-Replayed: true`. Pass your own key for cross-process dedup:
|
|
229
110
|
|
|
230
111
|
```ts
|
|
231
|
-
await ovra.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
from_wallet: "wal_…",
|
|
235
|
-
to_beneficiary: "ben_…",
|
|
236
|
-
amount: { amount: 250000, currency: "eur" },
|
|
237
|
-
},
|
|
112
|
+
await ovra.intents.create({
|
|
113
|
+
body: { agent: "agt_…", merchant: "Notion", amount: { amount: 7900, currency: "eur" }, purpose: "…" },
|
|
114
|
+
headers: { "Idempotency-Key": "your-stable-key" },
|
|
238
115
|
});
|
|
239
116
|
```
|
|
240
117
|
|
|
241
|
-
|
|
242
|
-
Re-sending the same key with a *different* body returns
|
|
243
|
-
`E_IDEMPOTENCY_CONFLICT` (HTTP 409). An in-flight collision returns
|
|
244
|
-
`E_IDEMPOTENCY_IN_FLIGHT`.
|
|
118
|
+
## MCP server
|
|
245
119
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
directly — the SDK auto-strips hey-api's outer `{data, request, response}`
|
|
250
|
-
wrapper for any `list*` call so you get the list shape verbatim:
|
|
251
|
-
|
|
252
|
-
```ts
|
|
253
|
-
const page1 = await ovra.cards.list({ query: { limit: 50 } });
|
|
254
|
-
// ^? { object: "list", data: Card[], has_more, next_cursor, url }
|
|
255
|
-
|
|
256
|
-
for (const card of page1.data) {
|
|
257
|
-
console.log(card.id, card.last4);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (page1.has_more) {
|
|
261
|
-
const page2 = await ovra.cards.list({
|
|
262
|
-
query: { limit: 50, starting_after: page1.next_cursor! },
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
Or use the `paginate` helper to iterate the full set:
|
|
268
|
-
|
|
269
|
-
```ts
|
|
270
|
-
import { paginate } from "@ovra/ts-sdk";
|
|
271
|
-
|
|
272
|
-
for await (const card of paginate(
|
|
273
|
-
(args) => ovra.cards.list({ query: args }),
|
|
274
|
-
{ limit: 50 },
|
|
275
|
-
)) {
|
|
276
|
-
console.log(card.id, card.last4);
|
|
277
|
-
}
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
The wire envelope (what `page1` deserializes to):
|
|
120
|
+
Want to call Ovra from Claude / Cursor / any MCP client? The same surface
|
|
121
|
+
ships as [`@ovra/mcp`](https://www.npmjs.com/package/@ovra/mcp) (10 tools).
|
|
122
|
+
Add to `.mcp.json`:
|
|
281
123
|
|
|
282
124
|
```json
|
|
283
125
|
{
|
|
284
|
-
"
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
}
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
## Retries
|
|
293
|
-
|
|
294
|
-
Connection errors and 5xx / 429 / 408 responses retry automatically with
|
|
295
|
-
jittered exponential backoff (500 ms → 4 s, 30 s wall-clock budget).
|
|
296
|
-
Tune or disable per-client:
|
|
297
|
-
|
|
298
|
-
```ts
|
|
299
|
-
const ovra = new Ovra({
|
|
300
|
-
apiKey: "…",
|
|
301
|
-
retry: { maxRetries: 5, maxElapsedMs: 60_000 },
|
|
302
|
-
// or: retry: false
|
|
303
|
-
});
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
## Agentic Commerce surface
|
|
307
|
-
|
|
308
|
-
The AP2 sandbox is the canonical buy pipeline as of 0.4.0. Catalog
|
|
309
|
-
discovery, the buy orchestrator, and the UCP-shaped order envelope
|
|
310
|
-
(line_items / fulfillment / adjustments / totals / permalink_url) all
|
|
311
|
-
live under `ovra.agenticCommerce`:
|
|
312
|
-
|
|
313
|
-
```ts
|
|
314
|
-
// Browse the 10-vertical catalog
|
|
315
|
-
const offers = await ovra.agenticCommerce.search({
|
|
316
|
-
body: { vertical: "saas-subscriptions", query: { merchant: "notion", plan: "team" } },
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
// One-call buy — mints DPAN + signs the 4-mandate AP2 chain + captures.
|
|
320
|
-
// The route reads `agent_id`, `offer_id`, `purpose`, optional `card_id`
|
|
321
|
-
// + `intent_id`. Amount + merchant are derived from the offer.
|
|
322
|
-
const order = await ovra.agenticCommerce.buy({
|
|
323
|
-
body: {
|
|
324
|
-
agent_id: "agt_…",
|
|
325
|
-
offer_id: "off_…",
|
|
326
|
-
purpose: "Monthly Notion Team Plan renewal",
|
|
327
|
-
},
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
// Re-verify the AP2 chain against the cached mandate set
|
|
331
|
-
const check = await ovra.agenticCommerce.verifyOrder({
|
|
332
|
-
path: { order_id: order.data.order_id },
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
// UCP-conformant refund — append to adjustments, debit totals, flip
|
|
336
|
-
// status to REFUNDED (full) or COMPENSATED (partial)
|
|
337
|
-
await ovra.agenticCommerce.refundOrder({
|
|
338
|
-
path: { order_id: order.data.order_id },
|
|
339
|
-
body: { amount_cents: 7900, reason: "customer cancelled" },
|
|
340
|
-
});
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
## Resource coverage
|
|
344
|
-
|
|
345
|
-
`Ovra` binds every `/v1/*` resource as an ergonomic namespace —
|
|
346
|
-
33 namespaces, 141 operations total, all of them auto-typed from the
|
|
347
|
-
generated client. Listed by domain:
|
|
348
|
-
|
|
349
|
-
### Money
|
|
350
|
-
|
|
351
|
-
- `wallets` — `list`, `create`, `get`, `update`, `delete`
|
|
352
|
-
- `transfers` — `list`, `create`, `get`
|
|
353
|
-
- `beneficiaries` — `list`, `create`, `get`, `update`, `delete`
|
|
354
|
-
- `refunds` — `list`, `create`, `get`
|
|
355
|
-
- `paymentRequests` — `list`, `create`, `get`, `cancel`
|
|
356
|
-
- `ledgerEntries` — `list`, `get` (double-entry ledger, read-only)
|
|
357
|
-
- `billing` — `getSubscription`, `createSubscription`,
|
|
358
|
-
`createPortalSession`, `getPaymentMethod`, `setupPaymentMethod`,
|
|
359
|
-
`fundWallet`, `getSepaInstructions`, `getBalance`, `collectOverage`
|
|
360
|
-
- `invoices` — `list`, `get`
|
|
361
|
-
|
|
362
|
-
### Agents & spending
|
|
363
|
-
|
|
364
|
-
- `agents` — `list`, `create`, `get`, `update`, `delete`, `freeze`,
|
|
365
|
-
`unfreeze`, `getByExternalId`
|
|
366
|
-
- `cards` — `list`, `create`, `get`, `update`, `freeze`, `unfreeze`,
|
|
367
|
-
`close`, `rotate`
|
|
368
|
-
- `intents` — `list`, `create`, `get`, `approve`, `deny`, `cancel`,
|
|
369
|
-
`issueCredentials`, `confirmPayment`
|
|
370
|
-
- `transactions` — `list`, `get` (read-only)
|
|
371
|
-
- `checkout` — `execute`, `confirm`
|
|
372
|
-
- `agenticCommerce` — `listVerticals`, `search`, `getOffer`,
|
|
373
|
-
`getMerchantJwk`, `buy`, `listOrders`, `getOrder`, `verifyOrder`,
|
|
374
|
-
`refundOrder`
|
|
375
|
-
- `runs` — `create`, `get`
|
|
376
|
-
- `outcomes` — `list`, `create`, `get`
|
|
377
|
-
- `forecast` — `get`
|
|
378
|
-
- `pay()` — one-call buy convenience wrapper around
|
|
379
|
-
`agenticCommerce.buy`
|
|
380
|
-
|
|
381
|
-
### Compliance & governance
|
|
382
|
-
|
|
383
|
-
- `policies` — `list`, `create`, `get`, `update`, `delete`, `apply`
|
|
384
|
-
- `approvalPolicies` — `create`, `get`, `update`, `delete`
|
|
385
|
-
- `disputes` — `list`, `create`, `get`, `resolve`, `listEvidence`,
|
|
386
|
-
`addEvidence` (nested per-dispute)
|
|
387
|
-
- `disputeEvidence` — `list`, `create` (standalone file uploads)
|
|
388
|
-
- `riskEvents` — `list`, `listViolations`, `listFraudAlerts`,
|
|
389
|
-
`updateFraudAlert`, `getAgentRisk`, `unfreezeAgent`, `getConfig`,
|
|
390
|
-
`updateConfig`, `getSummary`
|
|
391
|
-
- `auditEvents` — `list`, `get`, `verifyChain` (tamper-evident)
|
|
392
|
-
- `accessEvents` — `list`, `get`
|
|
393
|
-
- `metrics` — `list`
|
|
394
|
-
|
|
395
|
-
### Identity & access
|
|
396
|
-
|
|
397
|
-
- `customers` — `me`, `create`, `get`, `update`
|
|
398
|
-
- `apiKeys` — `list`, `create`, `get`, `delete` (requires `sk_*` scope)
|
|
399
|
-
- `delegations` — `list`, `create`, `get`, `delete` (mint `at_*` tokens)
|
|
400
|
-
- `passkeys` — `list`, `revoke`
|
|
401
|
-
- `merchantCategories` — `list`, `get` (MCC lookup)
|
|
402
|
-
|
|
403
|
-
### Webhooks & notifications
|
|
404
|
-
|
|
405
|
-
- `webhooks` — `list`, `create`, `get`, `update`, `delete`,
|
|
406
|
-
`rotateSecret`, `ping`, `listDeliveries`
|
|
407
|
-
- `notifications` — `list`, `unreadCount`, `markRead`, `markAllRead`
|
|
408
|
-
(user-scoped — token credential, not `sk_*`)
|
|
409
|
-
- `pushSubscriptions` — `getVapidPublicKey`, `register`, `delete`
|
|
410
|
-
(Web Push for the approve PWA)
|
|
411
|
-
|
|
412
|
-
### Scope requirements
|
|
413
|
-
|
|
414
|
-
Most write namespaces require an `sk_*` workspace key. The following
|
|
415
|
-
will 403 with a restricted (`rk_*`) or agent (`at_*`) token:
|
|
416
|
-
|
|
417
|
-
- `apiKeys.*`, `delegations.create|delete`, `webhooks.*` mutations,
|
|
418
|
-
`policies.*` mutations, `approvalPolicies.*`, `billing.*` mutations,
|
|
419
|
-
`riskEvents.updateConfig|unfreezeAgent`, `customers.create|update`
|
|
420
|
-
|
|
421
|
-
Reads on those surfaces are allowed with `rk_*`. `notifications`,
|
|
422
|
-
`pushSubscriptions`, and `passkeys` are user-scoped — they want a
|
|
423
|
-
session credential, not a workspace key.
|
|
424
|
-
|
|
425
|
-
### Plan requirements
|
|
426
|
-
|
|
427
|
-
A handful of namespaces are gated on the workspace's billing plan and
|
|
428
|
-
will throw `E_PLAN_UPGRADE_REQUIRED` (HTTP 402) or
|
|
429
|
-
`E_PERMISSION_DENIED` (HTTP 403) on Starter:
|
|
430
|
-
|
|
431
|
-
- **Business plan or higher** — `auditEvents.*`, `metrics.*`,
|
|
432
|
-
`riskEvents.*`, `delegations.*`
|
|
433
|
-
|
|
434
|
-
Catch the error and surface the upgrade prompt:
|
|
435
|
-
|
|
436
|
-
```ts
|
|
437
|
-
try {
|
|
438
|
-
await ovra.auditEvents.list();
|
|
439
|
-
} catch (err) {
|
|
440
|
-
if (err instanceof OvraError && err.code === "E_PLAN_UPGRADE_REQUIRED") {
|
|
441
|
-
// route the user to getovra.com/dashboard/billing
|
|
126
|
+
"mcpServers": {
|
|
127
|
+
"ovra": {
|
|
128
|
+
"command": "npx",
|
|
129
|
+
"args": ["-y", "@ovra/mcp"],
|
|
130
|
+
"env": { "OVRA_API_KEY": "sk_test_…" }
|
|
131
|
+
}
|
|
442
132
|
}
|
|
443
133
|
}
|
|
444
134
|
```
|
|
445
135
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
Prefer raw functions over the class? Every operation is also exported
|
|
449
|
-
from `@ovra/ts-sdk/api`:
|
|
450
|
-
|
|
451
|
-
```ts
|
|
452
|
-
import { listApiKeys } from "@ovra/ts-sdk/api";
|
|
453
|
-
const keys = await listApiKeys({ query: { limit: 10 } });
|
|
454
|
-
```
|
|
455
|
-
|
|
456
|
-
The full inventory is in `apps/api/openapi.json` (single source of
|
|
457
|
-
truth — the SDK is regenerated from it on every release).
|
|
458
|
-
|
|
459
|
-
## Publishing
|
|
460
|
-
|
|
461
|
-
> **Status (2026-05-28):** auto-publish via
|
|
462
|
-
> `.github/workflows/sdk-publish.yaml` is wired correctly but
|
|
463
|
-
> **currently blocked at the runner-allocation step** — the GitHub
|
|
464
|
-
> account has a billing issue ("recent account payments have failed
|
|
465
|
-
> or your spending limit needs to be increased"). All workflow runs
|
|
466
|
-
> exit in ~3-5 s before the YAML steps start. Until billing is
|
|
467
|
-
> restored, every release ships via the manual script below; both
|
|
468
|
-
> `0.4.0` and `0.4.1` were published this way.
|
|
136
|
+
## Resource coverage
|
|
469
137
|
|
|
470
|
-
|
|
471
|
-
|
|
138
|
+
33 typed namespaces — agents, cards, wallets, intents, transactions,
|
|
139
|
+
agentic-commerce (search / buy / order / refund / verify), policies,
|
|
140
|
+
delegations, customers, disputes, webhooks, audit, billing, and 20 more.
|
|
141
|
+
Generated functions for every operation are also accessible as raw exports.
|
|
472
142
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
pnpm --filter @ovra/ts-sdk version patch --no-git-tag-version
|
|
476
|
-
|
|
477
|
-
# Then publish (refuses if local <= remote, or if git tree is dirty)
|
|
478
|
-
pnpm run publish:sdk # or: pnpm --filter @ovra/ts-sdk run publish-manual
|
|
479
|
-
pnpm run publish:sdk:dry # dry-run (no upload, full pipeline otherwise)
|
|
480
|
-
```
|
|
143
|
+
See the [API reference](https://app.getovra.com/docs/errors) for the full
|
|
144
|
+
list with scope and plan requirements.
|
|
481
145
|
|
|
482
|
-
|
|
483
|
-
to npm as a publisher on the `@ovra` scope (`npm login`), clean git
|
|
484
|
-
tree, local version strictly greater than the version currently on
|
|
485
|
-
npm.
|
|
146
|
+
## Support
|
|
486
147
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
will regenerate, bump (patch), and publish automatically — the
|
|
491
|
-
workflow is already in place.
|
|
148
|
+
- Documentation: [app.getovra.com/docs/quickstart](https://app.getovra.com/docs/quickstart)
|
|
149
|
+
- Issues: [github.com/ovra/ovra/issues](https://github.com/ovra/ovra/issues)
|
|
150
|
+
- Email: support@getovra.com
|
|
492
151
|
|
|
493
152
|
## License
|
|
494
153
|
|
|
495
|
-
|
|
496
|
-
subscription.
|
|
154
|
+
UNLICENSED — internal use only until the public release lands.
|