{
	"info": {
		"_postman_id": "skyebrowse-public-api-v3",
		"name": "SkyeBrowse Public API",
		"description": "SkyeBrowse Public API v3 — Create 3D models, upload drone footage, and download processed assets.\n\n## Setup\n1. Set the `base_url` variable to your API base (default: `https://api.skyebrowse.com`)\n2. Set the `api_key` variable to your API key (starts with `sk_`)\n3. Optionally set `jwt_token` for API key management endpoints\n\n## Workflow\n1. Check Upload Quota\n2. Create Model → saves `model_id` and `input_id`\n3. Initiate Video Upload → saves `upload_id` and `presigned_url`\n4. Upload chunk to S3 presigned URL\n5. Get Next Part URL (repeat for each chunk)\n6. Finalize Upload\n7. Poll Model Status until \"Finished\"\n8. Download Assets (LAZ, GeoTIFF, Ortho JPG, Sketch, GLB)\n9. Enable Public Access → saves `public_url` for sharing",
		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
	},
	"variable": [
		{
			"key": "base_url",
			"value": "https://api.skyebrowse.com",
			"type": "string"
		},
		{
			"key": "api_key",
			"value": "sk_YOUR_API_KEY",
			"type": "string"
		},
		{
			"key": "jwt_token",
			"value": "YOUR_JWT_TOKEN",
			"type": "string"
		},
		{
			"key": "model_id",
			"value": "",
			"type": "string"
		},
		{
			"key": "input_id",
			"value": "",
			"type": "string"
		},
		{
			"key": "upload_id",
			"value": "",
			"type": "string"
		},
		{
			"key": "presigned_url",
			"value": "",
			"type": "string"
		},
		{
			"key": "glb_url",
			"value": "",
			"type": "string"
		},
		{
			"key": "part_number",
			"value": "1",
			"type": "string"
		},
		{
			"key": "public_id",
			"value": "",
			"type": "string"
		},
		{
			"key": "public_url",
			"value": "",
			"type": "string"
		}
	],
	"item": [
		{
			"name": "API Key Management",
			"description": "Manage organization API keys. These endpoints use JWT authentication (Bearer token), not API key auth.",
			"item": [
				{
					"name": "Create API Key",
					"event": [
						{
							"listen": "test",
							"script": {
								"type": "text/javascript",
								"exec": [
									"if (pm.response.code === 201) {",
									"    var json = pm.response.json();",
									"    pm.collectionVariables.set('api_key', json.apiKey);",
									"    console.log('API Key saved to collection variable. Store it securely — it will not be shown again.');",
									"    console.log('Key: ' + json.apiKey);",
									"}"
								]
							}
						}
					],
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Authorization",
								"value": "Bearer {{jwt_token}}"
							},
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"name\": \"My API Key\",\n  \"description\": \"Used for automated processing\",\n  \"expiresAt\": \"2027-12-31T00:00:00Z\"\n}"
						},
						"url": {
							"raw": "{{base_url}}/v3/organizations/me/api-keys",
							"host": ["{{base_url}}"],
							"path": ["v3", "organizations", "me", "api-keys"]
						},
						"description": "Create a new organization API key. The full key is only returned once — save it securely.\n\n**expiresAt** is optional. Omit it for a key that never expires."
					}
				},
				{
					"name": "List API Keys",
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "Authorization",
								"value": "Bearer {{jwt_token}}"
							}
						],
						"url": {
							"raw": "{{base_url}}/v3/organizations/me/api-keys",
							"host": ["{{base_url}}"],
							"path": ["v3", "organizations", "me", "api-keys"]
						},
						"description": "List all API keys for your organization. The full key value is not returned — only the prefix."
					}
				},
				{
					"name": "Revoke API Key",
					"request": {
						"method": "DELETE",
						"header": [
							{
								"key": "Authorization",
								"value": "Bearer {{jwt_token}}"
							}
						],
						"url": {
							"raw": "{{base_url}}/v3/organizations/me/api-keys/:id",
							"host": ["{{base_url}}"],
							"path": ["v3", "organizations", "me", "api-keys", ":id"],
							"variable": [
								{
									"key": "id",
									"value": "",
									"description": "API key ID to revoke"
								}
							]
						},
						"description": "Permanently revoke an API key. This cannot be undone."
					}
				}
			]
		},
		{
			"name": "Models",
			"description": "Create, list, and inspect 3D models.",
			"item": [
				{
					"name": "Check Upload Quota",
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							}
						],
						"url": {
							"raw": "{{base_url}}/v3/models/can-upload",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "can-upload"]
						},
						"description": "Check if your organization can upload new models based on your current plan quota."
					}
				},
				{
					"name": "Create Model",
					"event": [
						{
							"listen": "test",
							"script": {
								"type": "text/javascript",
								"exec": [
									"if (pm.response.code === 201) {",
									"    var json = pm.response.json();",
									"    pm.collectionVariables.set('model_id', json.id);",
									"    if (json.inputs && json.inputs.length > 0) {",
									"        pm.collectionVariables.set('input_id', json.inputs[0].id.toString());",
									"    }",
									"    console.log('Model ID: ' + json.id);",
									"    console.log('Input ID: ' + json.inputs[0].id);",
									"}"
								]
							}
						}
					],
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							},
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"name\": \"Building Survey 01\",\n  \"browseType\": \"widebrowse\",\n  \"videoSize\": 524288000,\n  \"fileName\": \"DJI_0001.MP4\",\n  \"latitude\": 37.7749,\n  \"longitude\": -122.4194,\n  \"address\": \"123 Main St, San Francisco, CA\",\n  \"droneType\": \"Mavic 3\",\n  \"resolution\": \"4k\",\n  \"tags\": [\"survey\", \"building\"]\n}"
						},
						"url": {
							"raw": "{{base_url}}/v3/models",
							"host": ["{{base_url}}"],
							"path": ["v3", "models"]
						},
						"description": "Create a new model. This auto-saves `model_id` and `input_id` for use in subsequent requests.\n\n**browseType**: `skyebrowse`, `widebrowse`, `manual`, or `external`\n**videoSize**: file size in bytes\n**resolution**: `1080p` or `4k`"
					}
				},
				{
					"name": "List Models",
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							}
						],
						"url": {
							"raw": "{{base_url}}/v3/models?page=1&take=10",
							"host": ["{{base_url}}"],
							"path": ["v3", "models"],
							"query": [
								{
									"key": "page",
									"value": "1",
									"description": "Page number (1-based; page=0 repeats page 1)"
								},
								{
									"key": "take",
									"value": "10",
									"description": "Items per page"
								},
								{
									"key": "status",
									"value": "Finished",
									"description": "Filter by status: Initialized, InQueue, Processing, Finished, Failed",
									"disabled": true
								},
								{
									"key": "name",
									"value": "",
									"description": "Search by model name",
									"disabled": true
								},
								{
									"key": "tags[]",
									"value": "",
									"description": "Filter by tag; array syntax required (repeat for multiple tags). A scalar tags= returns 500",
									"disabled": true
								},
								{
									"key": "startDate",
									"value": "",
									"description": "Filter from timestamp (ms)",
									"disabled": true
								},
								{
									"key": "endDate",
									"value": "",
									"description": "Filter to timestamp (ms)",
									"disabled": true
								},
								{
									"key": "archived",
									"value": "false",
									"description": "Include archived models",
									"disabled": true
								}
							]
						},
						"description": "List models for your organization. Optional filters are disabled by default — enable them as needed."
					}
				},
				{
					"name": "Get Model Details",
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							}
						],
						"url": {
							"raw": "{{base_url}}/v3/models/{{model_id}}",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "{{model_id}}"]
						},
						"description": "Get full details for a model including status, inputs, location, and asset URLs. Use this to poll for processing status."
					}
				}
			]
		},
		{
			"name": "Public Sharing",
			"description": "Create and revoke the public, unauthenticated viewer link for a model.",
			"item": [
				{
					"name": "Enable Public Access",
					"event": [
						{
							"listen": "test",
							"script": {
								"type": "text/javascript",
								"exec": [
									"if (pm.response.code === 201) {",
									"    var json = pm.response.json();",
									"    pm.collectionVariables.set('public_id', json.publicId);",
									"    pm.collectionVariables.set('public_url', json.publicUrl);",
									"    console.log('Public URL: ' + json.publicUrl);",
									"    console.log('Embed URL: ' + json.embedUrl);",
									"}"
								]
							}
						}
					],
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							}
						],
						"url": {
							"raw": "{{base_url}}/v3/models/{{model_id}}/public-access",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "{{model_id}}", "public-access"]
						},
						"description": "Publish the model to a public viewer link. No request body.\n\nReturns `publicId`, `publicUrl` and `embedUrl`. Idempotent — returns the existing link if the model is already public."
					}
				},
				{
					"name": "Get Public Access Status",
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							}
						],
						"url": {
							"raw": "{{base_url}}/v3/models/{{model_id}}/public-access",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "{{model_id}}", "public-access"]
						},
						"description": "Check whether the model is publicly shared. Returns `enabled: false` with null URLs when it is not."
					}
				},
				{
					"name": "Disable Public Access",
					"request": {
						"method": "DELETE",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							}
						],
						"url": {
							"raw": "{{base_url}}/v3/models/{{model_id}}/public-access",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "{{model_id}}", "public-access"]
						},
						"description": "Revoke the public link (204 No Content). Re-enabling afterwards generates a new `publicId`, so old links stay dead."
					}
				}
			]
		},
		{
			"name": "Video Upload",
			"description": "Multipart video upload flow using S3 presigned URLs.\n\n1. Initiate Upload → get first presigned URL + uploadId\n2. PUT chunk to S3 → save ETag from response\n3. Get Next Part URL (repeat for each chunk; URLs can be requested up front and chunks uploaded in parallel)\n4. Finalize Upload with all ETags",
			"item": [
				{
					"name": "1. Initiate Upload",
					"event": [
						{
							"listen": "test",
							"script": {
								"type": "text/javascript",
								"exec": [
									"if (pm.response.code === 201) {",
									"    var json = pm.response.json();",
									"    pm.collectionVariables.set('upload_id', json.uploadId);",
									"    pm.collectionVariables.set('presigned_url', json.presignedUrl);",
									"    pm.collectionVariables.set('part_number', json.partNumber.toString());",
									"    console.log('Upload ID: ' + json.uploadId);",
									"    console.log('Presigned URL saved. Upload your first chunk to it.');",
									"}"
								]
							}
						}
					],
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							},
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"fileName\": \"DJI_0001.MP4\"\n}"
						},
						"url": {
							"raw": "{{base_url}}/v3/models/{{model_id}}/videos/{{input_id}}",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "{{model_id}}", "videos", "{{input_id}}"]
						},
						"description": "Start a multipart upload. Returns the first presigned URL and an uploadId.\n\nAuto-saves `upload_id`, `presigned_url`, and `part_number` for use in next steps."
					}
				},
				{
					"name": "2. Upload Chunk to S3",
					"request": {
						"method": "PUT",
						"header": [
							{
								"key": "Content-Type",
								"value": "application/octet-stream"
							}
						],
						"body": {
							"mode": "file",
							"file": {
								"src": ""
							}
						},
						"url": {
							"raw": "{{presigned_url}}",
							"host": ["{{presigned_url}}"]
						},
						"description": "Upload a file chunk directly to S3 using the presigned URL.\n\n**Important:** Save the `ETag` header from the response — you need it for finalization.\n\nFor Postman: Select your file/chunk in the Body > binary tab.\n\nRecommended chunk size: 100-200 MB (minimum 5 MB, maximum 5 GB per part). Parts are independent — request all part URLs up front and upload 5-10 chunks concurrently for much faster uploads, especially on high-latency links."
					}
				},
				{
					"name": "3. Get Next Part URL",
					"event": [
						{
							"listen": "test",
							"script": {
								"type": "text/javascript",
								"exec": [
									"if (pm.response.code === 201) {",
									"    var json = pm.response.json();",
									"    pm.collectionVariables.set('presigned_url', json.presignedUrl);",
									"    pm.collectionVariables.set('part_number', json.partNumber.toString());",
									"    console.log('Next presigned URL saved for part ' + json.partNumber);",
									"}"
								]
							}
						},
						{
							"listen": "prerequest",
							"script": {
								"type": "text/javascript",
								"exec": [
									"var currentPart = parseInt(pm.collectionVariables.get('part_number'));",
									"pm.collectionVariables.set('part_number', (currentPart + 1).toString());"
								]
							}
						}
					],
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							},
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"fileName\": \"DJI_0001.MP4\"\n}"
						},
						"url": {
							"raw": "{{base_url}}/v3/models/{{model_id}}/videos/{{input_id}}/uploadId/{{upload_id}}/partNumber/{{part_number}}",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "{{model_id}}", "videos", "{{input_id}}", "uploadId", "{{upload_id}}", "partNumber", "{{part_number}}"]
						},
						"description": "Get a presigned URL for the next chunk. Repeat steps 2-3 for each chunk.\n\nThe part_number auto-increments via a pre-request script."
					}
				},
				{
					"name": "4. Finalize Upload",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							},
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"fileName\": \"DJI_0001.MP4\",\n  \"parts\": [\n    { \"partNumber\": 1, \"eTag\": \"\\\"ETAG_FROM_S3_RESPONSE\\\"\" }\n  ]\n}"
						},
						"url": {
							"raw": "{{base_url}}/v3/models/{{model_id}}/videos/{{input_id}}/uploadId/{{upload_id}}",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "{{model_id}}", "videos", "{{input_id}}", "uploadId", "{{upload_id}}"]
						},
						"description": "Complete the multipart upload. Include all part ETags collected from S3 responses.\n\n**Important:** Replace the `parts` array with actual ETag values from each chunk upload. Include quotes around ETag values as S3 returns them."
					}
				}
			]
		},
		{
			"name": "Flight Logs & Telemetry",
			"description": "Upload GPS flight logs and SRT subtitle files for improved geolocation.",
			"item": [
				{
					"name": "Upload Flight Log",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							},
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"fileName\": \"flight_log.txt\"\n}"
						},
						"url": {
							"raw": "{{base_url}}/v3/models/{{model_id}}/logs",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "{{model_id}}", "logs"]
						},
						"description": "Get a presigned URL to upload a flight log file. After receiving the URL, PUT your file to it with Content-Type: text/plain."
					}
				},
				{
					"name": "Upload SRT Subtitles",
					"request": {
						"method": "POST",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							},
							{
								"key": "Content-Type",
								"value": "application/json"
							}
						],
						"body": {
							"mode": "raw",
							"raw": "{\n  \"fileName\": \"DJI_0001.SRT\"\n}"
						},
						"url": {
							"raw": "{{base_url}}/v3/models/{{model_id}}/inputs/{{input_id}}/subtitles",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "{{model_id}}", "inputs", "{{input_id}}", "subtitles"]
						},
						"description": "Get a presigned URL to upload an SRT subtitle/telemetry file. After receiving the URL, PUT your file to it."
					}
				},
				{
					"name": "Upload File to Presigned URL",
					"request": {
						"method": "PUT",
						"header": [
							{
								"key": "Content-Type",
								"value": "text/plain"
							}
						],
						"body": {
							"mode": "file",
							"file": {
								"src": ""
							}
						},
						"url": {
							"raw": "{{presigned_url}}",
							"host": ["{{presigned_url}}"]
						},
						"description": "Upload a flight log or SRT file to the presigned URL returned by the previous request. Select your file in Body > binary."
					}
				}
			]
		},
		{
			"name": "Downloads",
			"description": "Download processed 3D model assets. All endpoints return presigned S3 download URLs.",
			"item": [
				{
					"name": "Download Point Cloud (LAZ)",
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							}
						],
						"url": {
							"raw": "{{base_url}}/v3/models/{{model_id}}/downloads/laz",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "{{model_id}}", "downloads", "laz"]
						},
						"description": "Get a presigned download URL for the point cloud in LAZ format."
					}
				},
				{
					"name": "Download GeoTIFF",
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							}
						],
						"url": {
							"raw": "{{base_url}}/v3/models/{{model_id}}/downloads/ortho",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "{{model_id}}", "downloads", "ortho"]
						},
						"description": "Get a presigned download URL for the orthophoto in GeoTIFF format."
					}
				},
				{
					"name": "Download Orthophoto (JPG)",
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							}
						],
						"url": {
							"raw": "{{base_url}}/v3/models/{{model_id}}/downloads/ortho-jpg",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "{{model_id}}", "downloads", "ortho-jpg"]
						},
						"description": "Get a presigned download URL for the orthophoto in JPG format."
					}
				},
				{
					"name": "Download Sketch",
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							}
						],
						"url": {
							"raw": "{{base_url}}/v3/models/{{model_id}}/downloads/sketch",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "{{model_id}}", "downloads", "sketch"]
						},
						"description": "Get a presigned download URL for the sketch/diagram."
					}
				},
				{
					"name": "Get GLB URL from Model Details",
					"event": [
						{
							"listen": "test",
							"script": {
								"type": "text/javascript",
								"exec": [
									"if (pm.response.code === 200) {",
									"    var json = pm.response.json();",
									"    var glbUrl = json.assets && json.assets.glbUrls && json.assets.glbUrls[0];",
									"    if (glbUrl) {",
									"        pm.collectionVariables.set('glb_url', glbUrl);",
									"        console.log('GLB URL saved to collection variable glb_url.');",
									"    } else {",
									"        console.log('No GLB URL returned for this model yet. Confirm the model status is Finished.');",
									"    }",
									"}"
								]
							}
						}
					],
					"request": {
						"method": "GET",
						"header": [
							{
								"key": "Authorization",
								"value": "{{api_key}}"
							}
						],
						"url": {
							"raw": "{{base_url}}/v3/models/{{model_id}}",
							"host": ["{{base_url}}"],
							"path": ["v3", "models", "{{model_id}}"]
						},
						"description": "Get model details and save the first assets.glbUrls value to the glb_url collection variable. GLB is returned in the model details payload instead of a separate /downloads/glb endpoint."
					}
				},
				{
					"name": "Download GLB",
					"request": {
						"method": "GET",
						"header": [],
						"url": {
							"raw": "{{glb_url}}",
							"host": ["{{glb_url}}"]
						},
						"description": "Download the GLB file from the presigned URL saved by the previous request. If this URL expires, run Get GLB URL from Model Details again."
					}
				}
			]
		}
	]
}
