trackrev 0.1.0 → 0.3.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.
Files changed (47) hide show
  1. package/README.md +988 -42
  2. package/package.json +21 -3
  3. package/src/commands/affiliates.js +162 -0
  4. package/src/commands/analytics.js +128 -0
  5. package/src/commands/attribution.js +55 -0
  6. package/src/commands/auth.js +41 -0
  7. package/src/commands/domains.js +60 -0
  8. package/src/commands/folders.js +69 -0
  9. package/src/commands/keys.js +54 -0
  10. package/src/commands/links.js +203 -0
  11. package/src/commands/me.js +25 -0
  12. package/src/commands/money.js +96 -0
  13. package/src/commands/people.js +94 -0
  14. package/src/commands/retargeting.js +49 -0
  15. package/src/commands/revenue.js +87 -0
  16. package/src/commands/settings.js +64 -0
  17. package/src/commands/webhooks.js +79 -0
  18. package/src/index.js +43 -294
  19. package/src/lib/api.js +87 -0
  20. package/src/lib/config.js +52 -0
  21. package/src/lib/output.js +95 -0
  22. package/src/lib/prompt.js +55 -0
  23. package/src/registry.d.ts +58 -0
  24. package/src/registry.js +840 -0
  25. package/src/sdk/client.js +177 -0
  26. package/src/sdk/index.d.ts +534 -0
  27. package/src/sdk/index.js +1 -0
  28. package/src/sdk/resources/attribution.js +20 -0
  29. package/src/sdk/resources/channels.js +16 -0
  30. package/src/sdk/resources/clicks.js +16 -0
  31. package/src/sdk/resources/commissions.js +40 -0
  32. package/src/sdk/resources/credits.js +23 -0
  33. package/src/sdk/resources/domains.js +35 -0
  34. package/src/sdk/resources/export.js +15 -0
  35. package/src/sdk/resources/folders.js +55 -0
  36. package/src/sdk/resources/keys.js +28 -0
  37. package/src/sdk/resources/links.js +131 -0
  38. package/src/sdk/resources/orders.js +15 -0
  39. package/src/sdk/resources/partners.js +37 -0
  40. package/src/sdk/resources/payouts.js +22 -0
  41. package/src/sdk/resources/programs.js +55 -0
  42. package/src/sdk/resources/referrals.js +83 -0
  43. package/src/sdk/resources/retargeting.js +23 -0
  44. package/src/sdk/resources/revenue.js +53 -0
  45. package/src/sdk/resources/settings.js +29 -0
  46. package/src/sdk/resources/visitors.js +25 -0
  47. package/src/sdk/resources/webhooks.js +41 -0
