curl --request POST \
--url https://api.ctrl-hub.com/v3/device-backups \
--header 'Content-Type: application/vnd.api+json' \
--header 'X-Session-Token: <api-key>' \
--data '
{
"data": {
"type": "device-backups",
"attributes": {
"upload_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"device": {
"app_version": "<string>",
"build_number": "<string>",
"platform": "<string>",
"os_version": "<string>",
"api_env": "<string>",
"schema_version": 123
}
},
"relationships": {
"organisation": {
"data": {
"type": "organisations",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}
}
}
}
'import requests
url = "https://api.ctrl-hub.com/v3/device-backups"
payload = { "data": {
"type": "device-backups",
"attributes": {
"upload_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"device": {
"app_version": "<string>",
"build_number": "<string>",
"platform": "<string>",
"os_version": "<string>",
"api_env": "<string>",
"schema_version": 123
}
},
"relationships": { "organisation": { "data": {
"type": "organisations",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
} } }
} }
headers = {
"X-Session-Token": "<api-key>",
"Content-Type": "application/vnd.api+json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-Session-Token': '<api-key>', 'Content-Type': 'application/vnd.api+json'},
body: JSON.stringify({
data: {
type: 'device-backups',
attributes: {
upload_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
device: {
app_version: '<string>',
build_number: '<string>',
platform: '<string>',
os_version: '<string>',
api_env: '<string>',
schema_version: 123
}
},
relationships: {
organisation: {data: {type: 'organisations', id: '3c90c3cc-0d44-4b50-8888-8dd25736052a'}}
}
}
})
};
fetch('https://api.ctrl-hub.com/v3/device-backups', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ctrl-hub.com/v3/device-backups",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'data' => [
'type' => 'device-backups',
'attributes' => [
'upload_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'device' => [
'app_version' => '<string>',
'build_number' => '<string>',
'platform' => '<string>',
'os_version' => '<string>',
'api_env' => '<string>',
'schema_version' => 123
]
],
'relationships' => [
'organisation' => [
'data' => [
'type' => 'organisations',
'id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a'
]
]
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/vnd.api+json",
"X-Session-Token: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.ctrl-hub.com/v3/device-backups"
payload := strings.NewReader("{\n \"data\": {\n \"type\": \"device-backups\",\n \"attributes\": {\n \"upload_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"device\": {\n \"app_version\": \"<string>\",\n \"build_number\": \"<string>\",\n \"platform\": \"<string>\",\n \"os_version\": \"<string>\",\n \"api_env\": \"<string>\",\n \"schema_version\": 123\n }\n },\n \"relationships\": {\n \"organisation\": {\n \"data\": {\n \"type\": \"organisations\",\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n }\n }\n }\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Session-Token", "<api-key>")
req.Header.Add("Content-Type", "application/vnd.api+json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.ctrl-hub.com/v3/device-backups")
.header("X-Session-Token", "<api-key>")
.header("Content-Type", "application/vnd.api+json")
.body("{\n \"data\": {\n \"type\": \"device-backups\",\n \"attributes\": {\n \"upload_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"device\": {\n \"app_version\": \"<string>\",\n \"build_number\": \"<string>\",\n \"platform\": \"<string>\",\n \"os_version\": \"<string>\",\n \"api_env\": \"<string>\",\n \"schema_version\": 123\n }\n },\n \"relationships\": {\n \"organisation\": {\n \"data\": {\n \"type\": \"organisations\",\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n }\n }\n }\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ctrl-hub.com/v3/device-backups")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Session-Token"] = '<api-key>'
request["Content-Type"] = 'application/vnd.api+json'
request.body = "{\n \"data\": {\n \"type\": \"device-backups\",\n \"attributes\": {\n \"upload_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"device\": {\n \"app_version\": \"<string>\",\n \"build_number\": \"<string>\",\n \"platform\": \"<string>\",\n \"os_version\": \"<string>\",\n \"api_env\": \"<string>\",\n \"schema_version\": 123\n }\n },\n \"relationships\": {\n \"organisation\": {\n \"data\": {\n \"type\": \"organisations\",\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n }\n }\n }\n }\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "device-backups",
"attributes": {
"reference": "K7QM-3X9P",
"file": {
"filename": "<string>",
"content_type": "<string>",
"size_bytes": 123,
"checksum": "<string>"
},
"device": {
"app_version": "<string>",
"build_number": "<string>",
"platform": "<string>",
"os_version": "<string>",
"api_env": "<string>",
"schema_version": 123
}
},
"meta": {
"created_at": "2023-11-07T05:31:56Z",
"expires_at": "2023-11-07T05:31:56Z"
},
"relationships": {
"organisation": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "organisations"
}
},
"author": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "users"
}
}
}
},
"jsonapi": {
"version": "1.0"
},
"included": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "organisations",
"attributes": {
"name": "Acme Construction Ltd",
"slug": "acme-construction",
"sandbox": false,
"description": "Leading construction company specializing in infrastructure projects",
"settings": {
"teams": {
"customer_representatives": [
"b234c567-8901-2345-6789-abcdef012345"
]
}
}
},
"meta": {
"v3": true,
"status": "active",
"created_at": "2023-01-15T10:30:00.000Z",
"updated_at": "2023-02-20T14:45:00.000Z",
"features": [
{
"name": "advanced_reporting",
"enabled": true,
"limit": 1000
}
]
},
"relationships": {
"users": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "users"
}
]
},
"service_accounts": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "service-accounts"
}
]
},
"groups": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "groups"
}
]
},
"teams": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "teams"
}
]
},
"nomenclature": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "nomenclature"
}
}
}
}
]
}{
"id": "98ca4a78-b66f-4234-9719-aaf832ee6669",
"status": "400",
"title": "A validation error was encountered",
"source": {
"parameter": "include"
},
"meta": {
"resource": "wrong_value"
}
}{
"id": "05fc9c8d-73b9-4697-9337-57f7a567a48f",
"status": "401",
"title": "You are not authorised to access this resource",
"detail": "In order to access this resource, you need the 'admin' role.",
"code": "AUTH.001"
}{
"id": "7b4c8f12-3e9a-4d5b-8c6f-1a2b3c4d5e6f",
"status": "404",
"title": "Resource not found",
"detail": "The requested resource could not be found or does not exist.",
"code": "NOT_FOUND.001"
}{
"id": "fe9d9a69-f0a7-4fdc-bb2c-176027f316c5",
"status": "500",
"title": "Internal Server Error",
"detail": "An unexpected error occurred on the server."
}Send a device backup
File a copy of the mobile app’s local database against an organisation, so that people there can recover work that never reached the server. Sent only when the operative asks.
The bytes go up first through POST /v3/uploads, and this names that upload. Only the person who
made the upload can file it: anyone else’s upload id is answered as not found. The file has to be a
SQLite database, which is checked against its own first bytes rather than the content type it was
uploaded with.
The organisation is the one the backup is filed against, and decides who can see it. The database itself is not scoped to it: a device caches data for every organisation its user belongs to, and the app says so before anything is sent.
The response carries a short reference for the operative to read out. Backups are kept for 30
days.
curl --request POST \
--url https://api.ctrl-hub.com/v3/device-backups \
--header 'Content-Type: application/vnd.api+json' \
--header 'X-Session-Token: <api-key>' \
--data '
{
"data": {
"type": "device-backups",
"attributes": {
"upload_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"device": {
"app_version": "<string>",
"build_number": "<string>",
"platform": "<string>",
"os_version": "<string>",
"api_env": "<string>",
"schema_version": 123
}
},
"relationships": {
"organisation": {
"data": {
"type": "organisations",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
}
}
}
}
'import requests
url = "https://api.ctrl-hub.com/v3/device-backups"
payload = { "data": {
"type": "device-backups",
"attributes": {
"upload_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"device": {
"app_version": "<string>",
"build_number": "<string>",
"platform": "<string>",
"os_version": "<string>",
"api_env": "<string>",
"schema_version": 123
}
},
"relationships": { "organisation": { "data": {
"type": "organisations",
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
} } }
} }
headers = {
"X-Session-Token": "<api-key>",
"Content-Type": "application/vnd.api+json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'X-Session-Token': '<api-key>', 'Content-Type': 'application/vnd.api+json'},
body: JSON.stringify({
data: {
type: 'device-backups',
attributes: {
upload_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
device: {
app_version: '<string>',
build_number: '<string>',
platform: '<string>',
os_version: '<string>',
api_env: '<string>',
schema_version: 123
}
},
relationships: {
organisation: {data: {type: 'organisations', id: '3c90c3cc-0d44-4b50-8888-8dd25736052a'}}
}
}
})
};
fetch('https://api.ctrl-hub.com/v3/device-backups', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.ctrl-hub.com/v3/device-backups",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'data' => [
'type' => 'device-backups',
'attributes' => [
'upload_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'device' => [
'app_version' => '<string>',
'build_number' => '<string>',
'platform' => '<string>',
'os_version' => '<string>',
'api_env' => '<string>',
'schema_version' => 123
]
],
'relationships' => [
'organisation' => [
'data' => [
'type' => 'organisations',
'id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a'
]
]
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/vnd.api+json",
"X-Session-Token: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.ctrl-hub.com/v3/device-backups"
payload := strings.NewReader("{\n \"data\": {\n \"type\": \"device-backups\",\n \"attributes\": {\n \"upload_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"device\": {\n \"app_version\": \"<string>\",\n \"build_number\": \"<string>\",\n \"platform\": \"<string>\",\n \"os_version\": \"<string>\",\n \"api_env\": \"<string>\",\n \"schema_version\": 123\n }\n },\n \"relationships\": {\n \"organisation\": {\n \"data\": {\n \"type\": \"organisations\",\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n }\n }\n }\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("X-Session-Token", "<api-key>")
req.Header.Add("Content-Type", "application/vnd.api+json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.ctrl-hub.com/v3/device-backups")
.header("X-Session-Token", "<api-key>")
.header("Content-Type", "application/vnd.api+json")
.body("{\n \"data\": {\n \"type\": \"device-backups\",\n \"attributes\": {\n \"upload_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"device\": {\n \"app_version\": \"<string>\",\n \"build_number\": \"<string>\",\n \"platform\": \"<string>\",\n \"os_version\": \"<string>\",\n \"api_env\": \"<string>\",\n \"schema_version\": 123\n }\n },\n \"relationships\": {\n \"organisation\": {\n \"data\": {\n \"type\": \"organisations\",\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n }\n }\n }\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.ctrl-hub.com/v3/device-backups")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["X-Session-Token"] = '<api-key>'
request["Content-Type"] = 'application/vnd.api+json'
request.body = "{\n \"data\": {\n \"type\": \"device-backups\",\n \"attributes\": {\n \"upload_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"device\": {\n \"app_version\": \"<string>\",\n \"build_number\": \"<string>\",\n \"platform\": \"<string>\",\n \"os_version\": \"<string>\",\n \"api_env\": \"<string>\",\n \"schema_version\": 123\n }\n },\n \"relationships\": {\n \"organisation\": {\n \"data\": {\n \"type\": \"organisations\",\n \"id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n }\n }\n }\n }\n}"
response = http.request(request)
puts response.read_body{
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "device-backups",
"attributes": {
"reference": "K7QM-3X9P",
"file": {
"filename": "<string>",
"content_type": "<string>",
"size_bytes": 123,
"checksum": "<string>"
},
"device": {
"app_version": "<string>",
"build_number": "<string>",
"platform": "<string>",
"os_version": "<string>",
"api_env": "<string>",
"schema_version": 123
}
},
"meta": {
"created_at": "2023-11-07T05:31:56Z",
"expires_at": "2023-11-07T05:31:56Z"
},
"relationships": {
"organisation": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "organisations"
}
},
"author": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "users"
}
}
}
},
"jsonapi": {
"version": "1.0"
},
"included": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "organisations",
"attributes": {
"name": "Acme Construction Ltd",
"slug": "acme-construction",
"sandbox": false,
"description": "Leading construction company specializing in infrastructure projects",
"settings": {
"teams": {
"customer_representatives": [
"b234c567-8901-2345-6789-abcdef012345"
]
}
}
},
"meta": {
"v3": true,
"status": "active",
"created_at": "2023-01-15T10:30:00.000Z",
"updated_at": "2023-02-20T14:45:00.000Z",
"features": [
{
"name": "advanced_reporting",
"enabled": true,
"limit": 1000
}
]
},
"relationships": {
"users": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "users"
}
]
},
"service_accounts": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "service-accounts"
}
]
},
"groups": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "groups"
}
]
},
"teams": {
"data": [
{
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "teams"
}
]
},
"nomenclature": {
"data": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"type": "nomenclature"
}
}
}
}
]
}{
"id": "98ca4a78-b66f-4234-9719-aaf832ee6669",
"status": "400",
"title": "A validation error was encountered",
"source": {
"parameter": "include"
},
"meta": {
"resource": "wrong_value"
}
}{
"id": "05fc9c8d-73b9-4697-9337-57f7a567a48f",
"status": "401",
"title": "You are not authorised to access this resource",
"detail": "In order to access this resource, you need the 'admin' role.",
"code": "AUTH.001"
}{
"id": "7b4c8f12-3e9a-4d5b-8c6f-1a2b3c4d5e6f",
"status": "404",
"title": "Resource not found",
"detail": "The requested resource could not be found or does not exist.",
"code": "NOT_FOUND.001"
}{
"id": "fe9d9a69-f0a7-4fdc-bb2c-176027f316c5",
"status": "500",
"title": "Internal Server Error",
"detail": "An unexpected error occurred on the server."
}Authorizations
Session token for authentication.
Body
The device backup to file.
The sender is taken from the authenticated principal rather than accepted here, and the reference, file details and expiry are decided by the API.
Show child attributes
Show child attributes
Response
An individual device backup.
JSON API response object
A copy of a mobile device's local database, sent by the operative so that people in their organisation can recover work that never reached the server. Kept for 30 days.
Show child attributes
Show child attributes
Show child attributes
Show child attributes
Related resources that can be included when a device backup is returned.
An organisation
- Option 1
- Option 2
Show child attributes
Show child attributes