Hydra is now in beta|Get started free|Follow our journey on X.com

Products API
On this page

Products

Products are the core resource in Hydra. Each product has a title, handle (URL slug), description, and status. Products can have up to 5 options (e.g. Size, Color) and 250 variants representing each combination.

Base URL: https://api.hydrajs.dev

Endpoints

Method Path Auth Description
GET /v1/products Publishable List products
POST /v1/products Secret Create a product
POST /v1/products/batch Secret Batch create products
GET /v1/products/{id} Publishable Get a product
PATCH /v1/products/{id} Secret Update a product
DELETE /v1/products/{id} Secret Delete a product (soft)
POST /v1/products/{id}/duplicate Secret Duplicate a product
GET /v1/products/{id}/variants Publishable List variants
POST /v1/products/{id}/variants Secret Create a variant
POST /v1/products/{id}/variants/generate Secret Generate variants from combinations
GET /v1/products/{id}/images Publishable List images
POST /v1/products/{id}/images Secret Upload an image
POST /v1/products/{id}/images/attach Secret Attach existing images
GET /v1/products/{id}/links Publishable List linked products
POST /v1/products/{id}/links Secret Create a product link
DELETE /v1/products/{id}/links/{target_id} Secret Delete a product link

List products

GET /v1/products

Returns a paginated list of products. Supports search, filtering, sorting, and sparse fieldsets.

Query parameters

Parameter Type Default Description
limit integer 25 Results per page (1–100)
cursor string - Pagination cursor from a previous response
sort string created_at Sort field: created_at, updated_at, title
order string desc Sort direction: asc, desc
status string - Filter by status: active, draft, archived, preorder, coming_soon, discontinued
collection_id string - Filter by collection membership (manual and automatic)
search string - Full-text search with typo correction
created_after ISO 8601 - Filter: created after this date
created_before ISO 8601 - Filter: created before this date
currency string - 3-letter ISO currency code for price conversion
fields string - Comma-separated fields to return

Request

curl https://api.hydrajs.dev/v1/products?status=active&limit=10 \
  -H "Authorization: Bearer pk_YOUR_KEY"

Response 200

{
	"data": [
		{
			"id": "prod_abc123",
			"title": "Classic T-Shirt",
			"handle": "classic-t-shirt",
			"status": "active",
			"product_type": "Apparel",
			"brand": "Hydra Basics",
			"created_at": "2026-01-15T10:30:00Z",
			"updated_at": "2026-08-01T14:22:00Z"
		}
	],
	"pagination": {
		"cursor": "eyJ0IjoiMjAyNi...",
		"has_more": true,
		"total": 42
	}
}

ℹPagination

All list endpoints use cursor-based pagination. Pass the cursor value from the response to fetch the next page.


Get a product

GET /v1/products/{id}

Returns a single product by ID. Use expand to include related resources inline.

Query parameters

Parameter Type Description
expand string Comma-separated: variants, images, prices, links
currency string 3-letter ISO currency code for price conversion
fields string Comma-separated fields to return

Request

curl https://api.hydrajs.dev/v1/products/prod_abc123?expand=variants,images \
  -H "Authorization: Bearer pk_YOUR_KEY"

Response 200

{
	"data": {
		"id": "prod_abc123",
		"title": "Classic T-Shirt",
		"handle": "classic-t-shirt",
		"subtitle": null,
		"description": "A comfortable everyday t-shirt.",
		"status": "active",
		"product_type": "Apparel",
		"brand": "Hydra Basics",
		"google_product_category": "Apparel & Accessories > Clothing > Shirts & Tops",
		"hs_code": "6109.10",
		"country_of_origin": "US",
		"fulfillment_type": "physical",
		"tags": ["basics", "cotton"],
		"options": [{ "name": "Size", "values": ["S", "M", "L", "XL"], "position": 0 }],
		"specifications": [
			{ "label": "Material", "value": "100% Cotton" },
			{ "label": "Weight", "value": "180 GSM" }
		],
		"seo": {
			"title": "Classic T-Shirt | Hydra Basics",
			"description": "A comfortable everyday t-shirt in 100% cotton."
		},
		"metadata": {},
		"variants": [
			{
				"id": "var_def456",
				"title": "S",
				"sku": "TSH-S",
				"barcode": null,
				"price": 2999,
				"sale_price": null,
				"cost": 800,
				"inventory_quantity": 50,
				"weight": 180,
				"weight_unit": "g",
				"hs_code": null,
				"country_of_origin": null,
				"options": { "Size": "S" },
				"position": 0
			}
		],
		"images": [
			{
				"id": "img_ghi789",
				"src": "https://cdn.hydrajs.dev/stores/store_xxx/img_ghi789.webp",
				"alt": "Classic T-Shirt front view",
				"width": 1200,
				"height": 1600,
				"position": 0
			}
		],
		"created_at": "2026-01-15T10:30:00Z",
		"updated_at": "2026-08-01T14:22:00Z"
	}
}

