The official Opencart API is poorly documented, with typos and omissions: docs.opencart.com/en-gb/system/users/api. Provided you have your API key, making a simple external API request using cURL or Postman to have e.g. order status updated seems impossible and overcomplicated: you'd have to use that API key to request a token first, and then make requests using that token. The token by itself is not enough, as it gets cross-validated with api_id
inside $this->session->data
. Or, how Daniel puts it:
ok the system is a little different compared to editing orders on the same server. you have to login then get a session id or token returned. you need to include that in each request.
Due to what seems like a bug, that token doesn't get linked to the required api_id
properly (despite Daniel claiming it got fixed in 2015 and then later in 2016), so the requests result in a permission error -- either error_permission
, or Warning: You do not have permission to access the API, depending on translation availability.
I was dealing with the code version after 46b7132
from 29 Jun 2016, and despite the commit message saying literally ok the API should be working again for now, it did not.
The advice that's the closest to a real fix is to manually copy system/library/session.php
and catalog/controller/startup/session.php
from the latest version, but it doesn't sound like a good idea because:
- this doesn't answer what exactly was the problem;
- there is too much code to copy, and the more code you copy, the higher are the chances of getting something off or messed up.
So lets take a close look at those two. The catalog/controller/startup
folder shows the most recent change in session.php
is a 4 year old commit b390799
Additional fixed (requires verification) to fix issues with the API login.php and also session start-up, which looks very relevant, yet doesn't fix the issue. But going back in that file's history reveals b358841
more token changes right after the aforementioned should be working for now, which introduces the following change. Changing those exact two lines in your copy of session.php
and fixes the external API that you login to using api_token
but at the same time it breaks those admin calls to the API, such as the one that updates the order status.
So the solution is to combine those two: keep the original if
looking for $this->request->get['token']
, and add an else if
from the above commit that looks for $this->request->get['api_token']
, with the corresponding body. That should satisfy both cases and your session.php
should look like this:
if (isset($this->request->get['token']) && isset($this->request->get['route']) && substr($this->request->get['route'], 0, 4) == 'api/') {
$this->db->query("DELETE FROM " . DB_PREFIX . "api_session
WHERE TIMESTAMPADD(HOUR, 1, date_modified) < NOW()");
$query = $this->db->query("SELECT DISTINCT * FROM " . DB_PREFIX . "api
a
LEFT JOIN " . DB_PREFIX . "api_session
as
ON (a.api_id = as.api_id) LEFT JOIN " . DB_PREFIX . "api_ip ai
ON (as.api_id = ai.api_id) WHERE a.status = '1' AND as.token = '" . $this->db->escape($this->request->get['token']) . "' AND ai.ip = '" . $this->db->escape($this->request->server['REMOTE_ADDR']) . "'");
if ($query->num_rows) {
$this->session->start('api', $query->row['session_id']);
// keep the session alive
$this->db->query("UPDATE " . DB_PREFIX . "api_session
SET date_modified = NOW() WHERE api_session_id = '" . (int)$query->row['api_session_id'] . "'");
}
} elseif (isset($this->request->get['api_token']) && isset($this->request->get['route']) && substr($this->request->get['route'], 0, 4) == 'api/') {
$this->db->query("DELETE FROM " . DB_PREFIX . "api_session
WHERE TIMESTAMPADD(HOUR, 1, date_modified) < NOW()");
$query = $this->db->query("SELECT DISTINCT * FROM " . DB_PREFIX . "api
a
LEFT JOIN " . DB_PREFIX . "api_session
as
ON (a.api_id = as.api_id) LEFT JOIN " . DB_PREFIX . "api_ip ai
ON (as.api_id = ai.api_id) WHERE a.status = '1' AND as.token = '" . $this->db->escape($this->request->get['api_token']) . "' AND ai.ip = '" . $this->db->escape($this->request->server['REMOTE_ADDR']) . "'");
if ($query->num_rows) {
$this->session->start('api', $query->row['session_id']);
// keep the session alive
$this->db->query("UPDATE " . DB_PREFIX . "api_session
SET date_modified = NOW() WHERE api_session_id = '" . (int)$query->row['api_session_id'] . "'");
}
} else {
$this->session->start();
}
Existing issues, for reference:
- https://stackoverflow.com/questions/42198431/opencart-api-issue-with-session-app-id-how-to-get-api-id
- https://stackoverflow.com/questions/54423754/can-any-one-explain-opencart-session-in-api-module
- https://stackoverflow.com/questions/34612212/opencart-2-api-session-issues
- https://forum.opencart.com/viewtopic.php?t=201959 (the genius advice to install a 3rd party marketplace extension to fix this core bug is a bonus)
- https://github.com/opencart/opencart/issues/7022
- https://github.com/opencart/opencart/issues/6202
- https://github.com/opencart/opencart/issues/4692
- https://github.com/opencart/opencart/issues/4578
- https://github.com/opencart/opencart/issues/3557