Files
insta-recipe/docs/API.md
Giancarmine Salucci 49bccf8f15 simplify
2026-02-18 01:21:44 +01:00

13 KiB

InstaRecipe API Documentation

This document describes the InstaRecipe API endpoints for the async queue-based recipe extraction system.

Base URL

All API endpoints are relative to your InstaRecipe instance:

https://your-instarecipe-instance.com/api

Authentication

Currently, no authentication is required for API access. This may change in future versions.

Content Type

All requests should use Content-Type: application/json unless otherwise specified.

Error Handling

All endpoints return standardized error responses:

{
	"error": "Error type",
	"message": "Human-readable error message",
	"details": {
		/* Additional error context */
	}
}

HTTP status codes follow REST conventions:

  • 200 - Success
  • 201 - Created
  • 400 - Bad Request (invalid input)
  • 404 - Not Found
  • 409 - Conflict (e.g., cannot retry pending item)
  • 410 - Gone (deprecated endpoint)
  • 500 - Internal Server Error

Queue Management Endpoints

POST /api/queue

Enqueue an Instagram URL for async processing.

Request:

{
	"url": "https://instagram.com/p/abc123"
}

Supported URL Formats:

  • Posts: https://instagram.com/p/{post-id}
  • Posts (www): https://www.instagram.com/p/{post-id}
  • Reels: https://instagram.com/reel/{reel-id}
  • IGTV: https://instagram.com/tv/{video-id}
  • With query parameters: https://instagram.com/reel/{reel-id}?utm_source=share

URL Requirements:

  • Must use HTTPS protocol
  • Hostname must be instagram.com or www.instagram.com
  • Any Instagram path is accepted (posts, reels, IGTV, etc.)
  • Query parameters and hash fragments are allowed

Examples:

// Post URL
{ "url": "https://instagram.com/p/ABC123" }

// Reel URL with tracking
{ "url": "https://www.instagram.com/reel/DSevV5CDcNm/?utm_source=ig_web_copy_link" }

// IGTV URL
{ "url": "https://instagram.com/tv/XYZ789" }

Response (201 Created):

{
	"id": "550e8400-e29b-41d4-a716-446655440000",
	"url": "https://instagram.com/reel/DSevV5CDcNm/?utm_source=ig_web_copy_link",
	"status": "pending",
	"phases": [
		{
			"name": "extraction",
			"status": "pending",
			"progress": 0
		},
		{
			"name": "parsing",
			"status": "pending",
			"progress": 0
		},
		{
			"name": "uploading",
			"status": "pending",
			"progress": 0
		}
	],
	"createdAt": "2024-12-21T10:30:00Z",
	"updatedAt": "2024-12-21T10:30:00Z"
}

Errors:

  • 400 - Invalid URL format (not a valid URL)
  • 400 - URL must use HTTPS protocol
  • 400 - URL must be from instagram.com domain
  • 400 - Missing or invalid URL parameter

GET /api/queue

List queue items with optional filtering, pagination, and sorting.

Query Parameters:

  • status (optional): Filter by status (pending, in_progress, success, error, unhealthy)
  • limit (optional): Number of items to return (default: 50, max: 100)
  • offset (optional): Number of items to skip (default: 0)
  • sort (optional): Sort field (createdAt, updatedAt, status) (default: createdAt)
  • order (optional): Sort order (asc, desc) (default: desc)

Examples:

GET /api/queue                                    # All items
GET /api/queue?status=error                       # Failed items only
GET /api/queue?limit=10&offset=20                # Pagination
GET /api/queue?sort=status&order=asc             # Sort by status

Response (200 OK):

{
	"items": [
		{
			"id": "550e8400-e29b-41d4-a716-446655440000",
			"url": "https://instagram.com/p/abc123",
			"status": "success",
			"phases": [
				{
					"name": "extraction",
					"status": "completed",
					"startedAt": "2024-12-21T10:30:01Z",
					"completedAt": "2024-12-21T10:30:15Z",
					"progress": 100
				},
				{
					"name": "parsing",
					"status": "completed",
					"startedAt": "2024-12-21T10:30:15Z",
					"completedAt": "2024-12-21T10:30:25Z",
					"progress": 100
				},
				{
					"name": "uploading",
					"status": "completed",
					"startedAt": "2024-12-21T10:30:25Z",
					"completedAt": "2024-12-21T10:30:30Z",
					"progress": 100
				}
			],
			"results": {
				"recipe": {
					"name": "Chocolate Chip Cookies",
					"description": "Delicious homemade cookies",
					"servings": 24,
					"ingredients": [
						{
							"food": "flour",
							"amount": 2.25,
							"unit": "cups"
						}
					],
					"steps": [
						{
							"instruction": "Preheat oven to 375°F",
							"time": 5
						}
					],
					"keywords": ["cookies", "dessert", "chocolate"],
					"image": "https://instagram.com/image.jpg"
				},
				"tandoorUrl": "https://tandoor.example.com/recipe/123",
				"extractedText": "Raw extracted text...",
				"thumbnail": "https://instagram.com/thumbnail.jpg"
			},
			"createdAt": "2024-12-21T10:30:00Z",
			"updatedAt": "2024-12-21T10:30:30Z"
		}
	],
	"total": 42,
	"hasMore": true
}

