Endpoints
POST /api/hub/subscriptions/{subscriptionId}/sync/apply
GET /api/hub/subscriptions/{subscriptionId}/conflicts
PUT /api/hub/subscriptions/{subscriptionId}/conflicts/{conflictId}
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
subscriptionId | string | Yes | Your subscription ID (from subscribe response) |
conflictId | string | Yes | Conflict ID (from list conflicts response) |
Apply pending updates
POST /api/hub/subscriptions/{subscriptionId}/sync/apply
Applies pending publisher version updates to your local catalog. For subscriptions with syncStrategy: "auto", this happens automatically. For "manual" subscriptions, call this endpoint when you’re ready to sync.
Request body
{
"targetVersion": 7
}
| Field | Type | Required | Description |
|---|---|---|---|
targetVersion | number | No | Specific version to sync to. Defaults to latest published version |
Response
{
"applied": true,
"fromVersion": 5,
"toVersion": 7,
"productsUpdated": 34,
"productsAdded": 12,
"productsRemoved": 3,
"conflictsCreated": 5,
"duration_ms": 2100
}
Apply sync examples
# Apply latest publisher updates
curl -X POST "https://app.alana.shopping/api/hub/subscriptions/sub_4k2n8x/sync/apply" \
-H "Authorization: Bearer sk_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{}'
# Apply a specific version
curl -X POST "https://app.alana.shopping/api/hub/subscriptions/sub_4k2n8x/sync/apply" \
-H "Authorization: Bearer sk_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"targetVersion": 7}'
const subscriptionId = 'sub_4k2n8x';
const response = await fetch(
`https://app.alana.shopping/api/hub/subscriptions/${subscriptionId}/sync/apply`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.ALANA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({}), // sync to latest
}
);
if (!response.ok) {
const error = await response.json();
throw new Error(error.error.message);
}
const result = await response.json();
console.log(`Synced from v${result.fromVersion} to v${result.toVersion}`);
console.log(`${result.productsUpdated} updated, ${result.productsAdded} added, ${result.productsRemoved} removed`);
if (result.conflictsCreated > 0) {
console.log(`${result.conflictsCreated} conflicts need resolution`);
}
import requests, os
subscription_id = "sub_4k2n8x"
response = requests.post(
f"https://app.alana.shopping/api/hub/subscriptions/{subscription_id}/sync/apply",
headers={
"Authorization": f"Bearer {os.environ['ALANA_API_KEY']}",
"Content-Type": "application/json",
},
json={}
)
response.raise_for_status()
result = response.json()
print(f"Synced v{result['fromVersion']} → v{result['toVersion']}")
print(f"Changes: +{result['productsAdded']} added, ~{result['productsUpdated']} updated, -{result['productsRemoved']} removed")
print(f"Conflicts created: {result['conflictsCreated']}")
List conflicts
GET /api/hub/subscriptions/{subscriptionId}/conflicts
Returns all unresolved conflicts for a subscription — fields where your local edits differ from the publisher’s new value.
Response
{
"conflicts": [
{
"id": "conflict_9a3b",
"productId": "prod_local_789",
"field": "description",
"localValue": "Our hand-curated description of this premium blazer...",
"remoteValue": "Classic linen blazer in sky blue. Machine washable. Available in S-XL.",
"publisherVersion": 7,
"createdAt": "2026-03-17T11:00:00Z",
"status": "pending"
},
{
"id": "conflict_7c1d",
"productId": "prod_local_456",
"field": "price",
"localValue": 89.99,
"remoteValue": 94.99,
"publisherVersion": 7,
"createdAt": "2026-03-17T11:00:00Z",
"status": "pending"
}
],
"total": 2
}
Conflict fields
| Field | Type | Description |
|---|---|---|
id | string | Conflict ID |
productId | string | Local product ID with the conflict |
field | string | Field name where the conflict exists |
localValue | any | Your current local value |
remoteValue | any | Publisher’s new value |
publisherVersion | number | Publisher version that introduced this change |
createdAt | string | When the conflict was created |
status | string | "pending", "resolved_local", "resolved_remote", "resolved_merged" |
List conflicts examples
curl "https://app.alana.shopping/api/hub/subscriptions/sub_4k2n8x/conflicts" \
-H "Authorization: Bearer sk_live_your_api_key_here"
const response = await fetch(
`https://app.alana.shopping/api/hub/subscriptions/${subscriptionId}/conflicts`,
{ headers: { 'Authorization': `Bearer ${process.env.ALANA_API_KEY}` } }
);
const { conflicts, total } = await response.json();
console.log(`${total} unresolved conflicts`);
conflicts.forEach(c => {
console.log(`${c.productId}.${c.field}: local="${c.localValue}" vs remote="${c.remoteValue}"`);
});
import requests, os
response = requests.get(
f"https://app.alana.shopping/api/hub/subscriptions/{subscription_id}/conflicts",
headers={"Authorization": f"Bearer {os.environ['ALANA_API_KEY']}"}
)
data = response.json()
print(f"{data['total']} unresolved conflicts")
for c in data["conflicts"]:
print(f" {c['productId']}.{c['field']}: local={c['localValue']!r} vs remote={c['remoteValue']!r}")
Resolve a conflict
PUT /api/hub/subscriptions/{subscriptionId}/conflicts/{conflictId}
Resolves a single field conflict by choosing local, remote, or a custom merged value.
Request body
{
"resolution": "keep_local"
}
{
"resolution": "merge",
"mergedData": "Classic sky blue linen blazer — our premium pick. Machine washable. Available in S-XL."
}
| Field | Type | Required | Description |
|---|---|---|---|
resolution | string | Yes | "keep_local", "accept_remote", or "merge" |
mergedData | any | No | Custom merged value (required when resolution is "merge") |
Response
{
"conflictId": "conflict_9a3b",
"status": "resolved_local",
"resolvedAt": "2026-03-17T11:30:00Z",
"resolvedBy": "user_abc"
}
Resolve conflict examples
# Keep your local value
curl -X PUT "https://app.alana.shopping/api/hub/subscriptions/sub_4k2n8x/conflicts/conflict_9a3b" \
-H "Authorization: Bearer sk_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"resolution": "keep_local"}'
# Accept the publisher's value
curl -X PUT "https://app.alana.shopping/api/hub/subscriptions/sub_4k2n8x/conflicts/conflict_7c1d" \
-H "Authorization: Bearer sk_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{"resolution": "accept_remote"}'
# Use a custom merged value
curl -X PUT "https://app.alana.shopping/api/hub/subscriptions/sub_4k2n8x/conflicts/conflict_9a3b" \
-H "Authorization: Bearer sk_live_your_api_key_here" \
-H "Content-Type: application/json" \
-d '{
"resolution": "merge",
"mergedData": "Classic sky blue linen blazer — our premium pick."
}'
// Keep local value
const response = await fetch(
`https://app.alana.shopping/api/hub/subscriptions/${subscriptionId}/conflicts/${conflictId}`,
{
method: 'PUT',
headers: {
'Authorization': `Bearer ${process.env.ALANA_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ resolution: 'keep_local' }),
}
);
const result = await response.json();
console.log(`Resolved: ${result.status}`);
import requests, os
# Accept remote value
response = requests.put(
f"https://app.alana.shopping/api/hub/subscriptions/{subscription_id}/conflicts/{conflict_id}",
headers={
"Authorization": f"Bearer {os.environ['ALANA_API_KEY']}",
"Content-Type": "application/json",
},
json={"resolution": "accept_remote"}
)
result = response.json()
print(f"Resolved as: {result['status']}")
Conflict workflow
Error responses
| HTTP status | Code | Description |
|---|---|---|
| 403 | INSUFFICIENT_PERMISSIONS | Not your subscription |
| 404 | SUBSCRIPTION_NOT_FOUND | Subscription ID not found |
| 404 | CONFLICT_NOT_FOUND | Conflict ID not found or already resolved |
| 409 | SYNC_ALREADY_RUNNING | A sync is already in progress |
| 422 | NO_PENDING_UPDATES | Already on the latest version (apply only) |
| 422 | MERGE_DATA_REQUIRED | mergedData is required when resolution is "merge" |