@metalabel/dfos-protocol 0.0.1

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.
@@ -0,0 +1,19 @@
1
+ {
2
+ "description": "Identity chain: genesis + key rotation",
3
+ "type": "identity",
4
+ "chain": [
5
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJjaWQiOiJiYWZ5cmVpYmFuanBnY3FmZmNmaHI0c3B0empmdGhoNXN6b2hoYm81dGpmdWxlbWt3N3VoZGVuNXVxeSJ9.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoiY3JlYXRlIiwiYXV0aEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImFzc2VydEtleXMiOlt7ImlkIjoia2V5X3I5ZXYzNGZ2YzIzejk5OXZlYWFmdDgiLCJ0eXBlIjoiTXVsdGlrZXkiLCJwdWJsaWNLZXlNdWx0aWJhc2UiOiJ6Nk1rcnpMTU53b0pTVjRQM1ljY1djYnRrOHZkOUx0Z01LbkxlYURMVXFMdUFTamIifV0sImNvbnRyb2xsZXJLZXlzIjpbeyJpZCI6ImtleV9yOWV2MzRmdmMyM3o5OTl2ZWFhZnQ4IiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa3J6TE1Od29KU1Y0UDNZY2NXY2J0azh2ZDlMdGdNS25MZWFETFVxTHVBU2piIn1dLCJjcmVhdGVkQXQiOiIyMDI2LTAzLTA3VDAwOjAwOjAwLjAwMFoifQ.EDryDK1uvtix-17cHun9t6MacFIx2rMmMF1QLzfD5TFlSsOvMcue97pCgGn3CXeLVFtVxgpCoh0kGSXioKKzAw",
6
+ "eyJhbGciOiJFZERTQSIsInR5cCI6ImRpZDpkZm9zOmlkZW50aXR5LW9wIiwia2lkIjoiZGlkOmRmb3M6ZTN2dnRjazQyZDRlYWNkbnp2dHJuNiNrZXlfcjlldjM0ZnZjMjN6OTk5dmVhYWZ0OCIsImNpZCI6ImJhZnlyZWljeW00Y3lpZWRubGQ3M3NtYngzMnN6YWVpN3hkdWxxbjRnM3N0ZTVlMncydWxhanIzb3FtIn0.eyJ2ZXJzaW9uIjoxLCJ0eXBlIjoidXBkYXRlIiwicHJldmlvdXNPcGVyYXRpb25DSUQiOiJiYWZ5cmVpYmFuanBnY3FmZmNmaHI0c3B0empmdGhoNXN6b2hoYm81dGpmdWxlbWt3N3VoZGVuNXVxeSIsImF1dGhLZXlzIjpbeyJpZCI6ImtleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa2ZVZDY1SnJBaGZkZ0Z1TUNjY1U5VGhRdmpCMmZKQU1VSGt1dWFqRjk5MmdLIn1dLCJhc3NlcnRLZXlzIjpbeyJpZCI6ImtleV9lejlhODc0dGNrcjNkdjkzM2QzY2tkIiwidHlwZSI6Ik11bHRpa2V5IiwicHVibGljS2V5TXVsdGliYXNlIjoiejZNa2ZVZDY1SnJBaGZkZ0Z1TUNjY1U5VGhRdmpCMmZKQU1VSGt1dWFqRjk5MmdLIn1dLCJjb250cm9sbGVyS2V5cyI6W3siaWQiOiJrZXlfZXo5YTg3NHRja3IzZHY5MzNkM2NrZCIsInR5cGUiOiJNdWx0aWtleSIsInB1YmxpY0tleU11bHRpYmFzZSI6Ino2TWtmVWQ2NUpyQWhmZGdGdU1DY2NVOVRoUXZqQjJmSkFNVUhrdXVhakY5OTJnSyJ9XSwiY3JlYXRlZEF0IjoiMjAyNi0wMy0wN1QwMDowMTowMC4wMDBaIn0.MScuoBlgOK3j5QX9tFcw1ou0o4LgJziGJEsZ5pvqiBr1SagAyAv5h-wajQhtg8IP7dLlM0U4leW2iRra945cDg"
7
+ ],
8
+ "expected": {
9
+ "did": "did:dfos:e3vvtck42d4eacdnzvtrn6",
10
+ "isDeleted": false,
11
+ "controllerKeys": [
12
+ {
13
+ "id": "key_ez9a874tckr3dv933d3ckd",
14
+ "type": "Multikey",
15
+ "publicKeyMultibase": "z6MkfUd65JrAhfdgFuMCccU9ThQvjB2fJAMUHkuuajF992gK"
16
+ }
17
+ ]
18
+ }
19
+ }
package/openapi.yaml ADDED
@@ -0,0 +1,408 @@
1
+ openapi: 3.1.0
2
+ info:
3
+ title: DFOS Protocol Registry
4
+ version: 0.1.0
5
+ description: |
6
+ Minimal registry API for the DFOS Protocol. Provides chain submission,
7
+ resolution, and paginated operation history for identity chains and content
8
+ chains. Linear chain enforcement — accepts same or longer chains, rejects forks.
9
+
10
+ This spec defines the interoperability surface. Any implementation serving
11
+ these endpoints with these semantics is a compatible DFOS registry.
12
+
13
+ servers:
14
+ - url: /
15
+ description: Registry root
16
+
17
+ # ---------------------------------------------------------------------------
18
+
19
+ paths:
20
+ /identities:
21
+ post:
22
+ operationId: submitIdentityChain
23
+ summary: Submit or extend an identity chain
24
+ description: |
25
+ Verifies the chain, derives the DID from the genesis CID, and stores it.
26
+ - 201: chain accepted (new or extended)
27
+ - 200: chain already stored (noop)
28
+ - 400: invalid chain (verification failed)
29
+ - 409: chain conflicts with stored chain (fork)
30
+ requestBody:
31
+ required: true
32
+ content:
33
+ application/json:
34
+ schema:
35
+ $ref: '#/components/schemas/SubmitChainRequest'
36
+ responses:
37
+ '201':
38
+ description: Chain accepted
39
+ content:
40
+ application/json:
41
+ schema:
42
+ $ref: '#/components/schemas/IdentityState'
43
+ '200':
44
+ description: Chain already stored (noop)
45
+ content:
46
+ application/json:
47
+ schema:
48
+ $ref: '#/components/schemas/IdentityState'
49
+ '400':
50
+ description: Invalid chain
51
+ content:
52
+ application/json:
53
+ schema:
54
+ $ref: '#/components/schemas/Error'
55
+ '409':
56
+ description: Fork conflict
57
+ content:
58
+ application/json:
59
+ schema:
60
+ $ref: '#/components/schemas/Error'
61
+
62
+ /identities/{did}:
63
+ get:
64
+ operationId: resolveIdentity
65
+ summary: Resolve current identity state
66
+ parameters:
67
+ - $ref: '#/components/parameters/did'
68
+ responses:
69
+ '200':
70
+ description: Verified identity state
71
+ content:
72
+ application/json:
73
+ schema:
74
+ $ref: '#/components/schemas/IdentityState'
75
+ '404':
76
+ description: Identity not found
77
+ content:
78
+ application/json:
79
+ schema:
80
+ $ref: '#/components/schemas/Error'
81
+
82
+ /identities/{did}/operations:
83
+ get:
84
+ operationId: listIdentityOperations
85
+ summary: List identity chain operations (newest-first, paginated)
86
+ parameters:
87
+ - $ref: '#/components/parameters/did'
88
+ - $ref: '#/components/parameters/cursor'
89
+ - $ref: '#/components/parameters/limit'
90
+ responses:
91
+ '200':
92
+ description: Paginated operation list
93
+ content:
94
+ application/json:
95
+ schema:
96
+ $ref: '#/components/schemas/PaginatedOperations'
97
+ '404':
98
+ description: Identity not found
99
+ content:
100
+ application/json:
101
+ schema:
102
+ $ref: '#/components/schemas/Error'
103
+
104
+ /entities:
105
+ post:
106
+ operationId: submitContentChain
107
+ summary: Submit or extend a content chain
108
+ description: |
109
+ Verifies the chain (resolving signing keys from stored identity chains),
110
+ derives the entity ID from the genesis CID, and stores it.
111
+ Same status code semantics as identity submission.
112
+ requestBody:
113
+ required: true
114
+ content:
115
+ application/json:
116
+ schema:
117
+ $ref: '#/components/schemas/SubmitChainRequest'
118
+ responses:
119
+ '201':
120
+ description: Chain accepted
121
+ content:
122
+ application/json:
123
+ schema:
124
+ $ref: '#/components/schemas/EntityState'
125
+ '200':
126
+ description: Chain already stored (noop)
127
+ content:
128
+ application/json:
129
+ schema:
130
+ $ref: '#/components/schemas/EntityState'
131
+ '400':
132
+ description: Invalid chain
133
+ content:
134
+ application/json:
135
+ schema:
136
+ $ref: '#/components/schemas/Error'
137
+ '409':
138
+ description: Fork conflict
139
+ content:
140
+ application/json:
141
+ schema:
142
+ $ref: '#/components/schemas/Error'
143
+
144
+ /entities/{entityId}:
145
+ get:
146
+ operationId: resolveEntity
147
+ summary: Resolve current entity state
148
+ parameters:
149
+ - $ref: '#/components/parameters/entityId'
150
+ responses:
151
+ '200':
152
+ description: Entity state
153
+ content:
154
+ application/json:
155
+ schema:
156
+ $ref: '#/components/schemas/EntityState'
157
+ '404':
158
+ description: Entity not found
159
+ content:
160
+ application/json:
161
+ schema:
162
+ $ref: '#/components/schemas/Error'
163
+
164
+ /entities/{entityId}/operations:
165
+ get:
166
+ operationId: listEntityOperations
167
+ summary: List content chain operations (newest-first, paginated)
168
+ parameters:
169
+ - $ref: '#/components/parameters/entityId'
170
+ - $ref: '#/components/parameters/cursor'
171
+ - $ref: '#/components/parameters/limit'
172
+ responses:
173
+ '200':
174
+ description: Paginated operation list
175
+ content:
176
+ application/json:
177
+ schema:
178
+ $ref: '#/components/schemas/PaginatedOperations'
179
+ '404':
180
+ description: Entity not found
181
+ content:
182
+ application/json:
183
+ schema:
184
+ $ref: '#/components/schemas/Error'
185
+
186
+ /operations/{cid}:
187
+ get:
188
+ operationId: resolveOperation
189
+ summary: Resolve a single operation by CID
190
+ parameters:
191
+ - $ref: '#/components/parameters/cid'
192
+ responses:
193
+ '200':
194
+ description: Operation found
195
+ content:
196
+ application/json:
197
+ schema:
198
+ $ref: '#/components/schemas/Operation'
199
+ '404':
200
+ description: Operation not found
201
+ content:
202
+ application/json:
203
+ schema:
204
+ $ref: '#/components/schemas/Error'
205
+
206
+ /documents/{cid}:
207
+ get:
208
+ operationId: resolveDocument
209
+ summary: Resolve a document by CID
210
+ description: |
211
+ This is the only endpoint that touches actual content.
212
+ All other endpoints deal purely in chain mechanics.
213
+ parameters:
214
+ - $ref: '#/components/parameters/cid'
215
+ responses:
216
+ '200':
217
+ description: Document found
218
+ content:
219
+ application/json:
220
+ schema:
221
+ $ref: '#/components/schemas/Document'
222
+ '404':
223
+ description: Document not found
224
+ content:
225
+ application/json:
226
+ schema:
227
+ $ref: '#/components/schemas/Error'
228
+
229
+ # ---------------------------------------------------------------------------
230
+
231
+ components:
232
+ parameters:
233
+ did:
234
+ name: did
235
+ in: path
236
+ required: true
237
+ schema:
238
+ type: string
239
+ pattern: '^did:dfos:[2346789acdefhknrtvz]{22}$'
240
+ example: 'did:dfos:e3vvtck42d4eacdnzvtrn6'
241
+
242
+ entityId:
243
+ name: entityId
244
+ in: path
245
+ required: true
246
+ schema:
247
+ type: string
248
+ pattern: '^[2346789acdefhknrtvz]{22}$'
249
+ example: '67t27rzc83v7c22n9t6z7c'
250
+
251
+ cid:
252
+ name: cid
253
+ in: path
254
+ required: true
255
+ schema:
256
+ type: string
257
+ example: 'bafyreieseogefhfjajtkzqq6ssq63nnt7bslwca7d5wbrfbdlhld5jos6i'
258
+
259
+ cursor:
260
+ name: cursor
261
+ in: query
262
+ required: false
263
+ description: Opaque pagination cursor (CID of last item on previous page)
264
+ schema:
265
+ type: string
266
+
267
+ limit:
268
+ name: limit
269
+ in: query
270
+ required: false
271
+ description: Page size (1-100, default 25)
272
+ schema:
273
+ type: integer
274
+ minimum: 1
275
+ maximum: 100
276
+ default: 25
277
+
278
+ schemas:
279
+ SubmitChainRequest:
280
+ type: object
281
+ required: [chain]
282
+ properties:
283
+ chain:
284
+ type: array
285
+ minItems: 1
286
+ items:
287
+ type: string
288
+ description: JWS compact token
289
+ description: |
290
+ Ordered array of JWS compact tokens, genesis-first.
291
+ The registry verifies the chain and either accepts (same or longer
292
+ than stored) or rejects (fork or invalid).
293
+
294
+ MultikeyPublicKey:
295
+ type: object
296
+ required: [id, type, publicKeyMultibase]
297
+ properties:
298
+ id:
299
+ type: string
300
+ example: 'key_ref_controller_01'
301
+ type:
302
+ type: string
303
+ enum: [Multikey]
304
+ publicKeyMultibase:
305
+ type: string
306
+ description: W3C Multikey encoded Ed25519 public key (z6Mk... prefix)
307
+ example: 'z6MkveUvhr2oyjZGmP4kHgsPSSa6t9mG9pWuPsYoGn2CzQ6o'
308
+
309
+ IdentityState:
310
+ type: object
311
+ required: [did, isDeleted, authKeys, assertKeys, controllerKeys]
312
+ properties:
313
+ did:
314
+ type: string
315
+ example: 'did:dfos:e3vvtck42d4eacdnzvtrn6'
316
+ isDeleted:
317
+ type: boolean
318
+ authKeys:
319
+ type: array
320
+ items:
321
+ $ref: '#/components/schemas/MultikeyPublicKey'
322
+ assertKeys:
323
+ type: array
324
+ items:
325
+ $ref: '#/components/schemas/MultikeyPublicKey'
326
+ controllerKeys:
327
+ type: array
328
+ items:
329
+ $ref: '#/components/schemas/MultikeyPublicKey'
330
+
331
+ EntityState:
332
+ type: object
333
+ required: [entityId, isDeleted, currentDocumentCID, genesisCID, headCID]
334
+ properties:
335
+ entityId:
336
+ type: string
337
+ example: '67t27rzc83v7c22n9t6z7c'
338
+ isDeleted:
339
+ type: boolean
340
+ currentDocumentCID:
341
+ type: string
342
+ nullable: true
343
+ description: CID of current document (null if cleared or deleted)
344
+ genesisCID:
345
+ type: string
346
+ description: CID of the genesis operation
347
+ headCID:
348
+ type: string
349
+ description: CID of the most recent operation
350
+
351
+ OperationEntry:
352
+ type: object
353
+ required: [cid, jwsToken, createdAt]
354
+ properties:
355
+ cid:
356
+ type: string
357
+ description: dag-cbor CID of the operation payload
358
+ jwsToken:
359
+ type: string
360
+ description: JWS compact token (the signed operation)
361
+ createdAt:
362
+ type: string
363
+ format: date-time
364
+ description: ISO 8601 timestamp from the operation payload
365
+
366
+ PaginatedOperations:
367
+ type: object
368
+ required: [operations, nextCursor]
369
+ properties:
370
+ operations:
371
+ type: array
372
+ items:
373
+ $ref: '#/components/schemas/OperationEntry'
374
+ description: Operations ordered newest-first
375
+ nextCursor:
376
+ type: string
377
+ nullable: true
378
+ description: |
379
+ Opaque cursor for the next page. Null when there are no more pages.
380
+ Pass as ?cursor=xxx on the next request.
381
+
382
+ Operation:
383
+ type: object
384
+ required: [cid, jwsToken]
385
+ properties:
386
+ cid:
387
+ type: string
388
+ jwsToken:
389
+ type: string
390
+
391
+ Document:
392
+ type: object
393
+ required: [cid, content]
394
+ properties:
395
+ cid:
396
+ type: string
397
+ content:
398
+ description: Opaque document content (application-defined)
399
+
400
+ Error:
401
+ type: object
402
+ required: [error, message]
403
+ properties:
404
+ error:
405
+ type: string
406
+ enum: [BAD_REQUEST, NOT_FOUND, CONFLICT]
407
+ message:
408
+ type: string
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@metalabel/dfos-protocol",
3
+ "version": "0.0.1",
4
+ "description": "DFOS Protocol — Ed25519 signed chain primitives, registry, and verification",
5
+ "license": "MIT",
6
+ "author": "Metalabel <hello@metalabel.com> (https://metalabel.com)",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/metalabel/metalabel-dfos.git",
10
+ "directory": "packages/dfos-protocol"
11
+ },
12
+ "homepage": "https://github.com/metalabel/metalabel-dfos/tree/main/packages/dfos-protocol",
13
+ "keywords": [
14
+ "dfos",
15
+ "protocol",
16
+ "ed25519",
17
+ "jws",
18
+ "did",
19
+ "verifiable",
20
+ "identity",
21
+ "content-addressing",
22
+ "dag-cbor",
23
+ "cid",
24
+ "metalabel"
25
+ ],
26
+ "type": "module",
27
+ "exports": {
28
+ ".": {
29
+ "import": "./dist/index.js",
30
+ "types": "./dist/index.d.ts"
31
+ },
32
+ "./crypto": {
33
+ "import": "./dist/crypto/index.js",
34
+ "types": "./dist/crypto/index.d.ts"
35
+ },
36
+ "./chain": {
37
+ "import": "./dist/chain/index.js",
38
+ "types": "./dist/chain/index.d.ts"
39
+ },
40
+ "./registry": {
41
+ "import": "./dist/registry/index.js",
42
+ "types": "./dist/registry/index.d.ts"
43
+ }
44
+ },
45
+ "files": [
46
+ "dist",
47
+ "schemas",
48
+ "examples",
49
+ "PROTOCOL.md",
50
+ "openapi.yaml",
51
+ "LICENSE",
52
+ "README.md"
53
+ ],
54
+ "dependencies": {
55
+ "@ipld/dag-cbor": "^9.2.5",
56
+ "@noble/curves": "^2.0.1",
57
+ "@noble/hashes": "^2.0.1",
58
+ "hono": "^4.12.3",
59
+ "multiformats": "^13.4.2",
60
+ "zod": "^4.3.6"
61
+ },
62
+ "devDependencies": {
63
+ "@types/node": "^24.10.4",
64
+ "ajv": "^8.18.0",
65
+ "ajv-formats": "^3.0.1",
66
+ "tsup": "^8.5.1",
67
+ "tsx": "^4.21.0",
68
+ "vitest": "^4.0.18"
69
+ },
70
+ "scripts": {
71
+ "test": "vitest run",
72
+ "generate-examples": "vitest run --config vitest.scripts.config.ts",
73
+ "build": "tsup"
74
+ }
75
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.dfos.com/document-envelope/v1",
4
+ "title": "Document Envelope",
5
+ "description": "Standard wrapper for content committed to by content chains. The documentCID in a content chain operation is CID(dagCborEncode(envelope)).",
6
+ "type": "object",
7
+ "required": ["content", "baseDocumentCID", "createdByDID", "createdAt"],
8
+ "properties": {
9
+ "content": {
10
+ "type": "object",
11
+ "required": ["$schema"],
12
+ "properties": {
13
+ "$schema": {
14
+ "type": "string",
15
+ "format": "uri",
16
+ "description": "URI identifying the JSON Schema that validates this content."
17
+ }
18
+ },
19
+ "description": "Application-defined content payload. Opaque to chains — validated by the schema identified in $schema."
20
+ },
21
+ "baseDocumentCID": {
22
+ "type": ["string", "null"],
23
+ "description": "CID of the previous document version (edit lineage), or null for the first version."
24
+ },
25
+ "createdByDID": {
26
+ "type": "string",
27
+ "pattern": "^did:",
28
+ "description": "DID of the identity that created this document version."
29
+ },
30
+ "createdAt": {
31
+ "type": "string",
32
+ "format": "date-time",
33
+ "description": "ISO 8601 timestamp of document creation."
34
+ }
35
+ },
36
+ "additionalProperties": false
37
+ }
@@ -0,0 +1,59 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.dfos.com/post/v1",
4
+ "title": "Post",
5
+ "description": "A post — the primary content type in DFOS. Covers short posts, long-form posts, comments, and replies via the format discriminator.",
6
+ "type": "object",
7
+ "required": ["$schema", "format"],
8
+ "properties": {
9
+ "$schema": {
10
+ "const": "https://schemas.dfos.com/post/v1"
11
+ },
12
+ "format": {
13
+ "type": "string",
14
+ "enum": ["short-post", "long-post", "comment", "reply"],
15
+ "description": "Immutable content format — set at creation, never changes. Determines rendering and interaction semantics."
16
+ },
17
+ "title": {
18
+ "type": "string",
19
+ "description": "Post title. Typically used for long-post format."
20
+ },
21
+ "body": {
22
+ "type": "string",
23
+ "description": "Post body content."
24
+ },
25
+ "cover": {
26
+ "$ref": "#/$defs/media",
27
+ "description": "Cover image for the post."
28
+ },
29
+ "attachments": {
30
+ "type": "array",
31
+ "items": { "$ref": "#/$defs/media" },
32
+ "description": "Attached media objects."
33
+ },
34
+ "topics": {
35
+ "type": "array",
36
+ "items": { "type": "string" },
37
+ "description": "Topic names this post belongs to. Stored as names for portability."
38
+ }
39
+ },
40
+ "additionalProperties": false,
41
+ "$defs": {
42
+ "media": {
43
+ "type": "object",
44
+ "required": ["id"],
45
+ "properties": {
46
+ "id": {
47
+ "type": "string",
48
+ "description": "Opaque media object identifier."
49
+ },
50
+ "uri": {
51
+ "type": "string",
52
+ "format": "uri",
53
+ "description": "Optional URI for the media object."
54
+ }
55
+ },
56
+ "additionalProperties": false
57
+ }
58
+ }
59
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://schemas.dfos.com/profile/v1",
4
+ "title": "Profile",
5
+ "description": "A profile — the displayable identity for any agent, person, group, or space. Lowest common denominator of a presence in the system.",
6
+ "type": "object",
7
+ "required": ["$schema"],
8
+ "properties": {
9
+ "$schema": {
10
+ "const": "https://schemas.dfos.com/profile/v1"
11
+ },
12
+ "name": {
13
+ "type": "string",
14
+ "description": "Display name."
15
+ },
16
+ "description": {
17
+ "type": "string",
18
+ "description": "Short bio or description."
19
+ },
20
+ "avatar": {
21
+ "$ref": "#/$defs/media",
22
+ "description": "Avatar image."
23
+ },
24
+ "banner": {
25
+ "$ref": "#/$defs/media",
26
+ "description": "Banner image."
27
+ },
28
+ "background": {
29
+ "$ref": "#/$defs/media",
30
+ "description": "Background image."
31
+ }
32
+ },
33
+ "additionalProperties": false,
34
+ "$defs": {
35
+ "media": {
36
+ "type": "object",
37
+ "required": ["id"],
38
+ "properties": {
39
+ "id": {
40
+ "type": "string",
41
+ "description": "Opaque media object identifier."
42
+ },
43
+ "uri": {
44
+ "type": "string",
45
+ "format": "uri",
46
+ "description": "Optional URI for the media object."
47
+ }
48
+ },
49
+ "additionalProperties": false
50
+ }
51
+ }
52
+ }