Services
REST API
How it works
The REST API has a single entry point, /api (ApiController). Every call is a POST with:
| Parameter | Contents |
|---|---|
username, password |
Credentials of a system user (the tenant). |
action |
class.method for the core, or plugins.<plugin>.class.method. |
| the rest | The action's parameters. |
curl -X POST https://mysite.com/api \
-d username=apiuser -d password=secret \
-d action=usuarios.obtenerPerfilUsuario -d usr_codigo=12 -d language=en
{ "status": "ok", "data": { "profile": { "…": "…" } } }
status |
Meaning |
|---|---|
ok |
Success; the data is in data. |
error |
The operation failed (credentials, validation, missing record…). Includes message. |
fail |
The call is malformed (unknown action, inactive plugin…). |
The language parameter changes the language of that call's messages.
Action resolution
| Action | File | Class |
|---|---|---|
usuarios.loginUsuario |
src/api/usuarios.php |
UsuariosApiController::loginUsuario() |
plugins.noticias.manager.obtenerNoticias |
plugins/noticias/src/Api/manager.php |
ManagerApiController::obtenerNoticias() |
Only public methods declared in the class itself can be called, and the class must extend RestProvider. Names are validated as identifiers, so an action cannot point to another file.
Batch calls
action=batch.call runs several actions in one request. bulk is an object (or a JSON string) with one key per action and its parameters; anything sent outside bulk is shared by every call. returnAs changes a result's key (to call the same action twice).
curl -X POST https://mysite.com/api -d username=… -d password=… \
-d action=batch.call \
--data-urlencode 'bulk={"plugins.tablas.paises.obtenerPaises":{},
"plugins.noticias.manager.obtenerNoticias":{"limit":5,"returnAs":"latest"}}'
Writing an endpoint
<?php
// src/api/productos.php -> productos.* actions
Koshkil::uses('sys.web.Rest.RestProvider');
Koshkil::usesModel('productos');
class ProductosApiController extends RestProvider {
// action=productos.listar&limit=10&offset=0&categoria=3
public function listar() {
$qb = TMProductos::where('usr_codigo', $this->apiUser()->usr_codigo)
->order('prd_nombre');
if ($categoria = $this->intParam('categoria')) {
$qb->where('cat_codigo', $categoria);
}
$total = $this->countRows(clone $qb);
$items = $this->paginate($qb)->getAsArray();
return $this->successResponse(['products' => $items, 'total' => $total]);
}
// action=productos.obtener&prd_codigo=15
public function obtener() {
$producto = TMProductos::where('prd_codigo', $this->intParam('prd_codigo'))
->where('usr_codigo', $this->apiUser()->usr_codigo)
->first();
if (!$producto) {
return $this->errorResponse('productos.not_found'); // key in the "api" domain
}
return $this->successResponse(['product' => $this->onlyRequestedFields($producto->record())]);
}
}
RestProvider helpers
| Method | Purpose |
|---|---|
apiUser() |
The authenticated user (the tenant). Always filter by it. |
params(), param($k, $def) |
The call's parameters. |
intParam($k), arrayParam($k), intList($v) |
Typed parameters. |
esc($v) |
Escapes a value for hand-written SQL. Not needed with where(), which already escapes. |
paginate($qb) |
Applies the limit and offset parameters. |
countRows($qb) |
Counts a query's records. |
onlyRequestedFields($r) |
Filters fields according to shortVersion. |
translated($record), requestedLanguage() |
Translatable content. |
findSubUser($value, $column) |
Finds a sub-user of the tenant. |
baseUrl(), absoluteUrl($path) |
Absolute URLs of the tenant's site. |
successResponse($data, $msg) |
['status' => 'ok', 'data' => …] |
errorResponse($key, $params) |
['status' => 'error', 'message' => …] (message from the api domain) |
failResponse($key, $params) |
['status' => 'fail', …] |
In-process use, without HTTP
The site can consume its own API without making a request, through ApiService. The client credentials go in config/domains/<domain>/api.php:
<?php
$API = [
'default' => ['username' => 'apiuser', 'password' => '…'],
// 'mobile' => ['username' => '…', 'password' => '…'],
];
Koshkil::uses('sys.web.Rest.ApiService');
$api = ApiService::domain(); // the current domain's 'default' client
$r = $api->call('plugins.noticias.manager.obtenerNoticias', ['limit' => 5]);
if ($r['status'] == 'ok') { $news = $r['data']['news']; }
$r = $api->batch([
'plugins.tablas.paises.obtenerPaises' => [],
'plugins.noticias.etiquetas.obtenerEtiquetas' => [],
]);
SuperController and FrontAjaxController already include ApiAccessTrait:
$response = $this->api('usuarios.loginUsuario', ['subuser' => $email, 'pass' => $password]);
$user = $this->apiData($response, 'users'); // data['users'], or null on error
This way the site and external applications share exactly the same logic and validations.