Repository navigation
Expand file tree
/
Copy pathopenapi.json
More file actions
287 lines (287 loc) · 13.1 KB
/
Copy pathopenapi.json
File metadata and controls
287 lines (287 loc) · 13.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
{
"openapi": "3.0.3",
"info": {
"title": "Global Patient Record Project - Local POC API",
"version": "0.1.0",
"description": "Synthetic-only research API. Not clinically or production ready. Use the locally hosted Swagger UI at /docs. Generate a demo clinician first, copy its complete JSON (including both PEM strings) into each access request, then enroll, append, and assemble a timeline with the same synthetic factor. Change the factor for each new record. Request bodies and decrypted responses are visible in the browser; never use real patient data or real credentials. There is no bearer-token authentication: record access requires both factors in the request body. This document describes the existing POC, not full FHIR conformance."
},
"servers": [
{ "url": "/", "description": "This local server, including its current port" }
],
"tags": [
{ "name": "Status", "description": "Local server readiness" },
{ "name": "Demo credentials", "description": "Generate ephemeral synthetic-workflow credentials" },
{ "name": "Records", "description": "Dual-unlock encrypted record operations" }
],
"paths": {
"/api/status": {
"get": {
"operationId": "getLocalStatus",
"tags": ["Status"],
"summary": "Check the local server",
"responses": {
"200": {
"description": "Local POC status",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Status" } } }
},
"403": { "$ref": "#/components/responses/Forbidden" },
"500": { "$ref": "#/components/responses/ServerError" }
}
}
},
"/api/clinicians": {
"post": {
"operationId": "createDemoClinician",
"tags": ["Demo credentials"],
"summary": "Generate a fresh demo clinician credential",
"description": "No request body is required. Copy the entire JSON response into the clinician field of subsequent requests. Keep this same credential for that record. This returns a private key for the synthetic local demo, not a production credential-enrollment mechanism. Do not save the response in source control or share it.",
"responses": {
"201": {
"description": "Fresh demo RSA credential; response includes a private key",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/ClinicianCredential" } } }
},
"403": { "$ref": "#/components/responses/Forbidden" },
"500": { "$ref": "#/components/responses/ServerError" }
}
}
},
"/api/enroll": {
"post": {
"operationId": "enrollRecord",
"tags": ["Records"],
"summary": "Enroll a new encrypted demo record",
"description": "Use a fresh synthetic factor and the complete clinician response. Reusing an enrolled factor returns 409. The store receives encrypted material, not the supplied private key or plaintext factor.",
"requestBody": { "$ref": "#/components/requestBodies/Access" },
"responses": {
"201": {
"description": "Record enrolled",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["enrolled"],
"properties": { "enrolled": { "type": "boolean", "enum": [true] } }
}
}
}
},
"400": { "$ref": "#/components/responses/BadRequest" },
"403": { "$ref": "#/components/responses/Forbidden" },
"409": { "$ref": "#/components/responses/Conflict" },
"413": { "$ref": "#/components/responses/TooLarge" },
"500": { "$ref": "#/components/responses/ServerError" }
}
}
},
"/api/records": {
"post": {
"operationId": "appendRecord",
"tags": ["Records"],
"summary": "Append an encrypted FHIR fragment and provenance event",
"description": "Enroll first. Supply the same factor and clinician. Supports only the listed resource types with POC JSON validation, not full FHIR profile validation. Resource JSON is limited to 262144 UTF-8 bytes; the entire request is limited to 524288 bytes.",
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/AppendRequest" },
"example": {
"biometricToken": "synthetic-swagger-factor-change-for-each-record-0001",
"clinician": {
"credentialId": "COPY_FROM_CLINICIAN_RESPONSE",
"publicKeyPem": "COPY_FROM_CLINICIAN_RESPONSE",
"privateKeyPem": "COPY_FROM_CLINICIAN_RESPONSE"
},
"resource": {
"resourceType": "Condition",
"id": "synthetic-swagger-condition",
"code": { "text": "SYNTHETIC SWAGGER EXAMPLE ONLY" }
}
}
}
}
},
"responses": {
"201": {
"description": "Opaque identifiers for the newly appended event and fragment",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/AppendResult" } } }
},
"400": { "$ref": "#/components/responses/BadRequest" },
"403": { "$ref": "#/components/responses/Forbidden" },
"404": { "$ref": "#/components/responses/NotFound" },
"409": { "$ref": "#/components/responses/Conflict" },
"413": { "$ref": "#/components/responses/TooLarge" },
"500": { "$ref": "#/components/responses/ServerError" }
}
}
},
"/api/timeline": {
"post": {
"operationId": "assembleTimeline",
"tags": ["Records"],
"summary": "Unlock and verify a record's complete timeline",
"description": "Read-only record operation expressed as POST so factors never appear in URL parameters. Verifies parent links, signatures, authorization MACs and content hashes before returning decrypted synthetic resources. The plaintext response is visible in Swagger UI. An enrolled record with no entries returns an empty array.",
"requestBody": { "$ref": "#/components/requestBodies/Access" },
"responses": {
"200": {
"description": "Verified, ordered timeline",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Timeline" } } }
},
"400": { "$ref": "#/components/responses/BadRequest" },
"403": { "$ref": "#/components/responses/Forbidden" },
"404": { "$ref": "#/components/responses/NotFound" },
"409": { "$ref": "#/components/responses/Conflict" },
"413": { "$ref": "#/components/responses/TooLarge" },
"500": { "$ref": "#/components/responses/ServerError" }
}
}
}
},
"components": {
"requestBodies": {
"Access": {
"required": true,
"description": "Use the complete clinician JSON from this local server and a synthetic factor of at least 16 characters. Replace every COPY_FROM_CLINICIAN_RESPONSE placeholder. Total body limit: 524288 bytes.",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/AccessRequest" },
"example": {
"biometricToken": "synthetic-swagger-factor-change-for-each-record-0001",
"clinician": {
"credentialId": "COPY_FROM_CLINICIAN_RESPONSE",
"publicKeyPem": "COPY_FROM_CLINICIAN_RESPONSE",
"privateKeyPem": "COPY_FROM_CLINICIAN_RESPONSE"
}
}
}
}
}
},
"schemas": {
"Status": {
"type": "object",
"required": ["status", "mode", "biometricMode"],
"properties": {
"status": { "type": "string", "enum": ["ok"] },
"mode": { "type": "string", "enum": ["local-poc"] },
"biometricMode": { "type": "string", "enum": ["synthetic-browser-token"] }
}
},
"ClinicianCredential": {
"type": "object",
"required": ["credentialId", "publicKeyPem", "privateKeyPem"],
"description": "Generated demo key pair and its derived identifier. Copy all three fields unchanged; placeholders are not usable keys.",
"properties": {
"credentialId": { "type": "string", "description": "Identifier bound to this public key" },
"publicKeyPem": { "type": "string", "description": "Full generated public PEM, preserving JSON newline escapes" },
"privateKeyPem": { "type": "string", "description": "Full generated demo private PEM. Sensitive even in a demo; do not persist or share." }
}
},
"AccessRequest": {
"type": "object",
"required": ["biometricToken", "clinician"],
"properties": {
"biometricToken": {
"type": "string",
"minLength": 16,
"description": "Synthetic demo factor, never a real biometric. Use the same value throughout a record's flow."
},
"clinician": { "$ref": "#/components/schemas/ClinicianCredential" }
}
},
"AppendRequest": {
"allOf": [
{ "$ref": "#/components/schemas/AccessRequest" },
{
"type": "object",
"required": ["resource"],
"properties": { "resource": { "$ref": "#/components/schemas/FhirResource" } }
}
]
},
"FhirResource": {
"type": "object",
"description": "Synthetic JSON resource with a supported type. Other JSON fields are permitted. This is not a full FHIR schema. Maximum serialized size: 262144 UTF-8 bytes.",
"required": ["resourceType"],
"properties": {
"resourceType": { "$ref": "#/components/schemas/ResourceType" },
"id": { "type": "string", "minLength": 1 }
},
"additionalProperties": true
},
"ResourceType": {
"type": "string",
"enum": ["AllergyIntolerance", "Condition", "Immunization", "MedicationStatement", "Observation"]
},
"AppendResult": {
"type": "object",
"required": ["eventId", "fragmentId"],
"properties": {
"eventId": { "type": "string" },
"fragmentId": { "type": "string" }
}
},
"ProvenanceEvent": {
"type": "object",
"required": ["schemaVersion", "eventId", "parents", "fragmentId", "resourceType", "contentHash", "recordedAt", "clinicianId"],
"properties": {
"schemaVersion": { "type": "integer", "enum": [1] },
"eventId": { "type": "string" },
"parents": { "type": "array", "items": { "type": "string" } },
"fragmentId": { "type": "string" },
"resourceType": { "$ref": "#/components/schemas/ResourceType" },
"contentHash": { "type": "string" },
"recordedAt": { "type": "string", "format": "date-time" },
"clinicianId": { "type": "string" }
}
},
"Timeline": {
"type": "object",
"required": ["entries"],
"properties": {
"entries": {
"type": "array",
"items": {
"type": "object",
"required": ["event", "resource"],
"properties": {
"event": { "$ref": "#/components/schemas/ProvenanceEvent" },
"resource": { "$ref": "#/components/schemas/FhirResource" }
}
}
}
}
},
"Error": {
"type": "object",
"required": ["error"],
"properties": { "error": { "type": "string" } }
}
},
"responses": {
"BadRequest": {
"description": "Invalid JSON, missing access fields, or unsupported/malformed FHIR JSON.",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
},
"Forbidden": {
"description": "Browser origin rejected, or the provided clinician/factor cannot unlock the record.",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
},
"NotFound": {
"description": "No record exists for the supplied factor.",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
},
"Conflict": {
"description": "Record already enrolled, a concurrent update conflicted, or stored history failed integrity verification, depending on the operation.",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
},
"TooLarge": {
"description": "Request body exceeds 524288 bytes.",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
},
"ServerError": {
"description": "Local POC failure. This existing API also maps invalid PEM credentials and too-short factors to 500. Concurrent update conflicts return 409. This spec does not change runtime validation/error behavior.",
"content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
}
}
}
}