@medalsocial/sdk 0.1.1 → 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 +261 -86
- package/SECURITY.md +15 -0
- package/dist/index.d.mts +583 -78
- package/dist/index.d.ts +583 -78
- package/dist/index.js +348 -100
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +338 -100
- package/dist/index.mjs.map +1 -1
- package/package.json +26 -24
package/README.md
CHANGED
|
@@ -1,124 +1,299 @@
|
|
|
1
|
-
|
|
1
|
+
# Medal Social SDK
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK for the [Medal Social](https://medalsocial.com) API. Manage posts, emails, contacts, deals, and GDPR compliance programmatically.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
|
|
8
|
+
npm install @medalsocial/sdk
|
|
9
|
+
# or
|
|
9
10
|
pnpm add @medalsocial/sdk
|
|
10
11
|
```
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
## Quick Start
|
|
13
14
|
|
|
14
15
|
```ts
|
|
15
|
-
import
|
|
16
|
+
import { Medal } from '@medalsocial/sdk';
|
|
16
17
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
//
|
|
24
|
-
await
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
34
|
-
await
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
44
|
-
await
|
|
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: '
|
|
47
|
-
consentTimestamp:
|
|
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
|
-
|
|
53
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
246
|
+
## Error Handling
|
|
82
247
|
|
|
83
|
-
|
|
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
|
-
|
|
93
|
-
|
|
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
|
-
|
|
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
|
-
|
|
265
|
+
## Retries
|
|
101
266
|
|
|
102
|
-
|
|
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
|
-
|
|
269
|
+
## Rate Limits
|
|
105
270
|
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
280
|
+
## Pagination
|
|
110
281
|
|
|
111
|
-
|
|
282
|
+
List endpoints use cursor-based pagination:
|
|
112
283
|
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
293
|
+
## Runtime Support
|
|
121
294
|
|
|
122
|
-
|
|
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.
|