xce009 commited on
Commit
c28ae12
·
1 Parent(s): 6c24b50
.gitignore CHANGED
@@ -1,7 +1,7 @@
1
  __pycache__/
2
  *.py[cod]
3
  *$py.class
4
-
5
  *.so
6
 
7
  .Python
 
1
  __pycache__/
2
  *.py[cod]
3
  *$py.class
4
+ postman_collection.json
5
  *.so
6
 
7
  .Python
app/api/server.py CHANGED
@@ -23,7 +23,7 @@ _START_TIME = time.time()
23
 
24
  async def _self_ping():
25
  import httpx
26
- health_url = f"http://{_settings.host}:{_settings.port}/health"
27
  while True:
28
  try:
29
  async with httpx.AsyncClient(timeout=30.0) as client:
@@ -34,7 +34,7 @@ async def _self_ping():
34
  _logger.warning("Self-ping returned: %s - %s", health_url, response.status_code)
35
  except Exception as exc:
36
  _logger.error("Self-ping error: %s", exc)
37
- await asyncio.sleep(3600)
38
 
39
 
40
  @asynccontextmanager
 
23
 
24
  async def _self_ping():
25
  import httpx
26
+ health_url = "https://aetherbase-llm-ready-data.hf.space/health"
27
  while True:
28
  try:
29
  async with httpx.AsyncClient(timeout=30.0) as client:
 
34
  _logger.warning("Self-ping returned: %s - %s", health_url, response.status_code)
35
  except Exception as exc:
36
  _logger.error("Self-ping error: %s", exc)
37
+ await asyncio.sleep(900)
38
 
39
 
40
  @asynccontextmanager
app/api/v1/batch.py CHANGED
@@ -68,7 +68,7 @@ def _batch_result_from_ok(result) -> BatchFileResult:
68
  )
69
  async def batch_files(
70
  files: Annotated[List[UploadFile], File(description="Files to convert")],
71
- token: str = require_auth,
72
  converter_service: ConverterService = Depends(get_converter_service),
73
  ):
74
  if not files:
@@ -120,7 +120,7 @@ async def batch_files(
120
  )
121
  async def batch_urls(
122
  body: BatchUrlRequest,
123
- token: str = require_auth,
124
  converter_service: ConverterService = Depends(get_converter_service),
125
  ):
126
  batch_start = time.perf_counter()
 
68
  )
69
  async def batch_files(
70
  files: Annotated[List[UploadFile], File(description="Files to convert")],
71
+ token: str = Depends(require_auth),
72
  converter_service: ConverterService = Depends(get_converter_service),
73
  ):
74
  if not files:
 
120
  )
121
  async def batch_urls(
122
  body: BatchUrlRequest,
123
+ token: str = Depends(require_auth),
124
  converter_service: ConverterService = Depends(get_converter_service),
125
  ):
126
  batch_start = time.perf_counter()
app/api/v1/convert.py CHANGED
@@ -110,7 +110,7 @@ async def convert_file(
110
  plain_text: bool = Form(False),
111
  return_json: bool = Form(False),
112
  mappings: Optional[str] = Form(None, description="JSON string with field mappings"),
113
- token: str = require_auth,
114
  converter_service: ConverterService = Depends(get_converter_service),
115
  extraction_service: ExtractionService = Depends(get_extraction_service),
116
  ):
@@ -160,7 +160,7 @@ async def convert_file(
160
  )
161
  async def convert_url(
162
  body: UrlRequest,
163
- token: str = require_auth,
164
  converter_service: ConverterService = Depends(get_converter_service),
165
  extraction_service: ExtractionService = Depends(get_extraction_service),
166
  ):
 
110
  plain_text: bool = Form(False),
111
  return_json: bool = Form(False),
112
  mappings: Optional[str] = Form(None, description="JSON string with field mappings"),
113
+ token: str = Depends(require_auth),
114
  converter_service: ConverterService = Depends(get_converter_service),
115
  extraction_service: ExtractionService = Depends(get_extraction_service),
116
  ):
 
160
  )
161
  async def convert_url(
162
  body: UrlRequest,
163
+ token: str = Depends(require_auth),
164
  converter_service: ConverterService = Depends(get_converter_service),
165
  extraction_service: ExtractionService = Depends(get_extraction_service),
166
  ):
app/api/v1/system.py CHANGED
@@ -33,7 +33,7 @@ _START_TIME = time.time()
33
 
