Hi All,
I’m working on a script that will add a list of items, collected by item id, to a given item set. Here’s the snippet (python)
def add_items_to_itemset(self, item_ids: Set[int], itemset_id: int) -> None:
"""
Add items to the specified item set.
Args:
item_ids: Set of item IDs to add to the item set
itemset_id: ID of the item set to add items to
"""
success_count = 0
error_count = 0
already_in_set = 0
print(f"\nDEBUG: Starting to add {len(item_ids)} items to item set {itemset_id}")
for idx, item_id in enumerate(item_ids, 1):
try:
print(f"\n--- Processing item {idx}/{len(item_ids)}: ID {item_id} ---")
# Get current item data
item_url = f"{self.api_url}/items/{item_id}"
print(f"DEBUG: GET request to: {item_url}")
response = self.session.get(item_url)
print(f"DEBUG: GET response status: {response.status_code}")
response.raise_for_status()
item_data = response.json()
item_title = item_data.get('o:title', 'Untitled')
print(f"DEBUG: Item title: '{item_title}'")
# Check if item is already in the item set
current_itemsets = item_data.get('o:item_set', [])
itemset_ids = [s['o:id'] for s in current_itemsets]
print(f"DEBUG: Current item sets for this item: {itemset_ids}")
if itemset_id in itemset_ids:
print(f"DEBUG: Item {item_id} is already in target item set {itemset_id} - skipping")
already_in_set += 1
success_count += 1
continue
# Add the new item set to the list with both @id and o:id
# (matching the format of existing item sets)
new_item_set = {
'@id': f"{self.api_url}/item_sets/{itemset_id}",
'o:id': itemset_id
}
current_itemsets.append(new_item_set)
new_itemset_ids = [s['o:id'] for s in current_itemsets]
print(f"DEBUG: Adding item set {itemset_id} to item {item_id}")
print(f"DEBUG: New item set list will be: {new_itemset_ids}")
print(f"DEBUG: New item set entry: {new_item_set}")
# Update the item data with new item sets
item_data['o:item_set'] = current_itemsets
# Remove fields that shouldn't be sent in PUT request
# These are read-only, computed, or JSON-LD context fields
fields_to_remove = ['@context', '@id', '@type', 'o:id', 'o:owner',
'o:created', 'o:modified', 'thumbnail_display_urls',
'o:primary_media', 'o:media', 'o:site', '@reverse']
update_data = {k: v for k, v in item_data.items() if k not in fields_to_remove}
print(f"DEBUG: PUT data keys: {list(update_data.keys())}")
print(f"DEBUG: o:item_set value: {update_data.get('o:item_set')}")
print(f"DEBUG: PUT request to: {item_url}")
response = self.session.put(
item_url,
json=update_data,
headers={'Content-Type': 'application/json'}
)
print(f"DEBUG: PUT response status: {response.status_code}")
# Print response details if there's an error
if response.status_code != 200:
print(f"ERROR: Response status: {response.status_code}", file=sys.stderr)
print(f"ERROR: Response headers: {dict(response.headers)}", file=sys.stderr)
print(f"ERROR: Response body: {response.text}", file=sys.stderr)
response.raise_for_status()
print(f"SUCCESS: Added item {item_id} ('{item_title}') to item set {itemset_id}")
success_count += 1
except requests.exceptions.RequestException as e:
print(f"ERROR: Failed to add item {item_id} to item set: {e}", file=sys.stderr)
if hasattr(e, 'response') and e.response is not None:
print(f"ERROR: Response status: {e.response.status_code}", file=sys.stderr)
print(f"ERROR: Response body: {e.response.text}", file=sys.stderr)
error_count += 1
print(f"\n=== FINAL SUMMARY ===")
print(f"Total items processed: {len(item_ids)}")
print(f"Successfully added: {success_count - already_in_set}")
print(f"Already in set (skipped): {already_in_set}")
print(f"Errors: {error_count}")
However, I’m getting the following response
ERROR: Response status: 403
ERROR: Response headers: {'Date': 'Tue, 27 Jan 2026 16:50:38 GMT', 'Server': 'Apache/2.4.52 (Ubuntu)', 'Omeka-S-Version': '4.1.1', 'Keep-Alive': 'timeout=5, max=52', 'Connection': 'Keep-Alive', 'Transfer-Encoding': 'chunked', 'Content-Type': 'application/ld+json'}
ERROR: Response body: {"errors":{"error":"Permission denied for the current user to update the Omeka\\Api\\Adapter\\ItemAdapter resource."}}
ERROR: Failed to add item 209995 to item set: 403 Client Error: Forbidden for url: https://urprojectsdev.lib.rochester.edu/api/items/209995
ERROR: Response status: 403
ERROR: Response body: {"errors":{"error":"Permission denied for the current user to update the Omeka\\Api\\Adapter\\ItemAdapter resource."}}
Its unclear to me what I need to do to resolve this 403, as the user’s api key has global admin access.
Here’s what I get for the item set when I put in a get request
{
"@context": "https://urprojectsdev.lib.rochester.edu/api-context",
"@id": "https://urprojectsdev.lib.rochester.edu/api/item_sets/213519",
"@type": "o:ItemSet",
"o:id": 213519,
"o:is_public": true,
"o:owner": {
"@id": "https://urprojectsdev.lib.rochester.edu/api/users/1",
"o:id": 1
},
"o:resource_class": null,
"o:resource_template": null,
"o:thumbnail": null,
"o:title": "Artists",
"thumbnail_display_urls": {
"large": null,
"medium": null,
"square": null
},
"o:created": {
"@value": "2026-01-20T16:07:02+00:00",
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
},
"o:modified": {
"@value": "2026-01-20T16:07:02+00:00",
"@type": "http://www.w3.org/2001/XMLSchema#dateTime"
},
"o:is_open": true,
"o:items": {
"@id": "https://urprojectsdev.lib.rochester.edu/api/items?item_set_id=213519"
},
"dcterms:title": [
{
"type": "literal",
"property_id": 1,
"property_label": "Title",
"is_public": true,
"@value": "Artists"
}
]
}
It’s unclear to me how I resolve this. Even doing this in insomnia, I get the permission denied issue, so I don’t think its my code, unless omeka is expecting something strange for the payload