{
	"info": {
		"_postman_id": "ocrforge-api-collection",
		"name": "OCR Forge API",
		"description": "API collection for OCR Forge — AI-powered document processing platform.\n\nSetup:\n1. Import this collection into Postman\n2. Import the ocrforge-dev environment\n3. Set your API key in the environment variable `apiKey`\n4. Run requests in order: Health → Create Job → Check Status → Get Result",
		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
	},
	"variable": [
		{
			"key": "baseUrl",
			"value": "https://api.ocrforge.co.za",
			"type": "string"
		},
		{
			"key": "apiKey",
			"value": "",
			"type": "string"
		},
		{
			"key": "jobId",
			"value": "",
			"type": "string"
		}
	],
	"item": [
		{
			"name": "Health",
			"item": [
				{
					"name": "Health Check",
					"request": {
						"method": "GET",
						"header": [],
						"url": {
							"raw": "{{baseUrl}}/health",
							"host": ["{{baseUrl}}"],
							"path": ["health"]
						},
						"description": "System health check. No authentication required."
					},
					"response": [
						{
							"name": "200 OK",
							"originalRequest": {
								"method": "GET",
								"header": [],
								"url": {
									"raw": "{{baseUrl}}/health",
									"host": ["{{baseUrl}}"],
									"path": ["health"]
								}
							},
							"status": "OK",
							"code": 200,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\"status\":\"ok\"}"
						}
					],
					"event": [
						{
							"listen": "test",
							"script": {
								"type": "text/javascript",
								"exec": [
									"pm.test('Status code is 200', function () {",
									"    pm.response.to.have.status(200);",
									"});"
								]
							}
						}
					]
				}
			]
		},
		{
			"name": "Documents",
			"item": [
				{
					"name": "Create Document Job",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "X-API-Key",
								"value": "{{apiKey}}",
								"type": "text"
							},
							{
								"key": "Content-Type",
								"value": "application/json",
								"type": "text"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"filename\": \"invoice.pdf\",\n  \"mimeType\": \"application/pdf\",\n  \"fileSize\": 245000,\n  \"outputFormat\": \"json\"\n}",
							"options": {
								"raw": {
									"language": "json"
								}
							}
						},
						"url": {
							"raw": "{{baseUrl}}/api/v1/documents",
							"host": ["{{baseUrl}}"],
							"path": ["api", "v1", "documents"]
						},
						"description": "Create a new OCR job. Returns a pre-signed S3 upload URL.\n\nAfter receiving the response, PUT your file to the `upload_url` with the file bytes as the body and `Content-Type` matching the mimeType. No encryption headers needed.\n\nThen poll the `status_url` for processing progress."
					},
					"response": [
						{
							"name": "202 Accepted",
							"originalRequest": {
								"method": "POST",
								"header": [
									{
										"key": "X-API-Key",
										"value": "{{apiKey}}",
										"type": "text"
									},
									{
										"key": "Content-Type",
										"value": "application/json",
										"type": "text"
									}
								],
								"body": {
									"mode": "raw",
									"raw": "{\n  \"filename\": \"invoice.pdf\",\n  \"mimeType\": \"application/pdf\",\n  \"fileSize\": 245000,\n  \"outputFormat\": \"json\"\n}"
								},
								"url": {
									"raw": "{{baseUrl}}/api/v1/documents",
									"host": ["{{baseUrl}}"],
									"path": ["api", "v1", "documents"]
								}
							},
							"status": "Accepted",
							"code": 202,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"job_id\": \"doc_a1b2c3d4e5f6\",\n  \"status\": \"awaiting_upload\",\n  \"upload_url\": \"https://s3.af-south-1.amazonaws.com/ocrforge-inbox-dev/...\",\n  \"upload_expires_in\": 900,\n  \"status_url\": \"/documents/doc_a1b2c3d4e5f6\",\n  \"instructions\": \"PUT your file to upload_url with the file bytes as body and Content-Type header matching the mimeType. No encryption headers needed. Then poll status_url for progress.\"\n}"
						},
						{
							"name": "400 Bad Request — missing filename",
							"originalRequest": {
								"method": "POST",
								"header": [
									{
										"key": "X-API-Key",
										"value": "{{apiKey}}",
										"type": "text"
									},
									{
										"key": "Content-Type",
										"value": "application/json",
										"type": "text"
									}
								],
								"body": {
									"mode": "raw",
									"raw": "{}"
								},
								"url": {
									"raw": "{{baseUrl}}/api/v1/documents",
									"host": ["{{baseUrl}}"],
									"path": ["api", "v1", "documents"]
								}
							},
							"status": "Bad Request",
							"code": 400,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"error\": \"filename is required\"\n}"
						},
						{
							"name": "401 Unauthorized",
							"originalRequest": {
								"method": "POST",
								"header": [
									{
										"key": "Content-Type",
										"value": "application/json",
										"type": "text"
									}
								],
								"body": {
									"mode": "raw",
									"raw": "{\n  \"filename\": \"invoice.pdf\",\n  \"mimeType\": \"application/pdf\"\n}"
								},
								"url": {
									"raw": "{{baseUrl}}/api/v1/documents",
									"host": ["{{baseUrl}}"],
									"path": ["api", "v1", "documents"]
								}
							},
							"status": "Unauthorized",
							"code": 401,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"message\": \"Unauthorized\"\n}"
						}
					],
					"event": [
						{
							"listen": "test",
							"script": {
								"type": "text/javascript",
								"exec": [
									"pm.test('Status code is 202', function () {",
									"    pm.response.to.have.status(202);",
									"});",
									"",
									"pm.test('Response has job_id', function () {",
									"    var jsonData = pm.response.json();",
									"    pm.expect(jsonData).to.have.property('job_id');",
									"    pm.expect(jsonData.job_id).to.match(/^doc_[a-f0-9]{12}$/);",
									"});",
									"",
									"pm.test('Response has upload_url', function () {",
									"    var jsonData = pm.response.json();",
									"    pm.expect(jsonData).to.have.property('upload_url');",
									"    pm.expect(jsonData.upload_url).to.include('https://');",
									"});",
									"",
									"pm.test('Response has status_url', function () {",
									"    var jsonData = pm.response.json();",
									"    pm.expect(jsonData).to.have.property('status_url');",
									"});",
									"",
									"pm.test('Status is awaiting_upload', function () {",
									"    var jsonData = pm.response.json();",
									"    pm.expect(jsonData.status).to.eql('awaiting_upload');",
									"});",
									"",
									"// Save job_id for subsequent requests",
									"if (pm.response.code === 202) {",
									"    var jsonData = pm.response.json();",
									"    pm.collectionVariables.set('jobId', jsonData.job_id);",
									"    console.log('Saved jobId:', jsonData.job_id);",
									"}"
								]
							}
						}
					]
				},
				{
					"name": "Check Job Status",
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "X-API-Key",
								"value": "{{apiKey}}",
								"type": "text"
							}
						],
						"url": {
							"raw": "{{baseUrl}}/api/v1/documents/{{jobId}}",
							"host": ["{{baseUrl}}"],
							"path": ["api", "v1", "documents", "{{jobId}}"]
						},
						"description": "Check the status of an OCR job.\n\nPossible statuses:\n- `awaiting_upload` — waiting for file upload to S3\n- `queued` — file received, waiting for OCR\n- `processing` — OCR engine is working\n- `completed` — results available\n- `failed` — processing failed"
					},
					"response": [
						{
							"name": "200 OK — awaiting upload",
							"originalRequest": {
								"method": "GET",
								"header": [
									{
										"key": "X-API-Key",
										"value": "{{apiKey}}",
										"type": "text"
									}
								],
								"url": {
									"raw": "{{baseUrl}}/api/v1/documents/{{jobId}}",
									"host": ["{{baseUrl}}"],
									"path": ["api", "v1", "documents", "{{jobId}}"]
								}
							},
							"status": "OK",
							"code": 200,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"job_id\": \"doc_a1b2c3d4e5f6\",\n  \"status\": \"awaiting_upload\",\n  \"filename\": \"invoice.pdf\",\n  \"file_size\": 245000,\n  \"mime_type\": \"application/pdf\",\n  \"output_format\": \"json\",\n  \"model_used\": null,\n  \"pages_total\": null,\n  \"pages_processed\": null,\n  \"confidence\": null,\n  \"processing_ms\": null,\n  \"created_at\": \"2026-04-09T12:00:00.000Z\",\n  \"completed_at\": null,\n  \"instructions\": \"PUT your file to the upload_url returned from POST /documents\"\n}"
						},
						{
							"name": "200 OK — completed",
							"originalRequest": {
								"method": "GET",
								"header": [
									{
										"key": "X-API-Key",
										"value": "{{apiKey}}",
										"type": "text"
									}
								],
								"url": {
									"raw": "{{baseUrl}}/api/v1/documents/{{jobId}}",
									"host": ["{{baseUrl}}"],
									"path": ["api", "v1", "documents", "{{jobId}}"]
								}
							},
							"status": "OK",
							"code": 200,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"job_id\": \"doc_a1b2c3d4e5f6\",\n  \"status\": \"completed\",\n  \"filename\": \"invoice.pdf\",\n  \"file_size\": 245000,\n  \"mime_type\": \"application/pdf\",\n  \"output_format\": \"json\",\n  \"model_used\": \"textract\",\n  \"pages_total\": 3,\n  \"pages_processed\": 3,\n  \"confidence\": 0.94,\n  \"processing_ms\": 4200,\n  \"created_at\": \"2026-04-09T12:00:00.000Z\",\n  \"completed_at\": \"2026-04-09T12:00:05.000Z\",\n  \"progress\": 100,\n  \"result_url\": \"/documents/doc_a1b2c3d4e5f6/result\"\n}"
						},
						{
							"name": "404 Not Found",
							"originalRequest": {
								"method": "GET",
								"header": [
									{
										"key": "X-API-Key",
										"value": "{{apiKey}}",
										"type": "text"
									}
								],
								"url": {
									"raw": "{{baseUrl}}/api/v1/documents/doc_nonexistent",
									"host": ["{{baseUrl}}"],
									"path": ["api", "v1", "documents", "doc_nonexistent"]
								}
							},
							"status": "Not Found",
							"code": 404,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"error\": \"Job not found\"\n}"
						}
					],
					"event": [
						{
							"listen": "test",
							"script": {
								"type": "text/javascript",
								"exec": [
									"pm.test('Status code is 200', function () {",
									"    pm.response.to.have.status(200);",
									"});",
									"",
									"pm.test('Response has job_id', function () {",
									"    var jsonData = pm.response.json();",
									"    pm.expect(jsonData).to.have.property('job_id');",
									"});",
									"",
									"pm.test('Response has status', function () {",
									"    var jsonData = pm.response.json();",
									"    pm.expect(jsonData).to.have.property('status');",
									"    pm.expect(jsonData.status).to.be.oneOf([",
									"        'awaiting_upload', 'queued', 'processing', 'completed', 'failed'",
									"    ]);",
									"});",
									"",
									"pm.test('Response has created_at', function () {",
									"    var jsonData = pm.response.json();",
									"    pm.expect(jsonData).to.have.property('created_at');",
									"});"
								]
							}
						}
					]
				},
				{
					"name": "Get Job Result",
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "X-API-Key",
								"value": "{{apiKey}}",
								"type": "text"
							}
						],
						"url": {
							"raw": "{{baseUrl}}/api/v1/documents/{{jobId}}/result",
							"host": ["{{baseUrl}}"],
							"path": ["api", "v1", "documents", "{{jobId}}", "result"]
						},
						"description": "Get the OCR result for a completed job. Returns a pre-signed S3 download URL.\n\nThis endpoint returns 409 if the job is not yet complete — check status first."
					},
					"response": [
						{
							"name": "200 OK — result ready",
							"originalRequest": {
								"method": "GET",
								"header": [
									{
										"key": "X-API-Key",
										"value": "{{apiKey}}",
										"type": "text"
									}
								],
								"url": {
									"raw": "{{baseUrl}}/api/v1/documents/{{jobId}}/result",
									"host": ["{{baseUrl}}"],
									"path": ["api", "v1", "documents", "{{jobId}}", "result"]
								}
							},
							"status": "OK",
							"code": 200,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"job_id\": \"doc_a1b2c3d4e5f6\",\n  \"status\": \"completed\",\n  \"download_url\": \"https://s3.af-south-1.amazonaws.com/ocrforge-results-dev/...\",\n  \"download_expires_in\": 3600,\n  \"filename\": \"invoice.pdf\",\n  \"pages\": 3,\n  \"confidence\": 0.94,\n  \"model_used\": \"textract\"\n}"
						},
						{
							"name": "409 Conflict — job not complete",
							"originalRequest": {
								"method": "GET",
								"header": [
									{
										"key": "X-API-Key",
										"value": "{{apiKey}}",
										"type": "text"
									}
								],
								"url": {
									"raw": "{{baseUrl}}/api/v1/documents/{{jobId}}/result",
									"host": ["{{baseUrl}}"],
									"path": ["api", "v1", "documents", "{{jobId}}", "result"]
								}
							},
							"status": "Conflict",
							"code": 409,
							"_postman_previewlanguage": "json",
							"header": [
								{
									"key": "Content-Type",
									"value": "application/json"
								}
							],
							"body": "{\n  \"error\": \"Job is not complete. Current status: awaiting_upload\",\n  \"status_url\": \"/documents/doc_a1b2c3d4e5f6\"\n}"
						}
					],
					"event": [
						{
							"listen": "test",
							"script": {
								"type": "text/javascript",
								"exec": [
									"// Result endpoint returns 200 if job is complete, 409 if not",
									"pm.test('Status code is 200 or 409', function () {",
									"    pm.expect(pm.response.code).to.be.oneOf([200, 409]);",
									"});",
									"",
									"if (pm.response.code === 200) {",
									"    pm.test('Response has download_url', function () {",
									"        var jsonData = pm.response.json();",
									"        pm.expect(jsonData).to.have.property('download_url');",
									"        pm.expect(jsonData.download_url).to.include('https://');",
									"    });",
									"",
									"    pm.test('Response has download_expires_in', function () {",
									"        var jsonData = pm.response.json();",
									"        pm.expect(jsonData).to.have.property('download_expires_in');",
									"        pm.expect(jsonData.download_expires_in).to.be.a('number');",
									"    });",
									"}",
									"",
									"if (pm.response.code === 409) {",
									"    pm.test('409 response has error message', function () {",
									"        var jsonData = pm.response.json();",
									"        pm.expect(jsonData).to.have.property('error');",
									"        pm.expect(jsonData.error).to.include('not complete');",
									"    });",
									"}"
								]
							}
						}
					]
				}
			]
		}
	]
}