34
  @router.get("/health", response_model=HealthResponse, summary="Health check")
35
  async def health(
36
- token: str = require_auth,
37
  ):
38
  return HealthResponse(
39
  success=True,
@@ -46,7 +46,7 @@ async def health(
46
 
47
  @router.get("/info", response_model=InfoResponse, summary="Server and environment information")
48
  async def info(
49
- token: str = require_auth,
50
  ):
51
  return InfoResponse(
52
  success=True,
@@ -63,7 +63,7 @@ async def info(
63
 
64
  @router.get("/formats", response_model=SupportedFormatsResponse, summary="List supported file formats")
65
  async def list_formats(
66
- token: str = require_auth,
67
  ):
68
  by_category = {
69
  "documents": [e for e in SUPPORTED_EXTENSIONS if e in DOCUMENT_EXTENSIONS],
@@ -85,7 +85,7 @@ async def list_formats(
85
 
86
  @router.get("/spacy-labels", response_model=SpacyLabelsResponse, summary="List available spaCy NER labels")
87
  async def list_spacy_labels(
88
- token: str = require_auth,
89
  extraction_service: ExtractionService = Depends(get_extraction_service),
90
  ):
91
  return SpacyLabelsResponse(
 
33
 
34
  @router.get("/health", response_model=HealthResponse, summary="Health check")
35
  async def health(
36
+ token: str = Depends(require_auth),
37
  ):
38
  return HealthResponse(
39
  success=True,
 
46
 
47
  @router.get("/info", response_model=InfoResponse, summary="Server and environment information")
48
  async def info(
49
+ token: str = Depends(require_auth),
50
  ):
51
  return InfoResponse(
52
  success=True,
 
63
 
64
  @router.get("/formats", response_model=SupportedFormatsResponse, summary="List supported file formats")
65
  async def list_formats(
66
+ token: str = Depends(require_auth),
67
  ):
68
  by_category = {
69
  "documents": [e for e in SUPPORTED_EXTENSIONS if e in DOCUMENT_EXTENSIONS],
 
85
 
86
  @router.get("/spacy-labels", response_model=SpacyLabelsResponse, summary="List available spaCy NER labels")
87
  async def list_spacy_labels(
88
+ token: str = Depends(require_auth),
89
  extraction_service: ExtractionService = Depends(get_extraction_service),
90
  ):
91
  return SpacyLabelsResponse(
app/core/security.py CHANGED
@@ -14,7 +14,7 @@ async def require_api_key(
14
  api_key: str = Security(_security),
15
  ) -> str:
16
  settings = get_settings()
17
- token = settings.API_KEY
18
 
19
  if not api_key:
20
  _logger.warning("Missing x-api-key header")
 
14
  api_key: str = Security(_security),
15
  ) -> str:
16
  settings = get_settings()
17
+ token = settings.api_key
18
 
19
  if not api_key:
20
  _logger.warning("Missing x-api-key header")
postman_collection.json CHANGED
@@ -2,7 +2,7 @@
2
  "info": {
3
  "_postman_id": "reconciliation-not-ai-extractor-service",
4
  "name": "Reconciliation-not-ai-extractor-service",
5
- "description": "Production-ready Postman collection for the Document-to-Markdown and structured data extraction API powered by Microsoft MarkItDown, RapidOCR, and spaCy.\n\n**Base URL:** `{{base_url}}`\n**Authentication:** Bearer token via `{{api_bearer_token}}`\n\n## Endpoints\n- **System:** Health, info, supported formats, spaCy labels\n- **Convert:** Single file and single URL to Markdown\n- **Batch:** Bulk conversion of files and URLs",
6
  "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
7
  },
8
  "item": [
@@ -17,10 +17,32 @@
17
  "header": [],
18
  "url": {
19
  "raw": "{{base_url}}/health",
20
- "host": ["{{base_url}}"],
21
- "path": ["health"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  },
23
- "description": "Quick health check without authentication. Returns `{\"status\": \"ok\", \"version\": \"1.0.0\"}`."
24
  },
25
  "response": []
26
  },
@@ -30,17 +52,22 @@
30
  "method": "GET",
31
  "header": [
32
  {
33
- "key": "Authorization",
34
- "value": "Bearer {{api_bearer_token}}",
35
  "type": "text"
36
  }
37
  ],
38
  "url": {
39
  "raw": "{{base_url}}/v1/health",
40
- "host": ["{{base_url}}"],
41
- "path": ["v1", "health"]
 
 
 
 
 
42
  },
43
- "description": "Authenticated health check with uptime and timestamp."
44
  },
45
  "response": []
46
  },
@@ -50,17 +77,22 @@
50
  "method": "GET",
51
  "header": [
52
  {
53
- "key": "Authorization",
54
- "value": "Bearer {{api_bearer_token}}",
55
  "type": "text"
56
  }
57
  ],
58
  "url": {
59
  "raw": "{{base_url}}/v1/info",
60
- "host": ["{{base_url}}"],
61
- "path": ["v1", "info"]
 
 
 
 
 
62
  },
63
- "description": "Server and environment information including app name, version, Python version, platform, max upload size, and supported extension count."
64
  },
65
  "response": []
66
  },
@@ -70,17 +102,22 @@
70
  "method": "GET",
71
  "header": [
72
  {
73
- "key": "Authorization",
74
- "value": "Bearer {{api_bearer_token}}",
75
  "type": "text"
76
  }
77
  ],
78
  "url": {
79
  "raw": "{{base_url}}/v1/formats",
80
- "host": ["{{base_url}}"],
81
- "path": ["v1", "formats"]
 
 
 
 
 
82
  },
83
- "description": "List all supported file formats grouped by category: documents, office, data, web, text, images, audio, archives."
84
  },
85
  "response": []
86
  },
@@ -90,17 +127,22 @@
90
  "method": "GET",
91
  "header": [
92
  {
93
- "key": "Authorization",
94
- "value": "Bearer {{api_bearer_token}}",
95
  "type": "text"
96
  }
97
  ],
98
  "url": {
99
  "raw": "{{base_url}}/v1/spacy-labels",
100
- "host": ["{{base_url}}"],
101
- "path": ["v1", "spacy-labels"]
 
 
 
 
 
102
  },
103
- "description": "Available spaCy NER labels, source types (entity, regex, token_attr), and example field mappings for structured JSON extraction."
104
  },
105
  "response": []
106
  }
@@ -116,8 +158,8 @@
116
  "method": "POST",
117
  "header": [
118
  {
119
- "key": "Authorization",
120
- "value": "Bearer {{api_bearer_token}}",
121
  "type": "text"
122
  }
123
  ],
@@ -128,34 +170,37 @@
128
  "key": "file",
129
  "type": "file",
130
  "src": "/path/to/your/document.pdf",
131
- "description": "The file to convert (PDF, DOCX, XLSX, PPTX, etc.)"
132
  },
133
  {
134
  "key": "plain_text",
135
  "value": "false",
136
- "type": "text",
137
- "description": "Return raw Markdown text instead of JSON wrapper (true/false)"
138
  },
139
  {
140
  "key": "return_json",
141
  "value": "false",
142
- "type": "text",
143
- "description": "Also extract structured JSON using spaCy NER and regex (true/false)"
144
  },
145
  {
146
  "key": "mappings",
147
- "value": "{\"company\": {\"source_type\": \"entity\", \"label\": \"ORG\"}, \"person\": {\"source_type\": \"entity\", \"label\": \"PERSON\"}}",
148
- "type": "text",
149
- "description": "Optional JSON string defining field extraction mappings"
150
  }
151
  ]