@@ -0,0 +1,35 @@
1
+ export function domains(request) {
2
+ return {
3
+ // Branded domains, each with the DNS records still needed.
4
+ list: () => {
5
+ return request("GET", "/domains");
6
+ },
7
+
8
+ // One domain, by id or by the domain name itself.
9
+ get: (idOrName) => {
10
+ return request("GET", `/domains/${encodeURIComponent(idOrName)}`);
11
+ },
12
+
13
+ // Register a branded domain, e.g. "go.brand.com". The response
14
+ // carries the verification records to add to your DNS; call verify()
15
+ // until the status turns active.
16
+ add: (domain) => {
17
+ return request("POST", "/domains", {
18
+ body: { domain },
19
+ });
20
+ },
21
+
22
+ // Re-check DNS now and return the fresh status.
23
+ verify: (idOrName) => {
24
+ return request("POST", `/domains/${encodeURIComponent(idOrName)}`, {
25
+ body: {},
26
+ });
27
+ },
28
+
29
+ // Detach it. Links keep working: slugs stay unique and resolve on
30
+ // the default short host.
31
+ remove: (idOrName) => {
32
+ return request("DELETE", `/domains/${encodeURIComponent(idOrName)}`);
33
+ },
34
+ };
35
+ }
@@ -0,0 +1,15 @@
1
+ export function exportCsv(request) {
2
+ return {
3
+ // A CSV of one dataset: "channels", "links", "orders" or "visitors",
4
+ // over a window of days (default 30) or from/to. It comes back as
5
+ // text rather than JSON, so it needs a client that can read
6
+ // plain-text responses.
7
+ csv: (kind, { days, from, to } = {}) => {
8
+ const params = new URLSearchParams({ kind });
9
+ if (days) params.set("days", String(days));
10
+ if (from) params.set("from", from);
11
+ if (to) params.set("to", to);
12
+ return request("GET", `/export?${params}`);
13
+ },
14
+ };
15
+ }
@@ -0,0 +1,55 @@
1
+ export function folders(request) {
2
+ return {
3
+ // Campaign folders, each with how many campaigns it holds.
4
+ list: () => {
5
+ return request("GET", "/folders");
6
+ },
7
+
8
+ // One folder.
9
+ get: (id) => {
10
+ return request("GET", `/folders/${encodeURIComponent(id)}`);
11
+ },
12
+
13
+ // Create a folder. Dates are YYYY-MM-DD.
14
+ create: (name, { description, startDate, endDate } = {}) => {
15
+ return request("POST", "/folders", {
16
+ body: {
17
+ name,
18
+ description,
19
+ start_date: startDate,
20
+ end_date: endDate,
21
+ },
22
+ });
23
+ },
24
+
25
+ // Rename or re-date a folder. Only the fields you pass change.
26
+ update: (id, { name, description, startDate, endDate } = {}) => {
27
+ return request("PATCH", `/folders/${encodeURIComponent(id)}`, {
28
+ body: {
29
+ name,
30
+ description,
31
+ start_date: startDate,
32
+ end_date: endDate,
33
+ },
34
+ });
35
+ },
36
+
37
+ // Remove the folder. The campaigns inside are not deleted — they
38
+ // move back to ungrouped, and the response says how many.
39
+ remove: (id) => {
40
+ return request("DELETE", `/folders/${encodeURIComponent(id)}`);
41
+ },
42
+
43
+ // File a campaign into a folder. destinationId is a campaign (the
44
+ // destination behind a set of links), not a single link. Pass null
45
+ // as folderId to un-file it.
46
+ assign: (destinationId, folderId) => {
47
+ return request("POST", "/folders/assign", {
48
+ body: {
49
+ destination_id: destinationId,
50
+ folder_id: folderId,
51
+ },
52
+ });
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,28 @@
1
+ export function keys(request) {
2
+ return {
3
+ // The workspace's API keys: prefixes and metadata only. Revoked keys
4
+ // are hidden unless you ask for them.
5
+ list: ({ includeRevoked } = {}) => {
6
+ const params = new URLSearchParams();
7
+ if (includeRevoked) params.set("include", "revoked");
8
+ const qs = params.toString();
9
+ return request("GET", qs ? `/keys?${qs}` : "/keys");
10
+ },
11
+
12
+ // Mint a key. scope is "secret" (server-side) or "public" (safe in a
13
+ // browser). The plaintext key comes back ONCE, as `secret` — it is
14
+ // never stored, so a lost key is re-issued, never recovered.
15
+ create: ({ scope, label } = {}) => {
16
+ return request("POST", "/keys", {
17
+ body: { scope, label },
18
+ });
19
+ },
20
+
21
+ // Revoke a key. The row stays so last_used_at survives for the audit
22
+ // trail. Revoking an already-revoked key succeeds. A key may revoke
23
+ // itself, and the next request with it gets a 401.
24
+ revoke: (id) => {
25
+ return request("DELETE", `/keys/${encodeURIComponent(id)}`);
26
+ },
27
+ };
28
+ }
@@ -0,0 +1,131 @@
1
+ export function links(request) {
2
+ return {
3
+ // Performance per link for a window: days (default 30) or from/to.
4
+ // withSettings attaches each link's own record (expiry, password…).
5
+ list: ({ days, from, to, limit, withSettings } = {}) => {
6
+ const params = new URLSearchParams();
7
+ if (days) params.set("days", String(days));
8
+ if (from) params.set("from", from);
9
+ if (to) params.set("to", to);
10
+ if (limit) params.set("limit", String(limit));
11
+ if (withSettings) params.set("include", "settings");
12
+ const qs = params.toString();
13
+ return request("GET", qs ? `/links?${qs}` : "/links");
14
+ },
15
+
16
+ // The links themselves, newest first, with no window. To page, pass
17
+ // the next_cursor the previous call returned.
18
+ records: ({ limit, cursor, channel, destinationId, q } = {}) => {
19
+ const params = new URLSearchParams({ view: "records" });
20
+ if (limit) params.set("limit", String(limit));
21
+ if (cursor) params.set("cursor", cursor);
22
+ if (channel) params.set("channel", channel);
23
+ if (destinationId) params.set("destination_id", destinationId);
24
+ if (q) params.set("q", q);
25
+ return request("GET", `/links?${params}`);
26
+ },
27
+
28
+ // One link, by id, slug or short code.
29
+ get: (idOrSlug) => {
30
+ return request("GET", `/links/${encodeURIComponent(idOrSlug)}`);
31
+ },
32
+
33
+ // Create a campaign: one destination plus a link per channel. Pass
34
+ // channel: "email", channels: ["email", "x"], or smart: true.
35
+ create: (
36
+ url,
37
+ name,
38
+ {
39
+ channel,
40
+ channels,
41
+ smart,
42
+ campaign,
43
+ slug,
44
+ tags,
45
+ targeting,
46
+ expiresAt,
47
+ maxClicks,
48
+ expiredRedirectUrl,
49
+ password,
50
+ retargeting,
51
+ folderId,
52
+ external,
53
+ } = {}
54
+ ) => {
55
+ return request("POST", "/links", {
56
+ body: {
57
+ url,
58
+ name,
59
+ channel,
60
+ channels,
61
+ smart,
62
+ campaign,
63
+ slug,
64
+ tags,
65
+ targeting,
66
+ expires_at: expiresAt,
67
+ max_clicks: maxClicks,
68
+ expired_redirect_url: expiredRedirectUrl,
69
+ password,
70
+ retargeting,
71
+ folder_id: folderId,
72
+ external,
73
+ },
74
+ });
75
+ },
76
+
77
+ // Edit one link. Only the fields you pass change.
78
+ update: (
79
+ id,
80
+ {
81
+ slug,
82
+ utmCampaign,
83
+ utmTerm,
84
+ utmContent,
85
+ targeting,
86
+ expiresAt,
87
+ maxClicks,
88
+ expiredRedirectUrl,
89
+ password,
90
+ retargeting,
91
+ } = {}
92
+ ) => {
93
+ return request("PATCH", `/links/${encodeURIComponent(id)}`, {
94
+ body: {
95
+ slug,
96
+ utm_campaign: utmCampaign,
97
+ utm_term: utmTerm,
98
+ utm_content: utmContent,
99
+ targeting,
100
+ expires_at: expiresAt,
101
+ max_clicks: maxClicks,
102
+ expired_redirect_url: expiredRedirectUrl,
103
+ password,
104
+ retargeting,
105
+ },
106
+ });
107
+ },
108
+
109
+ // Delete one link. The campaign and its other channels stay.
110
+ remove: (id) => {
111
+ return request("DELETE", `/links/${encodeURIComponent(id)}`);
112
+ },
113
+
114
+ // Up to 500 links in one call, each row { url, name, channel, ... }.
115
+ // A bad row is reported in the response rather than thrown, so read
116
+ // result.rows instead of only catching errors.
117
+ createMany: (rows) => {
118
+ return request("POST", "/links/bulk", { body: { rows } });
119
+ },
120
+
121
+ // The link's QR code as SVG. It comes back as text rather than JSON,
122
+ // so it needs a client that can read plain-text responses.
123
+ qr: (idOrSlug, { size } = {}) => {
124
+ const params = new URLSearchParams();
125
+ if (size) params.set("size", String(size));
126
+ const qs = params.toString();
127
+ const path = `/links/${encodeURIComponent(idOrSlug)}/qr`;
128
+ return request("GET", qs ? `${path}?${qs}` : path);
129
+ },
130
+ };
131
+ }
@@ -0,0 +1,15 @@
1
+ export function orders(request) {
2
+ return {
3
+ // Synced purchases, newest first. On a free workspace amount is null
4
+ // and revenue_visible says so — revenue figures are hidden there.
5
+ list: ({ limit, cursor, status, email } = {}) => {
6
+ const params = new URLSearchParams();
7
+ if (limit) params.set("limit", String(limit));
8
+ if (cursor) params.set("cursor", cursor);
9
+ if (status) params.set("status", status);
10
+ if (email) params.set("email", email);
11
+ const qs = params.toString();
12
+ return request("GET", qs ? `/orders?${qs}` : "/orders");
13
+ },
14
+ };
15
+ }
@@ -0,0 +1,37 @@
1
+ export function partners(request) {
2
+ return {
3
+ // Affiliates across every program, or one program's.
4
+ // status: pending | approved | rejected | banned | archived.
5
+ list: ({ programId, status } = {}) => {
6
+ const params = new URLSearchParams();
7
+ if (programId) params.set("program_id", programId);
8
+ if (status) params.set("status", status);
9
+ const qs = params.toString();
10
+ return request("GET", qs ? `/partners?${qs}` : "/partners");
11
+ },
12
+
13
+ // Approve, reject, ban or archive an affiliate. Unlike the dashboard
14
+ // this sends no email, so re-running it doesn't mail anyone twice.
15
+ setStatus: (programId, partnerId, status) => {
16
+ return request("POST", "/partners/status", {
17
+ body: {
18
+ program_id: programId,
19
+ partner_id: partnerId,
20
+ status,
21
+ },
22
+ });
23
+ },
24
+
25
+ // Move an affiliate into a group. Pass null as groupId to drop them
26
+ // back to the program's own terms.
27
+ setGroup: (programId, partnerId, groupId) => {
28
+ return request("POST", "/partners/group", {
29
+ body: {
30
+ program_id: programId,
31
+ partner_id: partnerId,
32
+ group_id: groupId,
33
+ },
34
+ });
35
+ },
36
+ };
37
+ }
@@ -0,0 +1,22 @@
1
+ export function payouts(request) {
2
+ return {
3
+ // Payout batches with open and all-time totals.
4
+ list: ({ status, limit } = {}) => {
5
+ const params = new URLSearchParams();
6
+ if (status) params.set("status", status);
7
+ if (limit) params.set("limit", String(limit));
8
+ const qs = params.toString();
9
+ return request("GET", qs ? `/payouts?${qs}` : "/payouts");
10
+ },
11
+
12
+ // Settle a batch you paid off-platform; reference is the rail's own
13
+ // id (a PayPal batch, a Wise transfer). The commissions it covers
14
+ // close out with it. Creating a batch stays dashboard-only, because
15
+ // it applies payout floors and fees that shouldn't live in two places.
16
+ settle: (id, { reference } = {}) => {
17
+ return request("PATCH", `/payouts/${encodeURIComponent(id)}`, {
18
+ body: { reference },
19
+ });
20
+ },
21
+ };
22
+ }
@@ -0,0 +1,55 @@
1
+ export function programs(request) {
2
+ return {
3
+ // The workspace's affiliate programs.
4
+ list: ({ includeArchived } = {}) => {
5
+ const params = new URLSearchParams();
6
+ if (includeArchived) params.set("include", "archived");
7
+ const qs = params.toString();
8
+ return request("GET", qs ? `/programs?${qs}` : "/programs");
9
+ },
10
+
11
+ // One program.
12
+ get: (id) => {
13
+ return request("GET", `/programs/${encodeURIComponent(id)}`);
14
+ },
15
+
16
+ // Change terms or status. Only the fields you pass change, because
17
+ // these values decide what affiliates are owed and an omitted field
18
+ // must never silently reset a rate. commissionRate is a fraction
19
+ // between 0 and 1 for percent programs, so 0.3 means 30%.
20
+ update: (
21
+ id,
22
+ {
23
+ name,
24
+ landingUrl,
25
+ commissionType,
26
+ commissionRate,
27
+ recurringMonths,
28
+ cookieWindowDays,
29
+ minPayout,
30
+ autoApprove,
31
+ status,
32
+ } = {}
33
+ ) => {
34
+ return request("PATCH", `/programs/${encodeURIComponent(id)}`, {
35
+ body: {
36
+ name,
37
+ landing_url: landingUrl,
38
+ commission_type: commissionType,
39
+ commission_rate: commissionRate,
40
+ recurring_months: recurringMonths,
41
+ cookie_window_days: cookieWindowDays,
42
+ min_payout: minPayout,
43
+ auto_approve: autoApprove,
44
+ status,
45
+ },
46
+ });
47
+ },
48
+
49
+ // The program's affiliate tiers. Each group shows its own overrides
50
+ // and a resolved block with the terms an affiliate in it really gets.
51
+ groups: (id) => {
52
+ return request("GET", `/programs/${encodeURIComponent(id)}/groups`);
53
+ },
54
+ };
55
+ }
@@ -0,0 +1,83 @@
1
+ export function referrals(request) {
2
+ return {
3
+ // A user opts into your referral program. tier: "free" | "paid".
4
+ enroll: (externalUserId, email, tier, { name, programId } = {}) => {
5
+ return request("POST", "/referrals/enroll", {
6
+ body: {
7
+ external_user_id: externalUserId,
8
+ email,
9
+ tier,
10
+ name,
11
+ program_id: programId,
12
+ },
13
+ });
14
+ },
15
+
16
+ // A new user signed up. Pass one of refCode, vid or referrerExternalUserId
17
+ // so we know who referred them.
18
+ reportSignup: (
19
+ newUserExternalUserId,
20
+ { refCode, vid, referrerExternalUserId, email } = {}
21
+ ) => {
22
+ return request("POST", "/referrals/report-signup", {
23
+ body: {
24
+ new_user_external_user_id: newUserExternalUserId,
25
+ ref_code: refCode,
26
+ _vid: vid,
27
+ referrer_external_user_id: referrerExternalUserId,
28
+ email,
29
+ },
30
+ });
31
+ },
32
+
33
+ // A user paid. amount is in major units (49.99). Needs a secret key.
34
+ reportPurchase: (
35
+ externalUserId,
36
+ orderId,
37
+ amount,
38
+ { currency, email, recurring, ts } = {}
39
+ ) => {
40
+ return request("POST", "/referrals/report-purchase", {
41
+ body: {
42
+ external_user_id: externalUserId,
43
+ order_id: orderId,
44
+ amount,
45
+ currency,
46
+ email,
47
+ recurring,
48
+ ts,
49
+ },
50
+ });
51
+ },
52
+
53
+ // A user's referral link, KPIs and earnings. days: 7 to 90.
54
+ stats: (externalUserId, { email, tier, days } = {}) => {
55
+ const params = new URLSearchParams({ external_user_id: externalUserId });
56
+ if (email) params.set("email", email);
57
+ if (tier) params.set("tier", tier);
58
+ if (days) params.set("days", String(days));
59
+ return request("GET", `/referrals/stats?${params}`);
60
+ },
61
+
62
+ // Where an affiliate gets paid. PayPal only for now.
63
+ setPayoutMethod: (externalUserId, paypalEmail) => {
64
+ return request("POST", "/referrals/payout-method", {
65
+ body: {
66
+ external_user_id: externalUserId,
67
+ method: "paypal",
68
+ paypal_email: paypalEmail,
69
+ },
70
+ });
71
+ },
72
+
73
+ // Switch between earning credits ("referrer") and cash ("affiliate").
74
+ setRewardMode: (externalUserId, mode) => {
75
+ return request("POST", "/referrals/reward-mode", {
76
+ body: {
77
+ external_user_id: externalUserId,
78
+ mode,
79
+ },
80
+ });
81
+ },
82
+ };
83
+ }
@@ -0,0 +1,23 @@
1
+ export function retargeting(request) {
2
+ return {
3
+ // Your ad-pixel library, plus the catalogue of providers and the id
4
+ // shape each one expects.
5
+ list: () => {
6
+ return request("GET", "/retargeting");
7
+ },
8
+
9
+ // Store a provider's pixel, e.g. set("meta", "1234567890"). The id
10
+ // must match that provider's shape — that check is what keeps an
11
+ // arbitrary string out of the loader snippet the consent page runs.
12
+ set: (provider, pixelId) => {
13
+ return request("PUT", `/retargeting/${encodeURIComponent(provider)}`, {
14
+ body: { pixel_id: pixelId },
15
+ });
16
+ },
17
+
18
+ // Drop that provider's pixel.
19
+ remove: (provider) => {
20
+ return request("DELETE", `/retargeting/${encodeURIComponent(provider)}`);
21
+ },
22
+ };
23
+ }
@@ -0,0 +1,53 @@
1
+ export function revenue(request) {
2
+ return {
3
+ // Connected billing providers. Credentials are never returned: each
4
+ // row only reports which fields are set.
5
+ connections: () => {
6
+ return request("GET", "/revenue/connections");
7
+ },
8
+
9
+ // One connection, without credentials.
10
+ connection: (id) => {
11
+ return request("GET", `/revenue/connections/${encodeURIComponent(id)}`);
12
+ },
13
+
14
+ // What can be connected, and which credential fields each provider
15
+ // needs. Stripe is absent by design — its key lives on the workspace.
16
+ providers: () => {
17
+ return request("GET", "/revenue/providers");
18
+ },
19
+
20
+ // Connect a provider, e.g. connect("paddle", { api_key: "..." }).
21
+ // The credentials are proved with one live read before anything is
22
+ // stored, so a bad key fails here instead of as an empty revenue
23
+ // column later. Re-connecting the same provider replaces the old row.
24
+ connect: (provider, credentials, { sandbox } = {}) => {
25
+ return request("POST", "/revenue/connections", {
26
+ body: { provider, credentials, sandbox },
27
+ });
28
+ },
29
+
30
+ // Set or clear the signing secret for instant webhook sync. With it
31
+ // set, sales arrive in seconds; without it the connection is
32
+ // cron-only. Pass null to clear.
33
+ setWebhookSecret: (id, webhookSecret) => {
34
+ return request("PATCH", `/revenue/connections/${encodeURIComponent(id)}`, {
35
+ body: { webhook_secret: webhookSecret },
36
+ });
37
+ },
38
+
39
+ // Disconnect a provider. Past orders are kept.
40
+ disconnect: (id) => {
41
+ return request("DELETE", `/revenue/connections/${encodeURIComponent(id)}`);
42
+ },
43
+
44
+ // Pull charges now. Omit connectionId to sync every connection plus
45
+ // Stripe. One row per provider comes back; a provider that fails
46
+ // reports its error without stopping the others.
47
+ sync: ({ connectionId } = {}) => {
48
+ return request("POST", "/revenue/sync", {
49
+ body: { connection_id: connectionId },
50
+ });
51
+ },
52
+ };
53
+ }
@@ -0,0 +1,29 @@
1
+ export function settings(request) {
2
+ return {
3
+ // Every transactional email, with whether it is on for this
4
+ // workspace. is_default: true means no preference is stored and the
5
+ // catalogue's own default applies.
6
+ notifications: () => {
7
+ return request("GET", "/settings/notifications");
8
+ },
9
+
10
+ // Turn one email on or off, e.g. setNotification("sale.created", false).
11
+ setNotification: (key, enabled) => {
12
+ return request("PATCH", "/settings/notifications", {
13
+ body: { key, enabled },
14
+ });
15
+ },
16
+
17
+ // The white-label settings affiliates see.
18
+ branding: () => {
19
+ return request("GET", "/settings/branding");
20
+ },
21
+
22
+ // Change the accent color or logo. Only the fields you pass change.
23
+ updateBranding: ({ color, logoUrl } = {}) => {
24
+ return request("PATCH", "/settings/branding", {
25
+ body: { color, logo_url: logoUrl },
26
+ });
27
+ },
28
+ };
29
+ }
@@ -0,0 +1,25 @@
1
+ export function visitors(request) {
2
+ return {
3
+ // Visitors, most recently seen first. To page, pass the previous
4
+ // call's next_cursor as cursor.
5
+ list: ({ limit, cursor, email } = {}) => {
6
+ const params = new URLSearchParams();
7
+ if (limit) params.set("limit", String(limit));
8
+ if (cursor) params.set("cursor", cursor);
9
+ if (email) params.set("email", email);
10
+ const qs = params.toString();
11
+ return request("GET", qs ? `/visitors?${qs}` : "/visitors");
12
+ },
13
+
14
+ // One visitor. Their timeline is journey() below.
15
+ get: (id) => {
16
+ return request("GET", `/visitors/${encodeURIComponent(id)}`);
17
+ },
18
+
19
+ // Every click, identify event and order for one visitor, oldest
20
+ // first. id is the visitor_id that clicks carry.
21
+ journey: (id) => {
22
+ return request("GET", `/visitors/${encodeURIComponent(id)}/journey`);
23
+ },
24
+ };
25
+ }
@@ -0,0 +1,41 @@
1
+ export function webhooks(request) {
2
+ return {
3
+ // The workspace's outbound endpoints. The signing secret is never
4
+ // included here — it is shown once, when the endpoint is created.
5
+ list: () => {
6
+ return request("GET", "/webhooks");
7
+ },
8
+
9
+ // The catalogue of events an endpoint can subscribe to.
10
+ events: () => {
11
+ return request("GET", "/webhooks/events");
12
+ },
13
+
14
+ // One endpoint, without its secret.
15
+ get: (id) => {
16
+ return request("GET", `/webhooks/${encodeURIComponent(id)}`);
17
+ },
18
+
19
+ // Create an endpoint. url must be https. events is a list, e.g.
20
+ // ["sale.created"]. The response carries the signing secret ONCE —
21
+ // store it then, because no later read returns it.
22
+ create: (url, events) => {
23
+ return request("POST", "/webhooks", {
24
+ body: { url, events },
25
+ });
26
+ },
27
+
28
+ // Change the URL, the event list, or pause it with active: false.
29
+ // Only the fields you pass change.
30
+ update: (id, { url, events, active } = {}) => {
31
+ return request("PATCH", `/webhooks/${encodeURIComponent(id)}`, {
32
+ body: { url, events, active },
33
+ });
34
+ },
35
+
36
+ // Remove the endpoint. Deliveries stop at once.
37
+ remove: (id) => {
38
+ return request("DELETE", `/webhooks/${encodeURIComponent(id)}`);
39
+ },
40
+ };
41
+ }