Page MenuHomePhabricator

🐣 Create an item
Closed, ResolvedPublic13 Estimated Story Points

Description

As a developer, I want to be able to create a new Wikidata item in its entirety
POST rest.php/wikibase/v0/entities/items

{
  "item": {
     "labels": {}, [label or description in one language code is mandatory]
     "descriptions": {},
     "aliases": {},
     "sitelinks": {}
     "statements": {}
  },
  "comment": ... (optional)
  "tags": [ ... ] (optional)
  "bot": false (optional)
}

Acceptance criteria

  • At least adding a label or a description in one language is mandatory
  • 201 response code on success and location header in the successful creation response
  • Ignore HTTP conditional request headers
  • Statement errors can be handled similar to those in POST /entities/items/{item_id}/statements. Only additional change is adding the the index of the problem statement to the path context of the error message in the form property_id/index_of_erronic_statement
  • Ignore id and type fields and don't consider them unexpected
  • ETag, last-modified and location (URI of the newly created item) as response headers.
  • Handle user authentication/authorization like in POST /entities/items/{item_id}/statements
  • Client can provide additional edit metadata: mediawiki tags, edit summary text to append to the automated summary, and a bot edit flag, like in POST /entities/items/{item_id}/statements

Error cases to consider

HTTP response coderesponse payload
Invalid edit tag400 "code": "invalid-edit-tag"
"message": "Invalid MediaWiki tag: {tag}"
Comment too long400 "code": "comment-too-long"
"message": "Comment must not be longer than {limit} characters"
Invalid data in field400 "code": "item-data-invalid-field"
"message": "Invalid input for '{field}'"
"context": { "path": "{field}", "value": "{value}" }
Unexpected field in item request400 "code": "unexpected-field"
"message": "The request body contains an unexpected field"
"context": { "field": "{field}" }
Item after changes missing label or description 400 "code": "missing-labels-and-descriptions"
"message": "Item requires at least a label or a description in a language"
When label, description or alias fields are selected but not filled (aka, empty)400 "code": "label/description/alias-list/alias-empty"
"message": "Label/Description/Alias list/Alias must not be empty"
"context": { "language": "{language_code}" }
Label and description have same value400 "code": "label-description-same-value"
"message": "Label and description for language '{language_code}' can not have the same value"
"context": { "language": "{language}" }
Label/description/alias too long400 "code": "label/description/alias-too-long"
"message": "Label/Description/Alias must be no more than {limit} characters long"
"context": { "language": "{language_code}", "character-limit": "{limit}" }
Label/description/alias invalid400 "code": "invalid-label/description/alias"
"message": "Not a valid label/description/alias: {label/desc/alias}"
"context": { "language": "{language_code}" }
Alias type invalid400 "code": "invalid-alias-list"
"message": "Not a valid alias list"
"context": { "language": "{language_code}" }
Invalid language code400 "code": "invalid-language-code"
"message": "Not a valid language code: {language_code}"
"context": { "path": "{field}", "language": "{language_code}" }
Duplicate alias400 "code": "duplicate-alias"
"message": "Alias list contains a duplicate alias: '{alias}'"
"context": { "language": "{language_code}", "alias": "{alias}" }
Item with label and description already exists400 "code": "item-label-description-duplicate"
"message": "Item '{duplicate_item_id}' already has label '{label}' associated with language code '{language}', using the same description text"
"context": { "language": "{language}", "label": "{label}", "description": "{description}", "matching-item-id": "{duplicate_item_id}" }
Sitelink conflict409 "code": "sitelink-conflict"
"message": "Sitelink is already being used on {other_item_id}"
"context": { "site-id": "{site_id}", "matching-item-id": "{duplicate_item_id}" }
Sitelink title field not provided400 "code": "sitelink-data-missing-title"
"message": "Mandatory sitelink title missing"
"context": { "site-id": "{site_id}" }
title is empty400 "code": "title-field-empty"
"message": "Title must not be empty"
"context": { "site-id": "{site_id}" }
Invalid title400 "code": "invalid-title-field"
"message": "Not a valid input for title field"
"context": { "site-id": "{site_id}" }
Value provided as a badge is not an item ID400 "code": "invalid-input-sitelink-badge"
"message": "Badge input is not an item ID: {value}"
"context": { "badge": "{value}", "site-id": "{site_id}" }
Item provided is not allowed as a sitelink badge400 "code": "item-not-a-badge"
"message": "Item ID provided as badge is not allowed as a badge: {item_id}"
"context": { "badge": "{item_id}", "site-id": "{site_id}" }
value of badges field is not a list400 "code": "invalid-sitelink-badges-format"
"message": "Value of badges field is not a list"
"context": { "site-id": "{site_id}" }
Title does not exist on the given site400 "code": "title-does-not-exist"
"message": "Page with title {title} does not exist on the given site"
"context": { "site-id": "{site_id}" }
Invalid site ID400 "code": "invalid-site-ID"
"message": "Not a valid site ID:'{site_id}'"
"context": { "site-id": "{site_id}" }
Invalid statement group object400 "code": "invalid-statement-group-type"
"message": "Not a valid statement group"
"context": { "path": "{property_id}" }
Invalid statement object400 "code": "invalid-statement-type"
"message": "Not a valid statement type"
"context": { "path": "{property_id/index}" }
Statement's Property ID value does not match the key of the statement group400 "code": "statement-group-property-id-mismatch"
"message": "Statement's Property ID does not match the statement group key"
"context": {
"path": "{property_id_key}/{index}/property/id",
"statement-group-property-id": "{property_id_key}",
"statement-property-id": "{property_id_value}"
}