152
  },
153
  "url": {
154
  "raw": "{{base_url}}/v1/convert/file",
155
- "host": ["{{base_url}}"],
156
- "path": ["v1", "convert", "file"]
 
 
 
 
 
 
157
  },
158
- "description": "Upload a single file and convert it to Markdown. Optionally enable structured JSON extraction with custom field mappings."
159
  },
160
  "response": []
161
  },
@@ -165,8 +210,8 @@
165
  "method": "POST",
166
  "header": [
167
  {
168
- "key": "Authorization",
169
- "value": "Bearer {{api_bearer_token}}",
170
  "type": "text"
171
  },
172
  {
@@ -177,7 +222,7 @@
177
  ],
178
  "body": {
179
  "mode": "raw",
180
- "raw": "{\n \"url\": \"https://example.com/document.pdf\",\n \"return_json\": false,\n \"mappings\": {\n \"company\": {\"source_type\": \"entity\", \"label\": \"ORG\"},\n \"email\": {\"source_type\": \"regex\", \"pattern\": \"\\\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Z|a-z]{2,}\\\\b\"}\n }\n}",
181
  "options": {
182
  "raw": {
183
  "language": "json"
@@ -186,10 +231,16 @@
186
  },
187
  "url": {
188
  "raw": "{{base_url}}/v1/convert/url",
189
- "host": ["{{base_url}}"],
190
- "path": ["v1", "convert", "url"]
 
 
 
 
 
 
191
  },
192
- "description": "Convert a remote document URL to Markdown. Optionally enable structured JSON extraction with custom field mappings."
193
  },
194
  "response": []
195
  }
@@ -205,8 +256,8 @@
205
  "method": "POST",
206
  "header": [
207
  {
208
- "key": "Authorization",
209
- "value": "Bearer {{api_bearer_token}}",
210
  "type": "text"
211
  }
212
  ],
@@ -216,23 +267,27 @@
216
  {
217
  "key": "files",
218
  "type": "file",
219
- "src": "/path/to/your/document1.pdf",
220
- "description": "First file (up to 10 files max)"
221
  },
222
  {
223
  "key": "files",
224
  "type": "file",
225
- "src": "/path/to/your/document2.docx",
226
- "description": "Second file"
227
  }
228
  ]