ℹInternal notes

The internal_notes field is only returned for secret key requests. Publishable keys never see this field.


Create a product

POST /v1/products

Creates a new product. Returns the created product with 201.

Request body

Field Type Required Description
title string Yes Product title (max 255 chars)
handle string No URL slug. Auto-generated from title if omitted
subtitle string No Short subtitle (max 65 chars). Requires subtitle extension
description string | null No Markdown description (max 10,000 chars). Set to null to clear
status string No draft (default), active, archived, preorder, coming_soon, discontinued
preorder_expected_date string No ISO 8601 date (e.g. 2026-12-01). Required when status is preorder
product_type string No Free-text category (max 100 chars)
brand string No Brand name (max 100 chars)
google_product_category string No Google taxonomy path (max 500 chars)
hs_code string No Harmonized System code for customs (max 20 chars)
country_of_origin string No ISO 3166-1 alpha-2 country code (2 letters, e.g. US)
fulfillment_type string No physical (default), digital, service
internal_notes string No Private notes (max 5,000 chars)
link_label string No Label for this product in linked product navigation (max 100 chars). Requires linked_products extension
link_heading string No Heading above linked products section (max 100 chars). Requires linked_products extension
qty_step integer No Minimum quantity increment. Requires qty_step extension
tags string[] No Array of tags (max 250 tags, each max 100 chars)
options object[] No Product options (max 5). Each: name, values[], position
variants object[] No Initial variants with pricing and inventory
specifications object[] No Key-value specs (max 50). Each: label, value
seo object No SEO metadata: title, description
metadata object No Arbitrary key-value pairs (max 50 keys)

ℹProduct statuses

Products with preorder status can be added to cart (requires preorder_expected_date). Products with coming_soon or discontinued status are visible on the storefront but cart/checkout is blocked.

Request

curl -X POST https://api.hydrajs.dev/v1/products \
  -H "Authorization: Bearer sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Classic T-Shirt",
    "status": "draft",
    "product_type": "Apparel",
    "tags": ["basics", "cotton"],
    "variants": [
      {
        "title": "Default",
        "price": 2999,
        "inventory_quantity": 100
      }
    ]
  }'

Response 201

Returns the full product object (same shape as Get a product).


Batch create products

POST /v1/products/batch

Creates multiple products in a single atomic request. All products are created within a transaction - if any product fails validation, none are created.

Request body

Field Type Required Description
products object[] Yes Array of product objects (1–50). Each object accepts the same fields as Create a product

⚠All-or-nothing

If any product in the batch has invalid data or a conflicting handle, the entire batch is rejected. No products are created.

Request

curl -X POST https://api.hydrajs.dev/v1/products/batch \
  -H "Authorization: Bearer sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "products": [
      {
        "title": "Classic T-Shirt",
        "status": "draft",
        "variants": [{ "title": "Default", "price": 2999 }]
      },
      {
        "title": "Slim Jeans",
        "product_type": "Apparel",
        "tags": ["denim"],
        "variants": [{ "title": "Default", "price": 5999 }]
      }
    ]
  }'

Response 201

{
	"data": [
		{
			"id": "prod_abc123",
			"title": "Classic T-Shirt",
			"handle": "classic-t-shirt",
			"status": "draft",
			"variants": [{ "id": "var_def456", "title": "Default", "price": 2999 }]
		},
		{
			"id": "prod_xyz789",
			"title": "Slim Jeans",
			"handle": "slim-jeans",
			"status": "draft",
			"variants": [{ "id": "var_ghi012", "title": "Default", "price": 5999 }]
		}
	]
}