Notes

Task breakdown

  • add to OAS
  • create ItemDeserializer (plugs together all the existing deserializers for labels, descriptions, aliases, statements, sitelinks)
  • create ItemCreator interface with a create( Item $item, EditMetadata $editMetadata ): ItemRevision; and implementation. We can likely reuse the existing EntityUpdater class which might then need a better name.
  • happy path
    • include revision ID and last modified date
    • edit summary
  • authorization
    • add new method to PermissionChecker::canCreateItem( User $user ): bool and implement it
  • top-level field validation
    • covers item-data-invalid-field, unexpected-field, missing-labels-and-descriptions
  • field validation
  • apply middlewares (the usual ones except the one for conditional requests)
  • spec tests
  • mark production ready

Event Timeline

There are a very large number of changes, so older changes are hidden. Show Older Changes
Muhammad_Yasser_Jazirahly_WMDE renamed this task from Create an item to 🐣 Create an item.Mar 6 2024, 10:35 AM

Updates from daily today:

  • For statement invalid field error, we'd add the actual field that is incorrect also in the path. Example - /property_id/0/rank
  • For statement missing field error, we'd would not do this because the field is actually missing and it doesn't make sense to add it to the path. But, we should add a new field called field in the context which names the actual field that is missing [insert clown emoji here]

Update from daily and my check:

  1. Remove the validation check for having at least a label or a description in a language (Ticket description will be updated)
  2. The statement group property id vs statement property id mismatch error occurs only when two separate *existing* property IDs are used, or as long as the property id of the statement inside the group exists.

If the property id of the statement inside the group does not exist, instead of the mismatch error, we get a "statement-data-invalid-field"

I'd propose as long as the property id does not exist, regardless of where it is, we should throw the "statement-data-invalid-field"

Hi @Ifrahkhanyaree_WMDE. I am waiting to use the Create Item API in my tool (https://bub2.wmcloud.org) to automatically create Wikidata entries for the metadata for the book. I could use action=wbeditentity but I prefer to use the Wikibase APIs for interacting with Wikidata. Any idea by when will this be live for production use?

Hiya @wassan.anmol117, we should have the endpoint live by next week! Ideally early next week but most definitely end of next week

Checked the two changes and a few extra things for good measure, so @WMDE-leszek doesn't have to go through every single thing. I think we're ready for it to be marked as production ready

Hiya @wassan.anmol117, we should have the endpoint live by next week! Ideally early next week but most definitely end of next week

Thanks. Can't wait to use it! :)

@wassan.anmol117 this has been enabled on master and will go live on wikidata.org with the next train on Wednesday 5th June :)

