Simple REST like API
Return value
Generally API calls return a JSON object with at least a status
key.
If API call is successful (but it doesn’t imply that all underlying actions have been successful too):
{ "status": "success" }
On top level error, reply is at least:
{ "status": "error", "errors": [ERROR1, ...] }
API call may add more keys described in “Reply” part.
An API session is automatically closed after 1 hour of inactivity.
Important
Under same API major version M, all API calls from M.m are available in M.m+1.
Session
Login
Request:
POST /api/v0/login
{
"username": USERNAME,
"password": PASSWORD
}
Reply:
{
"csrf-token": CSRF token for POST HTTP request,
"csrf-token-http-header": HTTP request header name for CSRF token,
"api": ["v0", "v0.1", "v0.2"] list of supported API version (new in v0.1)
}
If api
key is missing, caller must assume only v0 is
available.
Note
Notes for the beginners
Use
Content-type: application/json
for POST requests (but it doesn’t hurt if used for all type of requests)Cookies are used to track the session and they may be updated on each API call since the login phase; they must be saved and re-used for subsequent calls.
On a successful login, FortiPoc returns cookies similar to
fortipoc-csrftoken-<UID>
andfortipoc-sessionid-<UID>
.
POST
You must add the CSRF in HTTP header using
csrf-token-http-header
key value for the header key andcsrf-token
key value for the value.You must speficy a valid
Referer
HTTP header with your current host FQDN or IP as value as seen in your API URL (ex: https://10.0.0.1/ for https://10.0.0.1/api/v0/poc/launch).
Status
Get FortiPoC information
Request:
GET /api/v0/status
Reply:
{
"kvm": "official", # is it "official" (Intel) or "experimental" (AMD) kvm support
"fpuuid": "e8ed488f6b5ce2b0efce10aaed7a23df", # FortiPoC compact SN
"version": "1.7.4-dhcp4", # FortiPoC version
"hostname": "fortipoc", # FortiPoC hostname
"softkvm": false, # software emulation enabled or not
"nested": true, # nested virtualization enabled or not
"g_resources": true, # resource disk mounted or not
"username": "admin" # current user logged in
}
PoC
List
Request:
GET /api/v0/poc/list
Reply:
{
"pocs": {
"POC.PK": {
"pk": POC.PK,
"name": POC.NAME,
"running": true|false
},...
}
}
Launch
Request:
POST /api/v0/poc/launch
{
"poc": POC.PK,
"power-on": true(default)|false
}
When power-on
is true
(default if not specified), normal
launch with devices power on if they are configured to be powered on
on launch. If false
no device is powered on.
Reply:
{
"task": TASK.PK
}
Import (API >= v0.1)
The import uses multipart/form-data content type.
Request:
POST /api/v0.1/poc/import
Files are passed with fpocs
form field, you can import multiple files.
Set overwrite
form field to 1
if import must overwrite
existing PoC with same name.
Reply:
{
"status": "success",
"objects": {
"FILENAME" (or "INDEX"): {
"status": "success" | "error",
"pk": PK if sucessfully imported,
"name": POC name,
"errors": ["reason", ...]
}, ...
}
}
Delete (API >= v0.1)
Request:
POST /api/v0.1/poc/delete
{
"pocs": [POC.PK,...]
}
Reply:
{
"pocs": {
POC.PK: {
"status": "success" | "error"
"errors": ["REASON", ....]
}
}
}
Running Poc
List devices (API < v0.2)
Request:
GET /api/v0/poc/current/devices/list
Reply:
{
"devices": {
"DEVICE.PK": {
"name" : DEVICE.NAME,
"state" : device normalized state (as in GUI),
"pk" : DEVICE.PK,
"reason" : libvirt domain state, empty for lxc
"license": license information if any
}, ...
}
}
List devices (API == v0.2)
You can list all devices or specific devices using the primary key or the device name. You can specify multiple device in filter using comma.
Request:
GET /api/v0.2/poc/current/devices/list[?filter=<NAME or PK>,...]
Reply:
{
"devices": {
"DEVICE.PK": {
"name" : DEVICE.NAME,
"state" : device normalized state (as in GUI),
"pk" : DEVICE.PK,
"reason" : libvirt domain state, empty for lxc
"license": license information if any
}, ...
}
}
List devices (API >= v0.3)
You can list all devices or specific devices using the primary key or the device name or a part of the device name. You can specify multiple device in filter using comma.
Request:
GET /api/v0.3/poc/current/devices/list[?filter=<NAME or PK>,...]
Reply:
{
"devices": {
"DEVICE.PK": {
"name" : DEVICE.NAME,
"state" : device normalized state (as in GUI),
"pk" : DEVICE.PK,
"reason" : libvirt domain state, empty for lxc
"license": license information if any
}, ...
}
}
Actions devices
An action can be performed on a list of devices, each action is running in its own task.
Request:
POST /api/v0/poc/current/devices/ACTION
{
"devices": [DEVICE.PK, ...]
}
ACTION
can be:
poweron
: to power on devicesshutdown
: to cleanly shutdown devicespoweroff
: to power off devicesWarning
Device’s disks may be corrupted
rebuild
: to rebuild device’s disk as after PoC launch (device stays powered off)
Reply:
{
"devices": {
"DEVICE.PK": {
"pk": DEVICE.PK,
"status": "error"|"task",
"error": error if "status" is "error",
"task": TASK.PK
}, ...
}
}
Warning
If too many devices are processed in one call, you may reach the Web Server timeout.
Saving configuration (API >= 0.5)
To backup devices configuration, each save is running in its own task.
Request:
POST /api/v0/poc/current/devices/backup
{
"devices": { (source key) "DEVICE.PK" | DEVICE.NAME :
{
"filename": FILENAME,
"apply": "replace"|"yes"|"no"(default)
}, ...}
}
filename
: mandatory if apply isyes
orno
apply
:replace
: replace exiting configuration, keep same filenameyes
: device definition updated to use the new backupno
: just backup configuration
Reply:
{
"devices": {
"DEVICE.PK": {
"pk": DEVICE.PK,
"name": DEVICE.NAME,
"status": "error"|"task",
"error": error if "status" is "error",
"task": TASK.PK
},
"(source key)": {
"pk_or_name": (source key),
"status": "error",
"error": error message
}, ...
}
}
Tasks
List
Request:
POST /api/v0/task/list
{
"complete": false(default)|true
}
Return the list of tasks that have not yet been completed
(complete
is missing or false
) or all task (complete
is
true
).
Reply:
{
"tasks": [TASK.PK, ...]
}
Status
Request:
POST /api/v0/task/status
{
"tasks": [TASK.PK, ...]
}
Reply:
{
"tasks": {
"TASK.PK": {
"pk": TASK.PK,
"status": "data" or "error",
"data": { if status is "data"
"name" : name of the task,
"feedback_pk" : PK of the feedback log (use to fill the modal window in GUI),
"log_pk" : PK of the full log,
"returncode" : see details,
"signal" : null or signal value if task process aborted on signal,
"taskmgr_id" : see details,
"date_start" : UTC date of task start,
"date_end" : UTC date of task end (on complete or error),
"error": the task manager error is any,
"progress_error": internal error if unable to retrieve task progression
}
"error": internal error if status is "error"
}, ...
}
}
Details:
returncode
: is null until task process has returned, contains the task return code or:-3: task process aborted on signal (see
signal
value)or
taskmgr_id
error value
taskmgr_id
: id of the task in the task manager, can be:-1: task manager has not yet received the task
-2: task manager was unable to run task
-4: task manager fails to accomplish action
Logs
List
Request:
POST /api/v0/log/list
{
"poc": POC.PK
}
If poc
is not specified use the current running PoC if any.
Reply:
{
"poc": POC.PK,
"logs": {
"LOG.PK": {
"pk": LOG.PK,
"name": LOG.NAME,
"create_at": log UTC creation date
}, ...
}
}
Get
Return available log content.
Request:
GET /api/v0/log/[last|LOG.PK](/OFFSET)
Reply:
LOG content
You can access log of a running task, in this case, only available part or the log is returned.
You can retrieve “missing” part of the log by specifying OFFSET
the log offset you want to start receiving content. You can get only
the new lines between calls.
FortiPoC adds in the HTTP response a X-FortiPoC-Size
header with
the size of the log (it’s not the size of the HTTP content) so you
can use this value as OFFSET
on next call to complete the log.
Receiving an empty log doesn’t mean the task is complete, it either
means the OFFSET
is invalid (past the end of the file) or no new
content is available yet.
Shell script example
You can source this shell script in your console. It installs 4 functions based on curl and jq that handles the cookies and the CSRF:
fp_login HOST USERNAME [PASSWORD]
:fp_login 192.168.1.1 admin
or:
fp_login 192.168.1.1 admin your_password
fp_alive HOST
:fp_alive 192.168.1.1
fp_get HOST PATH
:fp_get 192.168.1.1 /api/v0/status
fp_post HOST PATH [DATA]
:fp_post 192.168.1.1 /api/v0/task/list
or:
fp_post 192.168.1.1 /api/v0/task/list '{"complete": true}'
fp_post_raw HOST PATH [CURL_OPTIONS]*
:fp_post 192.168.1.1 /api/v0.1/poc/import --form fpocs=file1.fpoc
or:
fp_post 192.168.1.1 /api/v0.1/poc/import --form fpocs=file1.fpoc --form fpocs=file2.fpoc --form overwrite=1