@myapihq/sdk 1.1.0-wip.4 → 1.2.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/dist/audience.d.ts +71 -0
- package/dist/audience.js +54 -0
- package/dist/client.d.ts +1 -1
- package/dist/client.js +8 -1
- package/dist/company.d.ts +20 -0
- package/dist/company.js +25 -0
- package/dist/config.d.ts +6 -0
- package/dist/config.js +20 -2
- package/dist/crm.d.ts +122 -0
- package/dist/crm.js +84 -0
- package/dist/database.d.ts +36 -0
- package/dist/database.js +65 -0
- package/dist/domain.d.ts +19 -1
- package/dist/domain.js +22 -2
- package/dist/email-verify.d.ts +30 -0
- package/dist/email-verify.js +17 -0
- package/dist/email.d.ts +33 -0
- package/dist/email.js +53 -0
- package/dist/exposes.d.ts +2 -0
- package/dist/exposes.js +14 -0
- package/dist/funnel.d.ts +10 -0
- package/dist/funnel.js +20 -0
- package/dist/hq.d.ts +10 -0
- package/dist/hq.js +28 -0
- package/dist/image.d.ts +14 -0
- package/dist/image.js +12 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.js +13 -2
- package/dist/llm.d.ts +50 -0
- package/dist/llm.js +27 -0
- package/dist/people.d.ts +54 -0
- package/dist/people.js +22 -0
- package/dist/pixel.d.ts +3 -0
- package/dist/pixel.js +15 -0
- package/dist/services.d.ts +17 -0
- package/dist/services.js +190 -0
- package/dist/storage.d.ts +3 -1
- package/dist/storage.js +7 -0
- package/dist/url.d.ts +2 -0
- package/dist/url.js +4 -0
- package/dist/webhook.d.ts +8 -1
- package/dist/webhook.js +14 -2
- package/dist/workflow.d.ts +2 -0
- package/dist/workflow.js +12 -0
- package/package.json +2 -1
- package/scripts/scaffold-sdk.js +501 -0
- package/src/audience.ts +162 -0
- package/src/client.ts +9 -1
- package/src/company.ts +48 -0
- package/src/config.ts +21 -1
- package/src/crm.ts +235 -0
- package/src/database.ts +103 -0
- package/src/domain.ts +44 -2
- package/src/email.ts +84 -0
- package/src/exposes.ts +16 -0
- package/src/funnel.ts +34 -0
- package/src/hq.ts +41 -0
- package/src/image.ts +31 -0
- package/src/index.ts +12 -1
- package/src/llm.ts +86 -0
- package/src/people.ts +84 -0
- package/src/pixel.ts +16 -0
- package/src/services.ts +220 -0
- package/src/storage.ts +15 -6
- package/src/url.ts +5 -0
- package/src/webhook.ts +25 -2
- package/src/workflow.ts +13 -0
package/src/company.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Scaffolded from schema by scripts/scaffold-sdk.js — DO NOT remove the
|
|
2
|
+
// EXPOSES array (the coverage tool verifies it against the live schema).
|
|
3
|
+
// Types and function names ARE editable; re-running the scaffolder against
|
|
4
|
+
// a service whose config has been changed will diff vs. existing file
|
|
5
|
+
// (use --dry-run to inspect).
|
|
6
|
+
|
|
7
|
+
import { request } from './client';
|
|
8
|
+
import { COMPANY_BASE as BASE_URL } from './config';
|
|
9
|
+
import type { Exposes } from './exposes';
|
|
10
|
+
|
|
11
|
+
import type { SearchFilter } from './audience';
|
|
12
|
+
import type { Person } from './people';
|
|
13
|
+
|
|
14
|
+
export const EXPOSES: Exposes = [
|
|
15
|
+
'POST /company/orgs/{org_id}/search',
|
|
16
|
+
'GET /company/orgs/{org_id}/{company_id}',
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
import type { Location, PersonCompany } from './people';
|
|
20
|
+
|
|
21
|
+
// Company is the same shape as PersonCompany (a person's embedded company)
|
|
22
|
+
// with an optional embedded people array when include_people=N is passed.
|
|
23
|
+
export interface Company extends PersonCompany {
|
|
24
|
+
people?: Person[]; // present when include_people=N is passed
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface CompanySearchResult {
|
|
28
|
+
companies: Company[];
|
|
29
|
+
total: number;
|
|
30
|
+
limit: number;
|
|
31
|
+
offset: number;
|
|
32
|
+
has_more: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface CompanySearchOptions extends SearchFilter {
|
|
36
|
+
include_people?: number; // 0..10; embed up to N people per company
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function searchCompanies(apiKey: string, orgId: string, options: CompanySearchOptions): Promise<CompanySearchResult> {
|
|
40
|
+
return request('POST', `${BASE_URL}/company/orgs/${encodeURIComponent(orgId)}/search`, apiKey, options);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function getCompany(apiKey: string, orgId: string, companyId: string, include_people?: number): Promise<Company> {
|
|
44
|
+
const _url = include_people != null
|
|
45
|
+
? `${BASE_URL}/company/orgs/${encodeURIComponent(orgId)}/${encodeURIComponent(companyId)}?include_people=${encodeURIComponent(String(include_people))}`
|
|
46
|
+
: `${BASE_URL}/company/orgs/${encodeURIComponent(orgId)}/${encodeURIComponent(companyId)}`;
|
|
47
|
+
return request('GET', _url, apiKey);
|
|
48
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
// Service base URLs. Today's reality: all primitives are served from the
|
|
2
|
+
// single `api.myapihq.com` gateway; per-service brand hosts
|
|
3
|
+
// (e.g. api.myllmapi.com) are aspirational and only some are wired in DNS
|
|
4
|
+
// + routed correctly. We default to the working hosts where they exist,
|
|
5
|
+
// and to the gateway where they don't (LLM, DATABASE, CRM, PEOPLE,
|
|
6
|
+
// COMPANY, AUDIENCE). The MYAPI_*_URL env vars stay as overrides for
|
|
7
|
+
// local dev — `source scripts/local-env.sh` still points everything at
|
|
8
|
+
// localhost:8080 regardless of the prod default.
|
|
9
|
+
|
|
10
|
+
const GATEWAY = 'https://api.myapihq.com';
|
|
11
|
+
|
|
12
|
+
export const HQ_BASE = process.env.MYAPI_HQ_URL ?? process.env.MYAPI_API_BASE ?? GATEWAY;
|
|
2
13
|
export const DOMAIN_BASE = process.env.MYAPI_DOMAIN_URL ?? 'https://api.mydomainapi.com';
|
|
3
14
|
export const FUNNEL_BASE = process.env.MYAPI_FUNNEL_URL ?? 'https://api.myfunnelapi.com';
|
|
4
15
|
export const IMAGE_BASE = process.env.MYAPI_IMAGE_URL ?? 'https://api.myimageapi.com';
|
|
@@ -8,3 +19,12 @@ export const EMAIL_BASE = process.env.MYAPI_EMAIL_URL ?? 'https://api.myemai
|
|
|
8
19
|
export const STORAGE_BASE = process.env.MYAPI_STORAGE_URL ?? 'https://api.mystorageapi.com';
|
|
9
20
|
export const URL_BASE = process.env.MYAPI_URL_URL ?? 'https://api.myurlto.com';
|
|
10
21
|
export const PIXEL_BASE = process.env.MYAPI_PIXEL_URL ?? 'https://api.mypixelapi.com';
|
|
22
|
+
// Per-service hosts below either don't resolve in DNS or 404 the routes;
|
|
23
|
+
// served via the gateway until backend wires the brand routing. Flip back
|
|
24
|
+
// to per-service hosts when DNS is sound and routes verified there.
|
|
25
|
+
export const PEOPLE_BASE = process.env.MYAPI_PEOPLE_URL ?? GATEWAY;
|
|
26
|
+
export const COMPANY_BASE = process.env.MYAPI_COMPANY_URL ?? GATEWAY;
|
|
27
|
+
export const AUDIENCE_BASE= process.env.MYAPI_AUDIENCE_URL?? GATEWAY;
|
|
28
|
+
export const LLM_BASE = process.env.MYAPI_LLM_URL ?? GATEWAY;
|
|
29
|
+
export const DATABASE_BASE= process.env.MYAPI_DATABASE_URL?? GATEWAY;
|
|
30
|
+
export const CRM_BASE = process.env.MYAPI_CRM_URL ?? GATEWAY;
|
package/src/crm.ts
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { request } from './client';
|
|
2
|
+
import { CRM_BASE as BASE_URL } from './config';
|
|
3
|
+
import type { Exposes } from './exposes';
|
|
4
|
+
|
|
5
|
+
export const EXPOSES: Exposes = [
|
|
6
|
+
'POST /crm/orgs/{org_id}/contacts',
|
|
7
|
+
'POST /crm/orgs/{org_id}/contacts/promote',
|
|
8
|
+
'POST /crm/orgs/{org_id}/contacts/search',
|
|
9
|
+
'GET /crm/orgs/{org_id}/contacts/{id}',
|
|
10
|
+
'PATCH /crm/orgs/{org_id}/contacts/{id}',
|
|
11
|
+
'DELETE /crm/orgs/{org_id}/contacts/{id}',
|
|
12
|
+
'GET /crm/orgs/{org_id}/contacts/{id}/events',
|
|
13
|
+
'POST /crm/orgs/{org_id}/companies',
|
|
14
|
+
'POST /crm/orgs/{org_id}/companies/promote',
|
|
15
|
+
'POST /crm/orgs/{org_id}/companies/search',
|
|
16
|
+
'GET /crm/orgs/{org_id}/companies/{id}',
|
|
17
|
+
'PATCH /crm/orgs/{org_id}/companies/{id}',
|
|
18
|
+
'DELETE /crm/orgs/{org_id}/companies/{id}',
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// Shared enums — fixed sets. Lifecycle stage is the same enum for contacts
|
|
22
|
+
// and companies (Simon's call: keep it simple, easier for agents).
|
|
23
|
+
export type LifecycleStage =
|
|
24
|
+
| 'cold'
|
|
25
|
+
| 'warm'
|
|
26
|
+
| 'qualified'
|
|
27
|
+
| 'customer'
|
|
28
|
+
| 'churned';
|
|
29
|
+
|
|
30
|
+
export type ContactSource =
|
|
31
|
+
| 'goldfox'
|
|
32
|
+
| 'email'
|
|
33
|
+
| 'pixel'
|
|
34
|
+
| 'webhook'
|
|
35
|
+
| 'manual';
|
|
36
|
+
|
|
37
|
+
// Reserved set of event kinds. Agents cannot write these directly — they
|
|
38
|
+
// are emitted by the platform when state changes (stage_changed) or the
|
|
39
|
+
// underlying service completes (email_sent, webhook_received, etc.).
|
|
40
|
+
export type EventKind =
|
|
41
|
+
| 'created'
|
|
42
|
+
| 'promoted'
|
|
43
|
+
| 'stage_changed'
|
|
44
|
+
| 'email_sent'
|
|
45
|
+
| 'email_opened'
|
|
46
|
+
| 'email_clicked'
|
|
47
|
+
| 'email_replied'
|
|
48
|
+
| 'pixel_visit'
|
|
49
|
+
| 'webhook_received';
|
|
50
|
+
|
|
51
|
+
// Goldfox enrichment is a live join — the response embeds the current
|
|
52
|
+
// Goldfox row when goldfox_person_id is set. v1: returns null until the
|
|
53
|
+
// BQ get-by-id helper lands backend-side. The field shape will become
|
|
54
|
+
// the existing Person from people.ts once enabled.
|
|
55
|
+
export interface Contact {
|
|
56
|
+
id: string;
|
|
57
|
+
org_id: string;
|
|
58
|
+
email: string | null;
|
|
59
|
+
first_name: string | null;
|
|
60
|
+
last_name: string | null;
|
|
61
|
+
company_id: string | null;
|
|
62
|
+
lifecycle_stage: LifecycleStage;
|
|
63
|
+
custom: Record<string, unknown>;
|
|
64
|
+
source: ContactSource;
|
|
65
|
+
goldfox_person_id: string | null;
|
|
66
|
+
goldfox_person?: unknown | null; // deferred until BQ helper lands
|
|
67
|
+
created_at: string;
|
|
68
|
+
updated_at: string;
|
|
69
|
+
last_engagement_at?: string | null; // only set after engagement events (email/pixel/webhook)
|
|
70
|
+
deleted_at?: string | null; // soft-delete marker
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface Company {
|
|
74
|
+
id: string;
|
|
75
|
+
org_id: string;
|
|
76
|
+
domain: string | null;
|
|
77
|
+
name: string | null;
|
|
78
|
+
lifecycle_stage: LifecycleStage;
|
|
79
|
+
custom: Record<string, unknown>;
|
|
80
|
+
source: ContactSource;
|
|
81
|
+
goldfox_company_id: string | null; // for Goldfox-sourced rows this equals the domain
|
|
82
|
+
goldfox_company?: unknown | null;
|
|
83
|
+
created_at: string;
|
|
84
|
+
updated_at: string;
|
|
85
|
+
deleted_at?: string | null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ContactEvent {
|
|
89
|
+
id: string;
|
|
90
|
+
contact_id: string;
|
|
91
|
+
kind: EventKind;
|
|
92
|
+
payload: Record<string, unknown>;
|
|
93
|
+
at: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── Inputs ────────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
export interface CreateContactInput {
|
|
99
|
+
email?: string | null;
|
|
100
|
+
first_name?: string;
|
|
101
|
+
last_name?: string;
|
|
102
|
+
company_id?: string; // override the auto-link by domain
|
|
103
|
+
lifecycle_stage?: LifecycleStage;
|
|
104
|
+
custom?: Record<string, unknown>;
|
|
105
|
+
source?: ContactSource; // defaults to 'manual' server-side
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface UpdateContactInput {
|
|
109
|
+
first_name?: string | null;
|
|
110
|
+
last_name?: string | null;
|
|
111
|
+
company_id?: string | null;
|
|
112
|
+
lifecycle_stage?: LifecycleStage;
|
|
113
|
+
custom?: Record<string, unknown>;
|
|
114
|
+
deleted_at?: null; // pass explicit null to restore a soft-deleted contact
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface ContactSearchFilter {
|
|
118
|
+
lifecycle_stage?: LifecycleStage[];
|
|
119
|
+
source?: ContactSource[];
|
|
120
|
+
email?: string;
|
|
121
|
+
company_id?: string;
|
|
122
|
+
min_last_engagement_days?: number; // engaged within N days
|
|
123
|
+
max_last_engagement_days?: number; // last engaged > N days ago — re-engagement filter
|
|
124
|
+
include_deleted?: boolean; // default false
|
|
125
|
+
limit?: number;
|
|
126
|
+
offset?: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface ContactSearchResult {
|
|
130
|
+
contacts: Contact[];
|
|
131
|
+
total: number;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export interface CreateCompanyInput {
|
|
135
|
+
domain?: string | null;
|
|
136
|
+
name?: string;
|
|
137
|
+
lifecycle_stage?: LifecycleStage;
|
|
138
|
+
custom?: Record<string, unknown>;
|
|
139
|
+
source?: ContactSource;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface UpdateCompanyInput {
|
|
143
|
+
name?: string | null;
|
|
144
|
+
lifecycle_stage?: LifecycleStage;
|
|
145
|
+
custom?: Record<string, unknown>;
|
|
146
|
+
deleted_at?: null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface CompanySearchFilter {
|
|
150
|
+
lifecycle_stage?: LifecycleStage[];
|
|
151
|
+
source?: ContactSource[];
|
|
152
|
+
domain?: string;
|
|
153
|
+
include_deleted?: boolean;
|
|
154
|
+
limit?: number;
|
|
155
|
+
offset?: number;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface CompanySearchResult {
|
|
159
|
+
companies: Company[];
|
|
160
|
+
total: number;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface EventsResponse {
|
|
164
|
+
events: ContactEvent[];
|
|
165
|
+
next_cursor?: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface ListEventsOptions {
|
|
169
|
+
limit?: number; // 1-200, default 50
|
|
170
|
+
cursor?: string;
|
|
171
|
+
kind?: EventKind;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ── Contacts ──────────────────────────────────────────────────────────────
|
|
175
|
+
|
|
176
|
+
export async function createContact(apiKey: string, orgId: string, input: CreateContactInput): Promise<Contact> {
|
|
177
|
+
return request('POST', `${BASE_URL}/crm/orgs/${encodeURIComponent(orgId)}/contacts`, apiKey, input);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function getContact(apiKey: string, orgId: string, id: string): Promise<Contact> {
|
|
181
|
+
return request('GET', `${BASE_URL}/crm/orgs/${encodeURIComponent(orgId)}/contacts/${encodeURIComponent(id)}`, apiKey);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function updateContact(apiKey: string, orgId: string, id: string, patch: UpdateContactInput): Promise<Contact> {
|
|
185
|
+
return request('PATCH', `${BASE_URL}/crm/orgs/${encodeURIComponent(orgId)}/contacts/${encodeURIComponent(id)}`, apiKey, patch);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function deleteContact(apiKey: string, orgId: string, id: string): Promise<void> {
|
|
189
|
+
return request('DELETE', `${BASE_URL}/crm/orgs/${encodeURIComponent(orgId)}/contacts/${encodeURIComponent(id)}`, apiKey);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function searchContacts(apiKey: string, orgId: string, filter: ContactSearchFilter = {}): Promise<ContactSearchResult> {
|
|
193
|
+
return request('POST', `${BASE_URL}/crm/orgs/${encodeURIComponent(orgId)}/contacts/search`, apiKey, filter);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function promoteContact(apiKey: string, orgId: string, goldfoxPersonId: string): Promise<Contact> {
|
|
197
|
+
return request('POST', `${BASE_URL}/crm/orgs/${encodeURIComponent(orgId)}/contacts/promote`, apiKey, { goldfox_person_id: goldfoxPersonId });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export async function getContactEvents(apiKey: string, orgId: string, id: string, opts: ListEventsOptions = {}): Promise<EventsResponse> {
|
|
201
|
+
const qs = new URLSearchParams();
|
|
202
|
+
if (opts.limit != null) qs.append('limit', String(opts.limit));
|
|
203
|
+
if (opts.cursor != null) qs.append('cursor', opts.cursor);
|
|
204
|
+
if (opts.kind != null) qs.append('kind', opts.kind);
|
|
205
|
+
const q = qs.toString();
|
|
206
|
+
const url = `${BASE_URL}/crm/orgs/${encodeURIComponent(orgId)}/contacts/${encodeURIComponent(id)}/events${q ? `?${q}` : ''}`;
|
|
207
|
+
return request('GET', url, apiKey);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ── Companies ─────────────────────────────────────────────────────────────
|
|
211
|
+
|
|
212
|
+
export async function createCompany(apiKey: string, orgId: string, input: CreateCompanyInput): Promise<Company> {
|
|
213
|
+
return request('POST', `${BASE_URL}/crm/orgs/${encodeURIComponent(orgId)}/companies`, apiKey, input);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export async function getCompany(apiKey: string, orgId: string, id: string): Promise<Company> {
|
|
217
|
+
return request('GET', `${BASE_URL}/crm/orgs/${encodeURIComponent(orgId)}/companies/${encodeURIComponent(id)}`, apiKey);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function updateCompany(apiKey: string, orgId: string, id: string, patch: UpdateCompanyInput): Promise<Company> {
|
|
221
|
+
return request('PATCH', `${BASE_URL}/crm/orgs/${encodeURIComponent(orgId)}/companies/${encodeURIComponent(id)}`, apiKey, patch);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function deleteCompany(apiKey: string, orgId: string, id: string): Promise<void> {
|
|
225
|
+
return request('DELETE', `${BASE_URL}/crm/orgs/${encodeURIComponent(orgId)}/companies/${encodeURIComponent(id)}`, apiKey);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export async function searchCompanies(apiKey: string, orgId: string, filter: CompanySearchFilter = {}): Promise<CompanySearchResult> {
|
|
229
|
+
return request('POST', `${BASE_URL}/crm/orgs/${encodeURIComponent(orgId)}/companies/search`, apiKey, filter);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Goldfox company id IS its domain (Simon's note in routes.md).
|
|
233
|
+
export async function promoteCompany(apiKey: string, orgId: string, domain: string): Promise<Company> {
|
|
234
|
+
return request('POST', `${BASE_URL}/crm/orgs/${encodeURIComponent(orgId)}/companies/promote`, apiKey, { goldfox_company_id: domain });
|
|
235
|
+
}
|
package/src/database.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { request } from './client';
|
|
2
|
+
import { DATABASE_BASE as BASE_URL } from './config';
|
|
3
|
+
import type { Exposes } from './exposes';
|
|
4
|
+
|
|
5
|
+
export const EXPOSES: Exposes = [
|
|
6
|
+
'GET /database/orgs/{org_id}/namespaces',
|
|
7
|
+
'POST /database/orgs/{org_id}/namespaces',
|
|
8
|
+
'GET /database/orgs/{org_id}/namespaces/{ns}',
|
|
9
|
+
'DELETE /database/orgs/{org_id}/namespaces/{ns}',
|
|
10
|
+
'GET /database/orgs/{org_id}/namespaces/{ns}/keys',
|
|
11
|
+
'GET /database/orgs/{org_id}/namespaces/{ns}/keys/{key}',
|
|
12
|
+
'PUT /database/orgs/{org_id}/namespaces/{ns}/keys/{key}',
|
|
13
|
+
'DELETE /database/orgs/{org_id}/namespaces/{ns}/keys/{key}',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
// Per-value JSON size cap. Mirrored server-side. Surfaced here so the CLI
|
|
17
|
+
// can fail fast before round-trip.
|
|
18
|
+
export const MAX_VALUE_BYTES = 256 * 1024;
|
|
19
|
+
|
|
20
|
+
export interface Namespace {
|
|
21
|
+
name: string;
|
|
22
|
+
org_id: string;
|
|
23
|
+
key_count: number; // approximate; eventually consistent
|
|
24
|
+
created_at: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface KeyEntry {
|
|
28
|
+
key: string;
|
|
29
|
+
value?: unknown; // present when values=true was requested or on get
|
|
30
|
+
etag: string;
|
|
31
|
+
updated_at: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ListKeysOptions {
|
|
35
|
+
prefix?: string;
|
|
36
|
+
limit?: number; // 1-1000, default 50
|
|
37
|
+
cursor?: string; // opaque from previous response
|
|
38
|
+
values?: boolean; // default false
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ListKeysResponse {
|
|
42
|
+
keys: KeyEntry[];
|
|
43
|
+
next_cursor?: string; // present when more results exist
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ListNamespacesResponse {
|
|
47
|
+
namespaces: Namespace[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function listNamespaces(apiKey: string, orgId: string): Promise<ListNamespacesResponse> {
|
|
51
|
+
return request('GET', `${BASE_URL}/database/orgs/${encodeURIComponent(orgId)}/namespaces`, apiKey);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function createNamespace(apiKey: string, orgId: string, name: string): Promise<Namespace> {
|
|
55
|
+
return request('POST', `${BASE_URL}/database/orgs/${encodeURIComponent(orgId)}/namespaces`, apiKey, { name });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function getNamespace(apiKey: string, orgId: string, name: string): Promise<Namespace> {
|
|
59
|
+
return request('GET', `${BASE_URL}/database/orgs/${encodeURIComponent(orgId)}/namespaces/${encodeURIComponent(name)}`, apiKey);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function deleteNamespace(apiKey: string, orgId: string, name: string): Promise<void> {
|
|
63
|
+
return request('DELETE', `${BASE_URL}/database/orgs/${encodeURIComponent(orgId)}/namespaces/${encodeURIComponent(name)}`, apiKey);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function listKeys(apiKey: string, orgId: string, ns: string, opts: ListKeysOptions = {}): Promise<ListKeysResponse> {
|
|
67
|
+
const qs = new URLSearchParams();
|
|
68
|
+
if (opts.prefix != null) qs.append('prefix', opts.prefix);
|
|
69
|
+
if (opts.limit != null) qs.append('limit', String(opts.limit));
|
|
70
|
+
if (opts.cursor != null) qs.append('cursor', opts.cursor);
|
|
71
|
+
if (opts.values) qs.append('values', 'true');
|
|
72
|
+
const q = qs.toString();
|
|
73
|
+
const url = `${BASE_URL}/database/orgs/${encodeURIComponent(orgId)}/namespaces/${encodeURIComponent(ns)}/keys${q ? `?${q}` : ''}`;
|
|
74
|
+
return request('GET', url, apiKey);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function getKey(apiKey: string, orgId: string, ns: string, key: string): Promise<KeyEntry> {
|
|
78
|
+
return request('GET', `${BASE_URL}/database/orgs/${encodeURIComponent(orgId)}/namespaces/${encodeURIComponent(ns)}/keys/${encodeURIComponent(key)}`, apiKey);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// `ifMatch` enables compare-and-swap: server returns 412 ETAG_MISMATCH when
|
|
82
|
+
// the stored etag differs. Omit for last-write-wins.
|
|
83
|
+
export async function putKey(apiKey: string, orgId: string, ns: string, key: string, value: unknown, ifMatch?: string): Promise<KeyEntry> {
|
|
84
|
+
const headers = ifMatch ? { 'If-Match': ifMatch } : undefined;
|
|
85
|
+
return request(
|
|
86
|
+
'PUT',
|
|
87
|
+
`${BASE_URL}/database/orgs/${encodeURIComponent(orgId)}/namespaces/${encodeURIComponent(ns)}/keys/${encodeURIComponent(key)}`,
|
|
88
|
+
apiKey,
|
|
89
|
+
{ value },
|
|
90
|
+
headers,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export async function deleteKey(apiKey: string, orgId: string, ns: string, key: string, ifMatch?: string): Promise<void> {
|
|
95
|
+
const headers = ifMatch ? { 'If-Match': ifMatch } : undefined;
|
|
96
|
+
return request(
|
|
97
|
+
'DELETE',
|
|
98
|
+
`${BASE_URL}/database/orgs/${encodeURIComponent(orgId)}/namespaces/${encodeURIComponent(ns)}/keys/${encodeURIComponent(key)}`,
|
|
99
|
+
apiKey,
|
|
100
|
+
undefined,
|
|
101
|
+
headers,
|
|
102
|
+
);
|
|
103
|
+
}
|
package/src/domain.ts
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { request } from './client';
|
|
2
2
|
import { DOMAIN_BASE as BASE_URL } from './config';
|
|
3
|
+
import type { Exposes } from './exposes';
|
|
4
|
+
|
|
5
|
+
export const EXPOSES: Exposes = [
|
|
6
|
+
'GET /domain/orgs/{org_id}/list',
|
|
7
|
+
'GET /domain/orgs/{org_id}/check/available/{name}',
|
|
8
|
+
'POST /domain/orgs/{org_id}/register',
|
|
9
|
+
'POST /domain/orgs/{org_id}/import',
|
|
10
|
+
'POST /domain/orgs/{org_id}/{domain}/assign',
|
|
11
|
+
'POST /domain/orgs/{org_id}/{domain}/renew',
|
|
12
|
+
'GET /domain/orgs/{org_id}/{domain}/settings',
|
|
13
|
+
'POST /domain/orgs/{org_id}/{domain}/settings',
|
|
14
|
+
'GET /domain/orgs/{org_id}/{domain}/status',
|
|
15
|
+
];
|
|
3
16
|
|
|
4
17
|
|
|
5
18
|
|
|
@@ -21,12 +34,41 @@ export interface DomainSettings {
|
|
|
21
34
|
is_robots_txt_managed: boolean;
|
|
22
35
|
}
|
|
23
36
|
|
|
37
|
+
// ICANN-required WHOIS contact info for domain registration. Sent in the
|
|
38
|
+
// register-domain request body when REGISTRAR_PROVIDER=cloudflare is set
|
|
39
|
+
// server-side; ignored (but accepted) under the legacy OpenProvider path.
|
|
40
|
+
// The CF account holds the zone; this is the WHOIS registrant.
|
|
41
|
+
export interface Registrant {
|
|
42
|
+
name: string;
|
|
43
|
+
email: string;
|
|
44
|
+
phone: string; // E.164 format recommended (e.g. "+33612345678")
|
|
45
|
+
street: string;
|
|
46
|
+
city: string;
|
|
47
|
+
state?: string; // Required for US / CA registrants; optional elsewhere.
|
|
48
|
+
postal_code: string;
|
|
49
|
+
country_code: string; // ISO 3166-1 alpha-2 (e.g. "US", "DE", "FR")
|
|
50
|
+
organization?: string; // Optional organization name (WHOIS "Registrant Organization" field).
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface RegisterDomainInput {
|
|
54
|
+
years?: number;
|
|
55
|
+
registrant: Registrant;
|
|
56
|
+
}
|
|
57
|
+
|
|
24
58
|
export async function checkDomain(apiKey: string, orgId: string, domain: string): Promise<{ available: boolean; price_cents: number }> {
|
|
25
59
|
return request('GET', `${BASE_URL}/domain/orgs/${encodeURIComponent(orgId)}/check/available/${encodeURIComponent(domain)}`, apiKey);
|
|
26
60
|
}
|
|
27
61
|
|
|
28
|
-
export async function registerDomain(apiKey: string, orgId: string, domain: string,
|
|
29
|
-
return request('POST', `${BASE_URL}/domain/orgs/${encodeURIComponent(orgId)}/register`, apiKey, {
|
|
62
|
+
export async function registerDomain(apiKey: string, orgId: string, domain: string, input: RegisterDomainInput): Promise<DomainRecord> {
|
|
63
|
+
return request('POST', `${BASE_URL}/domain/orgs/${encodeURIComponent(orgId)}/register`, apiKey, {
|
|
64
|
+
domain,
|
|
65
|
+
years: input.years,
|
|
66
|
+
registrant: input.registrant,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export async function renewDomain(apiKey: string, orgId: string, domain: string): Promise<DomainRecord> {
|
|
71
|
+
return request('POST', `${BASE_URL}/domain/orgs/${encodeURIComponent(orgId)}/${encodeURIComponent(domain)}/renew`, apiKey);
|
|
30
72
|
}
|
|
31
73
|
|
|
32
74
|
export async function importDomain(apiKey: string, orgId: string, payload: { domain: string; namecheap_api_user?: string; namecheap_api_key?: string; }): Promise<DomainRecord> {
|
package/src/email.ts
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
import { request } from './client';
|
|
2
2
|
import { EMAIL_BASE as BASE_URL } from './config';
|
|
3
|
+
import type { Exposes } from './exposes';
|
|
4
|
+
|
|
5
|
+
export const EXPOSES: Exposes = [
|
|
6
|
+
// Mailbox / sending activation
|
|
7
|
+
'POST /email/mailboxes/create',
|
|
8
|
+
'GET /email/mailboxes',
|
|
9
|
+
'POST /email/sending/activate',
|
|
10
|
+
// Send + read
|
|
11
|
+
'POST /email/send',
|
|
12
|
+
'GET /email/status/{message_id}',
|
|
13
|
+
'GET /email/sent',
|
|
14
|
+
'GET /email/inbox/{address}',
|
|
15
|
+
'GET /email/outbox/{address}',
|
|
16
|
+
'GET /email/message/{message_id}',
|
|
17
|
+
// Warmup
|
|
18
|
+
'POST /email/warmup/start',
|
|
19
|
+
'POST /email/warmup/pause',
|
|
20
|
+
'POST /email/warmup/resume',
|
|
21
|
+
'POST /email/warmup/stop',
|
|
22
|
+
'GET /email/warmup/stats/{address}',
|
|
23
|
+
// Templates
|
|
24
|
+
'POST /email/orgs/{org_id}/templates/generate',
|
|
25
|
+
'GET /email/orgs/{org_id}/template-jobs/{job_id}',
|
|
26
|
+
'GET /email/orgs/{org_id}/templates',
|
|
27
|
+
'GET /email/orgs/{org_id}/templates/{template_id}',
|
|
28
|
+
'POST /email/orgs/{org_id}/templates/{template_id}/edit',
|
|
29
|
+
'POST /email/orgs/{org_id}/templates/{template_id}/send-test',
|
|
30
|
+
'DELETE /email/orgs/{org_id}/templates/{template_id}',
|
|
31
|
+
'GET /email/templates/{template_id}/preview',
|
|
32
|
+
// Campaigns
|
|
33
|
+
'POST /email/orgs/{org_id}/campaigns',
|
|
34
|
+
'GET /email/orgs/{org_id}/campaigns',
|
|
35
|
+
'GET /email/orgs/{org_id}/campaigns/{campaign_id}',
|
|
36
|
+
'PATCH /email/orgs/{org_id}/campaigns/{campaign_id}',
|
|
37
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/contacts/upload-list',
|
|
38
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/contacts/upload-file',
|
|
39
|
+
'GET /email/orgs/{org_id}/campaigns/{campaign_id}/contacts/status',
|
|
40
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/start',
|
|
41
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/pause',
|
|
42
|
+
'POST /email/orgs/{org_id}/campaigns/{campaign_id}/resume',
|
|
43
|
+
'GET /email/orgs/{org_id}/campaigns/{campaign_id}/stats',
|
|
44
|
+
// Verify
|
|
45
|
+
'POST /email/orgs/{org_id}/verify',
|
|
46
|
+
];
|
|
3
47
|
|
|
4
48
|
export interface EmailMessage { message_id: string; from: string; subject: string; body?: string; received_at: string }
|
|
5
49
|
export interface EmailTemplate { id: string; name: string; subject: string; preview_url: string; created_at: string; updated_at: string }
|
|
@@ -12,6 +56,40 @@ export interface CampaignStats {
|
|
|
12
56
|
engagement: { sent: number; opens: number; clicks: number; page_visits: number };
|
|
13
57
|
}
|
|
14
58
|
|
|
59
|
+
// ── Email verification ──────────────────────────────────────────────────────
|
|
60
|
+
//
|
|
61
|
+
// Synchronous single-address verification — syntax + DNS + a Microsoft
|
|
62
|
+
// GetCredentialType probe. Cheap layer only: ~50% of inputs return a
|
|
63
|
+
// definitive verdict in <1s; the rest get verdict='unknown' with
|
|
64
|
+
// smtp_recommended=true, suggesting a downstream SMTP probe (not in this
|
|
65
|
+
// API today).
|
|
66
|
+
|
|
67
|
+
export type VerifyVerdict = 'deliverable' | 'undeliverable' | 'unknown';
|
|
68
|
+
|
|
69
|
+
export interface VerifyChecks {
|
|
70
|
+
syntax: { valid: boolean; detail?: string };
|
|
71
|
+
dns?: { valid: boolean; mx_records?: string[] };
|
|
72
|
+
microsoft?: {
|
|
73
|
+
verdict: string; // e.g. 'federated_unknown', 'not_microsoft_domain'
|
|
74
|
+
if_exists_result: number; // Microsoft GetCredentialType enum
|
|
75
|
+
domain_type: number;
|
|
76
|
+
federated: boolean;
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface VerifyResult {
|
|
81
|
+
email: string;
|
|
82
|
+
verdict: VerifyVerdict;
|
|
83
|
+
confidence: number; // 0..1
|
|
84
|
+
smtp_recommended: boolean;
|
|
85
|
+
checks: VerifyChecks;
|
|
86
|
+
elapsed_ms: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function verifyEmail(apiKey: string, orgId: string, email: string): Promise<VerifyResult> {
|
|
90
|
+
return request('POST', `${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/verify`, apiKey, { email });
|
|
91
|
+
}
|
|
92
|
+
|
|
15
93
|
// ── Mailbox ops (account-scoped) ─────────────────────────────────────────────
|
|
16
94
|
|
|
17
95
|
export async function createMailbox(apiKey: string, domain: string, username: string, displayName?: string): Promise<{ address: string; created_at: string }> {
|
|
@@ -96,6 +174,12 @@ export async function getTemplate(apiKey: string, orgId: string, templateId: str
|
|
|
96
174
|
return request('GET', `${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/templates/${encodeURIComponent(templateId)}`, apiKey);
|
|
97
175
|
}
|
|
98
176
|
|
|
177
|
+
// Public preview — no auth required. The endpoint is unauthenticated per
|
|
178
|
+
// the OpenAPI schema; access is gated by the unguessable template_id.
|
|
179
|
+
export async function getTemplatePreview(templateId: string): Promise<{ html?: string; subject?: string; preview_url?: string } & Record<string, unknown>> {
|
|
180
|
+
return request('GET', `${BASE_URL}/email/templates/${encodeURIComponent(templateId)}/preview`, undefined);
|
|
181
|
+
}
|
|
182
|
+
|
|
99
183
|
export async function editTemplate(apiKey: string, orgId: string, templateId: string, prompt: string): Promise<{ template_id: string; preview_url: string }> {
|
|
100
184
|
return request('POST', `${BASE_URL}/email/orgs/${encodeURIComponent(orgId)}/templates/${encodeURIComponent(templateId)}/edit`, apiKey, { prompt });
|
|
101
185
|
}
|
package/src/exposes.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// OpenAPI-templated endpoint declaration for SDK service modules.
|
|
2
|
+
//
|
|
3
|
+
// Each service module (hq, domain, funnel, ...) exports an `EXPOSES: Exposes`
|
|
4
|
+
// array listing the backend endpoints reachable through that module's
|
|
5
|
+
// functions. The integrations coverage tool walks these to verify CLI ↔ SDK
|
|
6
|
+
// parity (S-108).
|
|
7
|
+
//
|
|
8
|
+
// Form: `<METHOD> <PATH>` where path uses `{name}` for variable segments
|
|
9
|
+
// matching OpenAPI 3.x style.
|
|
10
|
+
//
|
|
11
|
+
// Mirrors packages/cli/src/exposes.ts — kept duplicated to avoid runtime
|
|
12
|
+
// dependencies between the packages (the SDK has zero runtime deps per NFR-005).
|
|
13
|
+
|
|
14
|
+
export type Endpoint = `${'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'} /${string}`;
|
|
15
|
+
|
|
16
|
+
export type Exposes = readonly Endpoint[];
|
package/src/funnel.ts
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
import { request } from './client';
|
|
2
2
|
import { FUNNEL_BASE as BASE_URL } from './config';
|
|
3
|
+
import type { Exposes } from './exposes';
|
|
4
|
+
|
|
5
|
+
export const EXPOSES: Exposes = [
|
|
6
|
+
'POST /funnel/orgs/{org_id}/funnels',
|
|
7
|
+
'GET /funnel/orgs/{org_id}/funnels',
|
|
8
|
+
'GET /funnel/orgs/{org_id}/funnels/{funnel_id}',
|
|
9
|
+
'DELETE /funnel/orgs/{org_id}/funnels/{funnel_id}',
|
|
10
|
+
'POST /funnel/orgs/{org_id}/funnels/{funnel_id}/push-page',
|
|
11
|
+
'POST /funnel/orgs/{org_id}/funnels/{funnel_id}/verify',
|
|
12
|
+
'GET /funnel/orgs/{org_id}/funnels/{funnel_id}/pages',
|
|
13
|
+
];
|
|
3
14
|
|
|
4
15
|
|
|
5
16
|
|
|
@@ -27,6 +38,29 @@ export async function deleteFunnel(apiKey: string, orgId: string, funnelId: stri
|
|
|
27
38
|
export async function pushFunnelPage(apiKey: string, orgId: string, funnelId: string, payload: { slug: string; html: string }): Promise<{ url: string }> {
|
|
28
39
|
return request('POST', `${BASE_URL}/funnel/orgs/${encodeURIComponent(orgId)}/funnels/${encodeURIComponent(funnelId)}/push-page`, apiKey, payload);
|
|
29
40
|
}
|
|
41
|
+
|
|
42
|
+
export interface FunnelPage {
|
|
43
|
+
slug: string;
|
|
44
|
+
url?: string;
|
|
45
|
+
published_at?: string;
|
|
46
|
+
updated_at?: string;
|
|
47
|
+
// Backend may include other fields; we type the common ones and let the
|
|
48
|
+
// rest pass through for printJson() / printTable() consumers.
|
|
49
|
+
[key: string]: unknown;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function listFunnelPages(apiKey: string, orgId: string, funnelId: string): Promise<FunnelPage[]> {
|
|
53
|
+
// Backend returns `{ pages: [...] }` for this endpoint (every other list
|
|
54
|
+
// endpoint returns a flat array). Unwrap so the SDK's contract matches the
|
|
55
|
+
// rest — callers expect an array.
|
|
56
|
+
const res = await request<{ pages?: FunnelPage[] } | FunnelPage[]>(
|
|
57
|
+
'GET',
|
|
58
|
+
`${BASE_URL}/funnel/orgs/${encodeURIComponent(orgId)}/funnels/${encodeURIComponent(funnelId)}/pages`,
|
|
59
|
+
apiKey,
|
|
60
|
+
);
|
|
61
|
+
if (Array.isArray(res)) return res;
|
|
62
|
+
return res?.pages ?? [];
|
|
63
|
+
}
|
|
30
64
|
export async function verifyFunnel(apiKey: string, orgId: string, funnelId: string, opts?: { html?: string; slug?: string }): Promise<VerifyResult> {
|
|
31
65
|
return request('POST', `${BASE_URL}/funnel/orgs/${encodeURIComponent(orgId)}/funnels/${encodeURIComponent(funnelId)}/verify`, apiKey, opts || {});
|
|
32
66
|
}
|