@nordsym/apiclaw 1.0.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/.github/ISSUE_TEMPLATE/add-api.yml +123 -0
- package/BRIEFING.md +30 -0
- package/CONCEPT.md +494 -0
- package/README.md +272 -0
- package/backend/convex/README.md +90 -0
- package/backend/convex/_generated/api.d.ts +55 -0
- package/backend/convex/_generated/api.js +23 -0
- package/backend/convex/_generated/dataModel.d.ts +60 -0
- package/backend/convex/_generated/server.d.ts +143 -0
- package/backend/convex/_generated/server.js +93 -0
- package/backend/convex/apiKeys.ts +75 -0
- package/backend/convex/purchases.ts +74 -0
- package/backend/convex/schema.ts +45 -0
- package/backend/convex/transactions.ts +57 -0
- package/backend/convex/tsconfig.json +25 -0
- package/backend/convex/users.ts +94 -0
- package/backend/package-lock.json +521 -0
- package/backend/package.json +15 -0
- package/dist/credits.d.ts +54 -0
- package/dist/credits.d.ts.map +1 -0
- package/dist/credits.js +209 -0
- package/dist/credits.js.map +1 -0
- package/dist/discovery.d.ts +37 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +109 -0
- package/dist/discovery.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +355 -0
- package/dist/index.js.map +1 -0
- package/dist/registry/apis.json +20894 -0
- package/dist/registry/parse_apis.py +146 -0
- package/dist/revenuecat.d.ts +61 -0
- package/dist/revenuecat.d.ts.map +1 -0
- package/dist/revenuecat.js +166 -0
- package/dist/revenuecat.js.map +1 -0
- package/dist/test.d.ts +6 -0
- package/dist/test.d.ts.map +1 -0
- package/dist/test.js +81 -0
- package/dist/test.js.map +1 -0
- package/dist/types.d.ts +96 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/webhooks/revenuecat.d.ts +48 -0
- package/dist/webhooks/revenuecat.d.ts.map +1 -0
- package/dist/webhooks/revenuecat.js +119 -0
- package/dist/webhooks/revenuecat.js.map +1 -0
- package/docs/revenuecat-setup.md +89 -0
- package/landing/next-env.d.ts +5 -0
- package/landing/next.config.mjs +6 -0
- package/landing/package-lock.json +1666 -0
- package/landing/package.json +27 -0
- package/landing/postcss.config.js +6 -0
- package/landing/src/app/api/keys/route.ts +71 -0
- package/landing/src/app/api/log/route.ts +37 -0
- package/landing/src/app/api/stats/route.ts +37 -0
- package/landing/src/app/globals.css +261 -0
- package/landing/src/app/layout.tsx +37 -0
- package/landing/src/app/page.tsx +753 -0
- package/landing/src/app/page.tsx.bak +567 -0
- package/landing/src/components/AddKeyModal.tsx +159 -0
- package/landing/tailwind.config.ts +34 -0
- package/landing/tsconfig.json +20 -0
- package/newsletter-template.html +71 -0
- package/outreach/OUTREACH-SYSTEM.md +211 -0
- package/outreach/email-template.html +179 -0
- package/outreach/targets.md +133 -0
- package/package.json +39 -0
- package/src/credits.ts +261 -0
- package/src/discovery.ts +147 -0
- package/src/index.ts +396 -0
- package/src/registry/apis.json +20894 -0
- package/src/registry/parse_apis.py +146 -0
- package/src/revenuecat.ts +239 -0
- package/src/test.ts +97 -0
- package/src/types.ts +110 -0
- package/src/webhooks/revenuecat.ts +187 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,133 @@
|
|
|
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).
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nordsym/apiclaw",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "The API layer for autonomous agents. MCP-native API discovery.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"apiclaw": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc && cp -r src/registry dist/",
|
|
12
|
+
"dev": "tsx watch src/index.ts",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"test": "tsx src/test.ts",
|
|
15
|
+
"prepublishOnly": "npm run build"
|
|
16
|
+
},
|
|
17
|
+
"keywords": ["mcp", "api", "agent", "discovery", "marketplace", "claude", "openai", "autonomous", "apiclaw"],
|
|
18
|
+
"author": "NordSym <gustav@nordsym.com>",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/nordsym/apiclaw"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://apiclaw.nordsym.com",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/nordsym/apiclaw/issues"
|
|
27
|
+
},
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@types/node": "^20.10.0",
|
|
36
|
+
"tsx": "^4.7.0",
|
|
37
|
+
"typescript": "^5.3.0"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/credits.ts
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
// Credit system for APIvault
|
|
2
|
+
// MVP: In-memory store. Production: Supabase
|
|
3
|
+
|
|
4
|
+
import { AgentCredits, Purchase, APICredentials, UsageRecord, TransactionFee } from './types.js';
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
6
|
+
import { getFeePercentage, hasProSubscription } from './revenuecat.js';
|
|
7
|
+
|
|
8
|
+
// In-memory stores (replace with Supabase in production)
|
|
9
|
+
const agentCredits: Map<string, AgentCredits> = new Map();
|
|
10
|
+
const purchases: Map<string, Purchase> = new Map();
|
|
11
|
+
const usage: Map<string, UsageRecord> = new Map();
|
|
12
|
+
|
|
13
|
+
// Mock API keys for demo (in production, these would be provisioned from providers)
|
|
14
|
+
const mockCredentials: Record<string, () => APICredentials> = {
|
|
15
|
+
'46elks': () => ({
|
|
16
|
+
type: 'basic',
|
|
17
|
+
username: `u_${randomUUID().slice(0, 8)}`,
|
|
18
|
+
password: `p_${randomUUID().slice(0, 16)}`
|
|
19
|
+
}),
|
|
20
|
+
'resend': () => ({
|
|
21
|
+
type: 'api_key',
|
|
22
|
+
api_key: `re_${randomUUID().replace(/-/g, '')}`
|
|
23
|
+
}),
|
|
24
|
+
'brave_search': () => ({
|
|
25
|
+
type: 'api_key',
|
|
26
|
+
api_key: `BSA${randomUUID().replace(/-/g, '').toUpperCase().slice(0, 20)}`
|
|
27
|
+
}),
|
|
28
|
+
'openrouter': () => ({
|
|
29
|
+
type: 'bearer',
|
|
30
|
+
api_key: `sk-or-v1-${randomUUID().replace(/-/g, '')}`
|
|
31
|
+
}),
|
|
32
|
+
'elevenlabs': () => ({
|
|
33
|
+
type: 'api_key',
|
|
34
|
+
api_key: `${randomUUID().replace(/-/g, '')}`
|
|
35
|
+
})
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Get or create agent credits account
|
|
40
|
+
*/
|
|
41
|
+
export function getAgentCredits(agentId: string): AgentCredits {
|
|
42
|
+
if (!agentCredits.has(agentId)) {
|
|
43
|
+
agentCredits.set(agentId, {
|
|
44
|
+
agent_id: agentId,
|
|
45
|
+
balance_usd: 0,
|
|
46
|
+
currency: 'USD',
|
|
47
|
+
created_at: new Date().toISOString(),
|
|
48
|
+
updated_at: new Date().toISOString()
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
return agentCredits.get(agentId)!;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Add credits to an agent's account
|
|
56
|
+
*/
|
|
57
|
+
export function addCredits(agentId: string, amountUsd: number): AgentCredits {
|
|
58
|
+
const credits = getAgentCredits(agentId);
|
|
59
|
+
credits.balance_usd += amountUsd;
|
|
60
|
+
credits.updated_at = new Date().toISOString();
|
|
61
|
+
return credits;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Purchase API access
|
|
66
|
+
* Returns credentials if successful
|
|
67
|
+
*/
|
|
68
|
+
export function purchaseAPIAccess(
|
|
69
|
+
agentId: string,
|
|
70
|
+
providerId: string,
|
|
71
|
+
amountUsd: number
|
|
72
|
+
): { success: boolean; purchase?: Purchase; error?: string } {
|
|
73
|
+
const credits = getAgentCredits(agentId);
|
|
74
|
+
|
|
75
|
+
// Check balance
|
|
76
|
+
if (credits.balance_usd < amountUsd) {
|
|
77
|
+
return {
|
|
78
|
+
success: false,
|
|
79
|
+
error: `Insufficient balance. Have $${credits.balance_usd.toFixed(2)}, need $${amountUsd.toFixed(2)}`
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check if provider exists
|
|
84
|
+
if (!mockCredentials[providerId]) {
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
error: `Unknown provider: ${providerId}`
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Deduct credits
|
|
92
|
+
credits.balance_usd -= amountUsd;
|
|
93
|
+
credits.updated_at = new Date().toISOString();
|
|
94
|
+
|
|
95
|
+
// Create purchase record
|
|
96
|
+
const purchaseId = `pur_${randomUUID().slice(0, 12)}`;
|
|
97
|
+
const purchase: Purchase = {
|
|
98
|
+
id: purchaseId,
|
|
99
|
+
agent_id: agentId,
|
|
100
|
+
provider_id: providerId,
|
|
101
|
+
amount_usd: amountUsd,
|
|
102
|
+
credits_purchased: calculateCredits(providerId, amountUsd),
|
|
103
|
+
status: 'active',
|
|
104
|
+
credentials: mockCredentials[providerId](),
|
|
105
|
+
created_at: new Date().toISOString()
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
purchases.set(purchaseId, purchase);
|
|
109
|
+
|
|
110
|
+
// Initialize usage tracking
|
|
111
|
+
usage.set(purchaseId, {
|
|
112
|
+
purchase_id: purchaseId,
|
|
113
|
+
provider_id: providerId,
|
|
114
|
+
units_used: 0,
|
|
115
|
+
units_remaining: purchase.credits_purchased,
|
|
116
|
+
cost_incurred_usd: 0,
|
|
117
|
+
last_used_at: new Date().toISOString()
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return { success: true, purchase };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Calculate credits based on provider pricing
|
|
125
|
+
*/
|
|
126
|
+
function calculateCredits(providerId: string, amountUsd: number): number {
|
|
127
|
+
// Simplified credit calculation per provider
|
|
128
|
+
const creditsPerDollar: Record<string, number> = {
|
|
129
|
+
'46elks': 30, // ~30 SMS per dollar
|
|
130
|
+
'resend': 1000, // ~1000 emails per dollar
|
|
131
|
+
'brave_search': 200, // ~200 searches per dollar
|
|
132
|
+
'openrouter': 100, // ~100k tokens per dollar (varies by model)
|
|
133
|
+
'elevenlabs': 3333 // ~3333 characters per dollar
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
return Math.floor(amountUsd * (creditsPerDollar[providerId] || 100));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get all purchases for an agent
|
|
141
|
+
*/
|
|
142
|
+
export function getAgentPurchases(agentId: string): Purchase[] {
|
|
143
|
+
return Array.from(purchases.values()).filter(p => p.agent_id === agentId);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get usage for a purchase
|
|
148
|
+
*/
|
|
149
|
+
export function getUsage(purchaseId: string): UsageRecord | null {
|
|
150
|
+
return usage.get(purchaseId) || null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Record usage (call this when API is used)
|
|
155
|
+
*/
|
|
156
|
+
export function recordUsage(purchaseId: string, unitsUsed: number, costUsd: number): UsageRecord | null {
|
|
157
|
+
const record = usage.get(purchaseId);
|
|
158
|
+
if (!record) return null;
|
|
159
|
+
|
|
160
|
+
record.units_used += unitsUsed;
|
|
161
|
+
record.units_remaining = Math.max(0, record.units_remaining - unitsUsed);
|
|
162
|
+
record.cost_incurred_usd += costUsd;
|
|
163
|
+
record.last_used_at = new Date().toISOString();
|
|
164
|
+
|
|
165
|
+
// Update purchase status if depleted
|
|
166
|
+
if (record.units_remaining === 0) {
|
|
167
|
+
const purchase = purchases.get(purchaseId);
|
|
168
|
+
if (purchase) purchase.status = 'exhausted';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return record;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get full balance summary for an agent
|
|
176
|
+
*/
|
|
177
|
+
export function getBalanceSummary(agentId: string): {
|
|
178
|
+
credits: AgentCredits;
|
|
179
|
+
active_purchases: Purchase[];
|
|
180
|
+
total_spent_usd: number;
|
|
181
|
+
} {
|
|
182
|
+
const credits = getAgentCredits(agentId);
|
|
183
|
+
const agentPurchases = getAgentPurchases(agentId);
|
|
184
|
+
const activePurchases = agentPurchases.filter(p => p.status === 'active');
|
|
185
|
+
const totalSpent = agentPurchases.reduce((sum, p) => sum + p.amount_usd, 0);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
credits,
|
|
189
|
+
active_purchases: activePurchases,
|
|
190
|
+
total_spent_usd: totalSpent
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Calculate transaction fee based on user's subscription
|
|
196
|
+
* Pro users: 2% fee
|
|
197
|
+
* Free users: 5% fee
|
|
198
|
+
*/
|
|
199
|
+
export async function calculateTransactionFee(
|
|
200
|
+
userId: string,
|
|
201
|
+
amountUsd: number
|
|
202
|
+
): Promise<TransactionFee> {
|
|
203
|
+
const isPro = await hasProSubscription(userId);
|
|
204
|
+
const feePercentage = isPro ? 0.02 : 0.05;
|
|
205
|
+
const feeAmount = Math.round(amountUsd * feePercentage * 100) / 100;
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
amount_usd: amountUsd,
|
|
209
|
+
fee_percentage: feePercentage,
|
|
210
|
+
fee_amount_usd: feeAmount,
|
|
211
|
+
net_amount_usd: Math.round((amountUsd - feeAmount) * 100) / 100,
|
|
212
|
+
tier: isPro ? 'pro' : 'free',
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Purchase API access with subscription-aware fees
|
|
218
|
+
*/
|
|
219
|
+
export async function purchaseAPIAccessWithFees(
|
|
220
|
+
agentId: string,
|
|
221
|
+
userId: string,
|
|
222
|
+
providerId: string,
|
|
223
|
+
amountUsd: number
|
|
224
|
+
): Promise<{
|
|
225
|
+
success: boolean;
|
|
226
|
+
purchase?: Purchase;
|
|
227
|
+
fee?: TransactionFee;
|
|
228
|
+
error?: string
|
|
229
|
+
}> {
|
|
230
|
+
// Calculate fees first
|
|
231
|
+
const fee = await calculateTransactionFee(userId, amountUsd);
|
|
232
|
+
|
|
233
|
+
// Total amount user pays = API cost + platform fee
|
|
234
|
+
const totalCost = amountUsd + fee.fee_amount_usd;
|
|
235
|
+
|
|
236
|
+
const credits = getAgentCredits(agentId);
|
|
237
|
+
|
|
238
|
+
if (credits.balance_usd < totalCost) {
|
|
239
|
+
return {
|
|
240
|
+
success: false,
|
|
241
|
+
error: `Insufficient balance. Have $${credits.balance_usd.toFixed(2)}, need $${totalCost.toFixed(2)} (includes ${fee.tier === 'pro' ? '2%' : '5%'} platform fee)`
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Make the purchase (deducts API cost)
|
|
246
|
+
const result = purchaseAPIAccess(agentId, providerId, amountUsd);
|
|
247
|
+
|
|
248
|
+
if (result.success && result.purchase) {
|
|
249
|
+
// Deduct platform fee separately
|
|
250
|
+
credits.balance_usd -= fee.fee_amount_usd;
|
|
251
|
+
credits.updated_at = new Date().toISOString();
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
success: true,
|
|
255
|
+
purchase: result.purchase,
|
|
256
|
+
fee,
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return result;
|
|
261
|
+
}
|
package/src/discovery.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Discovery engine for APIvault
|
|
2
|
+
// MVP: Keyword matching. Future: Embeddings + semantic search
|
|
3
|
+
|
|
4
|
+
import { SimpleAPI } from './types.js';
|
|
5
|
+
import { readFileSync } from 'fs';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { dirname, join } from 'path';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
|
|
12
|
+
// Load APIs from registry
|
|
13
|
+
const apisData = JSON.parse(
|
|
14
|
+
readFileSync(join(__dirname, 'registry', 'apis.json'), 'utf-8')
|
|
15
|
+
);
|
|
16
|
+
const apis: SimpleAPI[] = apisData.apis;
|
|
17
|
+
|
|
18
|
+
console.error(`[APIvault] Loaded ${apis.length} APIs from registry`);
|
|
19
|
+
|
|
20
|
+
export interface SimpleSearchResult {
|
|
21
|
+
api: SimpleAPI;
|
|
22
|
+
relevance_score: number;
|
|
23
|
+
match_reasons: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Discover APIs based on a natural language query
|
|
28
|
+
* MVP uses keyword matching; production would use embeddings
|
|
29
|
+
*/
|
|
30
|
+
export function discoverAPIs(
|
|
31
|
+
query: string,
|
|
32
|
+
options: {
|
|
33
|
+
category?: string;
|
|
34
|
+
maxResults?: number;
|
|
35
|
+
auth?: string;
|
|
36
|
+
httpsOnly?: boolean;
|
|
37
|
+
} = {}
|
|
38
|
+
): SimpleSearchResult[] {
|
|
39
|
+
const { category, maxResults = 10, auth, httpsOnly } = options;
|
|
40
|
+
|
|
41
|
+
const queryLower = query.toLowerCase();
|
|
42
|
+
const queryWords = queryLower.split(/\s+/).filter(w => w.length > 2);
|
|
43
|
+
|
|
44
|
+
const results: SimpleSearchResult[] = [];
|
|
45
|
+
|
|
46
|
+
for (const api of apis) {
|
|
47
|
+
// Category filter (case-insensitive)
|
|
48
|
+
if (category && api.category.toLowerCase() !== category.toLowerCase()) continue;
|
|
49
|
+
|
|
50
|
+
// Auth filter
|
|
51
|
+
if (auth && api.auth.toLowerCase() !== auth.toLowerCase()) continue;
|
|
52
|
+
|
|
53
|
+
// HTTPS filter
|
|
54
|
+
if (httpsOnly && !api.https) continue;
|
|
55
|
+
|
|
56
|
+
// Calculate relevance score
|
|
57
|
+
let score = 0;
|
|
58
|
+
const matchReasons: string[] = [];
|
|
59
|
+
|
|
60
|
+
for (const word of queryWords) {
|
|
61
|
+
// Name match (highest weight)
|
|
62
|
+
if (api.name.toLowerCase().includes(word)) {
|
|
63
|
+
score += 25;
|
|
64
|
+
matchReasons.push(`name: ${word}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Keyword match
|
|
68
|
+
if (api.keywords && api.keywords.some(k => k.toLowerCase().includes(word))) {
|
|
69
|
+
score += 15;
|
|
70
|
+
matchReasons.push(`keyword: ${word}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Description match
|
|
74
|
+
if (api.description.toLowerCase().includes(word)) {
|
|
75
|
+
score += 10;
|
|
76
|
+
matchReasons.push(`description: ${word}`);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Category match
|
|
80
|
+
if (api.category.toLowerCase().includes(word)) {
|
|
81
|
+
score += 8;
|
|
82
|
+
matchReasons.push(`category: ${word}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Only add secondary boosts if there's a primary match
|
|
87
|
+
if (score > 0) {
|
|
88
|
+
// Boost for HTTPS
|
|
89
|
+
if (api.https) {
|
|
90
|
+
score += 2;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Boost for known CORS support
|
|
94
|
+
if (api.cors === 'yes') {
|
|
95
|
+
score += 1;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (score > 0) {
|
|
100
|
+
results.push({
|
|
101
|
+
api,
|
|
102
|
+
relevance_score: Math.round(score * 100) / 100,
|
|
103
|
+
match_reasons: [...new Set(matchReasons)]
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Sort by relevance
|
|
109
|
+
results.sort((a, b) => b.relevance_score - a.relevance_score);
|
|
110
|
+
|
|
111
|
+
return results.slice(0, maxResults);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get detailed information about a specific API
|
|
116
|
+
*/
|
|
117
|
+
export function getAPIDetails(apiId: string): SimpleAPI | null {
|
|
118
|
+
return apis.find(api => api.id === apiId) || null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* List all APIs in a category
|
|
123
|
+
*/
|
|
124
|
+
export function listByCategory(category: string): SimpleAPI[] {
|
|
125
|
+
return apis.filter(api => api.category.toLowerCase() === category.toLowerCase());
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get all available categories
|
|
130
|
+
*/
|
|
131
|
+
export function getCategories(): string[] {
|
|
132
|
+
return [...new Set(apis.map(api => api.category))].sort();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get all APIs
|
|
137
|
+
*/
|
|
138
|
+
export function getAllAPIs(): SimpleAPI[] {
|
|
139
|
+
return apis;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Get API count
|
|
144
|
+
*/
|
|
145
|
+
export function getAPICount(): number {
|
|
146
|
+
return apis.length;
|
|
147
|
+
}
|