discovery-engine-api 0.2.62__tar.gz → 0.2.64__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: discovery-engine-api
3
- Version: 0.2.62
3
+ Version: 0.2.64
4
4
  Summary: Python SDK for the Discovery Engine API
5
5
  Project-URL: Homepage, https://www.leap-labs.com
6
6
  Project-URL: Documentation, https://disco.leap-labs.com/llms-full.txt
@@ -25,6 +25,7 @@ Requires-Python: >=3.10
25
25
  Requires-Dist: httpx>=0.24.0
26
26
  Requires-Dist: pydantic>=2.0.0
27
27
  Provides-Extra: dev
28
+ Requires-Dist: asyncpg>=0.27.0; extra == 'dev'
28
29
  Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
29
30
  Requires-Dist: pytest-timeout>=2.2.0; extra == 'dev'
30
31
  Requires-Dist: pytest>=7.0.0; extra == 'dev'
@@ -27,15 +27,24 @@ This is a computational pipeline, not prompt engineering over data. You cannot r
27
27
 
28
28
  ## Getting an API Key
29
29
 
30
- **Programmatic (for agents):** One call to `POST /v1/signup` with an email address. Returns a `disco_` API key immediately. Free tier active on creation (10 credits/month, unlimited public runs).
30
+ **Programmatic (for agents):** Two-step signup send a verification code to the email, then submit it to receive the API key. The email must be real: the code is sent there and must be read to complete signup.
31
31
 
32
32
  ```bash
33
+ # Step 1 — send verification code
33
34
  curl -X POST https://disco.leap-labs.com/v1/signup \
34
35
  -H "Content-Type: application/json" \
35
36
  -d '{"email": "agent@example.com"}'
37
+ # → {"status": "verification_required", "email": "agent@example.com"}
38
+
39
+ # Step 2 — submit code from email to get API key
40
+ curl -X POST https://disco.leap-labs.com/v1/signup/verify \
41
+ -H "Content-Type: application/json" \
42
+ -d '{"email": "agent@example.com", "code": "123456"}'
36
43
  # → {"key": "disco_...", "key_id": "...", "organization_id": "...", "tier": "free_tier", "credits": 10}
37
44
  ```
38
45
 
46
+ Codes expire after 15 minutes. If the email service is unavailable, `POST /v1/signup` returns the API key directly (same shape as the verify response) with no verification step required.
47
+
39
48
  **Manual (for humans):** Sign up at https://disco.leap-labs.com/sign-up, create key at https://disco.leap-labs.com/developers.
40
49
 
41
50
  ## Installation
@@ -54,7 +63,10 @@ from discovery import Engine
54
63
  # If you already have an API key:
55
64
  engine = Engine(api_key="disco_...")
56
65
 
57
- # Or sign up for one (zero-touch, no auth required):
66
+ # Or sign up for one.
67
+ # Sends a code to the email address and prompts for it interactively.
68
+ # Requires a terminal — for fully automated agents, use the two-step REST API
69
+ # in the "Getting an API Key" section above instead.
58
70
  engine = await Engine.signup(email="agent@example.com")
59
71
 
60
72
  # One-call method: submit, poll, and return results automatically
@@ -74,8 +86,8 @@ for pattern in result.patterns:
74
86
  If you need to do other work while Discovery Engine runs (recommended for agent workflows):
75
87
 
76
88
  ```python
77
- # Submit without waiting
78
- run = await engine.run_async(file="data.csv", target_column="outcome", wait=False)
89
+ # Submit and return immediately (wait=False is the default for run_async)
90
+ run = await engine.run_async(file="data.csv", target_column="outcome")
79
91
  print(f"Submitted run {run.run_id}, continuing with other work...")
80
92
 
81
93
  # ... do other things ...
@@ -84,7 +96,9 @@ print(f"Submitted run {run.run_id}, continuing with other work...")
84
96
  result = await engine.wait_for_completion(run.run_id, timeout=1800)
85
97
  ```
86
98
 
87
- This is the preferred pattern for agents. `engine.discover()` is a convenience wrapper that does this internally.
99
+ This is the preferred pattern for agents. `engine.discover()` is a convenience wrapper that does this internally with `wait=True`.
100
+
101
+ **Non-async contexts:** use `engine.discover_sync()` — same signature as `discover()`, runs in a managed event loop.
88
102
 
89
103
  ## Example Output
90
104
 