229
  },
230
  "url": {
231
  "raw": "{{base_url}}/v1/batch/files",
232
- "host": ["{{base_url}}"],
233
- "path": ["v1", "batch", "files"]
 
 
 
 
 
 
234
  },
235
- "description": "Upload and convert up to 10 files in a single request. Returns aggregated results with per-file success/failure status."
236
  },
237
  "response": []
238
  },
@@ -242,8 +297,8 @@
242
  "method": "POST",
243
  "header": [
244
  {
245
- "key": "Authorization",
246
- "value": "Bearer {{api_bearer_token}}",
247
  "type": "text"
248
  },
249
  {
@@ -254,7 +309,7 @@
254
  ],
255
  "body": {
256
  "mode": "raw",
257
- "raw": "{\n \"urls\": [\n \"https://example.com/report1.pdf\",\n \"https://example.com/report2.docx\"\n ]\n}",
258
  "options": {
259
  "raw": {
260
  "language": "json"
@@ -263,10 +318,16 @@
263
  },
264
  "url": {
265
  "raw": "{{base_url}}/v1/batch/urls",
266
- "host": ["{{base_url}}"],
267
- "path": ["v1", "batch", "urls"]
 
 
 
 
 
 
268
  },
269
- "description": "Convert up to 20 URLs in a single request. Returns aggregated results with per-URL success/failure status."
270
  },
271
  "response": []
272
  }
@@ -279,16 +340,16 @@
279
  "script": {
280
  "type": "text/javascript",
281
  "exec": [
282
- "// Validate required environment variables before each request",
283
- "const baseUrl = pm.environment.get('base_url');",
284
- "const token = pm.environment.get('api_bearer_token');",
285
  "",
286
  "if (!baseUrl) {",
287
- " pm.expect.fail('Missing environment variable: base_url');",
288
  "}",
289
  "",
290
- "if (!token && pm.request.url.path.join('/').startsWith('v1')) {",
291
- " pm.expect.fail('Missing environment variable: api_bearer_token');",
292
  "}"
293
  ]
294
  }
@@ -299,23 +360,28 @@
299
  "key": "base_url",
300
  "value": "http://localhost:7860",
301
  "type": "string",
302
- "description": "Base URL of the Reconciliation-not-ai-extractor-service (e.g., http://localhost:7860 or your deployed URL)"
303
  },
304
  {
305
- "key": "api_bearer_token",
306
  "value": "changeme",
307
  "type": "string",
308
- "description": "API bearer token for authenticated endpoints. Must match the service's API_BEARER_TOKEN environment variable."
309
  }
310
  ],
311
  "auth": {
312
- "type": "bearer",
313
- "bearer": [
 
 
 
 
 
314
  {
315
- "key": "token",
316
- "value": "{{api_bearer_token}}",
317
  "type": "string"
318
  }
319
  ]
320
  }
321
- }
 
