API documentation
How to send each photo and its metadata from Arguox Capture to an HTTPS endpoint you control.
Overview
When you tap Upload on a photo, Arguox sends one POST request to the HTTPS URL you set. The body is application/json and holds the action type, every metadata field you have turned on, and the image as Base64.
There's no Arguox API key, account or server in between. You provide the endpoint and the app posts to it directly.
Setup
Open API Config
Tap the gear on the camera screen to open Settings, then scroll down to API Config.
Choose a button mode
Pick Single for one CAPTURE button, or Dual for separate IN and OUT buttons. In Dual mode each button gets its own endpoint.
Enter your endpoint URL
Paste your HTTPS URL. In Dual mode, fill in one URL for IN and one for OUT.
Take a photo and tap Upload
After each capture the preview screen shows an Upload button. Tap it to post the photo. If you are offline or the request fails, the upload stays in the queue and retries later.
Choose your metadata fields (optional)
Under Metadata in Settings, turn Location, Date & Time and Device Info on or off. Fields you turn off are left out of the payload.
Endpoint
POST {your-configured-url}
Content-Type: application/json Your endpoint needs to accept a POST request with a JSON body. Arguox doesn't add any custom headers besides Content-Type.
http:// URLs, and your endpoint needs a valid TLS certificate. Payload fields
The payload only includes fields you have turned on in Settings. Fields that are dropped when you're offline are marked below.
Core (always present)
| Field | Description |
|---|---|
| action | "CAPTURE", "IN", or "OUT", depending on the button mode. |
| imageBase64 | The photo as a Base64-encoded JPEG. Decode it and save it as a .jpg on your server. |
| imageFileName | Suggested filename, e.g. IMG_20260514_103007.jpg. |
Time Toggle in Settings
| Field | Description |
|---|---|
| timestamp | ISO 8601 UTC timestamp from the device clock. |
| date | Local date, e.g. 2026-05-14. |
| time | Local time, in 12-hour or 24-hour format depending on your setting. |
| timezone | IANA timezone name, e.g. Etc/UTC. |
| unixTimestamp | Unix epoch seconds. |
Location Toggle in Settings Omitted offline
| Field | Description |
|---|---|
| latitude | GPS latitude. You can set the precision in Settings. |
| longitude | GPS longitude. |
| street | Street name, looked up from the coordinates with Nominatim. |
| barangay | Barangay or sub-district, if available. |
| city | City or municipality. |
| province | Province, state, or region. |
| country | Full country name. |
| countryCode | ISO 3166-1 alpha-2 (two-letter country code). |
| postalCode | Postal or ZIP code, if available. |
Device Info Toggle in Settings
| Field | Description |
|---|---|
| deviceName | The device name as people would read it. |
| deviceBrand | Manufacturer brand, e.g. Samsung. |
| deviceModel | Model identifier, e.g. SM-S921B. |
| osVersion | OS name and version, e.g. Android 15. |
Camera and manual details
| Field | Description |
|---|---|
| cameraType | "rear" or "front". |
| flashStatus | "on" or "off". |
| manualDetails | The free-text lines you set in Settings. An empty array if there are none. |
Environmental Toggle under Environmental in Settings All off by default
Each field has its own toggle. Anything you leave off is missing from both the overlay and the payload. Altitude comes from the GPS fix and works offline. Elevation, weather and humidity come from the Open-Meteo API, as does pressure on Android, so they're left out when there's no connection.
| Field | Description |
|---|---|
| altitude | Metres above sea level from the GPS fix, e.g. "12.3 m". |
| elevation | Ground elevation at your coordinates from Open-Meteo, e.g. "45 m". Android only; omitted offline. |
| pressure | Air pressure, e.g. "1013.2 hPa". On iOS it's read from the phone's barometer, and the toggle is greyed out on phones without one. On Android it comes from Open-Meteo and is left out offline. |
| weather | Current weather from Open-Meteo, e.g. "Partly cloudy". Omitted offline. |
| humidity | Relative humidity from the same Open-Meteo request, e.g. "72%". Omitted offline. |
Full example
A payload from the IN button in Dual mode, with Date & Time, Location and Device Info turned on:
{
"action": "IN",
"timestamp": "2026-05-14T10:30:00Z",
"date": "2026-05-14",
"time": "10:30:00 AM",
"timezone": "Etc/UTC",
"unixTimestamp": 1747218600,
"latitude": "37.7749",
"longitude": "-122.4194",
"street": "Main Street",
"barangay": "Sample District",
"city": "Sample City",
"province": "Sample Region",
"country": "Sample Country",
"countryCode": "--",
"postalCode": "00000",
"deviceName": "Samsung Galaxy S24",
"deviceBrand": "Samsung",
"deviceModel": "SM-S921B",
"osVersion": "Android 15",
"cameraType": "rear",
"flashStatus": "off",
"manualDetails": ["Job #1042", "Supervisor: Juan Lobby"],
"imageBase64": "BASE64_ENCODED_JPEG_DATA",
"imageFileName": "IMG_20260514_103007.jpg"
} Minimal payload (Single mode, offline)
{
"action": "CAPTURE",
"timestamp": "2026-05-14T10:30:00Z",
"date": "2026-05-14",
"time": "10:30:00 AM",
"timezone": "Etc/UTC",
"unixTimestamp": 1747218600,
"manualDetails": [],
"imageBase64": "BASE64_ENCODED_JPEG_DATA",
"imageFileName": "IMG_20260514_103007.jpg"
} Offline, the location fields are left out. The time, device info and manual details are still sent if you have them turned on.
Offline and the upload queue
Taking photos and saving them to the gallery don't depend on a connection. The free version does need internet, because it has to load ads. With Remove Ads the app works fully offline.
When the phone is offline at capture time:
- GPS and the street address are left off the overlay and the payload.
- The time comes from the phone's clock, same as when online.
- If you tap Upload, the request waits in a queue on the phone.
When the connection comes back, queued uploads go out on their own. The QUEUE pill on the camera shows how many are pending or failed.
imageFileName, or on action + unixTimestamp + deviceModel together. Timestamps
Arguox stamps each capture with the phone's own clock. It doesn't check the time against a server, so the shutter never waits on the network. If the phone's clock is wrong, the timestamp will be wrong too.
That time goes straight into the timestamp field in the payload.
Error handling
Any HTTP 2xx response counts as a success. Any other status, 3xx redirects included, counts as a failure and goes back in the queue for a retry.
How retries work
- Queued uploads retry on their own when the connection comes back.
- You can retry a single item from the queue.
- "Retry Failed" retries every failed item at once.
- Each item keeps its retry count and last retry time on the phone.
Recommended server response
HTTP/1.1 200 OK
Content-Type: application/json
{ "status": "ok" } Google Sheets & Drive
You can use a free Google Apps Script web app as your endpoint. Each upload saves the photo to your Google Drive and adds a row to a Google Sheet. You don't need a server or hosting, and it costs nothing.
Open Google Apps Script
Go to script.google.com and click New project. Name it whatever you like, for example "Arguox Receiver".
Paste the script below
Delete the default code and paste in the script below. It creates the Drive folder and the Sheet the first time an upload comes in.
Deploy it as a web app
Click Deploy, then New deployment, and choose Web app as the type. Set Execute as to Me and Who has access to Anyone. Click Deploy and copy the URL it gives you.
Add the URL to Arguox
In Arguox Capture, open Settings and go to API Config. Paste the deployment URL as your endpoint. Your next upload will show up in Drive and Sheets.
Apps Script code
function doPost(e) {
try {
var data = JSON.parse(e.postData.contents);
// 1. Save photo to Google Drive
var driveLink = "";
if (data.imageBase64 && data.imageFileName) {
var folder = getOrCreateFolder("Arguox Captures");
var bytes = Utilities.base64Decode(data.imageBase64);
var blob = Utilities.newBlob(bytes, "image/jpeg", data.imageFileName);
var file = folder.createFile(blob);
file.setSharing(DriveApp.Access.ANYONE_WITH_LINK, DriveApp.Permission.VIEW);
driveLink = file.getUrl();
}
// 2. Log row to Google Sheets
var sheet = getOrCreateSheet("Arguox Logs");
sheet.appendRow([
data.date || "",
data.time || "",
data.action || "",
data.latitude || "",
data.longitude || "",
[data.street, data.city, data.country].filter(Boolean).join(", "),
[data.deviceBrand, data.deviceModel].filter(Boolean).join(" "),
data.imageFileName || "",
driveLink,
new Date().toISOString(),
]);
return ContentService
.createTextOutput(JSON.stringify({ status: "ok", driveLink: driveLink }))
.setMimeType(ContentService.MimeType.JSON);
} catch (err) {
return ContentService
.createTextOutput(JSON.stringify({ status: "error", message: err.message }))
.setMimeType(ContentService.MimeType.JSON);
}
}
// ── helpers ──────────────────────────────────────────────────────────────────
function getOrCreateFolder(name) {
var folders = DriveApp.getFoldersByName(name);
return folders.hasNext() ? folders.next() : DriveApp.createFolder(name);
}
function getOrCreateSheet(name) {
var files = DriveApp.getFilesByName(name);
var ss;
if (files.hasNext()) {
ss = SpreadsheetApp.open(files.next());
} else {
ss = SpreadsheetApp.create(name);
var sheet = ss.getActiveSheet();
sheet.setName("Logs");
sheet.appendRow([
"Date", "Time", "Action",
"Latitude", "Longitude", "Address",
"Device", "Filename", "Drive Link", "Received At"
]);
sheet.setFrozenRows(1);
}
return ss.getActiveSheet();
} What you get
Google Drive
Arguox Captures /
IMG_20260519_093012.jpg
IMG_20260519_093847.jpg
IMG_20260519_102201.jpg
One file per upload, all in one folder.
Google Sheets: Arguox Logs
| Date | Time | Action | Address | Filename |
| 2026-05-19 | 09:30 | CAPTURE | Site St, Location City | IMG_093012.jpg |
| 2026-05-19 | 09:38 | IN | Site St, Location City | IMG_093847.jpg |
| 2026-05-19 | 10:22 | OUT | Site St, Location City | IMG_102201.jpg |
One row per upload, with a link to the photo in Drive.