# Image Upload API Upload images with AI-generated or manual captions for multimodal search. ## Overview Images are indexed with both: - **Visual embeddings** (Nova Multimodal) - find visually similar images - **Text embeddings** (captions) - find images by description ## Dashboard Image Tab The Image Upload tab provides: ### Caption Prompt Editor Customize the AI prompt used to generate image captions. Changes apply immediately to new uploads. **Location:** Image Upload tab → Caption Prompt (expandable section) **Default prompt:** Generates concise, searchable captions focusing on main subject, setting, and visual elements. ## Single Image Upload (Auto-Process) For the simplest workflow, use `autoProcess: true` to let AI generate captions automatically: ```graphql mutation CreateImageUploadUrl($filename: String!, $autoProcess: Boolean, $userCaption: String) { createImageUploadUrl(filename: $filename, autoProcess: $autoProcess, userCaption: $userCaption) { uploadUrl # Presigned S3 POST URL imageId # Unique identifier for this upload s3Uri # S3 URI for the image fields # Form fields for S3 POST (JSON string) } } ``` **Parameters:** - `filename` (required): Original filename with extension - `autoProcess`: Set to `true` for automatic AI caption generation after upload - `userCaption`: Optional user-provided caption (combined with AI caption if autoProcess=true) With `autoProcess: true`, just upload the file and processing happens automatically via EventBridge. ### Auto-Process JavaScript Example ```javascript async function uploadImageAutoProcess(imageFile, userCaption = '') { // 1. Get presigned URL with autoProcess enabled const urlRes = await fetch(ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY }, body: JSON.stringify({ query: `mutation($filename: String!, $autoProcess: Boolean!, $userCaption: String) { createImageUploadUrl(filename: $filename, autoProcess: $autoProcess, userCaption: $userCaption) { uploadUrl, imageId, fields } }`, variables: { filename: imageFile.name, autoProcess: true, userCaption: userCaption } }) }); const { uploadUrl, imageId, fields } = (await urlRes.json()).data.createImageUploadUrl; // 2. Upload to S3 - processing starts automatically const form = new FormData(); Object.entries(JSON.parse(fields)).forEach(([k, v]) => form.append(k, v)); form.append('file', imageFile); await fetch(uploadUrl, { method: 'POST', body: form }); return imageId; // Image will be processed and indexed automatically } ``` ## Single Image Upload (Manual Steps) For more control, use the 4-step process: ### Step 1: Get Upload URL ```graphql mutation CreateImageUploadUrl($filename: String!) { createImageUploadUrl(filename: $filename) { uploadUrl # Presigned S3 POST URL imageId # Unique identifier for this upload s3Uri # S3 URI for caption generation fields # Form fields for S3 POST (JSON string) } } ``` ### Step 2: Upload to S3 ```javascript const form = new FormData(); Object.entries(JSON.parse(fields)).forEach(([k, v]) => form.append(k, v)); form.append('file', imageFile); await fetch(uploadUrl, { method: 'POST', body: form }); ``` ### Step 3: Generate AI Caption (Optional) Use Bedrock vision models to generate a descriptive caption: ```graphql mutation GenerateCaption($imageS3Uri: String!) { generateCaption(imageS3Uri: $imageS3Uri) { caption # AI-generated description } } ``` The caption is optimized for search with relevant keywords. ### Step 4: Submit Image Finalize the upload with caption(s) to trigger processing: ```graphql mutation SubmitImage($input: SubmitImageInput!) { submitImage(input: $input) { imageId filename status # PROCESSING → INDEXED } } # Input { "input": { "imageId": "abc123", "userCaption": "My manual description", # Optional "aiCaption": "AI-generated description" # Optional } } ``` If both captions provided, they're combined: `"{userCaption}. {aiCaption}"` ## Complete JavaScript Example ```javascript const ENDPOINT = 'https://your-api.appsync-api.region.amazonaws.com/graphql'; const API_KEY = 'your-api-key'; async function uploadImageWithCaption(imageFile) { // 1. Get presigned URL const urlRes = await fetch(ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY }, body: JSON.stringify({ query: `mutation($filename: String!) { createImageUploadUrl(filename: $filename) { uploadUrl, imageId, s3Uri, fields } }`, variables: { filename: imageFile.name } }) }); const { uploadUrl, imageId, s3Uri, fields } = (await urlRes.json()).data.createImageUploadUrl; // 2. Upload to S3 const form = new FormData(); Object.entries(JSON.parse(fields)).forEach(([k, v]) => form.append(k, v)); form.append('file', imageFile); await fetch(uploadUrl, { method: 'POST', body: form }); // 3. Generate AI caption const captionRes = await fetch(ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY }, body: JSON.stringify({ query: `mutation($imageS3Uri: String!) { generateCaption(imageS3Uri: $imageS3Uri) { caption } }`, variables: { imageS3Uri: s3Uri } }) }); const aiCaption = (await captionRes.json()).data.generateCaption.caption; // 4. Submit with caption await fetch(ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY }, body: JSON.stringify({ query: `mutation($input: SubmitImageInput!) { submitImage(input: $input) { imageId, status } }`, variables: { input: { imageId, aiCaption } } }) }); return imageId; } ``` ## Batch Upload (ZIP Archive) Upload multiple images at once with optional automatic caption generation. ### Create ZIP Upload URL ```graphql mutation CreateZipUploadUrl($generateCaptions: Boolean) { createZipUploadUrl(generateCaptions: $generateCaptions) { uploadUrl uploadId fields } } ``` Set `generateCaptions: true` to auto-generate AI captions for images without manual captions. ### ZIP File Structure ``` images.zip ├── photo1.jpg ├── photo2.png ├── subfolder/ │ └── photo3.gif └── captions.json # Optional manifest ``` ### captions.json Format Provide manual captions for specific images: ```json { "photo1.jpg": "Sunset over the mountains with golden clouds", "photo2.png": "City skyline at night with lights reflecting on water", "subfolder/photo3.gif": "Animated loading spinner" } ``` Images not in the manifest will get AI-generated captions if `generateCaptions=true`. ### JavaScript Example ```javascript async function uploadImageZip(zipFile, generateCaptions = true) { // 1. Get presigned URL const res = await fetch(ENDPOINT, { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': API_KEY }, body: JSON.stringify({ query: `mutation($generateCaptions: Boolean) { createZipUploadUrl(generateCaptions: $generateCaptions) { uploadUrl, uploadId, fields } }`, variables: { generateCaptions } }) }); const { uploadUrl, uploadId, fields } = (await res.json()).data.createZipUploadUrl; // 2. Upload ZIP to S3 const form = new FormData(); Object.entries(JSON.parse(fields)).forEach(([k, v]) => form.append(k, v)); form.append('file', zipFile); await fetch(uploadUrl, { method: 'POST', body: form }); // Processing happens automatically via EventBridge return uploadId; } ``` ## Supported Formats - **Images:** JPG, JPEG, PNG, GIF, WebP, AVIF - **Max size:** 50 MB per image, 500 MB per ZIP ## Querying Images After indexing, images are searchable via: ```graphql query SearchKnowledgeBase($query: String!) { searchKnowledgeBase(query: $query) { results { content # Caption text s3Uri # Image location score # Relevance score } } } ``` Both text queries ("sunset mountains") and image similarity work through the unified Bedrock KB. ## Authentication All mutations require either: - **API Key:** `x-api-key` header - **Cognito:** JWT token in `Authorization` header Get your API key from the Settings page in the web UI.