2
  "info": {
3
  "_postman_id": "reconciliation-not-ai-extractor-service",
4
  "name": "Reconciliation-not-ai-extractor-service",
5
+ "description": "Production-ready Postman collection for the Document-to-Markdown and structured data extraction API powered by Microsoft MarkItDown, RapidOCR, and spaCy.\n\n**Base URL:** `{{base_url}}`\n**Authentication:** API key via `x-api-key` header\n\n## Endpoints\n- **System:** Health, info, supported formats, spaCy labels\n- **Convert:** Single file and single URL to Markdown\n- **Batch:** Bulk conversion of files and URLs",
6
  "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
7
  },
8
  "item": [
 
17
  "header": [],
18
  "url": {
19
  "raw": "{{base_url}}/health",
20
+ "host": [
21
+ "{{base_url}}"
22
+ ],
23
+ "path": [
24
+ "health"
25
+ ]
26
+ },
27
+ "description": "Quick health check without authentication."
28
+ },
29
+ "response": []
30
+ },
31
+ {
32
+ "name": "Ping (No Auth)",
33
+ "request": {
34
+ "method": "GET",
35
+ "header": [],
36
+ "url": {
37
+ "raw": "{{base_url}}/ping",
38
+ "host": [
39
+ "{{base_url}}"
40
+ ],
41
+ "path": [
42
+ "ping"
43
+ ]
44
  },
45
+ "description": "Simple ping endpoint without authentication."
46
  },
47
  "response": []
48
  },
 
52
  "method": "GET",
53
  "header": [
54
  {
55
+ "key": "x-api-key",
56
+ "value": "{{api_key}}",
57
  "type": "text"
58
  }
59
  ],
60
  "url": {
61
  "raw": "{{base_url}}/v1/health",
62
+ "host": [
63
+ "{{base_url}}"
64
+ ],
65
+ "path": [
66
+ "v1",
67
+ "health"
68
+ ]
69
  },
70
+ "description": "Authenticated health check."
71
  },
72
  "response": []
73
  },
 
77
  "method": "GET",
78
  "header": [
79
  {
80
+ "key": "x-api-key",
81
+ "value": "{{api_key}}",
82
  "type": "text"
83
  }
84
  ],
85
  "url": {
86
  "raw": "{{base_url}}/v1/info",
87
+ "host": [
88
+ "{{base_url}}"
89
+ ],
90
+ "path": [
91
+ "v1",
92
+ "info"
93
+ ]
94
  },
95
+ "description": "Server and environment information."
96
  },
97
  "response": []
98
  },
 
102
  "method": "GET",
103
  "header": [
104
  {
105
+ "key": "x-api-key",
106
+ "value": "{{api_key}}",
107
  "type": "text"
108
  }
109
  ],
110
  "url": {
111
  "raw": "{{base_url}}/v1/formats",
112
+ "host": [
113
+ "{{base_url}}"
114
+ ],
115
+ "path": [
116
+ "v1",
117
+ "formats"
118
+ ]
119
  },
120
+ "description": "List all supported file formats."
121
  },
122
  "response": []
123
  },
 
127
  "method": "GET",
128
  "header": [
129
  {
130
+ "key": "x-api-key",
131
+ "value": "{{api_key}}",
132
  "type": "text"
133
  }
134
  ],
135
  "url": {
136
  "raw": "{{base_url}}/v1/spacy-labels",
137
+ "host": [
138
+ "{{base_url}}"
139
+ ],
140
+ "path": [
141
+ "v1",
142
+ "spacy-labels"
143
+ ]
144
  },
145
+ "description": "Available spaCy NER labels and mappings."
146
  },
147
  "response": []
148
  }
 
158
  "method": "POST",
159
  "header": [
160
  {
161
+ "key": "x-api-key",
162
+ "value": "{{api_key}}",
163
  "type": "text"
164
  }
165
  ],
 
170
  "key": "file",
171
  "type": "file",
172
  "src": "/path/to/your/document.pdf",
173
+ "description": "The file to convert"
174
  },
175
  {
176
  "key": "plain_text",
177
  "value": "false",
178
+ "type": "text"
 
179
  },
180
  {
181
  "key": "return_json",
182
  "value": "false",
183
+ "type": "text"
 
184
  },
185
  {
186
  "key": "mappings",
187
+ "value": "{\"company\":{\"source_type\":\"entity\",\"label\":\"ORG\"}}",
188
+ "type": "text"
 
189
  }
190
  ]
191
  },