ℹRate limiting

A batch request counts as a single API call toward your rate limit and monthly quota, regardless of how many products are in the batch.


Update a product

PATCH /v1/products/{id}

Partially updates a product. Send only the fields you want to change. Returns the updated product.

Request body

All fields from Create a product are accepted, and all are optional. Additional fields:

Field Type Description
create_redirect boolean If true and handle changed, creates a URL redirect from the old handle

⚠Tags replace, not merge

Sending tags replaces the entire array. To add a tag, fetch the current tags, append, and send the full list.

Request

curl -X PATCH https://api.hydrajs.dev/v1/products/prod_abc123 \
  -H "Authorization: Bearer sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Premium T-Shirt",
    "status": "active",
    "tags": ["premium", "cotton", "new-arrival"]
  }'

Response 200

Returns the full updated product object.


Delete a product

DELETE /v1/products/{id}

Soft-deletes a product. The product is hidden from all queries and its handle is freed for reuse. Permanently purged after 30 days.

Request

curl -X DELETE https://api.hydrajs.dev/v1/products/prod_abc123 \
  -H "Authorization: Bearer sk_YOUR_KEY"

Response 204

Empty body.


Duplicate a product

POST /v1/products/{id}/duplicate

Creates a copy of an existing product with a new title and handle.

Request body

Field Type Required Description
title string Yes Title for the new product
include_variants boolean No Copy variants (default: true)
include_images boolean No Copy images (default: true)

Request

curl -X POST https://api.hydrajs.dev/v1/products/prod_abc123/duplicate \
  -H "Authorization: Bearer sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "title": "Classic T-Shirt (Copy)" }'

Response 201

Returns the new product object.


List variants

GET /v1/products/{id}/variants

Returns all variants for a product.

Request

curl https://api.hydrajs.dev/v1/products/prod_abc123/variants \
  -H "Authorization: Bearer pk_YOUR_KEY"

Response 200

{
	"data": [
		{
			"id": "var_def456",
			"title": "S",
			"sku": "TSH-S",
			"price": 2999,
			"sale_price": null,
			"cost": 800,
			"inventory_quantity": 50,
			"options": { "Size": "S" },
			"position": 0
		}
	]
}

Create a variant

POST /v1/products/{id}/variants

Adds a variant to a product.

Request body

Field Type Required Description
title string Yes Variant title (max 255 chars)
sku string No Stock keeping unit (max 100 chars)
barcode string No Barcode / UPC (max 100 chars)
price integer Yes Price in cents (e.g. 2999 = $29.99)
sale_price integer No Original price in cents for sale display
inventory_quantity integer No Stock count (default: 0)
weight integer No Weight in weight_unit units
weight_unit string No g (default), kg, oz, lb
hs_code string No Harmonized System code for customs (max 20 chars)
country_of_origin string No ISO 3166-1 alpha-2 country code (2 letters, e.g. US)
options object No Option values: { "Size": "M", "Color": "Blue" }

Response 201

Returns the created variant object.


Generate variants

POST /v1/products/{id}/variants/generate

Bulk-creates variants from option combinations. Maximum 250 variants per product.

Request body

Field Type Required Description
combinations object[] Yes Array of option maps: [{ "Size": "S", "Color": "Red" }, ...]

Response 201

{
	"data": {
		"variants": [
			{
				"id": "var_xxx",
				"title": "S / Red",
				"price": 0,
				"options": { "Size": "S", "Color": "Red" }
			}
		]
	}
}

List images

GET /v1/products/{id}/images

Returns all images for a product, ordered by position.

Response 200

{
	"data": [
		{
			"id": "img_ghi789",
			"src": "https://cdn.hydrajs.dev/stores/store_xxx/img_ghi789.webp",
			"alt": "Front view",
			"width": 1200,
			"height": 1600,
			"position": 0,
			"lqip": "base64..."
		}
	]
}

Upload an image

POST /v1/products/{id}/images

Uploads an image file and attaches it to the product. Send as multipart/form-data.

Form fields

Field Type Required Description
file File Yes Image file (JPEG, PNG, WebP, GIF, AVIF, HEIC). Max 10 MB
lqip string No Base64 ThumbHash for low-quality image placeholder

Request

