Data Logger Web Server API (1.0.3)

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.

Supported models and OS versions

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.

Getting started

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.

Your first request

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 returns 200. The outcome value in the body tells you whether it worked, and what counts as success depends on the command:

  • SetValueEx, ClockCheck, and ClockSet: outcome=1 means success.
  • FileControl: outcome=0 means 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.

Command syntax

Commands generally follow the form:

<URL>?command=<CommandName>&uri=<DataSource>&<arguments>
  • Put 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 ?.
  • Arguments are appended with &; 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>.
  • Credentials in the URL — a username/password may be supplied inline, for example 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, so uri=dl:Public and uri=dl%3APublic are 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 its x-request-url value in openapi.yaml.

Time formats

Input — the time argument of ClockSet and the timestamps in DataQuery p1 and p2:

  • Use ISO 8601 with a 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.
  • You can leave off trailing parts. They default to zero, so 2026-07-30 means midnight.
  • Don't use a space instead of the T. The server ignores the time of day and uses midnight.
  • Don't add a timezone suffix such as 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.25
  • html and toa5 use a space: 2026-07-30 16:15:31.25

Transport

  • Default ports: HTTP on 80, HTTPS on 443. Every model serves HTTP/1.1. HTTP/2 runs on the CR6, CR1000X(e), Granite 6, Granite 9, and Granite 10 only; the CR300, CR310, and CR350 use HTTP/1.1 only. Where it is supported, HTTP/2 runs only over TLS (port 443) — port 80 is always HTTP/1.1. TLS uses mbedTLS and supports session resumption.
  • Methods: GET, POST, PUT, and DELETE. POST carries HTML form submissions; PUT/DELETE operate on /{drive}/{path} files.
  • The server is reachable over any active interface — Ethernet, Wi-Fi, cellular, or USB (RNDIS) — on both IPv4 and IPv6. Address the data logger by DNS/host name (for example, 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).
  • Responses use either 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.
  • File uploads (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.

CORS

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.

Limits

  • Records per 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.
  • Paging a large query: use 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).
  • Upload size: a 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).
  • Idle timeout (HTTP, port 80): the data logger closes a connection when it has sent nothing for 60 seconds. It closes a connection within 40 seconds when the request headers don't arrive in full.
  • Concurrent HTTPS connections: set by the TLSConnections setting in Device Configuration Utility.

Response format

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.

toa5 and tob1 are record-table formats supported only by data-retrieval commands (DataQuery). Every other command supports html, json, and xml only. Each operation below lists the formats it supports.

Note: tob1 responses use the nonstandard type binary/octet_stream, not application/octet-stream. Don't match the Content-Type strictly.

Enabling security

Two mechanisms can be set on the data logger:

  1. Numeric security code (legacy) — set in the Status table via 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:
    • When user accounts exist, the web server ignores the numeric code.
    • When no user accounts exist and one or two levels are set, the web server ignores the code. Every request gets Read-Only access.
    • When no user accounts exist and all three levels are set, the web server refuses every request with 404. To use the API, create user accounts.
  2. Account Manager (HTTP Basic auth) — user accounts created in Device Configuration Utility. When enabled, the PakBus/TCP password and numeric code are not used for HTTP.

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.

Security guidance

  • 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:

    • Don't show these requests as links, and don't let a page load them on its own.
    • Ask the user to confirm before you send one.
    • Sign in with the lowest access level that works. For example, use a Read-Only account for a dashboard.
    • Keep the data logger off networks you don't trust.

Error responses

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.

Authentication

Authorization checks.

Report the caller's access level

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.

Authorizations:
basicAuth
query Parameters
command
required
string
Value: "CheckAuthorization"
anonymous
string
Enum: "true" "false"

If true, allow anonymous evaluation of the access level.

direct
string
Enum: "true" "false"

If true, report access based solely on whether this is a trusted USB (RNDIS) connection.

format
string
Default: "html"
Enum: "html" "json" "xml"

Response representation. Defaults to html.