GET /api/queue/{id}

Get details for a specific queue item.

Path Parameters:

  • id: Queue item UUID

Response (200 OK): Returns the same queue item structure as in the list response.

Errors:

  • 400 - Invalid UUID format
  • 404 - Queue item not found

POST /api/queue/{id}/retry

Retry a failed queue item. Only items with error or unhealthy status can be retried.

Path Parameters:

  • id: Queue item UUID

Response (200 OK):

{
	"success": true,
	"message": "Item queued for retry",
	"item": {
		"id": "550e8400-e29b-41d4-a716-446655440000",
		"status": "pending",
		"updatedAt": "2024-12-21T11:00:00Z"
	}
}

Errors:

  • 400 - Invalid UUID format
  • 404 - Queue item not found
  • 409 - Cannot retry item with current status (only error and unhealthy can be retried)

Real-time Updates

GET /api/queue/stream

Server-Sent Events (SSE) endpoint for real-time queue updates.

Query Parameters:

  • itemId (optional): Filter updates for specific item
  • status (optional): Filter updates by status

Headers:

Accept: text/event-stream
Cache-Control: no-cache

Response: SSE stream with the following event types:

connection

Sent when connection is established:

event: connection
data: {"message": "Connected to queue stream", "timestamp": "2024-12-21T10:30:00Z"}

queue-update

Sent when queue item status changes:

event: queue-update
data: {
  "itemId": "550e8400-e29b-41d4-a716-446655440000",
  "status": "in_progress",
  "timestamp": "2024-12-21T10:30:01Z",
  "progress": [
    {
      "name": "extraction",
      "status": "in_progress",
      "startedAt": "2024-12-21T10:30:01Z",
      "progress": 45
    }
  ]
}

ping

Keep-alive ping sent every 30 seconds:

event: ping
data: {"timestamp": "2024-12-21T10:30:30Z"}

Usage Examples:

JavaScript:

const eventSource = new EventSource('/api/queue/stream');

eventSource.addEventListener('connection', (event) => {
	console.log('Connected:', JSON.parse(event.data));
});

eventSource.addEventListener('queue-update', (event) => {
	const update = JSON.parse(event.data);
	console.log('Queue update:', update);
	updateUI(update);
});

eventSource.addEventListener('ping', (event) => {
	console.log('Keep-alive ping');
});

eventSource.onerror = (error) => {
	console.error('SSE error:', error);
	// Reconnect logic here
};

curl:

curl -N -H "Accept: text/event-stream" \
  "https://your-instance.com/api/queue/stream?itemId=550e8400-e29b-41d4-a716-446655440000"

Push Notifications

GET /api/notifications/vapid-key

Get the VAPID public key required for push notification subscriptions.

Response (200 OK):

{
	"publicKey": "BDummyPublicKeyForDevelopment...",
	"applicationServerKey": "BDummyPublicKeyForDevelopment..."
}

POST /api/notifications/subscribe

Subscribe to push notifications for queue processing updates.

Request:

{
	"subscription": {
		"endpoint": "https://fcm.googleapis.com/fcm/send/...",
		"keys": {
			"p256dh": "BIBn3E_YUVpW5f6_Eq_GH...",
			"auth": "tBiH_Y1nPSuVh7TRMhcf..."
		}
	},
	"clientId": "unique-client-identifier"
}

Response (200 OK):

{
	"success": true,
	"message": "Successfully subscribed to push notifications",
	"subscriptionCount": 5
}

Errors:

  • 400 - Invalid subscription object or missing clientId

DELETE /api/notifications/subscribe

Unsubscribe from push notifications.

Request:

{
	"clientId": "unique-client-identifier"
}

Response (200 OK):

{
	"success": true,
	"message": "Successfully unsubscribed from push notifications",
	"subscriptionCount": 4
}

Legacy Endpoints (Deprecated)

POST /api/extract ⚠️ DEPRECATED

Status: 410 Gone

