@medalsocial/sdk 0.1.2 → 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/README.md CHANGED
@@ -1,124 +1,299 @@
1
- ## Medal Social SDK (TypeScript)
1
+ # Medal Social SDK
2
2
 
3
- Simple, typed client for the Medal Social API. Supports authentication via Client ID/Secret (Basic) today and Bearer token. Provides convenience methods for common create actions.
3
+ TypeScript SDK for the [Medal Social](https://medalsocial.com) API. Manage posts, emails, contacts, deals, and GDPR compliance programmatically.
4
4
 
5
- ### Install
5
+ ## Install
6
6
 
7
7
  ```bash
8
- corepack enable
8
+ npm install @medalsocial/sdk
9
+ # or
9
10
  pnpm add @medalsocial/sdk
10
11
  ```
11
12
 
12
- ### Quick start
13
+ ## Quick Start
13
14
 
14
15
  ```ts
15
- import MedalSocialClient from '@medalsocial/sdk';
16
+ import { Medal } from '@medalsocial/sdk';
16
17
 
17
- const client = new MedalSocialClient({
18
- auth: { kind: 'basic', clientId: process.env.MEDAL_CLIENT_ID!, clientSecret: process.env.MEDAL_CLIENT_SECRET! },
19
- // or: auth: { kind: 'bearer', token: process.env.MEDAL_API_TOKEN! },
20
- // optional: baseUrl: 'https://api.medalsocial.com',
18
+ const medal = new Medal('medal_xxx');
19
+
20
+ // Create and schedule a social post
21
+ const { data: post } = await medal.posts.create({
22
+ content: 'Hello from the Medal Social SDK!',
23
+ channel_ids: ['ch_1'],
21
24
  });
25
+ await medal.posts.schedule(post.id, { scheduled_at: '2026-03-15T10:00:00Z' });
22
26
 
23
- // Create lead(s)
24
- await client.createLead([
25
- {
26
- name: 'Alex Example',
27
- email: 'lead.test@example.com',
28
- company: '',
29
- source: 'website',
30
- },
27
+ // Send a transactional email
28
+ await medal.emails.send({
29
+ template_slug: 'welcome',
30
+ to: 'user@example.com',
31
+ variables: { name: 'John' },
32
+ });
33
+
34
+ // Manage contacts
35
+ const { data: contactRef } = await medal.contacts.create({
36
+ email: 'john@example.com',
37
+ first_name: 'John',
38
+ status: 'lead',
39
+ });
40
+ const { data: contact } = await medal.contacts.get(contactRef.id);
41
+ ```
42
+
43
+ ## Authentication
44
+
45
+ Two authentication methods are supported:
46
+
47
+ ### API Key (recommended for server-side)
48
+
49
+ Create an API key in your workspace settings. Keys are prefixed with `medal_` and scoped to a single workspace.
50
+
51
+ ```ts
52
+ const medal = new Medal('medal_xxx');
53
+
54
+ // With options
55
+ const medal = new Medal('medal_xxx', {
56
+ baseUrl: 'https://api.medalsocial.com', // default
57
+ timeout: 30000, // default, in ms
58
+ });
59
+ ```
60
+
61
+ ### OAuth Access Token
62
+
63
+ For OAuth integrations, pass the access token and the target workspace ID:
64
+
65
+ ```ts
66
+ const medal = new Medal('oauth_access_token', {
67
+ workspaceId: 'workspace_id', // required for OAuth
68
+ });
69
+ ```
70
+
71
+ OAuth tokens are obtained through the Medal Social OAuth flow (`/api/auth/oauth2/authorize`). The `workspaceId` is required because OAuth tokens can access multiple workspaces.
72
+
73
+ ## Resources
74
+
75
+ ### Posts
76
+
77
+ ```ts
78
+ // List connected channels
79
+ const { data: channels } = await medal.posts.channels();
80
+
81
+ // Create a post
82
+ const { data } = await medal.posts.create({
83
+ type: 'social', // 'social' | 'newsletter' | 'blog'
84
+ content: 'Hello!',
85
+ channel_ids: ['ch_1'],
86
+ });
87
+
88
+ // Get post with per-channel variants
89
+ const { data: post } = await medal.posts.get(data.id);
90
+ console.log(post.variants); // platform-specific status, permalinks
91
+
92
+ // Update a draft
93
+ await medal.posts.update(data.id, { content: 'Updated!' });
94
+
95
+ // Schedule or publish
96
+ await medal.posts.schedule(data.id, { scheduled_at: '2026-03-15T10:00:00Z' });
97
+ await medal.posts.publish(data.id);
98
+
99
+ // List posts
100
+ const posts = await medal.posts.list({ status: 'draft', type: 'social', limit: 50 });
101
+
102
+ // Delete
103
+ await medal.posts.remove(data.id);
104
+ ```
105
+
106
+ ### Emails
107
+
108
+ ```ts
109
+ // Send transactional email
110
+ const { data: sent } = await medal.emails.send({
111
+ template_slug: 'welcome',
112
+ to: 'user@example.com',
113
+ name: 'John',
114
+ locale: 'en',
115
+ variables: { company: 'Acme' },
116
+ contact_id: 'c_123', // optional: link to a contact
117
+ });
118
+
119
+ // Check delivery status
120
+ const { data: status } = await medal.emails.get(sent.id);
121
+ console.log(status.status); // 'queued' | 'sent' | 'delivered' | 'opened' | 'clicked'
122
+
123
+ // Batch send (max 100 recipients)
124
+ const { data: batch } = await medal.emails.batch({
125
+ template_slug: 'newsletter',
126
+ default_locale: 'en',
127
+ recipients: [
128
+ { email: 'a@test.com', name: 'Alice', variables: { code: 'A1' } },
129
+ { email: 'b@test.com', name: 'Bob' },
130
+ ],
131
+ });
132
+ console.log(batch.batch_id, batch.total, batch.queued, batch.failed);
133
+
134
+ // Templates
135
+ const { data: templates } = await medal.emails.templates.list();
136
+ const { data: template } = await medal.emails.templates.get('welcome', {
137
+ locale: 'ar',
138
+ fallback_locale: 'en',
139
+ });
140
+ ```
141
+
142
+ ### Contacts
143
+
144
+ ```ts
145
+ // CRUD
146
+ const { data: created } = await medal.contacts.create({
147
+ email: 'john@example.com',
148
+ first_name: 'John',
149
+ last_name: 'Doe',
150
+ company: 'Acme',
151
+ job_title: 'CTO',
152
+ status: 'lead',
153
+ label_ids: ['lbl_1'],
154
+ custom_fields: { source: 'website' },
155
+ });
156
+
157
+ const { data: contact } = await medal.contacts.get(created.id);
158
+ const { data: updated } = await medal.contacts.update(created.id, { status: 'customer' });
159
+ const { data: removed } = await medal.contacts.remove(created.id);
160
+ console.log(updated.success, removed.success);
161
+
162
+ // List with filters
163
+ const contacts = await medal.contacts.list({
164
+ status: 'lead',
165
+ email_status: 'subscribed',
166
+ label_ids: ['lbl_1'],
167
+ search: 'john',
168
+ limit: 50,
169
+ });
170
+
171
+ // Activity timeline
172
+ const activities = await medal.contacts.activities('contact_id', { limit: 20 });
173
+
174
+ // Add a note
175
+ const { data: note } = await medal.contacts.addNote('contact_id', { content: 'Follow up next week' });
176
+ console.log(note.id);
177
+
178
+ // Bulk import (max 500)
179
+ const { data: result } = await medal.contacts.import([
180
+ { email: 'a@test.com', first_name: 'Alice' },
181
+ { email: 'b@test.com', first_name: 'Bob' },
31
182
  ]);
183
+ console.log(result.added, result.skipped);
184
+ ```
185
+
186
+ ### Deals
32
187
 
33
- // Create a note
34
- await client.createNote({
35
- name: 'Test Testnes',
36
- email: 'test@medalsocial.com',
37
- company: 'Medal Social Test company',
38
- phone: '+47 12345678',
39
- content: 'Hei jeg skal gjerne sende to konteinere til USA.',
40
- metadata: { Budsjett: '$100,000' },
188
+ ```ts
189
+ const { data: created } = await medal.deals.create({
190
+ title: 'Enterprise Partnership',
191
+ value: 50000,
192
+ currency: 'USD',
193
+ brand_name: 'Acme Corp',
194
+ contact_id: 'c_123',
195
+ notes: 'Initial outreach',
41
196
  });
197
+ const { data: deal } = await medal.deals.get(created.id);
42
198
 
43
- // Record cookie consent
44
- await client.createCookieConsent({
199
+ const { data: updated } = await medal.deals.update(deal.id, { status: 'won' });
200
+ const { data: unlinked } = await medal.deals.update(deal.id, { contact_id: null }); // unlink contact
201
+
202
+ const deals = await medal.deals.list({ status: 'open', search: 'Acme' });
203
+ const { data: removed } = await medal.deals.remove(deal.id);
204
+ console.log(updated.success, unlinked.success, removed.success);
205
+ ```
206
+
207
+ ### GDPR
208
+
209
+ ```ts
210
+ // Consent management
211
+ await medal.gdpr.recordConsent({
212
+ email: 'user@example.com',
213
+ consent_type: 'marketing_email', // | 'analytics_tracking' | 'third_party_sharing'
214
+ granted: true,
215
+ source: 'signup_form',
216
+ });
217
+
218
+ const { data: consents } = await medal.gdpr.getConsent('user@example.com');
219
+
220
+ // Data exports
221
+ const { data: exp } = await medal.gdpr.requestExport();
222
+ const { data: exports } = await medal.gdpr.listExports();
223
+ const { data: status } = await medal.gdpr.getExport(exp.request_id);
224
+ console.log(status.download_url); // available when status is 'completed'
225
+
226
+ // Cookie consent (website integration)
227
+ await medal.gdpr.cookieConsent({
45
228
  domain: 'example.com',
46
- consentStatus: 'partial',
47
- consentTimestamp: '2025-06-04T10:30:00Z',
48
- ipAddress: '88.151.164.19',
49
- userAgent: 'Mozilla/5.0',
229
+ consentStatus: 'granted',
230
+ consentTimestamp: new Date().toISOString(),
50
231
  cookiePreferences: {
51
- necessary: {
52
- allowed: true,
53
- cookieRecords: [
54
- { cookie: 'session_id', duration: 'Session', description: 'Essential for user authentication and session management' },
55
- ],
56
- },
57
- analytics: { allowed: false },
58
- marketing: { allowed: true },
59
- functional: { allowed: true },
232
+ necessary: { allowed: true },
233
+ analytics: { allowed: true },
234
+ marketing: { allowed: false },
60
235
  },
61
236
  });
237
+ ```
62
238
 
63
- // Create event signup
64
- await client.createEventSignup({
65
- contact: {
66
- name: 'Test Testnes',
67
- email: 'test@medalsocial.com',
68
- company: 'Medal Social Test company',
69
- },
70
- event: {
71
- externalId: 'eksadaasdasd',
72
- name: 'Product saus asd',
73
- description: 'Learn about our new product asd',
74
- time: '2025-06-15T14:00:00Z',
75
- location: 'Online',
76
- thumbnail: 'https://medalsocialdevstorage.blob.core.windows.net/images/d05bad9e-bc52-4f8e-8191-d6944d34055c.jpg',
77
- },
78
- });
239
+ ### Workspaces
240
+
241
+ ```ts
242
+ const { data: workspaces } = await medal.workspaces.list();
243
+ console.log(workspaces); // [{ id, name, slug }]
79
244
  ```
80
245
 
81
- ### API
246
+ ## Error Handling
82
247
 
83
- - `new MedalSocialClient(options)`
84
- - **auth** (required):
85
- - `{ kind: 'basic', clientId: string, clientSecret: string }`
86
- - `{ kind: 'bearer', token: string }`
87
- - **baseUrl** (optional): string. Defaults to `https://api.medalsocial.com`.
88
- - **timeoutMs** (optional): number. Defaults to 30000.
89
- - **userAgent** (optional): string.
90
- - **fetch** (optional): custom fetch implementation to override transport.
248
+ All API errors throw `MedalApiError` with structured error details:
91
249
 
92
- - `createLead(items: { name, email, company?, source? }[])` -> `{ status, data, headers }`
93
- - `createNote(input)` -> `{ status, data, headers }`
94
- - `createCookieConsent(input)` -> `{ status, data, headers }`
95
- - `createEventSignup(input)` -> `{ status, data, headers }`
96
- - `sendTransactionalEmail(input)` -> `{ status, data, headers }`
250
+ ```ts
251
+ import { Medal, MedalApiError } from '@medalsocial/sdk';
97
252
 
98
- Responses throw on non-2xx with an error containing `status` and `details` (parsed body when available).
253
+ try {
254
+ await medal.contacts.get('bad_id');
255
+ } catch (err) {
256
+ if (err instanceof MedalApiError) {
257
+ console.log(err.status); // 404
258
+ console.log(err.code); // 'NOT_FOUND'
259
+ console.log(err.message); // 'Contact not found'
260
+ console.log(err.details); // field-level validation errors (if any)
261
+ }
262
+ }
263
+ ```
99
264
 
100
- ### Runtime support
265
+ ## Retries
101
266
 
102
- - Node 20+ and modern browsers. The client uses `fetch`; in Node 20+ `fetch` is built-in. For older environments, provide a global `fetch` polyfill.
267
+ The SDK automatically retries on `429` (rate limited) and `5xx` errors, up to 3 attempts with linear backoff. The `Retry-After` header is respected when present.
103
268
 
104
- ### Docs
269
+ ## Rate Limits
105
270
 
106
- - API Reference (TypeDoc): published automatically from the `prod` branch via GitHub Pages.
107
- - Local: `pnpm docs` then open `docs/index.html`.
271
+ | Endpoint | Rate | Burst |
272
+ |----------|------|-------|
273
+ | Read (GET) | 300/min | 100 |
274
+ | Write (POST/PATCH/DELETE) | 60/min | 30 |
275
+ | Email send | 100/min | 50 |
276
+ | Email batch | 10/min | 5 |
277
+ | Contact import | 5/min | 3 |
278
+ | GDPR export | 5/hour | 2 |
108
279
 
109
- ### Contributing
280
+ ## Pagination
110
281
 
111
- PRs welcome! Use pnpm. Useful scripts:
282
+ List endpoints use cursor-based pagination:
112
283
 
113
- ```bash
114
- pnpm install
115
- pnpm build
116
- pnpm test
117
- pnpm docs
284
+ ```ts
285
+ let cursor: string | undefined;
286
+ do {
287
+ const page = await medal.contacts.list({ limit: 100, cursor });
288
+ console.log(page.data);
289
+ cursor = page.pagination.next_cursor ?? undefined;
290
+ } while (cursor);
118
291
  ```
119
292
 
120
- ### License
293
+ ## Runtime Support
121
294
 
122
- MIT
295
+ Node.js 18+ and modern browsers. Uses native `fetch` — no polyfills required.
123
296
 
297
+ ## License
124
298
 
299
+ MIT
package/SECURITY.md ADDED
@@ -0,0 +1,15 @@
1
+ # Security Policy
2
+
3
+ ## Reporting vulnerabilities
4
+
5
+ Email **security@medalsocial.com** with details. Please do not open public GitHub issues for security vulnerabilities.
6
+
7
+ ## Supply chain
8
+
9
+ This package is published from the [Medal Social monorepo](https://github.com/Medal-Social/medal-monorepo/tree/main/packages/sdk) with npm provenance enabled. You can verify any published version was built from the source repo using:
10
+
11
+ ```bash
12
+ npm audit signatures @medalsocial/sdk
13
+ ```
14
+
15
+ Only `alioftech` (ali@medalsocial.com) has publish access.