Is there any documentation on how to "fully" create a submission via REST API?

Using OJS 3.3.0.8

What I’d like to get is all the necessary steps (via REST API) to “Submit an Article” as in the “Submission Wizard”: 1. Start, 2. Upload Submission, 3. Enter Metadata, 4. Confirmation, 5. Next Steps.

Is this via REST API possible at all?

When creating a submission via Create a new submisssion the Submission is counted in the GUI (under Unassigned and under All Active) but it is not shown in the list.

What are the necessary steps to do it via REST API?

After “Create a new submission” I’ve tried Create a Publication. The documentation tells that
the path parameters publicationId and submissionId are required. But publicationId is not accepted.

# the path parameter "publicationId" returns an "api.404.endpointNotFound" error
$ curl -s \
>     -X POST \
>     "http://www.examle.com/index.php/ziaf/api/v1/submissions/${SUBMISSION_ID}/publications/1?apiToken=${API_TOKEN}" \
>     -H "Accept: application/json" \
>     -H "Content-Type: application/json"

{"error":"api.404.endpointNotFound","errorMessage":"The requested URL was not recognized."}

# the submission is there with no publication items
$ curl -s \
>     -X GET \
>     "http://www.example.com/index.php/ziaf/api/v1/submissions/${SUBMISSION_ID}/publications?apiToken=${API_TOKEN}"

{"itemsMax":0,"items":[]}

# requesting with POST we get a "<h1>404 Not Found</h1>"
$ curl -s \
>     -X POST \
>     "http://www.example.com/index.php/ziaf/api/v1/submissions/${SUBMISSION_ID}/publications?apiToken=${API_TOKEN}" \
>     -H "Accept: application/json" \
>     -H "Content-Type: application/json"

<h1>404 Not Found</h1>
1 Like

Faced same issue. Have anyone passed through this?

I also have the same problem.
It is not clear what API should be used to fully cover the submission/publication of an article.
Is there a tutorial, a white paper or anything to help?

I am currently running into the same issue. Have any of you found a solution?

Hi all,

Can anyone describe the problem in more details? I’m testing it locally and the POST request to the submissions endpoint works for me.
What your request and response looks like?

I can’t speak for the others in this thread, but I’d be happy to share the problems I’m running into while trying to figure this out. I’m keeping track of the progress I’m making, so hopefully I’ll be able to post a full guide if I do figure it out completely.

First off, I’m using OJS 3.3.0.7.

I’m running into two of the problems in this thread:

  1. When creating a submission, it does not show up in the GUI. I can request the information through the API, edit it etc, but nothing is seen in the interface. What I do get is these whitespaces (which I’m assuming might be related, but might not be):

  2. I’ve not yet figured out how to add a publication to a submission. The create a publication specifies that a publicationID should be added, but not where in the path, nor how you should get one for a publication that does not exist yet.

When posting to /submissions/{{submissionID}}/publications (e.g. without an publicationID) I get a 404-not found.

When posting with an arbitrary publicationID, I get a 403 with the message The requested URL was not recognized.

I can edit existing publications through the API quite easily, and the changes show up in the API as well. I’ll keep looking into it, but any help anyone can give would be welcome.

What is the response? Do you see something like

{
    "_href": "http://localhost:8000/index.php/publicknowledge/api/v1/submissions/27",
    "commentsForTheEditors": null,
    "contextId": 1,
    "currentPublicationId": 28,
    "dateLastActivity": "2023-08-31 13:52:46",
    "dateSubmitted": "2023-08-31 13:52:46",
    "id": 27,
    "lastModified": "2023-08-31 13:52:46",
    "locale": "en",
    "publications": [
        {
            "_href": "http://localhost:8000/index.php/publicknowledge/api/v1/submissions/27/publications/28",
            "authorsString": "",
            "authorsStringIncludeInBrowse": "",
            "authorsStringShort": "",
            "categoryIds": [],
            "coverImage": {
                "en": null,
                "fr_CA": null
            },
            "datePublished": null,
            "doiObject": null,
            "fullTitle": {
                "en": "",
                "fr_CA": ""
            },
            "galleys": [],
            "id": 28,
            "locale": "en",
            "pages": null,
            "prefix": {
                "en": "",
                "fr_CA": ""
            },
            "primaryContactId": null,
            "pub-id::publisher-id": null,
            "sectionId": 1,
            "status": 1,
            "submissionId": 27,
            "subtitle": {
                "en": "",
                "fr_CA": ""
            },
            "title": {
                "en": "",
                "fr_CA": ""
            },
            "urlPublished": "http://localhost:8000/index.php/publicknowledge/article/view/27/version/28",
            "version": 1
        }
    ],
    "reviewAssignments": [],
    "reviewRounds": [],
    "stageId": 1,
    "stages": [
        {
            "id": 1,
            "label": "Submission",
            "isActiveStage": true,
            "openQueryCount": 0,
            "currentUserAssignedRoles": [],
            "statusId": 1,
            "status": "No editor has been assigned to this submission.",
            "files": {
                "count": 0
            }
        },
        {
            "id": 3,
            "label": "Review",
            "isActiveStage": false,
            "openQueryCount": 0,
            "currentUserAssignedRoles": [],
            "files": {
                "count": 0
            }
        },
        {
            "id": 4,
            "label": "Copyediting",
            "isActiveStage": false,
            "openQueryCount": 0,
            "currentUserAssignedRoles": [],
            "files": {
                "count": 0
            }
        },
        {
            "id": 5,
            "label": "Production",
            "isActiveStage": false,
            "openQueryCount": 0,
            "currentUserAssignedRoles": [],
            "files": {
                "count": 0
            }
        }
    ],
    "status": 1,
    "statusLabel": "Queued",
    "submissionProgress": "start",
    "urlAuthorWorkflow": "http://localhost:8000/index.php/publicknowledge/authorDashboard/submission/27",
    "urlEditorialWorkflow": "http://localhost:8000/index.php/publicknowledge/workflow/access/27",
    "urlPublished": "http://localhost:8000/index.php/publicknowledge/article/view/27",
    "urlSubmissionWizard": "http://localhost:8000/index.php/publicknowledge/submission?id=27",
    "urlWorkflow": "http://localhost:8000/index.php/publicknowledge/submission?id=27"
}

