Download OpenAPI specification:
HTTP/HTTPS web server (?command=) API for Campbell Scientific data loggers.
Campbell Scientific data loggers run an on-board HTTP web server. The supported models are the CR1000X(e), CR6, Granite 9, Granite 10, Granite 6, CR310, CR300, and CR350. Every model serves HTTP/1.1; HTTP/2 is available on the CR6, CR1000X(e), and GRANITE models only (see Transport). External clients — browsers, apps, and integrations — send commands to this server.
Machine-readable files: the OpenAPI specification for this page is openapi.yaml, and a short summary for AI tools is llms.txt.
This documentation describes the API as implemented in the current released operating system for each model, or newer. Version numbers are from campbellsci.com.
| Model | Current OS version | Released |
|---|---|---|
| CR1000X, CR1000Xe | 8.5.1 or newer | 2026-04-17 |
| CR6 | 14.5.1 or newer | 2026-04-17 |
| Granite 9 | 3.2.0 or newer | 2025-12-23 |
| Granite 10 | 3.2.0 or newer | 2025-12-23 |
| Granite 6 | 3.2.1 or newer | 2025-04-07 |
| CR300, CR310 | 11.03.00 or newer | 2025-12-23 |
| CR350 | 1.9.0 or newer | 2025-09-18 |
Note: The API and documentation may change without notice. Validate behavior against the specific firmware version in use.
Note: This documentation is derived from, and audited against, the data logger firmware implementation.
The data logger exposes an HTTP API for data access and control. This section covers the request syntax common to every command and how access is secured.
Every ?command= request is a GET to the root path /. File upload and delete are the
exception: they use PUT and DELETE on /{drive}/{path} (see Files). Send HTTP
Basic credentials for a data logger user account (see Enabling security). For example,
to read the two most-recent records of a table as JSON:
curl --user user:pass \
'http://<datalogger>/?command=DataQuery&uri=dl:Public&mode=most-recent&p1=2&format=json'
The JSON response uses CSIJSON and has two parts. The head block describes the table:
its environment and the schema for each field (name, type, units, and process). The
data rows follow. Each row has a timestamp (time), a record number (no), and the field
values (vals):
{ "head": { "environment": { "…": "…" }, "fields": [ "…" ] },
"data": [ { "time": "2026-08-06T13:44:00", "no": 12, "vals": [ "…" ] } ] }
Check
outcome, not just the HTTP status. A command that fails still returns200. Theoutcomevalue in the body tells you whether it worked, and what counts as success depends on the command:
SetValueEx,ClockCheck, andClockSet:outcome=1means success.FileControl:outcome=0means success. Any other value is an error code.
Next: use DataQuery to retrieve records (five modes, all formats), and
BrowseSymbols to list the tables and fields you can query. The sections below cover
request syntax, response formats, security, and error handling.
Commands generally follow the form:
<URL>?command=<CommandName>&uri=<DataSource>&<arguments>
command= first, right after the ?. The server finds every other argument by
the & in front of it, so it ignores any other argument that comes right after the ?.&; optional arguments use a default. Argument names are not
case-sensitive.uri / the dl: scheme — the data source. When querying the data logger directly it
is the top-level source, so uri=dl: (or omitting uri) is assumed. A table or field is
addressed as dl:<table> or dl:<table>.<field>.http://admin:12345@192.168.24.62/?command=ClockCheck. Read Security guidance before
you use this form.Note: The server accepts
:and/in a value either as is or percent-encoded, souri=dl:Publicanduri=dl%3APublicare the same request. For what you must encode, see URL escape codes.
Note: Operations below are shown with placeholder paths (for example,
/DataQuery) because OpenAPI cannot put a query string in a path. The real request is always against/with?command=<CommandName>. The first line of each operation's description gives the real URL, and so does itsx-request-urlvalue in openapi.yaml.
Input — the time argument of ClockSet and the timestamps in DataQuery p1 and p2:
T between the date and the time: YYYY-MM-DDTHH:MM:SS, with optional
fractional seconds. For example, 2026-07-30T16:15:31 or 2026-07-30T16:15:31.25.2026-07-30 means midnight.T. The server ignores the time of day and uses midnight.Z or -07:00. The server ignores it, and it can
corrupt or drop the fractional seconds. Every time is the data logger's local clock time.Output — times in responses are the data logger's local time, with no timezone:
json and xml use a T: 2026-07-30T16:15:31.25html and toa5 use a space: 2026-07-30 16:15:31.25GET, POST, PUT, and DELETE. POST carries HTML form submissions;
PUT/DELETE operate on /{drive}/{path} files.datalogger.local), IPv4 address, or IPv6 address. In a URL, wrap an IPv6
literal in square brackets — for example, http://[2001:db8::10]/?command=ClockCheck.
A link-local address (one that starts with fe80:) also needs a zone ID. The zone ID names
the network interface on your computer that reaches the data logger. Write it after a
%25 (an encoded %), for example http://[fe80::1%25eth0]/. USB (RNDIS) connections are
treated as trusted for access-control purposes (see Enabling security).Transfer-Encoding: chunked or a Content-Length header, and may be
gzip-compressed (Content-Encoding: gzip). File downloads carry
Cache-Control: no-cache; ?command= responses don't carry a Cache-Control header.
The server does not serve range/partial requests (Accept-Ranges: none) — always
request whole files.PUT) accept either a Content-Length body or
Transfer-Encoding: chunked. A Transfer-Encoding header that is not chunked is
rejected with 500; an over-size Content-Length returns 413.The server sends no CORS headers. Responses never include Access-Control-Allow-Origin
or any other Access-Control-* header. Over HTTP/1.1, the server also drops OPTIONS
(preflight) requests without a reply.
So a web page from another origin can't read API responses. Its browser can still send a
simple GET, though (see Security guidance). To call the API from a web app, serve the
page from the data logger itself, or send the requests through your own server.
DataQuery: there's no fixed maximum. A query returns at most the records
the table holds. A larger p1 count (most-recent) or p3 count (since-record) is cut
to that number.mode=since-record. Set p3 to your page size and p1 to
one more than the last record number (no) you received, because since-record includes
record p1. Repeat until a page comes back empty. Record numbers can wrap (see Data).PUT with a Content-Length over 2,147,483,600 bytes returns 413.
In practice, the free space on the drive sets the limit (507).TLSConnections setting in Device
Configuration Utility.Most data commands accept a format argument that selects the response format:
format |
Content-Type | Notes |
|---|---|---|
html (default) |
text/html; charset=UTF-8 |
Human-readable |
json |
application/json; charset=UTF-8 |
CSI JSON |
xml |
text/xml; charset=UTF-8 |
CSI XML |
toa5 |
text/csv; charset=UTF-8 |
ASCII table (TOA5) |
tob1 |
binary/octet_stream |
Binary table (TOB1) |
Unrecognized values fall back to html.
toa5andtob1are record-table formats supported only by data-retrieval commands (DataQuery). Every other command supportshtml,json, andxmlonly. Each operation below lists the formats it supports.
Note:
tob1responses use the nonstandard typebinary/octet_stream, notapplication/octet-stream. Don't match theContent-Typestrictly.
Two mechanisms can be set on the data logger:
SetSecurity /
Security() to 1–65534, for up to three levels. You can't supply this code in an
HTTP request. The web server doesn't accept it, so it has no way to grant access:404. To use the API, create user accounts.Access levels. Every request is authorized at one of four levels (the numeric code is
what CheckAuthorization reports):
| Level | code |
What it allows |
|---|---|---|
| All | 1 |
Everything Read/Write allows, plus viewing, retrieving, stopping, deleting, and installing programs, FileControl, and file upload and delete (PUT and DELETE on /{drive}/{path}). |
| Read/Write | 2 |
Everything Read-Only allows, plus changing the clock (ClockSet) and writable variables (SetValueEx). Programs still cannot be viewed, retrieved, stopped, or deleted. Files cannot be uploaded or deleted. |
| Read-Only | 3 or 4 |
Data collection is unrestricted — query tables (DataQuery), browse symbols, read the clock, and list/download data files. The clock and writable variables cannot be changed, and programs cannot be viewed, retrieved, stopped, or deleted. |
| None | 0 |
Access denied. This level is never served — the data logger returns 401 to force a sign-in. |
Code 4. CheckAuthorization reports 4 instead of 3 when the data logger has no user
accounts. The access is the same as 3: Read-Only. The difference is what a client can do
about it. With 3, other credentials might get more access. With 4, no credentials can,
because every request gets Read-Only until an administrator creates user accounts. So treat
4 as Read-Only, and don't ask the user to sign in again.
Anonymous and failed sign-ins. A request with no Authorization header is treated as the
built-in anonymous user. When no user accounts exist, anonymous and failed sign-ins both
get Read-Only access. If a request resolves to None, the data logger returns 401 to
prompt for credentials instead of serving it.
USB (RNDIS) connections get All access without a sign-in. To make USB connections
sign in like any other, turn on the USBNotTrusted setting in Device Configuration
Utility.
Use HTTPS. Over HTTP (port 80), Basic auth sends the user name and password in cleartext. Base64 encoding isn't encryption.
Keep credentials out of the URL. A URL such as http://user:pass@host/ can end up in
logs, browser history, and bookmarks. Send an Authorization header instead.
State-changing commands are GETs. ClockSet, SetValueEx, and FileControl change
the data logger, but they're plain GET requests. Anything that loads a URL can trigger
them: link prefetchers, link scanners in email and chat, and cross-site request forgery
(CSRF) from another web page. A browser that has saved your credentials sends them along.
If you build a browser-based client:
On failure the server returns a standard HTTP status with a short HTML or header-only body:
| Status | Meaning |
|---|---|
400 Bad Request |
Malformed request, or a missing/invalid required argument. |
401 Authorization Required |
Authentication required or access level insufficient. The response carries WWW-Authenticate: Basic realm="<station name>". |
404 Not Found |
The requested file does not exist, no file matches a NewestFile expression, a DELETE failed, or the command is unknown or is missing an argument it needs. |
405 Method Not Allowed |
Operation not permitted on the target (for example, uploading .CSIPASSWD). |
413 Payload Too Large |
An uploaded file is too large to store. |
500 Internal Server Error |
An upload specified a Transfer-Encoding other than chunked (the only transfer encoding accepted for PUT). |
507 Insufficient Storage |
No room for the upload, or the target file is locked / cannot be opened. |
Successful command responses (200) also carry a per-command outcome code in the body.
See Check outcome, not just the HTTP status under Your first request, and each
command for its codes.
Request URL: GET /?command=CheckAuthorization.
Returns the access level granted to the current credentials without taking any other
action. The level is 1 (All), 2 (Read/Write), 3 (Read-Only), or 4 (Read-Only
because the data logger has no user accounts). See Enabling security. A request that
resolves to None gets 401 instead.
| command required | string Value: "CheckAuthorization" |
| anonymous | string Enum: "true" "false" If |
| direct | string Enum: "true" "false" If |
| format | string Default: "html" Enum: "html" "json" "xml" Response representation. Defaults to |
curl --user user:pass 'http://<datalogger>/?command=CheckAuthorization&format=json'
{- "authorization": 1
}Reading measurement data and browsing the symbol/table namespace.
Data tables. Measurements are stored in data tables. Each table is a fixed-size ring buffer of records. When a table fills up, the oldest records are overwritten. So you can retrieve only a limited, moving window of history. That window is held in CPU RAM. A memory card, onboard flash, or SSD can extend it further back. Each record has:
no) — it increases by one per record and is not reset in normal
operation. Only a program recompile or table re-initialization resets it. It is a 32-bit
unsigned counter, so treat it as an opaque serial number that can wrap.time) — the record's time on the logger's local clock, at sub-second
resolution. No timezone offset is included.Each field descriptor reports a type (for example xsd:float, xsd:unsignedInt, or
xsd:string with string_len) and a process. The process is the CRBasic operation that
produced the field: Smp (sample), Avg (average), Max, Min, and so on. Invalid or
not-a-number floating-point values are written as the string "NAN".
Request URL: GET /?command=DataQuery.
Retrieves records from a data table. The mode argument selects the retrieval strategy;
p1/p2/p3 supply its parameters.
mode |
Returns | p1 |
p2 |
p3 |
|---|---|---|---|---|
most-recent |
The newest p1 records. |
number of records (default 1) | — | — |
since-record |
Records numbered at or after p1, up to p3 records. Record p1 itself is included. |
starting record number | — | max records |
since-time |
Records with a timestamp at or after p1. |
start timestamp | — | — |
date-range |
Records with a timestamp in [p1, p2): at or after p1, and before p2. |
start timestamp | end timestamp | — |
backfill |
Records from the last p1 seconds, counted back from the timestamp of the table's newest record (not from the data logger's clock). |
seconds to back-fill (default 1) | — | — |
Timestamps in p1 and p2 use ISO 8601 with a T, in the data logger's local time,
with no timezone suffix. See Time formats in Getting started.
Skipping the header with headsig. Every json response has a signature in its
head block. The signature identifies the table's structure. Send that value back as
headsig on your next request. If the table hasn't changed, head holds only
transaction and signature, with no environment or fields:
{ "head": { "transaction": 0, "signature": 35503 },
"data": [ { "time": "2026-08-06T13:45:00", "no": 13, "vals": [ "…" ] } ] }
If the signature differs (for example, after the program changes), you get the full
header again. headsig affects the json format only.
Large queries. There's no fixed record limit. To page through a large table, see Limits in Getting started.
TOA5 and TOB1. For the file layouts, see the
TOA5
and TOB1
format specifications. A tob1 response has the nonstandard Content-Type
binary/octet_stream, so don't match application/octet-stream strictly.
Retrieval clamps, it does not reject. Because a table is a ring buffer with a limited history, a start (
since-record/since-time/date-range) that is older than the oldest record still available is silently advanced to the oldest available record, an over-largep3count is reduced, and a start in the future returns 0 records. The signal that a substitution happened is that the first record number returned differs from the one you asked for — so drive incremental collection ("everything since the last record I saw") from the returned record numbers, not from an assumed starting point.
| command required | string Value: "DataQuery" |
| uri required | string Example: uri=dl:onemin.ptemp Table (and optional field) as |
| mode required | string Enum: "most-recent" "since-record" "since-time" "date-range" "backfill" |
| p1 | string First mode argument (see table). |
| p2 | string Second mode argument — end timestamp for |
| p3 | string Third mode argument — max record count for |
| transaction | integer <int64> Client-supplied transaction id echoed back in the response. |
| headsig | integer The |
| format | string Default: "html" Enum: "html" "json" "xml" "toa5" "tob1" Response representation. Defaults to |
curl --user user:pass 'http://<datalogger>/?command=DataQuery&uri=dl:TestData&mode=most-recent&p1=2&format=json'
{- "head": {
- "transaction": 0,
- "signature": 35503,
- "environment": {
- "station_name": "Mt. Logan",
- "table_name": "TestData",
- "model": "CR1000X",
- "serial_no": "70019",
- "os_version": "CR1000X.8.5.1",
- "prog_name": "CPU:test_api.CR1X"
}, - "fields": [
- {
- "name": "PTemp",
- "type": "xsd:float",
- "units": "degC",
- "process": "Smp",
- "settable": false
}, - {
- "name": "PTemp_Avg",
- "type": "xsd:float",
- "units": "degC",
- "process": "Avg",
- "settable": false
}, - {
- "name": "PTemp_Max",
- "type": "xsd:float",
- "units": "degC",
- "process": "Max",
- "settable": false
}, - {
- "name": "PTemp_Min",
- "type": "xsd:float",
- "units": "degC",
- "process": "Min",
- "settable": false
}, - {
- "name": "BattV",
- "type": "xsd:float",
- "units": "Volts",
- "process": "Smp",
- "settable": false
}, - {
- "name": "Counter",
- "type": "xsd:unsignedInt",
- "process": "Smp",
- "settable": false
}, - {
- "name": "TestString",
- "type": "xsd:string",
- "process": "Smp",
- "settable": false,
- "string_len": 36
}
]
}, - "data": [
- {
- "time": "2026-08-06T13:43:00",
- "no": 11,
- "vals": [
- 24.64038,
- 24.6424,
- 24.6442,
- 24.64038,
- 11.74362,
- 137,
- "hello"
]
}, - {
- "time": "2026-08-06T13:44:00",
- "no": 12,
- "vals": [
- 24.63583,
- 24.63784,
- 24.64008,
- 24.63583,
- 11.70022,
- 149,
- "hello"
]
}
]
}Request URL: GET /?command=BrowseSymbols.
Lists the data logger's symbol namespace. With no uri, returns the set of tables. With
uri=<table>, returns that table's fields. Each symbol reports name, uri, type,
is_enabled, is_read_only, and can_expand.
type is a numeric class code. These three codes are the complete set:
type |
Meaning | can_expand |
|---|---|---|
6 |
A table. | true |
7 |
An array field. Browse uri=dl:<table>.<field> to list its elements. |
true |
8 |
A scalar field, or one element of an array field (for example, Temp(2)). |
false |
| command required | string Value: "BrowseSymbols" |
| uri | string Table (and optional field) whose children to enumerate. Omit to list tables. |
| format | string Default: "html" Enum: "html" "json" "xml" Response representation. Defaults to |
curl --user user:pass 'http://<datalogger>/?command=BrowseSymbols&uri=dl:&format=json'
{- "symbols": [
- {
- "name": "Status",
- "uri": "dl:Status",
- "type": 6,
- "is_enabled": true,
- "is_read_only": false,
- "can_expand": true
}, - {
- "name": "TestData",
- "uri": "dl:TestData",
- "type": 6,
- "is_enabled": true,
- "is_read_only": false,
- "can_expand": true
}, - {
- "name": "FastData",
- "uri": "dl:FastData",
- "type": 6,
- "is_enabled": true,
- "is_read_only": false,
- "can_expand": true
}, - {
- "name": "DataTableInfo",
- "uri": "dl:DataTableInfo",
- "type": 6,
- "is_enabled": true,
- "is_read_only": false,
- "can_expand": true
}, - {
- "name": "Public",
- "uri": "dl:Public",
- "type": 6,
- "is_enabled": true,
- "is_read_only": false,
- "can_expand": true
}
]
}Request URL: GET /?command=SetValueEx.
Sets a field to a value. The target is uri=<table>.<field> and the new value is
value=. Requires Read/Write (2) or All (1) access.
When you get 401 and when you get an outcome. The data logger checks your access
level first:
3 or 4) or None: you get 401, and nothing is set.200. The outcome in the body tells you whether the
value was set.outcome=1 means success. Any other value is a failure:
outcome |
description |
Meaning |
|---|---|---|
1 |
success |
The value was set. |
5 |
Read only |
The field can't be written (for example, a data-table field). |
6 |
Invalid table name |
No table has that name. |
7 |
Invalid field name |
The table has no field with that name. |
12 |
Blocked by security |
The field is in a protected system table (Status, ConstTable, or DataTableInfo), and the security check for writing to it failed. |
0 |
Unknown failure |
Any other failure. |
"Blocked by security" comes with outcome=12, not outcome=0.
| command required | string Value: "SetValueEx" |
| uri required | string Target field as |
| value required | string New value to write. |
| format | string Default: "html" Enum: "html" "json" "xml" Response representation. Defaults to |
curl --user user:pass 'http://<datalogger>/?command=SetValueEx&uri=dl:Public.TestFloat&value=12.34&format=json'
{- "outcome": 1,
- "description": "success"
}Request URL: GET /?command=ClockCheck.
Returns the current data logger time as an outcome, time, and description.
outcome=1 means success. time is the data logger's local time, to hundredths of a
second. It uses a T in json and xml and a space in html.
| command required | string Value: "ClockCheck" |
| format | string Default: "html" Enum: "html" "json" "xml" Response representation. Defaults to |
curl --user user:pass 'http://<datalogger>/?command=ClockCheck&format=json'
{- "outcome": 1,
- "time": "2026-07-31T14:18:17.70",
- "description": "The clock was checked"
}Request URL: GET /?command=ClockSet.
Sets the data logger time to time=. Requires Read/Write (2) or All (1)
access. Read-Only callers get 401, and the clock doesn't change.
outcome=1 means success. The data logger returns outcome=1 for every ClockSet it
accepts. It doesn't check that time is a valid time, so check the result with
ClockCheck.
The time in the response is the clock before the change. The data logger reads its
clock, sends the response, and then sets the new time. In the samples below, the request
sets 2026-07-30T16:15:31, and the response shows the time just before that change.
Send time in ISO 8601 with a T, in the data logger's local time. See Time formats
in Getting started.
| command required | string Value: "ClockSet" |
| time required | string New local time in ISO 8601 with a |
| format | string Default: "html" Enum: "html" "json" "xml" Response representation. Defaults to |
curl --user user:pass 'http://<datalogger>/?command=ClockSet&time=2026-07-30T16:15:31&format=json'
{- "outcome": 1,
- "time": "2026-07-30T16:14:58.47",
- "description": "The clock was set"
}File operations — the ?command= file commands and the method-level PUT/GET/DELETE
on /{drive}/{path}.
Drives. Files live on named drives, addressed as <drive>:<path> (for example
CPU:program.cr1x). In a URL the first / becomes the :, so that path is
/CPU/program.cr1x. The set of drives varies by model. Query the root
(GET /?command=ListFiles) to see what a given data logger has. Upload (PUT), download
(GET), and delete (DELETE) accept the same drives: every drive the data logger has.
| Drive | Storage | Notes |
|---|---|---|
CPU: |
Internal flash | Program and configuration; persistent. |
USR: |
RAM disk | Volatile — contents are lost on power-cycle/reset. |
CRD: |
Removable memory card | Present when a card is installed. |
USB: |
External USB flash | External module (CR6/CR1000X) or USB drive (Granite 9 and Granite 10). |
SSD: |
SATA SSD | Granite 9 and Granite 10 only. |
All drives are FAT: file and drive names are case-insensitive (case-preserving), and long
file names are supported. cpu:program.CR1X and CPU:PROGRAM.cr1x address the same file.
Request URL: GET /?command=FileControl.
Performs an action on a file or drive (compile and run a program, stop a program,
delete/hide/move/copy a file, format a drive, or install an OS). Action is a numeric
code; File (and File2 for move/copy) name the target(s).
Requires all-access (level 1); any lower access level is rejected with 401.
Action codes (BMP5 file-control set). Each action uses File, or both File and
File2, as noted:
| Action | What it does |
|---|---|
| 1 | Compile and run the program named by File, and mark it as the program to run on power-up. |
| 2 | Mark the program named by File as the program to run on power-up. Does not start it. |
| 3 | Mark the file named by File as hidden. |
| 4 | Delete the file named by File. |
| 5 | Format the drive named by File (for example, CRD:). Erases everything on the drive. |
| 6 | Compile and run the program named by File without deleting existing data tables. |
| 7 | Stop the currently running program. File is not used. |
| 8 | Stop the currently running program and delete its associated data tables. File is not used. |
| 9 | Install the operating system (*.obj) named by File. The .obj file must already have been uploaded to the CPU: drive with a PUT request (see Upload (write) a file). |
| 10 | Compile and run the program named by File, without changing which program is marked to run on power-up. |
| 11 | Pause the currently running program. File is not used. |
| 12 | Resume the currently paused program. File is not used. |
| 13 | Stop the currently running program, delete its associated data tables, then compile and run the program named by File, and mark that same file to run on power-up. |
| 14 | Stop the currently running program, delete its associated data tables, then compile and run the program named by File, without changing which program is marked to run on power-up. |
| 15 | Move (rename) the file named by File2 to the name given in File. |
| 16 | Move (rename) File2 to the name given in File, stop the currently running program, delete its associated data tables, then compile and run File (the moved program) and mark it to run on power-up. |
| 17 | Move (rename) File2 to the name given in File, stop the currently running program, delete its associated data tables, then compile and run File (the moved program) without changing which program is marked to run on power-up. |
| 18 | Copy the file named by File2 to the name given in File. |
| 19 | Copy File2 to the name given in File, stop the currently running program, delete its associated data tables, then compile and run File (the new copy) and mark it to run on power-up. |
| 20 | Copy File2 to the name given in File, stop the currently running program, delete its associated data tables, then compile and run File (the new copy) without changing which program is marked to run on power-up. |
outcome=0 means success. This is the opposite of SetValueEx and ClockSet. A failed
action still returns HTTP 200, so always read outcome. On success, description
describes what happened (for example, File deleted). On failure, it describes the error.
outcome |
description |
Meaning |
|---|---|---|
0 |
(the action's result) | Success. |
1 |
Permission denied |
The file or drive is in use — for example, deleting a file fails, or formatting a drive that holds the running or power-up program or active card tables. |
2 |
File write failed |
A move or copy couldn't write the new file. No partial file is left. |
5 |
Invalid drive name |
Action 5 named a drive that can't be formatted. |
13 |
Invalid file name |
The file doesn't exist, isn't a program file where one is needed, or the move or copy source can't be opened. Also returned for Action 9 when File doesn't match the staged .obj upload. |
22 |
File is locked by the program |
Action 4 named the running or power-up program, or a file that holds active card data. The data logger also returns 22 for an Action it doesn't support. |
23 |
— | Action 5 named a drive that has files open for writing. |
holdoff is the number of seconds to wait before you send the next request. Actions
that restart the program or install an OS take the data logger offline for a short time:
| Action | Typical holdoff |
|---|---|
| Compile and run (1, 6, 10, 12–14, 16, 17, 19, 20) or stop (7, 8) | 6, or 20 when cellular (PPP) is configured |
| Stop (7, 8) when no program is running | 1 |
Install an OS (9, .obj) |
75 |
Load a .web.obj.gz with Action 9 |
200 |
| Actions that don't restart anything (2, 3, 4, 5, 11, 15, 18) | 0 |
FileControl responses always carry holdoff and never carry time.
| command required | string Value: "FileControl" |
| File | string Target file (for example, |
| Action required | integer [ 1 .. 20 ] Numeric action code (see table). |
| File2 | string Second file, for move/rename (15/16/17) and copy (18/19/20). |
| format | string Default: "html" Enum: "html" "json" "xml" Response representation. Defaults to |
curl --user user:pass 'http://<datalogger>/?command=FileControl&File=CPU:old_data.dat&Action=4&format=json'
{- "outcome": 0,
- "holdoff": 0,
- "description": "File deleted"
}Request URL: GET /?command=ListFiles (drives) or GET /<drive>/?command=ListFiles
(files on one drive).
Lists files on the data logger. The listing is scoped by the URL path, not by a parameter:
GET /?command=ListFiles), it returns only the top-level drives
(for example, CPU/, CRD/) as directory entries — not their contents./<drive>/?command=ListFiles, for example
/CPU/?command=ListFiles. The first entry is the drive itself.Drive entries (is_dir: true) differ from file entries:
size is the drive's free space in bytes, not the size of its contents.size has about six significant digits and can be in scientific notation (for
example, 3.13958e+07). Parse size as a floating-point number. A file's size is
always a whole number.last_write is the date of the data logger's operating system at midnight, not a real
write time.| command required | string Value: "ListFiles" |
| format | string Default: "html" Enum: "html" "json" "xml" Response representation. Defaults to |
curl --user user:pass 'http://<datalogger>/?command=ListFiles&format=json'
{- "files": [
- {
- "path": "CPU/",
- "is_dir": true,
- "size": 31395800,
- "last_write": "2026-04-01T00:00:00",
- "run_now": false,
- "run_on_power_up": false,
- "read_only": false,
- "paused": false
}, - {
- "path": "CRD/",
- "is_dir": true,
- "size": 16231900000,
- "last_write": "2026-04-01T00:00:00",
- "run_now": false,
- "run_on_power_up": false,
- "read_only": false,
- "paused": false
}
]
}Request URL: GET /?command=NewestFile.
Returns the contents of the newest file matching a file expression in expr=
(for example, USR:*.jpg). The file is returned as raw bytes with
Content-Type: application/octet-stream (unlike a GET /{drive}/{path} download, this
command does not infer the type from the file extension).
| command required | string Value: "NewestFile" |
| expr required | string File expression / pattern to match (for example, |
curl --user user:pass 'http://<datalogger>/?command=NewestFile&expr=CRD:*.dat' -o newest.dat
Request URL: GET /{drive}/{path}, for example GET /CPU/program.cr1x.
Returns the contents of a file. Content-Type is inferred from the file extension (for
example, .txt → text/plain); a file whose extension the server does not recognize is
returned as application/octet-stream. Requesting a bare drive (for example, GET /CPU/)
returns that drive's INDEX.HTML / DEFAULT.HTML if present.
| drive required | string Enum: "CPU" "USR" "CRD" "USB" "SSD" Data logger drive. Upload ( |
| path required | string Example: myfile.txt File name on the drive. The request URL |
curl --user user:pass 'http://<datalogger>/CPU/program.cr1x' -o program.cr1x
Request URL: PUT /{drive}/{path}, for example PUT /CPU/testing.cr6.
Uploads a file to the data logger with an HTTP PUT request. The request body is written to
<drive>:<path>, replacing any existing file. Supports both Content-Length and
Transfer-Encoding: chunked. You can write to any drive the data logger has (see the
drive parameter). Requires All access (level 1). Any lower level gets 401.
Put the full target path in the URL, including the file name. The sample's trailing-slash
form (http://<datalogger>/CPU/) works only because curl adds the local file name to a URL
that ends in /. Other clients don't.
Special cases:
CPU:*.OBJ file stages an operating-system image instead of writing a
file. To install it, send FileControl with Action=9 and the same file name..CSIPASSWD file is rejected with 405.| drive required | string Enum: "CPU" "USR" "CRD" "USB" "SSD" Data logger drive. Upload ( |
| path required | string Example: myfile.txt File name on the drive. The request URL |
The file contents. Send the raw bytes of the file as the request body (for example, curl -T <file>). Redoc shows this as string <binary>, meaning "the file's raw bytes."
Raw file bytes.
curl -X PUT -T testing.cr6 --user user:pass 'http://<datalogger>/CPU/testing.cr6'
<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD><BODY>The request was malformed</BODY></HTML>
Request URL: DELETE /{drive}/{path}, for example DELETE /CPU/old.dat.
Deletes <drive>:<path> from the data logger. Requires All access (level 1). Any
lower level gets 401.
On success, the data logger returns 200 with a FileControl response in HTML:
outcome 0, holdoff 0, and description File deleted. If the delete fails — the
file doesn't exist, the program uses it, or it can't be removed — you get 404 with no
outcome.
| drive required | string Enum: "CPU" "USR" "CRD" "USB" "SSD" Data logger drive. Upload ( |
| path required | string Example: myfile.txt File name on the drive. The request URL |
curl -X DELETE --user user:pass 'http://<datalogger>/CPU/old.dat'
<!DOCTYPE html> <html><head><title>FileControl Response</title></head> <body> <h1>FileControl Response</h1> <table border="1"> <tr><td>outcome</td><td>0</td></tr> <tr><td>holdoff</td> <td>0</td></tr> <tr><td>description</td><td>File deleted</td></tr> </table> </body> </html>
Percent-encode these reserved characters wherever they appear in a value in the URL.
How the server decodes a URL. The server decodes every %xx escape in the path and the
query string. It does this before it splits the query into arguments. That has these
effects:
: and / work either way. uri=dl:Public and uri=dl%3APublic are the same
request, and so are File=CPU:file.dat and File=CPU%3Afile.dat.+ is a space in the query string. p1=2026-07-30+16:15:31 sends a space. For a
literal plus sign, send %2B. In the path (a file name in /{drive}/{path}), + stays a
plus sign.&. Because the server decodes first, %26 turns back into &
and ends the argument, just like a raw &.| Character | Escape code | Notes |
|---|---|---|
| space | %20 |
Required. In the query string, + also works. |
< |
%3C |
|
> |
%3E |
|
# |
%23 |
Required. Clients drop everything after a raw #. |
% |
%25 |
Required. |
+ |
%2B |
Required for a literal + in the query string. |
{ |
%7B |
|
} |
%7D |
|
| | | %7C |
|
\ |
%5C |
|
^ |
%5E |
|
[ |
%5B |
|
] |
%5D |
|
` |
%60 |
|
; |
%3B |
|
/ |
%2F |
Optional. The server accepts / either way. |
? |
%3F |
|
: |
%3A |
Optional. The server accepts : either way. |
@ |
%40 |
|
= |
%3D |
Optional in a value. |
& |
%26 |
Encoding doesn't help. A value can't contain &. |
$ |
%24 |
You don't need to encode ~. It's an unreserved character in RFC 3986, like letters,
digits, -, ., and _.
The version shown at the top of this document tracks the documentation, not the firmware.
Each firmware release also carries its own version (see Supported models and OS versions).
| Version | Date | Summary |
|---|---|---|
| 1.0.3 | 2026-09-25 | Fixed contradictions: file upload and delete need All access; PUT, GET, and DELETE accept the same drives; how the server decodes percent-escapes and +; which requests are GET; outcome polarity; the ClockSet sample. Added the real request URL to every operation, JSON-first samples, response schemas, and links to this spec and to llms.txt. Documented time formats, limits and paging, CORS, security guidance, the legacy numeric code, access code 4, USB trust, DataQuery headsig and interval rules, FileControl outcome codes and holdoff, and ListFiles drive sizes. |
| 1.0.2 | 2026-09-03 | Added a Related documents section linking to the WebSocket and MQTT API documents. |
| 1.0.1 | 2026-08-17 | Added a URL escape codes reference and a note on percent-encoding reserved characters in the URL. |
| 1.0.0 | 2026-08-12 | First release. Covers the HTTP web server commands (data, variables, clock, and files), the getting-started and security model, supported models and OS versions, and error responses. |