This synchronous extraction endpoint is deprecated. Use POST /api/queue instead.

Migration:

// ❌ Old synchronous approach
const response = await fetch('/api/extract', {
	method: 'POST',
	body: JSON.stringify({ url })
});
const result = await response.json(); // Wait 30-60 seconds

// ✅ New async queue approach
const response = await fetch('/api/queue', {
	method: 'POST',
	body: JSON.stringify({ url })
});
const queueItem = await response.json(); // Immediate response

POST /api/extract-stream ⚠️ DEPRECATED

Status: 410 Gone

This SSE endpoint is deprecated. Use POST /api/queue + GET /api/queue/stream instead.

Migration:

// ❌ Old approach
const response = await fetch('/api/extract-stream', {
	method: 'POST',
	body: JSON.stringify({ url })
});

// ✅ New approach
const queueResponse = await fetch('/api/queue', {
	method: 'POST',
	body: JSON.stringify({ url })
});
const item = await queueResponse.json();

const eventSource = new EventSource(`/api/queue/stream?itemId=${item.id}`);

Data Models

QueueItem

interface QueueItem {
	id: string; // UUID v4
	url: string; // Instagram URL
	status: 'pending' | 'in_progress' | 'success' | 'error' | 'unhealthy';

	phases: Array<{
		name: 'extraction' | 'parsing' | 'uploading';
		status: 'pending' | 'in_progress' | 'completed' | 'error';
		startedAt?: string; // ISO 8601 timestamp
		completedAt?: string; // ISO 8601 timestamp
		progress?: number; // 0-100
	}>;

	results?: {
		recipe?: Recipe; // Structured recipe data
		tandoorUrl?: string; // Tandoor recipe URL
		extractedText?: string; // Raw extracted text
		thumbnail?: string; // Image URL
	};

	error?: string; // Error message
	createdAt: string; // ISO 8601 timestamp
	updatedAt: string; // ISO 8601 timestamp
}

Recipe

interface Recipe {
	name: string;
	description?: string;
	servings?: number;
	prepTime?: number; // Minutes
	cookTime?: number; // Minutes
	totalTime?: number; // Minutes

	ingredients: Array<{
		food: string;
		amount?: number;
		unit?: string;
	}>;

	steps: Array<{
		instruction: string;
		time?: number; // Minutes
	}>;

	keywords?: string[]; // Recipe tags
	image?: string; // Image URL
	nutrition?: {
		// Nutritional information
		calories?: number;
		protein?: number;
		carbs?: number;
		fat?: number;
	};
}

Rate Limiting

Currently, no rate limiting is enforced, but this may change in future versions. Consider implementing client-side rate limiting to avoid overwhelming the service.

WebSocket Alternative

For applications that cannot use Server-Sent Events, consider polling the GET /api/queue/{id} endpoint every 5-10 seconds for status updates.

Error Recovery

When implementing clients, consider these error recovery strategies:

  1. Network Errors: Retry with exponential backoff
  2. SSE Connection Drops: Automatically reconnect after 5-10 seconds
  3. Queue Item Failures: Present retry option to users
  4. Push Notification Failures: Gracefully degrade to polling

Examples

Complete Processing Workflow

async function processInstagramUrl(url) {
	try {
		// 1. Enqueue URL
		const queueResponse = await fetch('/api/queue', {
			method: 'POST',
			headers: { 'Content-Type': 'application/json' },
			body: JSON.stringify({ url })
		});

		const queueItem = await queueResponse.json();
		console.log('Enqueued:', queueItem.id);

		// 2. Listen for real-time updates
		const eventSource = new EventSource(`/api/queue/stream?itemId=${queueItem.id}`);

		return new Promise((resolve, reject) => {
			eventSource.addEventListener('queue-update', (event) => {
				const update = JSON.parse(event.data);

				if (update.status === 'success') {
					eventSource.close();
					resolve(update.results);
				} else if (update.status === 'error') {
					eventSource.close();
					reject(new Error(update.error));
				}

				// Handle progress updates
				console.log('Progress:', update.progress);
			});

			eventSource.onerror = (error) => {
				eventSource.close();
				reject(error);
			};
		});
	} catch (error) {
		console.error('Processing failed:', error);
		throw error;
	}
}

// Usage
processInstagramUrl('https://instagram.com/p/abc123')
	.then((results) => {
		console.log('Recipe extracted:', results.recipe);
		if (results.tandoorUrl) {
			console.log('Uploaded to Tandoor:', results.tandoorUrl);
		}
	})
	.catch((error) => {
		console.error('Extraction failed:', error.message);
	});

For more examples and integration guides, see the Migration Documentation.