curl -X POST https://api.hydrajs.dev/v1/products/prod_abc123/images \
  -H "Authorization: Bearer sk_YOUR_KEY" \
  -F "file=@photo.jpg"

Response 201

Returns the created image object.


Attach existing images

POST /v1/products/{id}/images/attach

Attaches images from the media library to a product without re-uploading.

Request body

Field Type Required Description
image_ids string[] Yes Array of image IDs to attach

Request

curl -X POST https://api.hydrajs.dev/v1/products/prod_abc123/images/attach \
  -H "Authorization: Bearer sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "image_ids": ["img_aaa", "img_bbb"] }'

Response 201

Returns the attached image objects.


List linked products

GET /v1/products/{id}/links

Returns products linked to this product. Each linked product includes its title, handle, label, and first image. The current product is included in the response with is_current: true.

Requires the linked_products extension. Returns an empty array if the extension is disabled.

Request

curl https://api.hydrajs.dev/v1/products/prod_abc123/links \
  -H "Authorization: Bearer pk_YOUR_KEY"

Response 200

{
	"data": [
		{
			"product_id": "prod_abc123",
			"handle": "ring-polished-titanium",
			"title": "Ring — Polished Titanium",
			"link_label": "Polished Titanium",
			"image_src": "https://cdn.hydrajs.dev/stores/store_xxx/img_aaa.webp",
			"position": -1,
			"is_current": true
		},
		{
			"product_id": "prod_def456",
			"handle": "ring-gold",
			"title": "Ring — Gold",
			"link_label": "Gold",
			"image_src": "https://cdn.hydrajs.dev/stores/store_xxx/img_bbb.webp",
			"position": 0,
			"is_current": false
		}
	]
}

ℹStorefront rendering

The is_current product is included so storefronts can render a complete switcher from a single API call. Use link_label for button text (falls back to title if null). Use link_heading from the product object for the section heading (e.g. “Available in”).


POST /v1/products/{id}/links

Creates a bidirectional link between two products. Both A→B and B→A are created in a single transaction. The reverse link is appended to the end of the target product’s existing links.

Request body

Field Type Required Description
target_product_id string Yes Product ID to link to
position integer No Display position for forward link (default: 0)

Request

curl -X POST https://api.hydrajs.dev/v1/products/prod_abc123/links \
  -H "Authorization: Bearer sk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "target_product_id": "prod_def456", "position": 0 }'

Response 201

{
	"data": {
		"id": "pl_abc123",
		"source_product_id": "prod_abc123",
		"target_product_id": "prod_def456",
		"position": 0,
		"created_at": "2026-09-25T12:00:00Z"
	}
}

DELETE /v1/products/{id}/links/{target_id}

Removes a bidirectional link between two products. Both directions are deleted.

Request

curl -X DELETE https://api.hydrajs.dev/v1/products/prod_abc123/links/prod_def456 \
  -H "Authorization: Bearer sk_YOUR_KEY"

Response 204

Empty body.


Webhooks

Product changes fire the following webhook events:

Event Trigger
product.created Product created or duplicated
product.updated Product fields updated
product.deleted Product soft-deleted

See Webhooks for subscription setup.


The product object

Field Type Description
id string Unique ID (prefix: prod_)
title string Product title
handle string URL-safe slug
subtitle string | null Short subtitle
description string | null Markdown-formatted text
status string draft, active, archived, preorder, coming_soon, discontinued
preorder_expected_date string | null ISO 8601 date. Set when status is preorder
product_type string | null Free-text category
brand string | null Brand name
google_product_category string | null Google taxonomy path
hs_code string | null Harmonized System code for customs
country_of_origin string | null ISO 3166-1 alpha-2 country code
fulfillment_type string physical, digital, service
internal_notes string | null Private notes (secret key only)
link_label string | null Label for this product in linked product navigation
link_heading string | null Heading above linked products section
qty_step integer | null Minimum order quantity increment
tags string[] Product tags
options object[] Option definitions with name, values[], position
specifications object[] Key-value specs with label, value
seo object title, description
metadata object Arbitrary key-value pairs
variants object[] Expanded with ?expand=variants
images object[] Expanded with ?expand=images
links object[] Expanded with ?expand=links. Requires linked_products extension
created_at string ISO 8601 timestamp
updated_at string ISO 8601 timestamp