192
  "url": {
193
  "raw": "{{base_url}}/v1/convert/file",
194
+ "host": [
195
+ "{{base_url}}"
196
+ ],
197
+ "path": [
198
+ "v1",
199
+ "convert",
200
+ "file"
201
+ ]
202
  },
203
+ "description": "Upload a single file and convert it to Markdown."
204
  },
205
  "response": []
206
  },
 
210
  "method": "POST",
211
  "header": [
212
  {
213
+ "key": "x-api-key",
214
+ "value": "{{api_key}}",
215
  "type": "text"
216
  },
217
  {
 
222
  ],
223
  "body": {
224
  "mode": "raw",
225
+ "raw": "{\n \"url\": \"https://example.com/document.pdf\",\n \"return_json\": false,\n \"mappings\": {\n \"company\": {\n \"source_type\": \"entity\",\n \"label\": \"ORG\"\n }\n }\n}",
226
  "options": {
227
  "raw": {
228
  "language": "json"
 
231
  },
232
  "url": {
233
  "raw": "{{base_url}}/v1/convert/url",
234
+ "host": [
235
+ "{{base_url}}"
236
+ ],
237
+ "path": [
238
+ "v1",
239
+ "convert",
240
+ "url"
241
+ ]
242
  },
243
+ "description": "Convert a remote document URL to Markdown."
244
  },
245
  "response": []
246
  }
 
256
  "method": "POST",
257
  "header": [
258
  {
259
+ "key": "x-api-key",
260
+ "value": "{{api_key}}",
261
  "type": "text"
262
  }
263
  ],
 
267
  {
268
  "key": "files",
269
  "type": "file",
270
+ "src": "/path/to/document1.pdf"
 
271
  },
272
  {
273
  "key": "files",
274
  "type": "file",
275
+ "src": "/path/to/document2.docx"
 
276
  }
277
  ]
278
  },
279
  "url": {
280
  "raw": "{{base_url}}/v1/batch/files",
281
+ "host": [
282
+ "{{base_url}}"
283
+ ],
284
+ "path": [
285
+ "v1",
286
+ "batch",
287
+ "files"
288
+ ]
289
  },
290
+ "description": "Upload and convert multiple files."
291
  },
292
  "response": []
293
  },
 
297
  "method": "POST",
298
  "header": [
299
  {
300
+ "key": "x-api-key",
301
+ "value": "{{api_key}}",
302
  "type": "text"
303
  },
304
  {
 
309
  ],
310
  "body": {
311
  "mode": "raw",
312
+ "raw": "{\n \"urls\": [\n \"https://example.com/report1.pdf\",\n \"https://example.com/report2.docx\"\n ]\n}",
313
  "options": {
314
  "raw": {
315
  "language": "json"
 
318
  },
319
  "url": {
320
  "raw": "{{base_url}}/v1/batch/urls",
321
+ "host": [
322
+ "{{base_url}}"
323
+ ],
324
+ "path": [
325
+ "v1",
326
+ "batch",
327
+ "urls"
328
+ ]
329
  },
330
+ "description": "Convert multiple URLs."
331
  },
332
  "response": []
333
  }
 
340
  "script": {
341
  "type": "text/javascript",
342
  "exec": [
343
+ "// Validate required collection variables before each request",
344
+ "const baseUrl = pm.collectionVariables.get('base_url');",
345
+ "const apiKey = pm.collectionVariables.get('api_key');",
346
  "",
347
  "if (!baseUrl) {",
348
+ " pm.expect.fail('Missing collection variable: base_url');",
349
  "}",
350
  "",
351
+ "if (!apiKey && pm.request.url.path.join('/').startsWith('v1')) {",
352
+ " pm.expect.fail('Missing collection variable: api_key');",
353
  "}"
354
  ]
355
  }
 
360
  "key": "base_url",
361
  "value": "http://localhost:7860",
362
  "type": "string",
363
+ "description": "Base URL of the service"
364
  },
365
  {
366
+ "key": "api_key",
367
  "value": "changeme",
368
  "type": "string",
369
+ "description": "API key for authenticated endpoints"
370
  }
371
  ],
372
  "auth": {
373
+ "type": "apikey",
374
+ "apikey": [
375
+ {
376
+ "key": "value",
377
+ "value": "{{api_key}}",
378
+ "type": "string"
379
+ },
380
  {
381
+ "key": "key",
382
+ "value": "x-api-key",
383
  "type": "string"
384
  }
385
  ]
386
  }
387
+ }