vintasend-api 0.0.0-stage → 1.0.0-alpha6
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/LICENSE +21 -0
- package/README.md +273 -2
- package/dist/app.d.ts +33 -0
- package/dist/app.js +33 -0
- package/dist/app.js.map +1 -0
- package/dist/config.d.ts +13 -0
- package/dist/config.js +39 -0
- package/dist/config.js.map +1 -0
- package/dist/contract/types.d.ts +196 -0
- package/dist/contract/types.js +12 -0
- package/dist/contract/types.js.map +1 -0
- package/dist/domain/capabilities.d.ts +13 -0
- package/dist/domain/capabilities.js +19 -0
- package/dist/domain/capabilities.js.map +1 -0
- package/dist/domain/filters.d.ts +16 -0
- package/dist/domain/filters.js +82 -0
- package/dist/domain/filters.js.map +1 -0
- package/dist/domain/pagination.d.ts +20 -0
- package/dist/domain/pagination.js +24 -0
- package/dist/domain/pagination.js.map +1 -0
- package/dist/domain/schemas.d.ts +90 -0
- package/dist/domain/schemas.js +52 -0
- package/dist/domain/schemas.js.map +1 -0
- package/dist/domain/serialize.d.ts +19 -0
- package/dist/domain/serialize.js +101 -0
- package/dist/domain/serialize.js.map +1 -0
- package/dist/errors.d.ts +45 -0
- package/dist/errors.js +94 -0
- package/dist/errors.js.map +1 -0
- package/dist/exports.d.ts +14 -0
- package/dist/exports.js +14 -0
- package/dist/exports.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/authenticate.d.ts +34 -0
- package/dist/middleware/authenticate.js +68 -0
- package/dist/middleware/authenticate.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +35 -0
- package/dist/middleware/error-handler.js +89 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/routes/notifications.d.ts +14 -0
- package/dist/routes/notifications.js +119 -0
- package/dist/routes/notifications.js.map +1 -0
- package/dist/routes/validation.d.ts +14 -0
- package/dist/routes/validation.js +44 -0
- package/dist/routes/validation.js.map +1 -0
- package/dist/services/github-template-client.d.ts +26 -0
- package/dist/services/github-template-client.js +148 -0
- package/dist/services/github-template-client.js.map +1 -0
- package/dist/services/github-template-preview-config.d.ts +8 -0
- package/dist/services/github-template-preview-config.js +51 -0
- package/dist/services/github-template-preview-config.js.map +1 -0
- package/dist/services/notification-preview.d.ts +22 -0
- package/dist/services/notification-preview.js +66 -0
- package/dist/services/notification-preview.js.map +1 -0
- package/dist/services/notification-service-port.d.ts +40 -0
- package/dist/services/notification-service-port.js +19 -0
- package/dist/services/notification-service-port.js.map +1 -0
- package/dist/services/paged-notification-reader.d.ts +22 -0
- package/dist/services/paged-notification-reader.js +36 -0
- package/dist/services/paged-notification-reader.js.map +1 -0
- package/dist/services/service-loader.d.ts +15 -0
- package/dist/services/service-loader.js +56 -0
- package/dist/services/service-loader.js.map +1 -0
- package/dist/services/template-path-resolver.d.ts +4 -0
- package/dist/services/template-path-resolver.js +17 -0
- package/dist/services/template-path-resolver.js.map +1 -0
- package/openapi.yaml +666 -0
- package/package.json +66 -4
package/openapi.yaml
ADDED
|
@@ -0,0 +1,666 @@
|
|
|
1
|
+
openapi: 3.1.0
|
|
2
|
+
info:
|
|
3
|
+
title: VintaSend Dashboard API
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: |
|
|
6
|
+
HTTP contract between a VintaSend notification service and the VintaSend
|
|
7
|
+
dashboard UI.
|
|
8
|
+
|
|
9
|
+
This document is the source of truth for the contract. The TypeScript server
|
|
10
|
+
in this repository implements it; any other implementation (for example one
|
|
11
|
+
backed by the Python `vintasend` package) must match it exactly so the same
|
|
12
|
+
dashboard UI can consume either.
|
|
13
|
+
|
|
14
|
+
Conventions:
|
|
15
|
+
* All timestamps are ISO-8601 strings in UTC, and are `null` when unset.
|
|
16
|
+
* `page` is 1-indexed in the API, in every implementation of this contract,
|
|
17
|
+
and clients never convert. Backends are not consistent about this — the
|
|
18
|
+
TypeScript VintaSend backends are 0-indexed while the Python ones are
|
|
19
|
+
1-indexed — so a server must read its backend's `pagination.oneIndexed`
|
|
20
|
+
capability rather than copy another implementation's offset. That
|
|
21
|
+
capability is backend-facing and is not published on the wire.
|
|
22
|
+
* Every non-2xx response uses the `ErrorResponse` envelope, and every 400
|
|
23
|
+
carries `details.issues`, a list of `{ path, message }`: `path` names
|
|
24
|
+
the offending field, and is empty for the body as a whole.
|
|
25
|
+
* Request bodies are JSON. A request that declares `application/json` (or
|
|
26
|
+
any `application/*+json`) must carry valid JSON, so an empty body there
|
|
27
|
+
is a 400. A request that declares no media type, or another one, counts
|
|
28
|
+
as an omitted body when its body is empty, and is a 400 otherwise.
|
|
29
|
+
* `hasMore` is true when the next page has at least one row, so a list
|
|
30
|
+
that exactly fills its last page never offers an empty one.
|
|
31
|
+
license:
|
|
32
|
+
name: MIT
|
|
33
|
+
servers:
|
|
34
|
+
- url: http://localhost:3333
|
|
35
|
+
description: Local development
|
|
36
|
+
security:
|
|
37
|
+
- apiKey: []
|
|
38
|
+
tags:
|
|
39
|
+
- name: notifications
|
|
40
|
+
- name: system
|
|
41
|
+
|
|
42
|
+
paths:
|
|
43
|
+
/health:
|
|
44
|
+
get:
|
|
45
|
+
tags: [system]
|
|
46
|
+
summary: Liveness probe
|
|
47
|
+
security: []
|
|
48
|
+
responses:
|
|
49
|
+
'200':
|
|
50
|
+
description: The service is up.
|
|
51
|
+
content:
|
|
52
|
+
application/json:
|
|
53
|
+
schema:
|
|
54
|
+
type: object
|
|
55
|
+
required: [status, apiVersion]
|
|
56
|
+
properties:
|
|
57
|
+
status:
|
|
58
|
+
type: string
|
|
59
|
+
enum: [ok]
|
|
60
|
+
apiVersion:
|
|
61
|
+
type: string
|
|
62
|
+
example: v1
|
|
63
|
+
|
|
64
|
+
/api/v1/capabilities:
|
|
65
|
+
get:
|
|
66
|
+
tags: [system]
|
|
67
|
+
summary: Filter capabilities of the configured backend
|
|
68
|
+
description: |
|
|
69
|
+
Flat dotted capability keys (for example `orderBy.sentAt`,
|
|
70
|
+
`stringLookups.includes`). Consumers use this to hide sorting and
|
|
71
|
+
filtering affordances the backend cannot honour. Missing keys mean
|
|
72
|
+
"supported".
|
|
73
|
+
|
|
74
|
+
Only capabilities a client can act on appear here. Capabilities that
|
|
75
|
+
describe how the server talks to its backend — the `pagination.*`
|
|
76
|
+
namespace, which reports how the backend numbers its pages — are filtered
|
|
77
|
+
out, because the API's own `page` parameter is 1-indexed either way and a
|
|
78
|
+
client that saw them might convert a second time.
|
|
79
|
+
responses:
|
|
80
|
+
'200':
|
|
81
|
+
description: Capability map.
|
|
82
|
+
content:
|
|
83
|
+
application/json:
|
|
84
|
+
schema:
|
|
85
|
+
type: object
|
|
86
|
+
required: [data]
|
|
87
|
+
properties:
|
|
88
|
+
data:
|
|
89
|
+
type: object
|
|
90
|
+
additionalProperties:
|
|
91
|
+
type: boolean
|
|
92
|
+
'401':
|
|
93
|
+
$ref: '#/components/responses/Unauthorized'
|
|
94
|
+
'403':
|
|
95
|
+
$ref: '#/components/responses/Forbidden'
|
|
96
|
+
|
|
97
|
+
/api/v1/notifications:
|
|
98
|
+
get:
|
|
99
|
+
tags: [notifications]
|
|
100
|
+
summary: List notifications
|
|
101
|
+
description: |
|
|
102
|
+
Filters are combined with AND. String filters (`bodyTemplate`,
|
|
103
|
+
`subjectTemplate`, `contextName`) are matched with the most precise
|
|
104
|
+
lookup the backend supports: case-insensitive `includes` when available,
|
|
105
|
+
otherwise a case-insensitive exact match, otherwise a plain equality
|
|
106
|
+
match. The template-version filters match exactly and never match a
|
|
107
|
+
notification whose version is null. Ordering defaults to `createdAt desc`,
|
|
108
|
+
and is dropped when the backend reports it cannot order by the requested
|
|
109
|
+
field.
|
|
110
|
+
parameters:
|
|
111
|
+
- $ref: '#/components/parameters/page'
|
|
112
|
+
- $ref: '#/components/parameters/pageSize'
|
|
113
|
+
- name: status
|
|
114
|
+
in: query
|
|
115
|
+
schema:
|
|
116
|
+
$ref: '#/components/schemas/NotificationStatus'
|
|
117
|
+
- name: notificationType
|
|
118
|
+
in: query
|
|
119
|
+
schema:
|
|
120
|
+
$ref: '#/components/schemas/NotificationType'
|
|
121
|
+
- name: adapterUsed
|
|
122
|
+
in: query
|
|
123
|
+
schema: { type: string, minLength: 1 }
|
|
124
|
+
- name: userId
|
|
125
|
+
in: query
|
|
126
|
+
schema: { type: string, minLength: 1 }
|
|
127
|
+
- name: bodyTemplate
|
|
128
|
+
in: query
|
|
129
|
+
schema: { type: string, minLength: 1 }
|
|
130
|
+
- name: subjectTemplate
|
|
131
|
+
in: query
|
|
132
|
+
schema: { type: string, minLength: 1 }
|
|
133
|
+
- name: contextName
|
|
134
|
+
in: query
|
|
135
|
+
schema: { type: string, minLength: 1 }
|
|
136
|
+
- name: tenant
|
|
137
|
+
in: query
|
|
138
|
+
schema: { type: string, minLength: 1 }
|
|
139
|
+
- name: requestedTemplateVersion
|
|
140
|
+
in: query
|
|
141
|
+
description: >-
|
|
142
|
+
Notifications pinned to this template version. Unpinned notifications never
|
|
143
|
+
match, since a null version is not version zero.
|
|
144
|
+
schema: { type: integer, minimum: 0 }
|
|
145
|
+
- name: usedTemplateVersion
|
|
146
|
+
in: query
|
|
147
|
+
description: >-
|
|
148
|
+
Notifications that rendered this template version. Notifications that have not
|
|
149
|
+
been sent, and those whose renderer has no versions, never match.
|
|
150
|
+
schema: { type: integer, minimum: 0 }
|
|
151
|
+
- name: createdAtFrom
|
|
152
|
+
in: query
|
|
153
|
+
schema: { type: string, format: date-time }
|
|
154
|
+
- name: createdAtTo
|
|
155
|
+
in: query
|
|
156
|
+
schema: { type: string, format: date-time }
|
|
157
|
+
- name: sentAtFrom
|
|
158
|
+
in: query
|
|
159
|
+
schema: { type: string, format: date-time }
|
|
160
|
+
- name: sentAtTo
|
|
161
|
+
in: query
|
|
162
|
+
schema: { type: string, format: date-time }
|
|
163
|
+
- name: orderByField
|
|
164
|
+
in: query
|
|
165
|
+
schema:
|
|
166
|
+
type: string
|
|
167
|
+
enum: [sendAfter, sentAt, readAt, createdAt, updatedAt]
|
|
168
|
+
- name: orderByDirection
|
|
169
|
+
in: query
|
|
170
|
+
schema:
|
|
171
|
+
type: string
|
|
172
|
+
enum: [asc, desc]
|
|
173
|
+
responses:
|
|
174
|
+
'200':
|
|
175
|
+
$ref: '#/components/responses/NotificationPage'
|
|
176
|
+
'400':
|
|
177
|
+
$ref: '#/components/responses/BadRequest'
|
|
178
|
+
'401':
|
|
179
|
+
$ref: '#/components/responses/Unauthorized'
|
|
180
|
+
'403':
|
|
181
|
+
$ref: '#/components/responses/Forbidden'
|
|
182
|
+
|
|
183
|
+
/api/v1/notifications/pending:
|
|
184
|
+
get:
|
|
185
|
+
tags: [notifications]
|
|
186
|
+
summary: List notifications awaiting send
|
|
187
|
+
parameters:
|
|
188
|
+
- $ref: '#/components/parameters/page'
|
|
189
|
+
- $ref: '#/components/parameters/pageSize'
|
|
190
|
+
responses:
|
|
191
|
+
'200':
|
|
192
|
+
$ref: '#/components/responses/NotificationPage'
|
|
193
|
+
'400':
|
|
194
|
+
$ref: '#/components/responses/BadRequest'
|
|
195
|
+
'401':
|
|
196
|
+
$ref: '#/components/responses/Unauthorized'
|
|
197
|
+
'403':
|
|
198
|
+
$ref: '#/components/responses/Forbidden'
|
|
199
|
+
|
|
200
|
+
/api/v1/notifications/future:
|
|
201
|
+
get:
|
|
202
|
+
tags: [notifications]
|
|
203
|
+
summary: List notifications scheduled for the future
|
|
204
|
+
parameters:
|
|
205
|
+
- $ref: '#/components/parameters/page'
|
|
206
|
+
- $ref: '#/components/parameters/pageSize'
|
|
207
|
+
responses:
|
|
208
|
+
'200':
|
|
209
|
+
$ref: '#/components/responses/NotificationPage'
|
|
210
|
+
'400':
|
|
211
|
+
$ref: '#/components/responses/BadRequest'
|
|
212
|
+
'401':
|
|
213
|
+
$ref: '#/components/responses/Unauthorized'
|
|
214
|
+
'403':
|
|
215
|
+
$ref: '#/components/responses/Forbidden'
|
|
216
|
+
|
|
217
|
+
/api/v1/notifications/one-off:
|
|
218
|
+
get:
|
|
219
|
+
tags: [notifications]
|
|
220
|
+
summary: List one-off notifications
|
|
221
|
+
parameters:
|
|
222
|
+
- $ref: '#/components/parameters/page'
|
|
223
|
+
- $ref: '#/components/parameters/pageSize'
|
|
224
|
+
responses:
|
|
225
|
+
'200':
|
|
226
|
+
$ref: '#/components/responses/NotificationPage'
|
|
227
|
+
'400':
|
|
228
|
+
$ref: '#/components/responses/BadRequest'
|
|
229
|
+
'401':
|
|
230
|
+
$ref: '#/components/responses/Unauthorized'
|
|
231
|
+
'403':
|
|
232
|
+
$ref: '#/components/responses/Forbidden'
|
|
233
|
+
|
|
234
|
+
/api/v1/notifications/{id}:
|
|
235
|
+
get:
|
|
236
|
+
tags: [notifications]
|
|
237
|
+
summary: Get one notification with its context payloads
|
|
238
|
+
description: |
|
|
239
|
+
Looks the id up as a user notification first, then as a one-off one.
|
|
240
|
+
parameters:
|
|
241
|
+
- $ref: '#/components/parameters/notificationId'
|
|
242
|
+
responses:
|
|
243
|
+
'200':
|
|
244
|
+
description: The notification.
|
|
245
|
+
content:
|
|
246
|
+
application/json:
|
|
247
|
+
schema:
|
|
248
|
+
type: object
|
|
249
|
+
required: [data]
|
|
250
|
+
properties:
|
|
251
|
+
data:
|
|
252
|
+
$ref: '#/components/schemas/NotificationDetail'
|
|
253
|
+
'401':
|
|
254
|
+
$ref: '#/components/responses/Unauthorized'
|
|
255
|
+
'403':
|
|
256
|
+
$ref: '#/components/responses/Forbidden'
|
|
257
|
+
'404':
|
|
258
|
+
$ref: '#/components/responses/NotFound'
|
|
259
|
+
|
|
260
|
+
/api/v1/notifications/{id}/preview:
|
|
261
|
+
get:
|
|
262
|
+
tags: [notifications]
|
|
263
|
+
summary: Render the notification's templates
|
|
264
|
+
description: |
|
|
265
|
+
Fetches the body and subject templates from the template source at the
|
|
266
|
+
commit SHA persisted with the notification, then renders them with the
|
|
267
|
+
stored context (or regenerates the context when none was stored).
|
|
268
|
+
Notifications that are still `PENDING_SEND` and have no stored SHA fall
|
|
269
|
+
back to the current `main` commit.
|
|
270
|
+
parameters:
|
|
271
|
+
- $ref: '#/components/parameters/notificationId'
|
|
272
|
+
responses:
|
|
273
|
+
'200':
|
|
274
|
+
description: Rendered templates.
|
|
275
|
+
content:
|
|
276
|
+
application/json:
|
|
277
|
+
schema:
|
|
278
|
+
type: object
|
|
279
|
+
required: [data]
|
|
280
|
+
properties:
|
|
281
|
+
data:
|
|
282
|
+
$ref: '#/components/schemas/NotificationPreview'
|
|
283
|
+
'401':
|
|
284
|
+
$ref: '#/components/responses/Unauthorized'
|
|
285
|
+
'403':
|
|
286
|
+
$ref: '#/components/responses/Forbidden'
|
|
287
|
+
'404':
|
|
288
|
+
$ref: '#/components/responses/NotFound'
|
|
289
|
+
'409':
|
|
290
|
+
description: |
|
|
291
|
+
No commit SHA is available for this notification, so its templates
|
|
292
|
+
cannot be resolved (`PREVIEW_UNAVAILABLE`).
|
|
293
|
+
content:
|
|
294
|
+
application/json:
|
|
295
|
+
schema:
|
|
296
|
+
$ref: '#/components/schemas/ErrorResponse'
|
|
297
|
+
'502':
|
|
298
|
+
description: |
|
|
299
|
+
The template source could not be reached, or refused the request —
|
|
300
|
+
for example a rate limit, a revoked token, or a template file that
|
|
301
|
+
does not exist at that commit (`UPSTREAM_ERROR`).
|
|
302
|
+
content:
|
|
303
|
+
application/json:
|
|
304
|
+
schema:
|
|
305
|
+
$ref: '#/components/schemas/ErrorResponse'
|
|
306
|
+
|
|
307
|
+
/api/v1/notifications/{id}/resend:
|
|
308
|
+
post:
|
|
309
|
+
tags: [notifications]
|
|
310
|
+
summary: Resend a notification
|
|
311
|
+
description: |
|
|
312
|
+
Creates a new notification based on an existing one. Only user
|
|
313
|
+
notifications that have already been sent or failed can be resent.
|
|
314
|
+
parameters:
|
|
315
|
+
- $ref: '#/components/parameters/notificationId'
|
|
316
|
+
requestBody:
|
|
317
|
+
required: false
|
|
318
|
+
content:
|
|
319
|
+
application/json:
|
|
320
|
+
schema:
|
|
321
|
+
type: object
|
|
322
|
+
properties:
|
|
323
|
+
useStoredContext:
|
|
324
|
+
type: boolean
|
|
325
|
+
default: false
|
|
326
|
+
description: |
|
|
327
|
+
Reuse the context stored with the original notification
|
|
328
|
+
instead of regenerating it from current data.
|
|
329
|
+
responses:
|
|
330
|
+
'201':
|
|
331
|
+
description: The newly created notification.
|
|
332
|
+
content:
|
|
333
|
+
application/json:
|
|
334
|
+
schema:
|
|
335
|
+
type: object
|
|
336
|
+
required: [data]
|
|
337
|
+
properties:
|
|
338
|
+
data:
|
|
339
|
+
$ref: '#/components/schemas/UserNotification'
|
|
340
|
+
'400':
|
|
341
|
+
$ref: '#/components/responses/BadRequest'
|
|
342
|
+
'401':
|
|
343
|
+
$ref: '#/components/responses/Unauthorized'
|
|
344
|
+
'403':
|
|
345
|
+
$ref: '#/components/responses/Forbidden'
|
|
346
|
+
'409':
|
|
347
|
+
description: |
|
|
348
|
+
The notification cannot be resent — it does not exist, is a one-off
|
|
349
|
+
notification, or is scheduled for the future (`CONFLICT`).
|
|
350
|
+
content:
|
|
351
|
+
application/json:
|
|
352
|
+
schema:
|
|
353
|
+
$ref: '#/components/schemas/ErrorResponse'
|
|
354
|
+
|
|
355
|
+
/api/v1/notifications/{id}/cancel:
|
|
356
|
+
post:
|
|
357
|
+
tags: [notifications]
|
|
358
|
+
summary: Cancel a pending notification
|
|
359
|
+
parameters:
|
|
360
|
+
- $ref: '#/components/parameters/notificationId'
|
|
361
|
+
responses:
|
|
362
|
+
'200':
|
|
363
|
+
description: The notification was cancelled.
|
|
364
|
+
content:
|
|
365
|
+
application/json:
|
|
366
|
+
schema:
|
|
367
|
+
type: object
|
|
368
|
+
required: [data]
|
|
369
|
+
properties:
|
|
370
|
+
data:
|
|
371
|
+
type: object
|
|
372
|
+
required: [id, status]
|
|
373
|
+
properties:
|
|
374
|
+
id: { type: string }
|
|
375
|
+
status:
|
|
376
|
+
$ref: '#/components/schemas/NotificationStatus'
|
|
377
|
+
'401':
|
|
378
|
+
$ref: '#/components/responses/Unauthorized'
|
|
379
|
+
'403':
|
|
380
|
+
$ref: '#/components/responses/Forbidden'
|
|
381
|
+
'404':
|
|
382
|
+
$ref: '#/components/responses/NotFound'
|
|
383
|
+
'409':
|
|
384
|
+
description: The notification is not in `PENDING_SEND` status (`CONFLICT`).
|
|
385
|
+
content:
|
|
386
|
+
application/json:
|
|
387
|
+
schema:
|
|
388
|
+
$ref: '#/components/schemas/ErrorResponse'
|
|
389
|
+
|
|
390
|
+
components:
|
|
391
|
+
securitySchemes:
|
|
392
|
+
apiKey:
|
|
393
|
+
type: http
|
|
394
|
+
scheme: bearer
|
|
395
|
+
description: |
|
|
396
|
+
Shared secret configured as `VINTASEND_API_KEY` for the standalone
|
|
397
|
+
server. The dashboard calls the API from its server side only, so the
|
|
398
|
+
key is never exposed to a browser. A host that mounts the API inside its
|
|
399
|
+
own server authenticates callers its own way instead.
|
|
400
|
+
|
|
401
|
+
parameters:
|
|
402
|
+
page:
|
|
403
|
+
name: page
|
|
404
|
+
in: query
|
|
405
|
+
description: |
|
|
406
|
+
1-indexed page number: `page=1` is the first page. Translating this to
|
|
407
|
+
the backend's own numbering is the server's job.
|
|
408
|
+
schema: { type: integer, minimum: 1, default: 1 }
|
|
409
|
+
pageSize:
|
|
410
|
+
name: pageSize
|
|
411
|
+
in: query
|
|
412
|
+
schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
|
|
413
|
+
notificationId:
|
|
414
|
+
name: id
|
|
415
|
+
in: path
|
|
416
|
+
required: true
|
|
417
|
+
schema: { type: string }
|
|
418
|
+
|
|
419
|
+
responses:
|
|
420
|
+
NotificationPage:
|
|
421
|
+
description: A page of notifications.
|
|
422
|
+
content:
|
|
423
|
+
application/json:
|
|
424
|
+
schema:
|
|
425
|
+
$ref: '#/components/schemas/PaginatedNotifications'
|
|
426
|
+
BadRequest:
|
|
427
|
+
description: |
|
|
428
|
+
The request failed validation (`BAD_REQUEST`). `details.issues` lists
|
|
429
|
+
what was wrong.
|
|
430
|
+
content:
|
|
431
|
+
application/json:
|
|
432
|
+
schema:
|
|
433
|
+
$ref: '#/components/schemas/ErrorResponse'
|
|
434
|
+
Unauthorized:
|
|
435
|
+
description: |
|
|
436
|
+
No valid credential was presented — a missing or wrong API key, or
|
|
437
|
+
whatever the host's authentication refuses (`UNAUTHORIZED`).
|
|
438
|
+
content:
|
|
439
|
+
application/json:
|
|
440
|
+
schema:
|
|
441
|
+
$ref: '#/components/schemas/ErrorResponse'
|
|
442
|
+
Forbidden:
|
|
443
|
+
description: |
|
|
444
|
+
The caller is authenticated and not allowed to do this (`FORBIDDEN`).
|
|
445
|
+
Answered by a host's own authentication; the API key never produces
|
|
446
|
+
it. Unlike a 401, it is not a reason to sign in again.
|
|
447
|
+
content:
|
|
448
|
+
application/json:
|
|
449
|
+
schema:
|
|
450
|
+
$ref: '#/components/schemas/ErrorResponse'
|
|
451
|
+
NotFound:
|
|
452
|
+
description: No such notification (`NOT_FOUND`).
|
|
453
|
+
content:
|
|
454
|
+
application/json:
|
|
455
|
+
schema:
|
|
456
|
+
$ref: '#/components/schemas/ErrorResponse'
|
|
457
|
+
|
|
458
|
+
schemas:
|
|
459
|
+
NotificationStatus:
|
|
460
|
+
type: string
|
|
461
|
+
enum: [PENDING_SEND, SENT, FAILED, READ, CANCELLED]
|
|
462
|
+
|
|
463
|
+
NotificationType:
|
|
464
|
+
type: string
|
|
465
|
+
enum: [EMAIL, SMS, PUSH, IN_APP]
|
|
466
|
+
|
|
467
|
+
NotificationAttachment:
|
|
468
|
+
type: object
|
|
469
|
+
required: [id, filename, contentType, size]
|
|
470
|
+
properties:
|
|
471
|
+
id: { type: string }
|
|
472
|
+
filename: { type: string }
|
|
473
|
+
contentType: { type: string }
|
|
474
|
+
size: { type: integer }
|
|
475
|
+
description: { type: string }
|
|
476
|
+
|
|
477
|
+
NotificationBase:
|
|
478
|
+
type: object
|
|
479
|
+
required:
|
|
480
|
+
- id
|
|
481
|
+
- kind
|
|
482
|
+
- notificationType
|
|
483
|
+
- title
|
|
484
|
+
- contextName
|
|
485
|
+
- status
|
|
486
|
+
- sendAfter
|
|
487
|
+
- sentAt
|
|
488
|
+
- readAt
|
|
489
|
+
- createdAt
|
|
490
|
+
- updatedAt
|
|
491
|
+
- adapterUsed
|
|
492
|
+
- bodyTemplate
|
|
493
|
+
- subjectTemplate
|
|
494
|
+
- gitCommitSha
|
|
495
|
+
- requestedTemplateVersion
|
|
496
|
+
- usedTemplateVersion
|
|
497
|
+
- tenant
|
|
498
|
+
properties:
|
|
499
|
+
id: { type: string }
|
|
500
|
+
kind:
|
|
501
|
+
type: string
|
|
502
|
+
enum: [user, one-off]
|
|
503
|
+
description: Discriminates the two notification variants.
|
|
504
|
+
notificationType:
|
|
505
|
+
$ref: '#/components/schemas/NotificationType'
|
|
506
|
+
title:
|
|
507
|
+
type: [string, 'null']
|
|
508
|
+
contextName: { type: string }
|
|
509
|
+
status:
|
|
510
|
+
$ref: '#/components/schemas/NotificationStatus'
|
|
511
|
+
sendAfter:
|
|
512
|
+
type: [string, 'null']
|
|
513
|
+
format: date-time
|
|
514
|
+
sentAt:
|
|
515
|
+
type: [string, 'null']
|
|
516
|
+
format: date-time
|
|
517
|
+
readAt:
|
|
518
|
+
type: [string, 'null']
|
|
519
|
+
format: date-time
|
|
520
|
+
createdAt:
|
|
521
|
+
type: [string, 'null']
|
|
522
|
+
format: date-time
|
|
523
|
+
updatedAt:
|
|
524
|
+
type: [string, 'null']
|
|
525
|
+
format: date-time
|
|
526
|
+
adapterUsed:
|
|
527
|
+
type: [string, 'null']
|
|
528
|
+
bodyTemplate: { type: string }
|
|
529
|
+
subjectTemplate:
|
|
530
|
+
type: [string, 'null']
|
|
531
|
+
gitCommitSha:
|
|
532
|
+
type: [string, 'null']
|
|
533
|
+
requestedTemplateVersion:
|
|
534
|
+
type: [integer, 'null']
|
|
535
|
+
description: >-
|
|
536
|
+
The template version this notification asked for, pinned when it was created or
|
|
537
|
+
updated. Null means it was left unpinned and renders whatever version is current
|
|
538
|
+
at send time. Always null when the service's template renderer has no versions.
|
|
539
|
+
usedTemplateVersion:
|
|
540
|
+
type: [integer, 'null']
|
|
541
|
+
description: >-
|
|
542
|
+
The template version the renderer reported after the notification was sent. Null
|
|
543
|
+
until it has been sent, and always null when the template renderer has no
|
|
544
|
+
versions. On an unpinned notification this is the only record of what went out.
|
|
545
|
+
tenant:
|
|
546
|
+
type: [string, 'null']
|
|
547
|
+
|
|
548
|
+
UserNotification:
|
|
549
|
+
allOf:
|
|
550
|
+
- $ref: '#/components/schemas/NotificationBase'
|
|
551
|
+
- type: object
|
|
552
|
+
required: [userId]
|
|
553
|
+
properties:
|
|
554
|
+
kind:
|
|
555
|
+
type: string
|
|
556
|
+
enum: [user]
|
|
557
|
+
userId: { type: string }
|
|
558
|
+
|
|
559
|
+
OneOffNotification:
|
|
560
|
+
allOf:
|
|
561
|
+
- $ref: '#/components/schemas/NotificationBase'
|
|
562
|
+
- type: object
|
|
563
|
+
required: [emailOrPhone, firstName, lastName]
|
|
564
|
+
properties:
|
|
565
|
+
kind:
|
|
566
|
+
type: string
|
|
567
|
+
enum: [one-off]
|
|
568
|
+
emailOrPhone: { type: string }
|
|
569
|
+
firstName:
|
|
570
|
+
type: [string, 'null']
|
|
571
|
+
lastName:
|
|
572
|
+
type: [string, 'null']
|
|
573
|
+
|
|
574
|
+
Notification:
|
|
575
|
+
oneOf:
|
|
576
|
+
- $ref: '#/components/schemas/UserNotification'
|
|
577
|
+
- $ref: '#/components/schemas/OneOffNotification'
|
|
578
|
+
discriminator:
|
|
579
|
+
propertyName: kind
|
|
580
|
+
mapping:
|
|
581
|
+
user: '#/components/schemas/UserNotification'
|
|
582
|
+
one-off: '#/components/schemas/OneOffNotification'
|
|
583
|
+
|
|
584
|
+
NotificationDetailFields:
|
|
585
|
+
type: object
|
|
586
|
+
required: [contextUsed, contextParameters, extraParams, attachments]
|
|
587
|
+
properties:
|
|
588
|
+
contextUsed:
|
|
589
|
+
description: Context the notification was rendered with, when stored.
|
|
590
|
+
nullable: true
|
|
591
|
+
contextParameters:
|
|
592
|
+
description: Parameters used to generate the context.
|
|
593
|
+
nullable: true
|
|
594
|
+
extraParams:
|
|
595
|
+
nullable: true
|
|
596
|
+
attachments:
|
|
597
|
+
type: array
|
|
598
|
+
items:
|
|
599
|
+
$ref: '#/components/schemas/NotificationAttachment'
|
|
600
|
+
|
|
601
|
+
NotificationDetail:
|
|
602
|
+
oneOf:
|
|
603
|
+
- allOf:
|
|
604
|
+
- $ref: '#/components/schemas/UserNotification'
|
|
605
|
+
- $ref: '#/components/schemas/NotificationDetailFields'
|
|
606
|
+
- allOf:
|
|
607
|
+
- $ref: '#/components/schemas/OneOffNotification'
|
|
608
|
+
- $ref: '#/components/schemas/NotificationDetailFields'
|
|
609
|
+
|
|
610
|
+
PaginatedNotifications:
|
|
611
|
+
type: object
|
|
612
|
+
required: [data, page, pageSize, hasMore]
|
|
613
|
+
properties:
|
|
614
|
+
data:
|
|
615
|
+
type: array
|
|
616
|
+
items:
|
|
617
|
+
$ref: '#/components/schemas/Notification'
|
|
618
|
+
page: { type: integer }
|
|
619
|
+
pageSize: { type: integer }
|
|
620
|
+
hasMore:
|
|
621
|
+
type: boolean
|
|
622
|
+
description: |
|
|
623
|
+
True when the next page has at least one row. Backends are not
|
|
624
|
+
required to report a total count, so after a full page the server
|
|
625
|
+
reads the one row that would come next.
|
|
626
|
+
|
|
627
|
+
NotificationPreview:
|
|
628
|
+
type: object
|
|
629
|
+
required:
|
|
630
|
+
- gitCommitSha
|
|
631
|
+
- bodyTemplatePath
|
|
632
|
+
- subjectTemplatePath
|
|
633
|
+
- renderedBodyHtml
|
|
634
|
+
- renderedSubjectHtml
|
|
635
|
+
properties:
|
|
636
|
+
gitCommitSha: { type: string }
|
|
637
|
+
bodyTemplatePath: { type: string }
|
|
638
|
+
subjectTemplatePath:
|
|
639
|
+
type: [string, 'null']
|
|
640
|
+
renderedBodyHtml: { type: string }
|
|
641
|
+
renderedSubjectHtml: { type: string }
|
|
642
|
+
|
|
643
|
+
ErrorResponse:
|
|
644
|
+
type: object
|
|
645
|
+
required: [error]
|
|
646
|
+
properties:
|
|
647
|
+
error:
|
|
648
|
+
type: object
|
|
649
|
+
required: [code, message]
|
|
650
|
+
properties:
|
|
651
|
+
code:
|
|
652
|
+
type: string
|
|
653
|
+
enum:
|
|
654
|
+
- BAD_REQUEST
|
|
655
|
+
- UNAUTHORIZED
|
|
656
|
+
- FORBIDDEN
|
|
657
|
+
- NOT_FOUND
|
|
658
|
+
- CONFLICT
|
|
659
|
+
- PREVIEW_UNAVAILABLE
|
|
660
|
+
- UPSTREAM_ERROR
|
|
661
|
+
- INTERNAL_ERROR
|
|
662
|
+
message: { type: string }
|
|
663
|
+
details:
|
|
664
|
+
description: |
|
|
665
|
+
Optional machine-readable context. Every 400 carries `issues`, a
|
|
666
|
+
list of `{ path, message }`, and may carry other keys beside it.
|