To what specific request would that be a potential response?

To the calls described above that give me error messages, I don’t get that response.

Posting to {{baseURL}}/submissions/{{submissionID}}/publications results in <h1>404 Not Found</h1>

Posting to {{baseURL}}/submissions/{{submissionID}}/publications/14532 or any other arbitrary publication ID, I get

{
    "error": "api.404.endpointNotFound",
    "errorMessage": "The requested URL was not recognized."
}

Which is not surprising.

If you’re refering to the first question about creating a submission, I get this response:

{
    "_href": "https://testplatform.openjournals.nl/IISG/api/v1/submissions/12523",
    "contextId": 29,
    "currentPublicationId": 0,
    "dateLastActivity": "2023-09-01 09:23:53",
    "dateSubmitted": "2023-08-23 00:00:00",
    "id": 12523,
    "lastModified": "2023-09-01 09:23:53",
    "locale": "en_US",
    "publications": [],
    "reviewAssignments": [],
    "reviewRounds": [],
    "stageId": 1,
    "stages": [
        {
            "id": 1,
            "label": "Submission",
            "isActiveStage": true,
            "queries": [],
            "currentUserAssignedRoles": [],
            "statusId": 1,
            "status": "No editor has been assigned to this submission.",
            "files": {
                "count": 0
            }
        },
        {
            "id": 3,
            "label": "Review",
            "isActiveStage": false,
            "queries": [],
            "currentUserAssignedRoles": [],
            "files": {
                "count": 0
            }
        },
        {
            "id": 4,
            "label": "Copyediting",
            "isActiveStage": false,
            "queries": [],
            "currentUserAssignedRoles": [],
            "files": {
                "count": 0
            }
        },
        {
            "id": 5,
            "label": "Production",
            "isActiveStage": false,
            "queries": [],
            "currentUserAssignedRoles": [],
            "files": {
                "count": 0
            }
        }
    ],
    "status": 1,
    "statusLabel": "Queued",
    "submissionProgress": 0,
    "urlAuthorWorkflow": "https://testplatform.openjournals.nl/IISG/authorDashboard/submission/12523",
    "urlEditorialWorkflow": "https://testplatform.openjournals.nl/IISG/workflow/access/12523",
    "urlPublished": "https://testplatform.openjournals.nl/IISG/article/view/12523",
    "urlWorkflow": "https://testplatform.openjournals.nl/IISG/workflow/access/12523"
}

Yes, it looks that you are right. Creating a new submission in OJS 3.3 doesn’t create new publication. That API endpoint was rewritten in 3.4 with the introduction of the new submission wizard

That sounds like a bug. I’ve opened an issue: Unable to create a new publication through submission API · Issue #9275 · pkp/pkp-lib · GitHub

Thank you for your answer. In that case, I’ll put this on hold until we’ve upgraded to 3.4, as it does not seem possible in 3.3.

I’m also grappling with this in 2024 (well, almost 2025). The API is a bit of a moving target, as all APIs are. I’m working with the 3.4.0-8 stable version. I was able to create a submission using a POST to the target /submissions. This seems to also create an associated publication as illustrated by the response @KayWP showed. I then tried to edit that publication using a PUT to the endpoint /submissions/<subid>/publications/<pubid>. Unfortunately I’m hung up on getting it to accept any data because it’s not clear what the structure of the multilingual fields like title, keywords, and abstract should be. I set up my server using only the default ‘locale’ of en in the config.inc.php, and that’s the only thing shown in Administration => site settings. Ordinarily a locale would use the more specific code of en_US instead of en, but I’ve tried both and it won’t accept either one. The format of what I submitted looks like