@wassan.anmol117 this has been enabled on master and will go live on wikidata.org with the next train on Wednesday 5th June :)

Awesome, thanks!

@Ollie.Shotton_WMDE @Ifrahkhanyaree_WMDE Did this go live yesterday? I tried testing the API on Postman but it is giving me the following error even after I am sending a POST request:

{
    "messageTranslations": {
        "en": "The request method (GET) was not the allowed method for this path (POST)"
    },
    "httpCode": 405,
    "httpReason": "Method Not Allowed"
}

This is the endpoint that I'm trying to hit: https://wikidata.org/w/rest.php/wikibase/v0/entities/items

Hi @wassan.anmol117, it's live on Wikidata since yesterday. Have you explicitly selected the method to be POST when testing it on Postman? I think it needs that even after the body has been defined

Let us know if that helps!

Hi @wassan.anmol117,

What version of Postman are you using? Have you tried alternative tools, or something like curl on the command line?

This curl command returns a label-description-same-value error for me (as expected):

curl --request POST \
  --url https://www.wikidata.org/w/rest.php/wikibase/v0/entities/items \
  --header 'Content-Type: application/json' \
  --data '{ "item": { "labels": { "en": "test" }, "descriptions": { "en": "test" } } }'

Hi @wassan.anmol117,

What version of Postman are you using? Have you tried alternative tools, or something like curl on the command line?

This curl command returns a label-description-same-value error for me (as expected):

curl --request POST \
  --url https://www.wikidata.org/w/rest.php/wikibase/v0/entities/items \
  --header 'Content-Type: application/json' \
  --data '{ "item": { "labels": { "en": "test" }, "descriptions": { "en": "test" } } }'

@Ollie.Shotton_WMDE I think I have found the issue. As per the Wikidata REST API documentation, the endpoint should be https://wikidata.org/w/rest.php/wikibase/v0 but the endpoint in your comment has www as well. I tried with adding www in the endpoint and it worked.
But I think the API should work even without www.

Ahh, right, yes! So what is happening here is that wikidata.org is redirected, via a "301 Moved Permanently" response, to www.wikidata.org. Postman is automatically following this redirect but changing the POST method to a GET method, hence the error you saw. You can see this redirect if you turn off Automatically follow redirects in Postman's settings. This is a client issue and not something we can change in the Wikibase REST API.

We have changed the Wikidata REST API documentation to now include the www. so others are less likely to trip over this issue. We have also brought this to the attention of the Wikidata team to see if a "308 Permanent Redirect" would be a better redirect response to return, as clients shouldn't change the HTTP method when following a 308 redirect.

Side note, I suggest you use test.wikidata.org while testing the Create Item endpoint to not spam wikidata.org with empty/test Items. :)

@Ollie.Shotton_WMDE @Ifrahkhanyaree_WMDE While using the API, I observed that I was able to make a POST call with unauthenticated API requests. For example, I created this item on https://test.wikidata.org without the Authorization header and it worked. Is this expected?

Hi @wassan.anmol117, yes, that is to be expected. Just like via the Wikidata UI, you can create anonymous edits that will be associated with an IP address (in the future this will be temporary anonymous accounts). If you want your edits to be associated with a particular account, then you would need to authenticate. There are other benefits to authenticating, such as an increased rate limit.

Hi @wassan.anmol117, yes, that is to be expected. Just like via the Wikidata UI, you can create anonymous edits that will be associated with an IP address (in the future this will be temporary anonymous accounts). If you want your edits to be associated with a particular account, then you would need to authenticate. There are other benefits to authenticating, such as an increased rate limit.

Alright, got it. Thank you.