Responses

Request samples

curl --user user:pass 'http://<datalogger>/?command=CheckAuthorization&format=json'

Response samples

Content type
{
  • "authorization": 1
}

Data

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:

  • a record number (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.
  • a timestamp (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".

Query records from a data table

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-large p3 count 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.

Authorizations:
basicAuth
query Parameters
command
required
string
Value: "DataQuery"
uri
required
string
Example: uri=dl:onemin.ptemp

Table (and optional field) as dl:<table> or dl:<table>.<field>.

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 date-range (records before this time).

p3
string

Third mode argument — max record count for since-record.

transaction
integer <int64>

Client-supplied transaction id echoed back in the response.

headsig
integer

The signature from the head block of an earlier json response. If it still matches the table, the response head omits environment and fields. Applies to json only.

format
string
Default: "html"
Enum: "html" "json" "xml" "toa5" "tob1"

Response representation. Defaults to html. toa5 (TOA5 ASCII table) and tob1 (TOB1 binary table) are record-table formats supported only by data-retrieval commands.

Responses

Request samples

curl --user user:pass 'http://<datalogger>/?command=DataQuery&uri=dl:TestData&mode=most-recent&p1=2&format=json'

Response samples

Content type
Example
{
  • "head": {
    },
  • "data": [
    ]
}

Enumerate tables and fields (symbols)

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
Authorizations:
basicAuth
query Parameters
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 html.

Responses

Request samples

curl --user user:pass 'http://<datalogger>/?command=BrowseSymbols&uri=dl:&format=json'

Response samples

Content type
Example
{
  • "symbols": [
    ]
}

Variables

Setting public/variable values.

Set a public variable / table field

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:

  • Read-Only (3 or 4) or None: you get 401, and nothing is set.
  • Read/Write or All: you get 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.

Authorizations:
basicAuth
query Parameters
command
required
string
Value: "SetValueEx"
uri
required
string

Target field as <table>.<field> (optionally with an array index).

value
required
string

New value to write.

format
string
Default: "html"
Enum: "html" "json" "xml"

Response representation. Defaults to html.

Responses

Request samples

curl --user user:pass 'http://<datalogger>/?command=SetValueEx&uri=dl:Public.TestFloat&value=12.34&format=json'

Response samples

Content type
Example
{
  • "outcome": 1,
  • "description": "success"
}

Clock

Reading and setting the data logger clock.

Read the data logger clock

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.

Authorizations:
basicAuth
query Parameters
command
required
string
Value: "ClockCheck"
format
string
Default: "html"
Enum: "html" "json" "xml"

Response representation. Defaults to html.

Responses

Request samples

curl --user user:pass 'http://<datalogger>/?command=ClockCheck&format=json'

Response samples

Content type
{
  • "outcome": 1,
  • "time": "2026-07-31T14:18:17.70",
  • "description": "The clock was checked"
}

Set the data logger clock

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.

Authorizations:
basicAuth
query Parameters
command
required
string
Value: "ClockSet"
time
required
string

New local time in ISO 8601 with a T, for example, 2026-07-30T16:15:31.

format
string
Default: "html"
Enum: "html" "json" "xml"

Response representation. Defaults to html.

Responses

Request samples

curl --user user:pass 'http://<datalogger>/?command=ClockSet&time=2026-07-30T16:15:31&format=json'

Response samples

Content type
{
  • "outcome": 1,
  • "time": "2026-07-30T16:14:58.47",
  • "description": "The clock was set"
}

Files

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.

Perform a file-control action

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.

Authorizations:
basicAuth
query Parameters
command
required
string
Value: "FileControl"
File
string

Target file (for example, CPU:program.CR6).

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 html.

Responses

Request samples

curl --user user:pass 'http://<datalogger>/?command=FileControl&File=CPU:old_data.dat&Action=4&format=json'

Response samples

Content type
Example
{
  • "outcome": 0,
  • "holdoff": 0,
  • "description": "File deleted"
}

List files (scoped by URL path)

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:

  • At the root (GET /?command=ListFiles), it returns only the top-level drives (for example, CPU/, CRD/) as directory entries — not their contents.
  • To list a drive's files, request /<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.
Authorizations:
basicAuth
query Parameters
command
required
string
Value: "ListFiles"
format
string
Default: "html"
Enum: "html" "json" "xml"

Response representation. Defaults to html.

Responses

Request samples

curl --user user:pass 'http://<datalogger>/?command=ListFiles&format=json'

Response samples

Content type
Example
{
  • "files": [
    ]
}

Retrieve the newest file matching an expression

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).

Authorizations:
basicAuth
query Parameters
command
required
string
Value: "NewestFile"
expr
required
string

File expression / pattern to match (for example, USR:*.jpg).

Responses

Request samples

curl --user user:pass 'http://<datalogger>/?command=NewestFile&expr=CRD:*.dat' -o newest.dat

Download a file

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.

Authorizations:
basicAuth
path Parameters
drive
required
string
Enum: "CPU" "USR" "CRD" "USB" "SSD"

Data logger drive. Upload (PUT), download (GET), and delete (DELETE) accept the same drives: every drive the data logger has. The drive set varies by model (see Files). SSD is Granite 9 and Granite 10 only. A drive the data logger doesn't have returns 507 for PUT and 404 for GET and DELETE.

path
required
string
Example: myfile.txt

File name on the drive. The request URL /{drive}/{path} maps internally to the data logger path <drive>:<path> (the first / becomes :).

Responses

Request samples

curl --user user:pass 'http://<datalogger>/CPU/program.cr1x' -o program.cr1x

Upload (write) a file

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:

  • Uploading a 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.
  • Uploading a .CSIPASSWD file is rejected with 405.
Authorizations:
basicAuth
path Parameters
drive
required
string
Enum: "CPU" "USR" "CRD" "USB" "SSD"

Data logger drive. Upload (PUT), download (GET), and delete (DELETE) accept the same drives: every drive the data logger has. The drive set varies by model (see Files). SSD is Granite 9 and Granite 10 only. A drive the data logger doesn't have returns 507 for PUT and 404 for GET and DELETE.

path
required
string
Example: myfile.txt

File name on the drive. The request URL /{drive}/{path} maps internally to the data logger path <drive>:<path> (the first / becomes :).

Request Body schema: application/octet-stream
required

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."

string <binary>

Raw file bytes.

Responses

Request samples

curl -X PUT -T testing.cr6 --user user:pass 'http://<datalogger>/CPU/testing.cr6'

Response samples

Content type
text/html
<HTML><HEAD><TITLE>400 Bad Request</TITLE></HEAD><BODY>The request was malformed</BODY></HTML>

Delete a file

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.

Authorizations:
basicAuth
path Parameters
drive
required
string
Enum: "CPU" "USR" "CRD" "USB" "SSD"

Data logger drive. Upload (PUT), download (GET), and delete (DELETE) accept the same drives: every drive the data logger has. The drive set varies by model (see Files). SSD is Granite 9 and Granite 10 only. A drive the data logger doesn't have returns 507 for PUT and 404 for GET and DELETE.

path
required
string
Example: myfile.txt

File name on the drive. The request URL /{drive}/{path} maps internally to the data logger path <drive>:<path> (the first / becomes :).

Responses

Request samples

curl -X DELETE --user user:pass 'http://<datalogger>/CPU/old.dat'

Response samples

Content type
text/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>

URL escape codes

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.
  • A value can't contain &. 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 _.

Related documents

The data logger exposes two other real-time interfaces, each documented separately:

  • WebSocket API — the JSON message protocol over a WebSocket connection for live data streaming.
  • MQTT API — publish and subscribe over MQTT: topic structure, MQTT settings, command and control, Campbell Cloud onboarding, recipes, the JSON data formats for state, status, and metadata, and the CRBasic MQTT instructions.

Revision history

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.