@@ -277,6 +291,93 @@ engine.discover(
277
291
  - API keys: https://disco.leap-labs.com/developers
278
292
  - Credits: https://disco.leap-labs.com/account
279
293
 
294
+ ## Paying for Credits (Programmatic)
295
+
296
+ Agents can attach a payment method and purchase credits entirely via the API — no browser required.
297
+
298
+ **Step 1 — Get your Stripe publishable key**
299
+
300
+ ```python
301
+ account = await engine.get_account()
302
+ stripe_pk = account["stripe_publishable_key"]
303
+ stripe_customer_id = account["stripe_customer_id"]
304
+ ```
305
+
306
+ Or via REST:
307
+
308
+ ```bash
309
+ curl https://disco.leap-labs.com/v1/account \
310
+ -H "Authorization: Bearer disco_..."
311
+ # → { "stripe_publishable_key": "pk_live_...", "stripe_customer_id": "cus_...", "credits": {...}, ... }
312
+ ```
313
+
314
+ **Step 2 — Tokenize a card using the Stripe API**
315
+
316
+ Use the publishable key to create a Stripe PaymentMethod. Card data goes directly to Stripe — Discovery Engine never sees it.
317
+
318
+ ```python
319
+ import requests
320
+
321
+ pm_response = requests.post(
322
+ "https://api.stripe.com/v1/payment_methods",
323
+ auth=(stripe_pk, ""), # publishable key as username, empty password
324
+ data={
325
+ "type": "card",
326
+ "card[number]": "4242424242424242",
327
+ "card[exp_month]": "12",
328
+ "card[exp_year]": "2028",
329
+ "card[cvc]": "123",
330
+ },
331
+ )
332
+ payment_method_id = pm_response.json()["id"] # "pm_..."
333
+ ```
334
+
335
+ **Step 3 — Attach the payment method**
336
+
337
+ ```python
338
+ result = await engine.add_payment_method(payment_method_id)
339
+ # → {"payment_method_attached": True, "card_last4": "4242", "card_brand": "visa"}
340
+ ```
341
+
342
+ Or via REST:
343
+
344
+ ```bash
345
+ curl -X POST https://disco.leap-labs.com/v1/account/payment-method \
346
+ -H "Authorization: Bearer disco_..." \
347
+ -H "Content-Type: application/json" \
348
+ -d '{"payment_method_id": "pm_..."}'
349
+ ```
350
+
351
+ **Step 4 — Purchase credits**
352
+
353
+ Credits are sold in packs of 20 ($20/pack, $1.00/credit).
354
+
355
+ ```python
356
+ result = await engine.purchase_credits(packs=1)
357
+ # → {"purchased_credits": 20, "total_credits": 30, "charge_amount_usd": 20.0, "stripe_payment_id": "pi_..."}
358
+ ```
359
+
360
+ Or via REST:
361
+
362
+ ```bash
363
+ curl -X POST https://disco.leap-labs.com/v1/account/credits/purchase \
364
+ -H "Authorization: Bearer disco_..." \
365
+ -H "Content-Type: application/json" \
366
+ -d '{"packs": 1}'
367
+ ```
368
+
369
+ **Subscriptions (optional)**
370
+
371
+ For regular usage, subscribe to a paid plan instead of buying packs:
372
+
373
+ ```python
374
+ # Plans: free_tier ($0, 10 cr/mo), tier_1 ($49, 50 cr/mo), tier_2 ($199, 200 cr/mo)
375
+ result = await engine.subscribe(plan="tier_1")
376
+ # → {"plan": "tier_1", "name": "Researcher", "monthly_credits": 50, "price_usd": 49}
377
+ ```
378
+
379
+ Requires a payment method on file. See `GET /v1/plans` for full plan details.
380
+
280
381
  ## Estimate Before Running
281
382
 
282
383
  Before submitting a private analysis, estimate the cost and time:
@@ -1,6 +1,6 @@
1
1
  """Discovery Engine Python SDK."""
2
2
 
3
- __version__ = "0.2.62"
3
+ __version__ = "0.2.64"
4
4
 
5
5
  from discovery.client import Engine
6
6
  from discovery.types import (
@@ -217,9 +217,12 @@ class Engine:
217
217
  ) -> "Engine":
218
218
  """Create an account and return a configured Engine instance.
219
219
 
220
- Zero-touch signup: one call with an email address, returns a fully
221
- provisioned Engine with a ``disco_`` API key. Free tier is active
222
- immediately.
220
+ Sends a 6-digit verification code to the email address. Prompts for
221
+ the code interactively, then provisions the account and returns a
222
+ configured Engine with a ``disco_`` API key.
223
+
224
+ If the email service is unavailable, falls back to direct provisioning
225
+ and returns immediately (no code required).
223
226
 
224
227
  Args:
225
228
  email: Email address for the new account.
@@ -235,16 +238,40 @@ class Engine:
235
238
  body: Dict[str, Any] = {"email": email}
236
239
  if name:
237
240
  body["name"] = name
241
+
238
242
  async with cls._make_anon_client() as client:
239
243
  response = await client.post("/v1/signup", json=body)
240
244
  cls._raise_for_status(response)
241
245
  data = response.json()
242
246
 
243
- engine = cls(api_key=data["key"], quiet=quiet)
247
+ # Direct provisioning fallback (Resend unavailable) — already have the key
248
+ if data.get("key"):
249
+ engine = cls(api_key=data["key"], quiet=quiet)
250
+ if not quiet:
251
+ print(
252
+ f"Account created. Tier: {data.get('tier', 'free_tier')}, "
253
+ f"Credits: {data.get('credits', 0)}"
254
+ )
255
+ return engine
256
+
257
+ # Verification required — prompt for the code
258
+ if not quiet:
259
+ print(f"A verification code has been sent to {data['email']}.")
260
+ code = input("Enter verification code: ").strip()
261
+
262
+ async with cls._make_anon_client() as client:
263
+ verify_response = await client.post(
264
+ "/v1/signup/verify",
265
+ json={"email": email, "code": code},
266
+ )
267
+ cls._raise_for_status(verify_response)
268
+ key_data = verify_response.json()
269
+
270
+ engine = cls(api_key=key_data["key"], quiet=quiet)
244
271
  if not quiet:
245
272
  print(
246
- f"Account created. Tier: {data.get('tier', 'free_tier')}, "
247
- f"Credits: {data.get('credits', 0)}"
273
+ f"Account created. Tier: {key_data.get('tier', 'free_tier')}, "
274
+ f"Credits: {key_data.get('credits', 0)}"
248
275
  )
249
276
  return engine
250
277
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "discovery-engine-api"
3
- version = "0.2.62"
3
+ version = "0.2.64"
4
4
  description = "Python SDK for the Discovery Engine API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -32,6 +32,7 @@ dev = [
32
32
  "pytest>=7.0.0",
33
33
  "pytest-asyncio>=0.21.0",
34
34
  "pytest-timeout>=2.2.0",
35
+ "asyncpg>=0.27.0",
35
36
  ]
36
37
  pandas = [
37
38
  "pandas>=2.0.0",