@nordsym/apiclaw 1.0.0 → 1.1.1
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/AGENTS.md +74 -0
- package/HEARTBEAT.md +4 -0
- package/IDENTITY.md +22 -0
- package/README.md +193 -202
- package/SOUL.md +36 -0
- package/STATUS.md +237 -0
- package/TOOLS.md +36 -0
- package/USER.md +17 -0
- package/{backend/convex → convex}/_generated/api.d.ts +12 -6
- package/convex/analytics.ts +90 -0
- package/convex/credits.ts +211 -0
- package/convex/http.ts +578 -0
- package/convex/providers.ts +516 -0
- package/convex/purchases.ts +183 -0
- package/convex/ratelimit.ts +104 -0
- package/convex/schema.ts +220 -0
- package/convex/telemetry.ts +81 -0
- package/convex.json +3 -0
- package/dist/credentials.d.ts +19 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +158 -0
- package/dist/credentials.js.map +1 -0
- package/dist/credits.d.ts +14 -11
- package/dist/credits.d.ts.map +1 -1
- package/dist/credits.js +151 -99
- package/dist/credits.js.map +1 -1
- package/dist/discovery.d.ts +7 -16
- package/dist/discovery.d.ts.map +1 -1
- package/dist/discovery.js +33 -40
- package/dist/discovery.js.map +1 -1
- package/dist/execute.d.ts +19 -0
- package/dist/execute.d.ts.map +1 -0
- package/dist/execute.js +285 -0
- package/dist/execute.js.map +1 -0
- package/dist/index.js +175 -31
- package/dist/index.js.map +1 -1
- package/dist/proxy.d.ts +6 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +19 -0
- package/dist/proxy.js.map +1 -0
- package/dist/registry/apis.json +95362 -202
- package/dist/registry/apis_expanded.json +100853 -0
- package/dist/stripe.d.ts +68 -0
- package/dist/stripe.d.ts.map +1 -0
- package/dist/stripe.js +196 -0
- package/dist/stripe.js.map +1 -0
- package/dist/telemetry.d.ts +28 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +50 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/test.d.ts +3 -2
- package/dist/test.d.ts.map +1 -1
- package/dist/test.js +105 -75
- package/dist/test.js.map +1 -1
- package/dist/types.d.ts +0 -28
- package/dist/types.d.ts.map +1 -1
- package/dist/webhook.d.ts +2 -0
- package/dist/webhook.d.ts.map +1 -0
- package/dist/webhook.js +90 -0
- package/dist/webhook.js.map +1 -0
- package/landing/DESIGN.md +343 -0
- package/landing/package-lock.json +1196 -7
- package/landing/package.json +5 -1
- package/landing/public/android-chrome-192x192.png +0 -0
- package/landing/public/android-chrome-512x512.png +0 -0
- package/landing/public/apple-touch-icon.png +0 -0
- package/landing/public/demo.gif +0 -0
- package/landing/public/demo.mp4 +0 -0
- package/landing/public/favicon-16x16.png +0 -0
- package/landing/public/favicon-32x32.png +0 -0
- package/landing/public/favicon.ico +0 -0
- package/landing/public/favicon.svg +3 -0
- package/landing/public/icon.svg +47 -0
- package/landing/public/logo-mono.svg +37 -0
- package/landing/public/logo-simple.svg +45 -0
- package/landing/public/logo.svg +84 -0
- package/landing/public/og-template.html +184 -0
- package/landing/public/site.webmanifest +31 -0
- package/landing/scripts/generate-assets.js +284 -0
- package/landing/scripts/generate-pngs.js +48 -0
- package/landing/scripts/generate-stats.js +42 -0
- package/landing/src/app/admin/page.tsx +348 -0
- package/landing/src/app/api/auth/magic-link/route.ts +73 -0
- package/landing/src/app/api/auth/session/route.ts +38 -0
- package/landing/src/app/api/auth/verify/route.ts +43 -0
- package/landing/src/app/api/og/route.tsx +84 -0
- package/landing/src/app/globals.css +439 -100
- package/landing/src/app/layout.tsx +37 -7
- package/landing/src/app/page.tsx +627 -552
- package/landing/src/app/providers/dashboard/login/page.tsx +176 -0
- package/landing/src/app/providers/dashboard/page.tsx +589 -0
- package/landing/src/app/providers/dashboard/verify/page.tsx +106 -0
- package/landing/src/app/providers/layout.tsx +14 -0
- package/landing/src/app/providers/page.tsx +402 -0
- package/landing/src/app/providers/register/page.tsx +670 -0
- package/landing/src/components/ProviderDashboard.tsx +794 -0
- package/landing/src/hooks/useDashboardData.ts +99 -0
- package/landing/src/lib/apis.json +116054 -0
- package/landing/src/lib/convex-client.ts +106 -0
- package/landing/src/lib/mock-data.ts +285 -0
- package/landing/src/lib/stats.json +6 -0
- package/landing/tailwind.config.ts +12 -11
- package/landing/tsconfig.tsbuildinfo +1 -0
- package/package.json +21 -20
- package/scripts/SYMBOT-FIX.md +238 -0
- package/scripts/demo-simulation.py +177 -0
- package/scripts/expand-more.py +502 -0
- package/scripts/expand-registry.py +434 -0
- package/scripts/history-sanitizer.ts +272 -0
- package/scripts/mass-scrape.py +1308 -0
- package/scripts/sync-and-deploy.sh +36 -0
- package/src/credentials.ts +177 -0
- package/src/credits.ts +190 -122
- package/src/discovery.ts +45 -58
- package/src/execute.ts +350 -0
- package/src/index.ts +184 -32
- package/src/proxy.ts +24 -0
- package/src/registry/apis.json +95362 -202
- package/src/registry/apis_expanded.json +100853 -0
- package/src/stripe.ts +243 -0
- package/src/telemetry.ts +71 -0
- package/src/test.ts +127 -89
- package/src/types.ts +0 -34
- package/src/webhook.ts +107 -0
- package/.github/ISSUE_TEMPLATE/add-api.yml +0 -123
- package/BRIEFING.md +0 -30
- package/backend/convex/apiKeys.ts +0 -75
- package/backend/convex/purchases.ts +0 -74
- package/backend/convex/schema.ts +0 -45
- package/backend/convex/transactions.ts +0 -57
- package/backend/convex/users.ts +0 -94
- package/backend/package-lock.json +0 -521
- package/backend/package.json +0 -15
- package/dist/registry/parse_apis.py +0 -146
- package/dist/revenuecat.d.ts +0 -61
- package/dist/revenuecat.d.ts.map +0 -1
- package/dist/revenuecat.js +0 -166
- package/dist/revenuecat.js.map +0 -1
- package/dist/webhooks/revenuecat.d.ts +0 -48
- package/dist/webhooks/revenuecat.d.ts.map +0 -1
- package/dist/webhooks/revenuecat.js +0 -119
- package/dist/webhooks/revenuecat.js.map +0 -1
- package/docs/revenuecat-setup.md +0 -89
- package/landing/src/app/api/keys/route.ts +0 -71
- package/landing/src/app/api/log/route.ts +0 -37
- package/landing/src/app/api/stats/route.ts +0 -37
- package/landing/src/app/page.tsx.bak +0 -567
- package/landing/src/components/AddKeyModal.tsx +0 -159
- package/newsletter-template.html +0 -71
- package/outreach/OUTREACH-SYSTEM.md +0 -211
- package/outreach/email-template.html +0 -179
- package/outreach/targets.md +0 -133
- package/src/registry/parse_apis.py +0 -146
- package/src/revenuecat.ts +0 -239
- package/src/webhooks/revenuecat.ts +0 -187
- /package/{backend/convex → convex}/README.md +0 -0
- /package/{backend/convex → convex}/_generated/api.js +0 -0
- /package/{backend/convex → convex}/_generated/dataModel.d.ts +0 -0
- /package/{backend/convex → convex}/_generated/server.d.ts +0 -0
- /package/{backend/convex → convex}/_generated/server.js +0 -0
- /package/{backend/convex → convex}/tsconfig.json +0 -0
package/outreach/targets.md
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
# APIClaw Outreach Targets
|
|
2
|
-
|
|
3
|
-
> API providers att kontakta för listing i APIClaw registry
|
|
4
|
-
> Senast uppdaterad: 2026-06-17
|
|
5
|
-
|
|
6
|
-
## 🇸🇪 Sverige / Norden — PRIORITET 1
|
|
7
|
-
|
|
8
|
-
### SMS & Communications
|
|
9
|
-
| Företag | Kategori | API Docs | Kontakt | Status |
|
|
10
|
-
|---------|----------|----------|---------|--------|
|
|
11
|
-
| **46elks** | SMS, Voice | https://46elks.com/docs | hello@46elks.com | ⬜ Pending |
|
|
12
|
-
| **Sinch** | SMS, Voice, Video | https://developers.sinch.com/ | - | ⬜ Pending |
|
|
13
|
-
| **Link Mobility** | SMS, RCS | https://linkmobility.com/developers | - | ⬜ Pending |
|
|
14
|
-
|
|
15
|
-
### Payments & Banking
|
|
16
|
-
| Företag | Kategori | API Docs | Kontakt | Status |
|
|
17
|
-
|---------|----------|----------|---------|--------|
|
|
18
|
-
| **Klarna** | BNPL, Payments | https://docs.klarna.com/ | - | ⬜ Pending |
|
|
19
|
-
| **Trustly** | Bank payments | https://developers.trustly.com/ | - | ⬜ Pending |
|
|
20
|
-
| **Tink** | Open Banking | https://docs.tink.com/ | - | ⬜ Pending |
|
|
21
|
-
| **Open Payments** | PSD2, Banking | https://docs.openpayments.io/ | - | ⬜ Pending |
|
|
22
|
-
| **Neonomics** | Open Banking | https://developer.neonomics.io/ | - | ⬜ Pending |
|
|
23
|
-
| **Nordea API Market** | Banking | https://developer.nordeaopenbanking.com/ | - | ⬜ Pending |
|
|
24
|
-
| **Yapily** | Open Banking | https://docs.yapily.com/ | - | ⬜ Pending |
|
|
25
|
-
|
|
26
|
-
### Identity & Verification
|
|
27
|
-
| Företag | Kategori | API Docs | Kontakt | Status |
|
|
28
|
-
|---------|----------|----------|---------|--------|
|
|
29
|
-
| **BankID** | eID | https://www.bankid.com/utvecklare | - | ⬜ Pending |
|
|
30
|
-
| **Freja eID** | eID | https://org.frejaeid.com/developers | - | ⬜ Pending |
|
|
31
|
-
| **BehavioSec** | Behavioral biometrics | - | - | ⬜ Pending |
|
|
32
|
-
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
## 🌍 Internationella — PRIORITET 2
|
|
36
|
-
|
|
37
|
-
### SMS & Communications
|
|
38
|
-
| Företag | Kategori | API Docs | Kontakt | Status |
|
|
39
|
-
|---------|----------|----------|---------|--------|
|
|
40
|
-
| **Twilio** | SMS, Voice, Video | https://www.twilio.com/docs | - | ⬜ Pending |
|
|
41
|
-
| **Plivo** | SMS, Voice | https://www.plivo.com/docs/ | - | ⬜ Pending |
|
|
42
|
-
| **Infobip** | Omnichannel | https://www.infobip.com/docs | - | ⬜ Pending |
|
|
43
|
-
| **MessageBird (Bird)** | SMS, Email | https://developers.messagebird.com/ | - | ⬜ Pending |
|
|
44
|
-
| **Telnyx** | SMS, Voice | https://developers.telnyx.com/ | - | ⬜ Pending |
|
|
45
|
-
| **Vonage** | Communications | https://developer.vonage.com/ | - | ⬜ Pending |
|
|
46
|
-
|
|
47
|
-
### Email
|
|
48
|
-
| Företag | Kategori | API Docs | Kontakt | Status |
|
|
49
|
-
|---------|----------|----------|---------|--------|
|
|
50
|
-
| **SendGrid** | Transactional email | https://docs.sendgrid.com/ | - | ⬜ Pending |
|
|
51
|
-
| **Mailgun** | Transactional email | https://documentation.mailgun.com/ | - | ⬜ Pending |
|
|
52
|
-
| **Postmark** | Transactional email | https://postmarkapp.com/developer | - | ⬜ Pending |
|
|
53
|
-
| **Mailjet** | Email | https://dev.mailjet.com/ | - | ⬜ Pending |
|
|
54
|
-
| **Resend** | Dev-first email | https://resend.com/docs | - | ⬜ Pending |
|
|
55
|
-
| **Amazon SES** | Email | https://docs.aws.amazon.com/ses/ | - | ⬜ Pending |
|
|
56
|
-
|
|
57
|
-
### Payments
|
|
58
|
-
| Företag | Kategori | API Docs | Kontakt | Status |
|
|
59
|
-
|---------|----------|----------|---------|--------|
|
|
60
|
-
| **Stripe** | Payments | https://stripe.com/docs/api | - | ⬜ Pending |
|
|
61
|
-
| **PayPal** | Payments | https://developer.paypal.com/ | - | ⬜ Pending |
|
|
62
|
-
| **Adyen** | Payments | https://docs.adyen.com/ | - | ⬜ Pending |
|
|
63
|
-
| **Square** | Payments, POS | https://developer.squareup.com/ | - | ⬜ Pending |
|
|
64
|
-
| **Paddle** | SaaS billing | https://developer.paddle.com/ | - | ⬜ Pending |
|
|
65
|
-
| **Lemon Squeezy** | SaaS billing | https://docs.lemonsqueezy.com/ | - | ⬜ Pending |
|
|
66
|
-
|
|
67
|
-
### Search
|
|
68
|
-
| Företag | Kategori | API Docs | Kontakt | Status |
|
|
69
|
-
|---------|----------|----------|---------|--------|
|
|
70
|
-
| **Algolia** | Search-as-a-service | https://www.algolia.com/doc/ | - | ⬜ Pending |
|
|
71
|
-
| **Meilisearch** | Open-source search | https://www.meilisearch.com/docs | - | ⬜ Pending |
|
|
72
|
-
| **Typesense** | Search | https://typesense.org/docs/ | - | ⬜ Pending |
|
|
73
|
-
| **Elasticsearch** | Search/Analytics | https://www.elastic.co/guide/ | - | ⬜ Pending |
|
|
74
|
-
|
|
75
|
-
### AI & Machine Learning
|
|
76
|
-
| Företag | Kategori | API Docs | Kontakt | Status |
|
|
77
|
-
|---------|----------|----------|---------|--------|
|
|
78
|
-
| **OpenAI** | LLM | https://platform.openai.com/docs | - | ⬜ Pending |
|
|
79
|
-
| **Anthropic** | LLM | https://docs.anthropic.com/ | - | ⬜ Pending |
|
|
80
|
-
| **Cohere** | NLP/Embeddings | https://docs.cohere.com/ | - | ⬜ Pending |
|
|
81
|
-
| **Replicate** | ML models | https://replicate.com/docs | - | ⬜ Pending |
|
|
82
|
-
| **Hugging Face** | ML models | https://huggingface.co/docs | - | ⬜ Pending |
|
|
83
|
-
|
|
84
|
-
### Storage & CDN
|
|
85
|
-
| Företag | Kategori | API Docs | Kontakt | Status |
|
|
86
|
-
|---------|----------|----------|---------|--------|
|
|
87
|
-
| **Cloudflare** | CDN, Workers | https://developers.cloudflare.com/ | - | ⬜ Pending |
|
|
88
|
-
| **Cloudinary** | Media management | https://cloudinary.com/documentation | - | ⬜ Pending |
|
|
89
|
-
| **Imgix** | Image CDN | https://docs.imgix.com/ | - | ⬜ Pending |
|
|
90
|
-
| **Uploadcare** | File uploads | https://uploadcare.com/docs/ | - | ⬜ Pending |
|
|
91
|
-
|
|
92
|
-
### Analytics & Data
|
|
93
|
-
| Företag | Kategori | API Docs | Kontakt | Status |
|
|
94
|
-
|---------|----------|----------|---------|--------|
|
|
95
|
-
| **Mixpanel** | Product analytics | https://developer.mixpanel.com/ | - | ⬜ Pending |
|
|
96
|
-
| **Amplitude** | Product analytics | https://www.docs.developers.amplitude.com/ | - | ⬜ Pending |
|
|
97
|
-
| **Segment** | CDP | https://segment.com/docs/ | - | ⬜ Pending |
|
|
98
|
-
| **PostHog** | Open-source analytics | https://posthog.com/docs | - | ⬜ Pending |
|
|
99
|
-
|
|
100
|
-
### Auth & Identity
|
|
101
|
-
| Företag | Kategori | API Docs | Kontakt | Status |
|
|
102
|
-
|---------|----------|----------|---------|--------|
|
|
103
|
-
| **Auth0** | Identity | https://auth0.com/docs | - | ⬜ Pending |
|
|
104
|
-
| **Clerk** | Auth for devs | https://clerk.com/docs | - | ⬜ Pending |
|
|
105
|
-
| **Stytch** | Passwordless auth | https://stytch.com/docs | - | ⬜ Pending |
|
|
106
|
-
| **WorkOS** | Enterprise SSO | https://workos.com/docs | - | ⬜ Pending |
|
|
107
|
-
|
|
108
|
-
---
|
|
109
|
-
|
|
110
|
-
## 📊 Status Legend
|
|
111
|
-
- ⬜ Pending — Inte kontaktad
|
|
112
|
-
- 📧 Sent — Email skickat
|
|
113
|
-
- 👀 Viewed — Öppnat/läst
|
|
114
|
-
- 💬 Replied — Svar mottaget
|
|
115
|
-
- ✅ Listed — Listad i APIClaw
|
|
116
|
-
- ❌ Declined — Avböjt
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## 📝 Notes
|
|
121
|
-
|
|
122
|
-
### Prioritering
|
|
123
|
-
1. **Svenska/Nordiska först** — närmare relation, enklare pitch
|
|
124
|
-
2. **Dev-fokuserade APIs** — förstår värdet av discoverability
|
|
125
|
-
3. **Mindre/nyare APIs** — hungrigare på exposure
|
|
126
|
-
|
|
127
|
-
### Kontakt-strategi
|
|
128
|
-
- Hitta developer relations / partnerships email
|
|
129
|
-
- Alternativt: support@ eller info@
|
|
130
|
-
- LinkedIn outreach som backup
|
|
131
|
-
|
|
132
|
-
### Tracking
|
|
133
|
-
Uppdatera status här ELLER synka till Airtable (TBD).
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Parse public-apis README and convert to APIClaw registry format
|
|
4
|
-
"""
|
|
5
|
-
import re
|
|
6
|
-
import json
|
|
7
|
-
import hashlib
|
|
8
|
-
|
|
9
|
-
# Read the README content
|
|
10
|
-
with open('/tmp/public-apis-readme.md', 'r') as f:
|
|
11
|
-
content = f.read()
|
|
12
|
-
|
|
13
|
-
apis = []
|
|
14
|
-
current_category = None
|
|
15
|
-
|
|
16
|
-
# Split by lines
|
|
17
|
-
lines = content.split('\n')
|
|
18
|
-
|
|
19
|
-
for i, line in enumerate(lines):
|
|
20
|
-
# Check for category headers (### Animals, ### Anime, etc.)
|
|
21
|
-
if line.startswith('### ') and not line.startswith('### API'):
|
|
22
|
-
current_category = line[4:].strip()
|
|
23
|
-
continue
|
|
24
|
-
|
|
25
|
-
# Skip non-table rows
|
|
26
|
-
if not line.startswith('|'):
|
|
27
|
-
continue
|
|
28
|
-
|
|
29
|
-
# Skip header rows and separator rows
|
|
30
|
-
if '---' in line or 'API | Description' in line or 'API|Description' in line:
|
|
31
|
-
continue
|
|
32
|
-
|
|
33
|
-
# Parse table row
|
|
34
|
-
# Format: | [Name](url) | Description | Auth | HTTPS | CORS |
|
|
35
|
-
parts = line.split('|')
|
|
36
|
-
if len(parts) < 5:
|
|
37
|
-
continue
|
|
38
|
-
|
|
39
|
-
# Extract API info
|
|
40
|
-
api_cell = parts[1].strip() if len(parts) > 1 else ''
|
|
41
|
-
desc_cell = parts[2].strip() if len(parts) > 2 else ''
|
|
42
|
-
auth_cell = parts[3].strip() if len(parts) > 3 else ''
|
|
43
|
-
https_cell = parts[4].strip() if len(parts) > 4 else ''
|
|
44
|
-
cors_cell = parts[5].strip() if len(parts) > 5 else ''
|
|
45
|
-
|
|
46
|
-
# Extract name and link from markdown link format [Name](url)
|
|
47
|
-
link_match = re.match(r'\[([^\]]+)\]\(([^)]+)\)', api_cell)
|
|
48
|
-
if not link_match:
|
|
49
|
-
continue
|
|
50
|
-
|
|
51
|
-
name = link_match.group(1)
|
|
52
|
-
link = link_match.group(2)
|
|
53
|
-
|
|
54
|
-
# Generate slug ID
|
|
55
|
-
slug = re.sub(r'[^a-z0-9]+', '-', name.lower()).strip('-')
|
|
56
|
-
|
|
57
|
-
# Parse auth type
|
|
58
|
-
auth = 'None'
|
|
59
|
-
if '`apiKey`' in auth_cell or 'apiKey' in auth_cell:
|
|
60
|
-
auth = 'apiKey'
|
|
61
|
-
elif '`OAuth`' in auth_cell or 'OAuth' in auth_cell:
|
|
62
|
-
auth = 'OAuth'
|
|
63
|
-
elif '`X-Mashape-Key`' in auth_cell:
|
|
64
|
-
auth = 'apiKey'
|
|
65
|
-
elif '`User-Agent`' in auth_cell:
|
|
66
|
-
auth = 'User-Agent'
|
|
67
|
-
elif 'No' in auth_cell:
|
|
68
|
-
auth = 'None'
|
|
69
|
-
|
|
70
|
-
# Parse HTTPS
|
|
71
|
-
https = 'Yes' in https_cell
|
|
72
|
-
|
|
73
|
-
# Parse CORS
|
|
74
|
-
cors = 'unknown'
|
|
75
|
-
if 'Yes' in cors_cell:
|
|
76
|
-
cors = 'yes'
|
|
77
|
-
elif 'No' in cors_cell:
|
|
78
|
-
cors = 'no'
|
|
79
|
-
|
|
80
|
-
# Generate keywords from category and description
|
|
81
|
-
keywords = []
|
|
82
|
-
if current_category:
|
|
83
|
-
keywords.append(current_category.lower().replace(' & ', '-').replace(' ', '-'))
|
|
84
|
-
|
|
85
|
-
# Add some common keywords from description
|
|
86
|
-
desc_lower = desc_cell.lower()
|
|
87
|
-
for keyword in ['data', 'api', 'free', 'real-time', 'json', 'rest', 'graphql']:
|
|
88
|
-
if keyword in desc_lower:
|
|
89
|
-
keywords.append(keyword)
|
|
90
|
-
|
|
91
|
-
api_entry = {
|
|
92
|
-
"id": slug,
|
|
93
|
-
"name": name,
|
|
94
|
-
"description": desc_cell,
|
|
95
|
-
"category": current_category or "Uncategorized",
|
|
96
|
-
"auth": auth,
|
|
97
|
-
"https": https,
|
|
98
|
-
"cors": cors,
|
|
99
|
-
"link": link,
|
|
100
|
-
"pricing": "unknown", # public-apis doesn't have pricing info
|
|
101
|
-
"keywords": list(set(keywords))
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
apis.append(api_entry)
|
|
105
|
-
|
|
106
|
-
# Remove duplicates by ID (keep first occurrence)
|
|
107
|
-
seen_ids = set()
|
|
108
|
-
unique_apis = []
|
|
109
|
-
for api in apis:
|
|
110
|
-
if api['id'] not in seen_ids:
|
|
111
|
-
seen_ids.add(api['id'])
|
|
112
|
-
unique_apis.append(api)
|
|
113
|
-
else:
|
|
114
|
-
# Make ID unique by appending category
|
|
115
|
-
cat_slug = re.sub(r'[^a-z0-9]+', '-', api['category'].lower()).strip('-')
|
|
116
|
-
new_id = f"{api['id']}-{cat_slug}"
|
|
117
|
-
if new_id not in seen_ids:
|
|
118
|
-
api['id'] = new_id
|
|
119
|
-
seen_ids.add(new_id)
|
|
120
|
-
unique_apis.append(api)
|
|
121
|
-
|
|
122
|
-
print(f"Parsed {len(unique_apis)} unique APIs")
|
|
123
|
-
|
|
124
|
-
# Write to JSON
|
|
125
|
-
output = {
|
|
126
|
-
"version": "1.0.0",
|
|
127
|
-
"source": "https://github.com/public-apis/public-apis",
|
|
128
|
-
"lastUpdated": "2026-02-16",
|
|
129
|
-
"count": len(unique_apis),
|
|
130
|
-
"apis": unique_apis
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
with open('/Users/gustavhemmingsson/clawd/products/api-discovery/src/registry/apis.json', 'w') as f:
|
|
134
|
-
json.dump(output, f, indent=2)
|
|
135
|
-
|
|
136
|
-
print(f"Wrote to apis.json")
|
|
137
|
-
|
|
138
|
-
# Print some stats
|
|
139
|
-
categories = {}
|
|
140
|
-
for api in unique_apis:
|
|
141
|
-
cat = api['category']
|
|
142
|
-
categories[cat] = categories.get(cat, 0) + 1
|
|
143
|
-
|
|
144
|
-
print("\nAPIs per category:")
|
|
145
|
-
for cat, count in sorted(categories.items(), key=lambda x: -x[1]):
|
|
146
|
-
print(f" {cat}: {count}")
|
package/src/revenuecat.ts
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
// RevenueCat integration for APIClaw
|
|
2
|
-
// Handles Pro subscription checking and fee calculation
|
|
3
|
-
|
|
4
|
-
const REVENUECAT_API_URL = 'https://api.revenuecat.com/v1';
|
|
5
|
-
const PROJECT_ID = '0d074df4';
|
|
6
|
-
|
|
7
|
-
// Entitlement identifiers
|
|
8
|
-
export const ENTITLEMENTS = {
|
|
9
|
-
PRO: 'pro',
|
|
10
|
-
} as const;
|
|
11
|
-
|
|
12
|
-
// Product identifiers
|
|
13
|
-
export const PRODUCTS = {
|
|
14
|
-
PRO_MONTHLY: 'apiclaw_pro_monthly',
|
|
15
|
-
} as const;
|
|
16
|
-
|
|
17
|
-
// Fee structure
|
|
18
|
-
const FEES = {
|
|
19
|
-
FREE: 0.05, // 5% transaction fee
|
|
20
|
-
PRO: 0.02, // 2% transaction fee
|
|
21
|
-
} as const;
|
|
22
|
-
|
|
23
|
-
interface RevenueCatSubscriber {
|
|
24
|
-
subscriber: {
|
|
25
|
-
entitlements: Record<string, {
|
|
26
|
-
product_identifier: string;
|
|
27
|
-
expires_date: string | null;
|
|
28
|
-
purchase_date: string;
|
|
29
|
-
}>;
|
|
30
|
-
subscriptions: Record<string, {
|
|
31
|
-
expires_date: string | null;
|
|
32
|
-
purchase_date: string;
|
|
33
|
-
product_plan_identifier?: string;
|
|
34
|
-
billing_issues_detected_at?: string | null;
|
|
35
|
-
unsubscribe_detected_at?: string | null;
|
|
36
|
-
}>;
|
|
37
|
-
non_subscriptions: Record<string, unknown>;
|
|
38
|
-
first_seen: string;
|
|
39
|
-
original_app_user_id: string;
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Get RevenueCat API key from environment
|
|
45
|
-
*/
|
|
46
|
-
function getApiKey(): string {
|
|
47
|
-
const key = process.env.REVENUECAT_SECRET_KEY;
|
|
48
|
-
if (!key) {
|
|
49
|
-
throw new Error('REVENUECAT_SECRET_KEY not set');
|
|
50
|
-
}
|
|
51
|
-
return key;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Fetch subscriber info from RevenueCat
|
|
56
|
-
*/
|
|
57
|
-
async function getSubscriber(userId: string): Promise<RevenueCatSubscriber | null> {
|
|
58
|
-
try {
|
|
59
|
-
const response = await fetch(
|
|
60
|
-
`${REVENUECAT_API_URL}/subscribers/${encodeURIComponent(userId)}`,
|
|
61
|
-
{
|
|
62
|
-
headers: {
|
|
63
|
-
'Authorization': `Bearer ${getApiKey()}`,
|
|
64
|
-
'Content-Type': 'application/json',
|
|
65
|
-
},
|
|
66
|
-
}
|
|
67
|
-
);
|
|
68
|
-
|
|
69
|
-
if (response.status === 404) {
|
|
70
|
-
return null; // User doesn't exist in RevenueCat
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (!response.ok) {
|
|
74
|
-
throw new Error(`RevenueCat API error: ${response.status}`);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
return await response.json() as RevenueCatSubscriber;
|
|
78
|
-
} catch (error) {
|
|
79
|
-
console.error('Error fetching subscriber:', error);
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Check if a user has Pro subscription
|
|
86
|
-
*/
|
|
87
|
-
export async function hasProSubscription(userId: string): Promise<boolean> {
|
|
88
|
-
const subscriber = await getSubscriber(userId);
|
|
89
|
-
|
|
90
|
-
if (!subscriber) {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const proEntitlement = subscriber.subscriber.entitlements[ENTITLEMENTS.PRO];
|
|
95
|
-
|
|
96
|
-
if (!proEntitlement) {
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Check if entitlement is active (not expired)
|
|
101
|
-
if (proEntitlement.expires_date) {
|
|
102
|
-
const expiresAt = new Date(proEntitlement.expires_date);
|
|
103
|
-
if (expiresAt < new Date()) {
|
|
104
|
-
return false;
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Get all entitlements for a user
|
|
113
|
-
*/
|
|
114
|
-
export async function getEntitlements(userId: string): Promise<{
|
|
115
|
-
pro: boolean;
|
|
116
|
-
expiresAt: string | null;
|
|
117
|
-
purchaseDate: string | null;
|
|
118
|
-
}> {
|
|
119
|
-
const subscriber = await getSubscriber(userId);
|
|
120
|
-
|
|
121
|
-
const result = {
|
|
122
|
-
pro: false,
|
|
123
|
-
expiresAt: null as string | null,
|
|
124
|
-
purchaseDate: null as string | null,
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
if (!subscriber) {
|
|
128
|
-
return result;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const proEntitlement = subscriber.subscriber.entitlements[ENTITLEMENTS.PRO];
|
|
132
|
-
|
|
133
|
-
if (proEntitlement) {
|
|
134
|
-
const expiresAt = proEntitlement.expires_date
|
|
135
|
-
? new Date(proEntitlement.expires_date)
|
|
136
|
-
: null;
|
|
137
|
-
|
|
138
|
-
result.pro = !expiresAt || expiresAt > new Date();
|
|
139
|
-
result.expiresAt = proEntitlement.expires_date;
|
|
140
|
-
result.purchaseDate = proEntitlement.purchase_date;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return result;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Get fee percentage based on subscription status
|
|
148
|
-
* Pro = 2%, Free = 5%
|
|
149
|
-
*/
|
|
150
|
-
export async function getFeePercentage(userId: string): Promise<number> {
|
|
151
|
-
const isPro = await hasProSubscription(userId);
|
|
152
|
-
return isPro ? FEES.PRO : FEES.FREE;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Calculate platform fee for a transaction
|
|
157
|
-
*/
|
|
158
|
-
export async function calculateFee(userId: string, amountUsd: number): Promise<{
|
|
159
|
-
feePercentage: number;
|
|
160
|
-
feeAmount: number;
|
|
161
|
-
netAmount: number;
|
|
162
|
-
isPro: boolean;
|
|
163
|
-
}> {
|
|
164
|
-
const isPro = await hasProSubscription(userId);
|
|
165
|
-
const feePercentage = isPro ? FEES.PRO : FEES.FREE;
|
|
166
|
-
const feeAmount = amountUsd * feePercentage;
|
|
167
|
-
|
|
168
|
-
return {
|
|
169
|
-
feePercentage,
|
|
170
|
-
feeAmount: Math.round(feeAmount * 100) / 100, // Round to 2 decimals
|
|
171
|
-
netAmount: Math.round((amountUsd - feeAmount) * 100) / 100,
|
|
172
|
-
isPro,
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Create or update subscriber attributes
|
|
178
|
-
*/
|
|
179
|
-
export async function setSubscriberAttributes(
|
|
180
|
-
userId: string,
|
|
181
|
-
attributes: Record<string, string>
|
|
182
|
-
): Promise<boolean> {
|
|
183
|
-
try {
|
|
184
|
-
const formattedAttributes: Record<string, { value: string }> = {};
|
|
185
|
-
for (const [key, value] of Object.entries(attributes)) {
|
|
186
|
-
formattedAttributes[`$${key}`] = { value };
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
const response = await fetch(
|
|
190
|
-
`${REVENUECAT_API_URL}/subscribers/${encodeURIComponent(userId)}/attributes`,
|
|
191
|
-
{
|
|
192
|
-
method: 'POST',
|
|
193
|
-
headers: {
|
|
194
|
-
'Authorization': `Bearer ${getApiKey()}`,
|
|
195
|
-
'Content-Type': 'application/json',
|
|
196
|
-
},
|
|
197
|
-
body: JSON.stringify({ attributes: formattedAttributes }),
|
|
198
|
-
}
|
|
199
|
-
);
|
|
200
|
-
|
|
201
|
-
return response.ok;
|
|
202
|
-
} catch (error) {
|
|
203
|
-
console.error('Error setting subscriber attributes:', error);
|
|
204
|
-
return false;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Grant promotional entitlement (for testing/promos)
|
|
210
|
-
*/
|
|
211
|
-
export async function grantPromoEntitlement(
|
|
212
|
-
userId: string,
|
|
213
|
-
durationDays: number = 30
|
|
214
|
-
): Promise<boolean> {
|
|
215
|
-
try {
|
|
216
|
-
const response = await fetch(
|
|
217
|
-
`${REVENUECAT_API_URL}/subscribers/${encodeURIComponent(userId)}/entitlements/${ENTITLEMENTS.PRO}/promotional`,
|
|
218
|
-
{
|
|
219
|
-
method: 'POST',
|
|
220
|
-
headers: {
|
|
221
|
-
'Authorization': `Bearer ${getApiKey()}`,
|
|
222
|
-
'Content-Type': 'application/json',
|
|
223
|
-
},
|
|
224
|
-
body: JSON.stringify({
|
|
225
|
-
duration: 'daily',
|
|
226
|
-
duration_count: durationDays,
|
|
227
|
-
}),
|
|
228
|
-
}
|
|
229
|
-
);
|
|
230
|
-
|
|
231
|
-
return response.ok;
|
|
232
|
-
} catch (error) {
|
|
233
|
-
console.error('Error granting promo entitlement:', error);
|
|
234
|
-
return false;
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Export types for use elsewhere
|
|
239
|
-
export type { RevenueCatSubscriber };
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
// RevenueCat webhook handler for APIClaw
|
|
2
|
-
// Handles subscription lifecycle events and syncs to Convex
|
|
3
|
-
|
|
4
|
-
import { Hono } from 'hono';
|
|
5
|
-
|
|
6
|
-
const CONVEX_URL = process.env.CONVEX_URL || 'https://agile-crane-840.convex.cloud';
|
|
7
|
-
|
|
8
|
-
// RevenueCat webhook event types
|
|
9
|
-
type WebhookEventType =
|
|
10
|
-
| 'INITIAL_PURCHASE'
|
|
11
|
-
| 'RENEWAL'
|
|
12
|
-
| 'CANCELLATION'
|
|
13
|
-
| 'UNCANCELLATION'
|
|
14
|
-
| 'NON_RENEWING_PURCHASE'
|
|
15
|
-
| 'SUBSCRIPTION_PAUSED'
|
|
16
|
-
| 'SUBSCRIPTION_EXTENDED'
|
|
17
|
-
| 'EXPIRATION'
|
|
18
|
-
| 'BILLING_ISSUE'
|
|
19
|
-
| 'PRODUCT_CHANGE'
|
|
20
|
-
| 'TRANSFER';
|
|
21
|
-
|
|
22
|
-
interface RevenueCatWebhookEvent {
|
|
23
|
-
api_version: string;
|
|
24
|
-
event: {
|
|
25
|
-
type: WebhookEventType;
|
|
26
|
-
id: string;
|
|
27
|
-
app_id: string;
|
|
28
|
-
app_user_id: string;
|
|
29
|
-
original_app_user_id: string;
|
|
30
|
-
aliases: string[];
|
|
31
|
-
product_id: string;
|
|
32
|
-
entitlement_ids: string[];
|
|
33
|
-
period_type: string;
|
|
34
|
-
purchased_at_ms: number;
|
|
35
|
-
expiration_at_ms: number | null;
|
|
36
|
-
environment: 'SANDBOX' | 'PRODUCTION';
|
|
37
|
-
store: string;
|
|
38
|
-
is_trial_conversion?: boolean;
|
|
39
|
-
cancel_reason?: string;
|
|
40
|
-
price_in_purchased_currency?: number;
|
|
41
|
-
currency?: string;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
interface UserSubscriptionUpdate {
|
|
46
|
-
userId: string;
|
|
47
|
-
isPro: boolean;
|
|
48
|
-
subscriptionStatus: 'active' | 'cancelled' | 'expired' | 'paused' | 'billing_issue';
|
|
49
|
-
productId: string | null;
|
|
50
|
-
expiresAt: number | null;
|
|
51
|
-
updatedAt: number;
|
|
52
|
-
eventType: WebhookEventType;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Update user subscription status in Convex
|
|
57
|
-
*/
|
|
58
|
-
async function updateConvexUser(update: UserSubscriptionUpdate): Promise<boolean> {
|
|
59
|
-
try {
|
|
60
|
-
const response = await fetch(`${CONVEX_URL}/api/mutation`, {
|
|
61
|
-
method: 'POST',
|
|
62
|
-
headers: {
|
|
63
|
-
'Content-Type': 'application/json',
|
|
64
|
-
},
|
|
65
|
-
body: JSON.stringify({
|
|
66
|
-
path: 'users:updateSubscription',
|
|
67
|
-
args: update,
|
|
68
|
-
}),
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
if (!response.ok) {
|
|
72
|
-
console.error('Convex update failed:', await response.text());
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return true;
|
|
77
|
-
} catch (error) {
|
|
78
|
-
console.error('Error updating Convex:', error);
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Process webhook event and determine subscription state
|
|
85
|
-
*/
|
|
86
|
-
function processEvent(event: RevenueCatWebhookEvent['event']): UserSubscriptionUpdate {
|
|
87
|
-
const now = Date.now();
|
|
88
|
-
const expiresAt = event.expiration_at_ms;
|
|
89
|
-
const isExpired = expiresAt ? expiresAt < now : false;
|
|
90
|
-
|
|
91
|
-
let subscriptionStatus: UserSubscriptionUpdate['subscriptionStatus'] = 'active';
|
|
92
|
-
let isPro = true;
|
|
93
|
-
|
|
94
|
-
switch (event.type) {
|
|
95
|
-
case 'INITIAL_PURCHASE':
|
|
96
|
-
case 'RENEWAL':
|
|
97
|
-
case 'UNCANCELLATION':
|
|
98
|
-
case 'SUBSCRIPTION_EXTENDED':
|
|
99
|
-
subscriptionStatus = 'active';
|
|
100
|
-
isPro = true;
|
|
101
|
-
break;
|
|
102
|
-
|
|
103
|
-
case 'CANCELLATION':
|
|
104
|
-
// Still active until expiration
|
|
105
|
-
subscriptionStatus = 'cancelled';
|
|
106
|
-
isPro = !isExpired;
|
|
107
|
-
break;
|
|
108
|
-
|
|
109
|
-
case 'EXPIRATION':
|
|
110
|
-
subscriptionStatus = 'expired';
|
|
111
|
-
isPro = false;
|
|
112
|
-
break;
|
|
113
|
-
|
|
114
|
-
case 'SUBSCRIPTION_PAUSED':
|
|
115
|
-
subscriptionStatus = 'paused';
|
|
116
|
-
isPro = false;
|
|
117
|
-
break;
|
|
118
|
-
|
|
119
|
-
case 'BILLING_ISSUE':
|
|
120
|
-
subscriptionStatus = 'billing_issue';
|
|
121
|
-
// Keep Pro until grace period ends
|
|
122
|
-
isPro = !isExpired;
|
|
123
|
-
break;
|
|
124
|
-
|
|
125
|
-
case 'PRODUCT_CHANGE':
|
|
126
|
-
case 'TRANSFER':
|
|
127
|
-
subscriptionStatus = 'active';
|
|
128
|
-
isPro = event.entitlement_ids.includes('pro');
|
|
129
|
-
break;
|
|
130
|
-
|
|
131
|
-
default:
|
|
132
|
-
isPro = event.entitlement_ids.includes('pro') && !isExpired;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
userId: event.app_user_id,
|
|
137
|
-
isPro,
|
|
138
|
-
subscriptionStatus,
|
|
139
|
-
productId: event.product_id,
|
|
140
|
-
expiresAt: expiresAt,
|
|
141
|
-
updatedAt: now,
|
|
142
|
-
eventType: event.type,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* Create Hono router for webhook handling
|
|
148
|
-
*/
|
|
149
|
-
export function createWebhookRouter(): Hono {
|
|
150
|
-
const app = new Hono();
|
|
151
|
-
|
|
152
|
-
// Health check
|
|
153
|
-
app.get('/webhooks/revenuecat/health', (c) => {
|
|
154
|
-
return c.json({ status: 'ok', service: 'revenuecat-webhook' });
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// RevenueCat webhook endpoint
|
|
158
|
-
app.post('/webhooks/revenuecat', async (c) => {
|
|
159
|
-
try {
|
|
160
|
-
const body = await c.req.json<RevenueCatWebhookEvent>();
|
|
161
|
-
|
|
162
|
-
console.log(`[RevenueCat] Received ${body.event.type} for user ${body.event.app_user_id}`);
|
|
163
|
-
|
|
164
|
-
// Process the event
|
|
165
|
-
const update = processEvent(body.event);
|
|
166
|
-
|
|
167
|
-
// Update Convex
|
|
168
|
-
const success = await updateConvexUser(update);
|
|
169
|
-
|
|
170
|
-
if (success) {
|
|
171
|
-
console.log(`[RevenueCat] Updated user ${update.userId}: isPro=${update.isPro}`);
|
|
172
|
-
return c.json({ success: true });
|
|
173
|
-
} else {
|
|
174
|
-
return c.json({ success: false, error: 'Failed to update database' }, 500);
|
|
175
|
-
}
|
|
176
|
-
} catch (error) {
|
|
177
|
-
console.error('[RevenueCat] Webhook error:', error);
|
|
178
|
-
return c.json({ success: false, error: 'Internal error' }, 500);
|
|
179
|
-
}
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
return app;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Export for testing
|
|
186
|
-
export { processEvent, updateConvexUser };
|
|
187
|
-
export type { RevenueCatWebhookEvent, UserSubscriptionUpdate };
|
|
File without changes
|
|
File without changes
|