"title": {
  "en": "This is the title"
}

but I get a response of

(400) caused by "{"title":{"en":["This field is multilingual. Separate values must be provided for each supported language."]}}

It’s not at all clear to me what is desired here and the documentation is pretty sparse on this topic. I see examples using "en" and "en_US" but it would be more useful if there was an end-to-end tutorial on how to create and edit a submission/publication. The example shows a value for title being LocaleObject with an example of keys "en" and "fr_CA". I assume these are taken from some subset of CLDR but it’s unclear what OJS expects for the keys in a default setup. I could understand it being complicated if I had added other languages…but I’m just using the default.

Hi @kmccurley,

Have a walk through the author’s submission process; that should cover all of the API exchanges necessary to create a new submission.

The locale codes to use depend on the branch. With 3.3.0-x and previous, we generally used xx_YY codes. Starting with 3.4.0, we modernized by starting to prefer xx codes where the _YY didn’t add meaningful information (thus en rather than en_US). We’ll be further standardizing locale codes in 3.5.0, e.g. renaming pt_PT to pt and adopting the upstream Weblate languages list. 3.5.0 will also introduce a potential break between UI languages and metadata languages, in order to better support metadata that’s in languages beyond those supported by the UI (e.g. Latin – though I would one day love to see a UI translation in Latin too).

For 3.4.0, en and fr_CA are correct for English and Canadian French.

Regards,
Alec Smecher
Public Knowledge Project Team

It appears that the wizard takes a quite different path that what is done in the description of the API. When I enter data into the submission (publication) form, it causes a POST to /submissions/<subid>/publications/<pubid> rather than a PUT, and the content-type is application/x-www-form-urlencoded rather than being a JSON payload. This is unusual for a nested dictionary of data, because x-www-form-urlencoded is not normally capable of representing nested data, and there isn’t a standard for how it should be encoded. Apparently jquery and php have consistent encoding/decoding, but it uses keys like "abstract[en]". What I actually saw sent as the payload from the browser was

title%5Ben%5D=My+second+title&keywords%5Ben%5D%5B%5D=first+keyword&keywords%5Ben%5D%5B%5D=second+keyword&abstract%5Ben%5D=%3Cp%3EThis+is+the+abstract.%3C%2Fp%3E

Swagger seems to have defined their own encoding for something called deepObject (an aside: deepObject appears to only be capable of nesting two levels deep).

The API documentation says to send application/json, but as far as I can tell, that doesn’t actually work and it’s not what the UI of OJS uses. I couldn’t figure out the code in lib/pkp/classes/publication/Repository.php where the validation for the data to submissions/<subid>/publications/<pubid> happens.

Since the API is built with swagger/OpenAPI, I assume that the normal method for building a client would be to use one of the code generators. Unfortunately when I tried

curl -H "Content-type: application/json" -X POST -d '{"options": {"packageName": "ojs_api"},"swaggerUrl": "https://raw.githubusercontent.com/pkp/ojs/refs/heads/main/docs/dev/swagger-source.json"}' https://generator.swagger.io/api/gen/clients/python

it responded with a 500 error saying simply “something bad happened”. I also tried downloading the swagger codegen java binary and building from the swagger-source.json, but it died. It will probably require someone with more swagger experience than I have to figure out how to build a client for this API.

Hi @kmccurley,

Thanks for sharing the details you’ve found so far. Could you confirm that the data that goes through the submission wizard that you’re referring to is on OJS 3.4?

I will have a look at how the process for submitting the application/json for the submissions/<subid>/publications/<pubid> works, but one place to start looking may be in the API controller where it converts the string input to the object schema (which is here for the Publication object. The validation happens based on the rules in the schema files for the most part.

As for generating an API client, we’ve never directly pursued this, but we do use redoc for displaying the API documentation as described in the docs repository so there may be some redoc specific things that are getting in the way of the swagger codegen you mentioned.

Regards,

Erik
PKP Team

The version I’m using was installed from git on the branch stable-3_4_0.

I got a little bit further by hacking an encoder for the x-www-form-urlencoded but after I edit the publication the next step would be to add the authors (contributors). In our workflow we don’t require an email from every author so I had to relax the requirement for this but that was expected and fixed by using the “author requirements” plugin.

The next step for me is to add contributors (authors) but that has me stumped. There is an argument userGroupId that appears to be a foreign key in the user_groups table. It appears to me that the correct value would correspond to some key corresponding to role_id of 65536, which is ROLE_ID_AUTHOR in lib/pkp/classes/security/Role.php. By inspecting the database I see that there are two rows with that value of role_id (with different values of permit_self_registration). I don’t see any way to access these keys through the API - I only saw them by inspecting the database manually. The API documentation probably needs to say how to find this.

It appears to me that the documentation for the API is incomplete.