Example: API Product Ingestion
A complete walkthrough of the core API flow: create a product, trigger optimization, and retrieve the generated content. No Shopify connection required.
Step 1 — Create a product
POST your product data to /api/v1/products. Include a title, description, and at least one image.
Use external_id to supply your own identifier — Beaconed uses it for deduplication,
so sending the same external_id twice returns the existing product rather than creating a duplicate.
Set source is handled automatically — you don't need to include it. All API-created products are tagged source: "api".
POST /api/v1/products
curl -X POST https://beaconed.ai/api/v1/products \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"product": {
"title": "Organic Cotton T-Shirt",
"description": "Soft, breathable organic cotton tee. Available in six colors.",
"vendor": "Acme Apparel",
"product_type": "Apparel",
"external_id": "acme-SKU-1042",
"images": [
{
"id": "img-1",
"src": "https://cdn.example.com/products/cotton-tee.jpg",
"altText": ""
}
]
}
}'
require "net/http"
require "json"
uri = URI("https://beaconed.ai/api/v1/products")
req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json", "Authorization" => "Bearer YOUR_API_KEY")
req.body = {
product: {
title: "Organic Cotton T-Shirt",
description: "Soft, breathable organic cotton tee. Available in six colors.",
vendor: "Acme Apparel",
product_type: "Apparel",
external_id: "acme-SKU-1042",
images: [{ id: "img-1", src: "https://cdn.example.com/products/cotton-tee.jpg", altText: "" }]
}
}.to_json
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
product = JSON.parse(response.body)["data"]
const response = await fetch("https://beaconed.ai/api/v1/products", {
method: "POST",
headers: {
"Authorization": "Bearer YOUR_API_KEY",
"Content-Type": "application/json"
},
body: JSON.stringify({
product: {
title: "Organic Cotton T-Shirt",
description: "Soft, breathable organic cotton tee. Available in six colors.",
vendor: "Acme Apparel",
product_type: "Apparel",
external_id: "acme-SKU-1042",
images: [{ id: "img-1", src: "https://cdn.example.com/products/cotton-tee.jpg", altText: "" }]
}
})
});
const { data: product } = await response.json();
The response includes the product's id — you'll need it for the next steps.
Response
{
"data": {
"id": "a1b2c3d4",
"source": "api",
"external_id": "acme-SKU-1042",
"title": "Organic Cotton T-Shirt",
"vendor": "Acme Apparel",
"product_type": "Apparel",
"optimization_status": null,
"created_at": "2026-04-07T15:23:08Z"
}
}
Step 2 — Trigger optimization
Once your product exists, request optimization with a single POST. Beaconed queues the job and generates content asynchronously — results are typically ready within 20–30 seconds.
POST /api/v1/products/:id/optimization
curl -X POST https://beaconed.ai/api/v1/products/a1b2c3d4/optimization \
-H "Authorization: Bearer YOUR_API_KEY"
uri = URI("https://beaconed.ai/api/v1/products/#{product["id"]}/optimization")
req = Net::HTTP::Post.new(uri, "Authorization" => "Bearer YOUR_API_KEY")
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
await fetch(`https://beaconed.ai/api/v1/products/${product.id}/optimization`, {
method: "POST",
headers: { "Authorization": "Bearer YOUR_API_KEY" }
});
Response
{
"data": {
"message": "Preparing updates",
"product_id": "a1b2c3d4",
"status": "queued"
}
}
Step 3 — Retrieve the results
Poll /api/v1/optimizations with your product ID to fetch the generated content.
Each optimization record covers one field — title, description, meta_title,
meta_description, alt_text, and more.
GET /api/v1/optimizations
curl "https://beaconed.ai/api/v1/optimizations?product_id=a1b2c3d4" \
-H "Authorization: Bearer YOUR_API_KEY"
uri = URI("https://beaconed.ai/api/v1/optimizations")
uri.query = URI.encode_www_form(product_id: product["id"])
req = Net::HTTP::Get.new(uri, "Authorization" => "Bearer YOUR_API_KEY")
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
optimizations = JSON.parse(response.body)["data"]
const res = await fetch(
`https://beaconed.ai/api/v1/optimizations?product_id=${product.id}`,
{ headers: { "Authorization": "Bearer YOUR_API_KEY" } }
);
const { data: optimizations } = await res.json();
Response
{
"data": [
{
"id": "opt-001",
"field": "meta_title",
"status": "pending",
"original_content": "",
"optimized_content": "Organic Cotton T-Shirt | Soft & Sustainable | Acme Apparel"
},
{
"id": "opt-002",
"field": "meta_description",
"status": "pending",
"original_content": "",
"optimized_content": "Shop Acme Apparel's organic cotton tees — breathable, sustainable, and available in six colors. Free shipping on orders over $50."
},
{
"id": "opt-003",
"field": "description",
"status": "pending",
"original_content": "Soft, breathable organic cotton tee. Available in six colors.",
"optimized_content": "Made from 100% GOTS-certified organic cotton, this everyday tee is as good for the planet as it is to wear. Preshrunk fabric, reinforced seams, and a relaxed fit that holds its shape wash after wash. Available in six colorways."
}
]
}
Step 4 — Approve an optimization
When you're ready to use the generated content, approve the optimization. The status changes to
approved and the content is ready to sync back to your platform.
POST /api/v1/optimizations/:id/approval
curl -X POST https://beaconed.ai/api/v1/optimizations/opt-001/approval \
-H "Authorization: Bearer YOUR_API_KEY"
uri = URI("https://beaconed.ai/api/v1/optimizations/#{optimization["id"]}/approval")
req = Net::HTTP::Post.new(uri, "Authorization" => "Bearer YOUR_API_KEY")
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
await fetch(`https://beaconed.ai/api/v1/optimizations/${optimization.id}/approval`, {
method: "POST",
headers: { "Authorization": "Bearer YOUR_API_KEY" }
});
You can also use webhooks to receive optimization results automatically instead of polling. See the Webhooks guide.