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.
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 request is a GET to the data logger. It uses a ?command= query string and HTTP
Basic credentials (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": [ "…" ] } ] }
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>
&; optional arguments use a default.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.Note: Any reserved character used in a value in the URL — an argument value, a
uri, a file name, or inline credentials — must be percent-encoded. 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>— see each operation's request sample for the actual URL.
GET, 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://[fe80::1]/?command=ClockCheck. USB
(RNDIS) connections are treated as trusted for access-control purposes.Transfer-Encoding: chunked or a Content-Length header, always
carry Cache-Control: no-cache, and may be gzip-compressed (Content-Encoding: gzip).
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.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.
Two combinable mechanisms:
SetSecurity /
Security() to 1–65534. Weak; if set with no user accounts it must be supplied.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/delete. |
| 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. |
| Read-Only | 3 (4 when no accounts are configured) |
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. |
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 are trusted (full
access) unless you configure otherwise.
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 — or a file matching a NewestFile expression — does not exist. |
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-operation outcome code in the body
(for example, 1 = success for SetValueEx/ClockSet) — see each command for its meaning.
Returns the access level granted to the current credentials without taking any other action.
| 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=html'
<!DOCTYPE html> <html><head><title>CheckAuthorizationResponse</title></head><body><h1>CheckAuthorization</h1> <table border="1"><tr><td>authorization</td><td>1</td></tr></table></body> </html>
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".
Retrieves records from a data table. The mode argument selects the retrieval strategy;
p1/p2/p3 supply its parameters.
mode |
p1 |
p2 |
p3 |
|---|---|---|---|
most-recent |
number of records (default 1) | — | — |
since-record |
starting record number | — | max records |
since-time |
start timestamp | — | — |
date-range |
start timestamp | end timestamp | — |
backfill |
seconds to back-fill (default 1) | — | — |
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 Header signature; lets the client skip the header block if unchanged. |
| 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=html'
<!DOCTYPE html> <HTML><HEAD><TITLE>Table Display</TITLE><meta http-equiv="Pragma" content="no-cache"><meta http-equiv="expires" content="0"> </HEAD><BODY> <h1>Table Name: TestData</h1> <table border="1" cellpadding="2" cellspacing="0"> <tr valign="middle" align="center"> <th nowrap>TimeStamp</th><th nowrap>Record</th><th nowrap>PTemp</th><th nowrap>PTemp_Avg</th> <th nowrap>PTemp_Max</th><th nowrap>PTemp_Min</th><th nowrap>BattV</th><th nowrap>Counter</th><th nowrap>TestString</th> </tr> <tr valign="middle" align="center"> <td nowrap>2026-08-06 13:43:00.000</td><td nowrap>11</td><td nowrap>24.64038</td><td nowrap>24.6424</td> <td nowrap>24.6442</td><td nowrap>24.64038</td><td nowrap>11.74362</td><td nowrap>137</td><td nowrap>"hello"</td> </tr> <tr valign="middle" align="center"> <td nowrap>2026-08-06 13:44:00.000</td><td nowrap>12</td><td nowrap>24.63583</td><td nowrap>24.63784</td> <td nowrap>24.64008</td><td nowrap>24.63583</td><td nowrap>11.70022</td><td nowrap>149</td><td nowrap>"hello"</td> </tr> </table> </BODY></HTML>
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:
6 = table, 7 = array/expandable, 8 = scalar field.
| 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=html'
<!DOCTYPE html> <html><head><title>BrowseSymbols Response</title></head><body> <h1>BrowseSymbols Response</h1> <table border="1"> <tr><th>name</th><th>uri</th><th>type</th><th>is_enabled</th><th>is_read_only</th><th>can_expand</th></tr> <tr><td>Status</td><td>dl:Status</td><td>6</td><td>true</td><td>false</td><td>true</td></tr> <tr><td>Public</td><td>dl:Public</td><td>6</td><td>true</td><td>false</td><td>true</td></tr> <!-- ... more tables ... --> </table></body> </html>
Sets a field to a value. The target is uri=<table>.<field> and the new value is
value=. Requires read/write access; a read-only caller receives 401.
The response reports an outcome (1 = success, 0 = failure) and a description
(for example, "success", "Read only", "Invalid table name", "Invalid field name",
"Blocked by security").
| 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=html'
<!DOCTYPE html> <html><head><title>SetValueExResponse</title></head><body><h1>SetValueEx</h1> <table border="1"><tr><td>outcome</td><td>1</td></tr><tr><td>description</td><td>success</td></tr></table></body> </html>
Returns the current data logger time as an outcome (1 = success), time, and
description.
| 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=html'
<!DOCTYPE html> <html><head><title>ClockCheck Response</title></head><body> <h1>ClockCheck Response</h1> <table border="1"> <tr><td>outcome</td><td>1</td></tr> <tr><td>time</td><td>2026-07-31 14:18:17.70</td></tr> <tr><td>description</td><td>The clock was checked</td></tr> </table> </body> </html>
Sets the data logger time to time=. Requires read/write access (read-only callers
receive 401). Returns the resulting outcome/time/description.
| command required | string Value: "ClockSet" |
| time required | string New time, for example, |
| 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=html'
<!DOCTYPE html> <html><head><title>ClockSet Response</title></head><body> <h1>ClockSet Response</h1> <table border="1"> <tr><td>outcome</td><td>1</td></tr> <tr><td>time</td><td>2026-07-31 14:18:17.82</td></tr> <tr><td>description</td><td>The clock was set</td></tr> </table> </body> </html>
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 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.
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. |
| 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=html'
<!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>
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.GET /<drive>/?command=ListFiles), it returns the files inside
that drive.(Internally the server checks whether the path's 4th character is / — that is, a
<drive>/ prefix — to decide whether to enumerate a drive's contents.)
| 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=html'
<!DOCTYPE html> <html><head><title>ListFiles Response</title></head> <body><h1>ListFiles Response</h1><table border="1"> <tr><td><b>Path</b></td><td><b>Is Directory</b></td><td><b>Size</b></td> <td><b>Last Write</b></td><td><b>Run Now</b></td><td><b>Run On Power Up</b></td> <td><b>Read Only</b></td><td><b>Paused</b></td></tr> <tr><td>CPU/</td><td>true</td><td>3.13958e+07</td><td>2026-04-01T00:00:00</td><td>false</td><td>false</td><td>false</td><td>false</td></tr> <tr><td>CRD/</td><td>true</td><td>1.62319e+10</td><td>2026-04-01T00:00:00</td><td>false</td><td>false</td><td>false</td><td>false</td></tr> </table></body></html>
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
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. ( |
| 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
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. Writes to the /cpu/, /crd/, or /usb/ directory and
requires read/write access.
Special cases:
CPU:*.OBJ file initiates an operating-system install..CSIPASSWD file is rejected with 405.| drive required | string Enum: "CPU" "USR" "CRD" "USB" "SSD" Data logger drive. ( |
| 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/'
<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD><BODY>The request was malformed</BODY></HTML>
Deletes <drive>:<path> from the data logger.
| drive required | string Enum: "CPU" "USR" "CRD" "USB" "SSD" Data logger drive. ( |
| 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'
Percent-encode these reserved characters wherever they appear in a value in the URL.
| Character | Escape code |
|---|---|
| space | %20 |
< |
%3C |
> |
%3E |
# |
%23 |
% |
%25 |
+ |
%2B |
{ |
%7B |
} |
%7D |
| | | %7C |
\ |
%5C |
^ |
%5E |
~ |
%7E |
[ |
%5B |
] |
%5D |
` |
%60 |
; |
%3B |
/ |
%2F |
? |
%3F |
: |
%3A |
@ |
%40 |
= |
%3D |
& |
%26 |
$ |
%24 |
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.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. |