REST API overview
Arc's REST endpoints for ingestion, querying, and administration: MessagePack and Line Protocol writes, JSON and Arrow query responses, and auth header formats.
Arc provides a comprehensive REST API for data ingestion, querying, and management.
Base URL
http://localhost:8000Authentication
All endpoints (except public ones) require authentication. Arc supports multiple authentication methods for compatibility with various clients:
Bearer token (standard)
curl -H "Authorization: Bearer $ARC_TOKEN" http://localhost:8000/api/v1/queryToken header (InfluxDB 2.x style)
curl -H "Authorization: Token $ARC_TOKEN" http://localhost:8000/api/v1/queryAPI key header
curl -H "x-api-key: $ARC_TOKEN" http://localhost:8000/api/v1/queryQuery parameter (InfluxDB 1.x style)
For InfluxDB 1.x client compatibility, tokens can be passed via the p query parameter:
curl "http://localhost:8000/write?db=mydb&p=$ARC_TOKEN" -d 'cpu,host=server01 usage=45.2'Public endpoints (no auth required)
GET /health- Health checkGET /ready- Readiness probeGET /metrics- Prometheus metricsGET /api/v1/auth/verify- Token verification
Quick examples
Write data (MessagePack)
import os
import msgpack
import requests
ARC_TOKEN = os.environ["ARC_TOKEN"]
data = {
"m": "cpu",
"columns": {
"time": [1697472000000],
"host": ["server01"],
"usage": [45.2]
}
}
response = requests.post(
"http://localhost:8000/api/v1/write/msgpack",
headers={
"Authorization": f"Bearer {ARC_TOKEN}",
"Content-Type": "application/msgpack",
"x-arc-database": "default"
},
data=msgpack.packb(data)
)Query data (JSON)
curl -X POST http://localhost:8000/api/v1/query \
-H "Authorization: Bearer $ARC_TOKEN" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT * FROM default.cpu LIMIT 10", "format": "json"}'Query data (Apache Arrow)
For large result sets, use Arrow format:
import os
import requests
import pyarrow as pa
ARC_TOKEN = os.environ["ARC_TOKEN"]
response = requests.post(
"http://localhost:8000/api/v1/query/arrow",
headers={"Authorization": f"Bearer {ARC_TOKEN}"},
json={"sql": "SELECT * FROM default.cpu LIMIT 100000"}
)
reader = pa.ipc.open_stream(response.content)
arrow_table = reader.read_all()Query data (MessagePack)
MessagePack is the best general-purpose binary format for clients: it is
columnar, carries per-column type names, supports SHOW statements, and honors
Accept-Encoding — noticeably faster end-to-end than JSON on large result
sets. Arrow is faster still for raw throughput, but does not accept SHOW and
has no response compression.
import os
import requests
import msgpack
ARC_TOKEN = os.environ["ARC_TOKEN"]
response = requests.post(
"http://localhost:8000/api/v1/query/msgpack",
headers={
"Authorization": f"Bearer {ARC_TOKEN}",
"Accept-Encoding": "zstd", # optional, typically ~40% smaller
},
json={"sql": "SELECT * FROM default.cpu LIMIT 100000"}
)
result = msgpack.unpackb(response.content, raw=False)
# NOTE: "data" is COLUMNAR — an array of columns, not an array of rows.
for name, type_name, column in zip(result["columns"], result["types"], result["data"]):
print(name, type_name, column[:5])The response is a single MessagePack map:
| Field | Meaning |
|---|---|
success | bool |
columns | column names |
types | wire type name per column, parallel to columns |
data | array of columns, each an array of values |
row_count | number of rows |
execution_time_ms | server-side execution time |
timestamp | RFC3339, UTC |
profile | present only when x-arc-profile: true |
The truncated and truncation_reason completion fields are JSON-only. The
POST /api/v1/query/msgpack endpoint materializes the full result before
committing response headers, so a DuckDB failure becomes a proper 5xx rather
than a marked partial body. A failure while writing the MessagePack body can
still produce a partial response without an in-band truncation marker.
The row cap is not JSON-only: the MessagePack envelope carries
rows_capped and row_cap as a pair, exactly as the JSON response does. So
this endpoint has no failure marker but does have a cap marker, and a client
reading it still has to check for one.
Type vocabulary
The types values are a stable contract. Scalars are
bool, int8/int16/int32/int64, uint8/uint16/uint32/uint64,
float32/float64, utf8, large_utf8, binary, large_binary, date32,
and null. Timestamps carry their unit, e.g. timestamp[us].
Three values tell you a column is not natively typed on the wire:
string_encoded— a recognized type Arc transmits as text (DATE64,TIME,INTERVAL,DURATION,FLOAT16, fixed-size binary).list/struct/map— nested values, transmitted as text.unknown:<detail>— a type with no published Arc name (an engine-levelENUMarrives here). Never bind to the text after the prefix; it is diagnostic.
SUM(int_col) and other decimal-producing aggregates are normalized to
int64 (scale 0) or float64 (scaled), matching the Arrow endpoint — the
values are numbers, not strings.
Health check
curl http://localhost:8000/healthHealth & monitoring
GET /health
Health check endpoint.
Response:
{
"status": "ok",
"time": "2024-12-02T10:30:00Z",
"uptime": "1h 23m 45s",
"uptime_sec": 5025
}GET /ready
Kubernetes readiness probe.
Response:
{
"status": "ready",
"time": "2024-12-02T10:30:00Z",
"uptime_sec": 5025
}GET /metrics
Prometheus-format metrics.
Response: text/plain (Prometheus format)
Or request JSON:
curl -H "Accept: application/json" http://localhost:8000/metricsGET /api/v1/metrics
All metrics in JSON format.
GET /api/v1/metrics/memory
Detailed memory statistics including the Go runtime and the query engine.
GET /api/v1/metrics/query-pool
Query engine connection pool statistics.
GET /api/v1/metrics/endpoints
Per-endpoint request statistics.
GET /api/v1/metrics/timeseries/:type
Timeseries metrics data.
Parameters:
:type-system,application, orapi?duration_minutes=30- Time range (default: 30, max: 1440)
GET /api/v1/logs
Recent application logs. Requires an admin token (Authorization: Bearer <admin-token>) when authentication is enabled.
Query Parameters:
?limit=100- Number of logs (default: 100, max: 1000)?level=error- Filter by level (error, warn, info, debug)?since_minutes=60- Time range (default: 60, max: 1440)
Data ingestion
POST /api/v1/write/msgpack
High-performance MessagePack binary writes (recommended).
Headers:
Authorization: Bearer TOKENContent-Type: application/msgpackContent-Encoding: gzip(optional)x-arc-database: default(optional)
Body (MessagePack):
{
"m": "measurement_name",
"columns": {
"time": [1697472000000, 1697472001000],
"host": ["server01", "server02"],
"value": [45.2, 67.8]
}
}Response: 204 No Content
Tag columns (optional): tag_keys names the columns that are tags:
{
"m": "cpu",
"columns": {
"time": [1697472000000, 1697472001000],
"host": ["server01", "server02"],
"value": [45.2, 67.8]
},
"tag_keys": ["host"]
}Arc records the tag names in each file it writes. Compaction uses them to
find and remove duplicate rows: a row is removed only when another row is
identical in every column, such as a batch a client sent twice. Without
tag_keys, the files carry no tag names, and compaction removes duplicates
only if another file in the same partition has them.
Arc leaves out a name that is empty, is time, contains a comma, starts
with _, repeats an earlier name, or is not a column of the data. Names
must match a column exactly, including case. The write still succeeds, and
the server logs a warning naming what it left out, at most once per second.
A name with characters other than letters, digits, _ and -, such as
service.name, is kept. Compaction then does not remove duplicates from
batches of files that include it.
Null values: Columns may contain nulls, including a column whose values are all null in a given batch. Arc keeps such a column and stores every value as NULL, so it is queryable and returns NULLs rather than failing to resolve:
# 'depth' is null for this entire batch — the column is still created
{"m": "sensors", "columns": {
"time": [1697472000000, 1697472001000],
"value": [1.5, 2.5],
"depth": [None, None],
}}A later batch carrying real values for that column determines its type normally; the all-null batch does not pin it. Column types only need to be consistent within a single write, not across writes — reads union columns by name across files.
The time column is the one exception: it must be a numeric epoch on every
row. A null, string, or otherwise non-numeric time is rejected with
400 Bad Request rather than stored, because a non-timestamp time column
makes the affected partition un-compactable.
GET /api/v1/write/msgpack/stats
MessagePack ingestion statistics.
GET /api/v1/write/msgpack/spec
MessagePack format specification.
POST /write
InfluxDB 1.x Line Protocol compatible endpoint. This path matches InfluxDB's native API for drop-in client compatibility.
Query Parameters:
db- Target database name (required)rp- Retention policy (optional, ignored)precision- Timestamp precision:ns,us,ms,s(default:ns)p- Authentication token (InfluxDB 1.x style)
Headers:
Content-Type: text/plainAuthorization: Bearer TOKEN(or usepquery param)
Body:
cpu,host=server01 usage=45.2 1697472000000000000
mem,host=server01 used=8.2,total=16.0 1697472000000000000Example:
curl -X POST "http://localhost:8000/write?db=mydb&p=$ARC_TOKEN" \
-d 'cpu,host=server01 usage=45.2'POST /api/v2/write
InfluxDB 2.x compatible endpoint. This path matches InfluxDB's native API for drop-in client compatibility.
Query Parameters:
bucket- Target database/bucket name (required)org- Organization (optional, ignored)precision- Timestamp precision:ns,us,ms,s(default:ns)
Headers:
Content-Type: text/plainAuthorization: Token $ARC_TOKEN(InfluxDB 2.x style)
Example:
curl -X POST "http://localhost:8000/api/v2/write?bucket=mydb&org=myorg" \
-H "Authorization: Token $ARC_TOKEN" \
-d 'cpu,host=server01 usage=45.2'POST /api/v1/write/line-protocol
Arc-native Line Protocol endpoint. Uses headers instead of query parameters.
Headers:
Content-Type: text/plainAuthorization: Bearer TOKENx-arc-database: default- Target database
POST /api/v1/write/line-protocol/flush
Force buffer flush to disk.
GET /api/v1/write/line-protocol/stats
Line Protocol ingestion statistics.
GET /api/v1/write/line-protocol/health
Line Protocol handler health.
POST /api/v1/write/tle
Stream TLE (Two-Line Element) satellite orbital data. Parses TLE entries into tags (NORAD ID, name, classification) and fields (orbital elements + derived metrics).
Headers:
Authorization: Bearer TOKENX-Arc-Database: satellites(default:default)X-Arc-Measurement: satellite_tle(default:satellite_tle)
curl -X POST "http://localhost:8000/api/v1/write/tle" \
-H "Authorization: Bearer $ARC_TOKEN" \
-H "X-Arc-Database: satellites" \
--data-binary @stations.tleReturns 204 No Content on success.
See TLE Integration for full documentation including schema, format details, and example queries.
GET /api/v1/write/tle/stats
TLE handler statistics.
Data import
Bulk import endpoints for CSV, Parquet, Line Protocol, and TLE files. All endpoints use multipart/form-data with field name file, support gzip auto-detection, and enforce a 500 MB size limit.
POST /api/v1/import/csv
Bulk import a CSV file. See CSV Import for full documentation.
curl -X POST "http://localhost:8000/api/v1/import/csv?measurement=sensors" \
-H "Authorization: Bearer $ARC_TOKEN" \
-H "X-Arc-Database: iot" \
-F "file=@data.csv"POST /api/v1/import/parquet
Bulk import a Parquet file. See Parquet Import for full documentation.
curl -X POST "http://localhost:8000/api/v1/import/parquet?measurement=metrics" \
-H "Authorization: Bearer $ARC_TOKEN" \
-H "X-Arc-Database: production" \
-F "file=@data.parquet"POST /api/v1/import/lp
Bulk import a Line Protocol file. See Line Protocol Bulk Import for full documentation.
curl -X POST "http://localhost:8000/api/v1/import/lp" \
-H "Authorization: Bearer $ARC_TOKEN" \
-H "X-Arc-Database: mydb" \
-F "file=@export.lp"POST /api/v1/import/tle
Bulk import a TLE file. See TLE Integration for full documentation.
curl -X POST "http://localhost:8000/api/v1/import/tle" \
-H "Authorization: Bearer $ARC_TOKEN" \
-H "X-Arc-Database: satellites" \
-F "file=@catalog.tle"GET /api/v1/import/stats
Import handler statistics (total requests, records imported, errors).
Querying
POST /api/v1/query
Execute SQL queries with JSON response.
Request:
{
"sql": "SELECT * FROM default.cpu LIMIT 10",
"format": "json"
}Response:
{
"columns": ["time", "host", "usage"],
"types": ["TIMESTAMP", "VARCHAR", "DOUBLE"],
"data": [
[1697472000000, "server01", 45.2],
[1697472001000, "server02", 67.8]
],
"row_count": 2,
"execution_time_ms": 12
}Stream completion fields (JSON):
| Field | Meaning |
|---|---|
truncated | Optional. Present as true when streaming fails after the response has started; absent on a complete response. |
truncation_reason | Optional. The sanitized failure reason, accompanying truncated: true. |
rows_capped | Optional. Present as true when the result stopped at a governance row cap. The body is complete and valid up to that cap. |
row_cap | Optional. The cap that was reached, accompanying rows_capped: true. |
row_count | Rows actually returned, not a guarantee that the full result was delivered. |
A streamed JSON response can begin successfully and still fail after some rows
have been sent. Clients must check truncated after reading the complete
response. If it is true, treat the query as failed rather than displaying
the partial rows as a complete result. An HTTP 200 response alone does not
establish that streaming completed successfully.
truncated and rows_capped mean nearly opposite things and have to be
checked separately. truncated says the body is short because the stream
failed, so its rows must not be trusted. rows_capped says the body is
short because policy said so: it is a complete, valid result up to the
cap, and a success to be labelled rather than an error. A client that checks
only truncated silently under-reports a capped result.
The cap is an Enterprise feature: it needs query governance licensed and an
authenticated token, so rows_capped and row_cap never appear on an OSS
deployment. Client libraries should still handle them, so the same code
behaves correctly against an Enterprise server.
POST /api/v1/query/arrow
Execute SQL queries with Apache Arrow IPC response.
Request:
{
"sql": "SELECT * FROM default.cpu LIMIT 10000"
}Response: application/vnd.apache.arrow.stream
Incomplete streams: A failed Arrow IPC stream carries a non-empty
Arc-Stream-Truncated HTTP response trailer containing the failure reason.
The server also writes an invalid Arrow IPC message so a decoder rejects
the incomplete stream instead of accepting fewer rows as a complete result.
Treat either a decoding failure or a non-empty truncation trailer as a query
failure. Trailers are available after the response body has been consumed;
they are not ordinary initial response headers.
Capped streams: a non-empty Arc-Rows-Capped trailer means the stream
stopped at a governance row cap, and its value is the cap. This is the
opposite of Arc-Stream-Truncated: the Arrow stream is complete, decodes
cleanly, and simply holds fewer rows than the query matched. Read both — one
reports a failure, the other a labelled success. Every registered trailer is
emitted on every response, carrying an empty value when it was never set, so
the contract for both is "non-empty means set".
Response header: X-Arc-Query-ID when query management is enabled (v26.09.2+ on this endpoint; the JSON endpoint has always returned it). It is the id GET /api/v1/queries/:id and DELETE /api/v1/queries/:id take.
Optional stream encodings (v26.09.1+), opted in per request — use them for network-constrained clients pulling large result sets; leave them off for same-host consumers (they trade CPU for wire bytes):
| Header | Effect |
|---|---|
x-arc-arrow-dictionary: true | Dictionary-encode low-cardinality string columns (adaptive, first-batch analysis). Columns arrive as standard Arrow dictionary arrays — pyarrow, polars, and pandas read them transparently. |
x-arc-arrow-compression: zstd (or lz4) | Arrow IPC buffer compression, decompressed natively by Arrow clients. |
Combined, these halved wire size on a 500M-row benchmark (39 → 19.4 bytes/row).
Response compression (v26.09.1+)
POST /api/v1/query (JSON) and POST /api/v1/query/msgpack honor the
standard Accept-Encoding request header — send Accept-Encoding: zstd, gzip
and the response body is compressed (zstd preferred; Content-Encoding set
accordingly). curl, browsers, and HTTP libraries negotiate this
automatically. Typical savings: JSON −68%, msgpack −38% on large results.
Clients that omit the header get identical responses to previous versions.
POST /api/v1/query/estimate
Estimate query cost before execution.
Request:
{
"sql": "SELECT * FROM default.cpu WHERE time > now() - INTERVAL '1 hour'"
}GET /api/v1/measurements
List all measurements across databases.
GET /api/v1/query/:measurement
Query a specific measurement directly.
Authentication
GET /api/v1/auth/verify
Verify token validity (public endpoint).
Response:
{
"valid": true,
"token_id": "abc123",
"name": "my-token",
"is_admin": false
}GET /api/v1/auth/tokens
List all tokens (admin only).
POST /api/v1/auth/tokens
Create a new token (admin only).
Request:
{
"name": "my-service",
"description": "Token for my service",
"is_admin": false
}Response:
{
"id": "abc123",
"name": "my-service",
"token": "arc_xxxxxxxxxxxxxxxxxxxxxxxx",
"is_admin": false,
"created_at": "2024-12-02T10:30:00Z"
}GET /api/v1/auth/tokens/:id
Get token details (admin only).
DELETE /api/v1/auth/tokens/:id
Delete/revoke a token (admin only).
POST /api/v1/auth/tokens/:id/rotate
Rotate a token (admin only).
POST /api/v1/auth/tokens/:id/revoke
Revoke a token (admin only).
GET /api/v1/auth/cache/stats
Token cache statistics (admin only).
POST /api/v1/auth/cache/invalidate
Invalidate token cache (admin only).
Compaction
Available in main; ships in v27.01.1
Per-cycle lookup (/cycles, /cycles/{id}), the cycle_id job filter, a
working limit on /history, and a real active_jobs count are merged in
main but are not in a released binary yet. The first release carrying
them is v27.01.1. On v26.09.x the /cycles routes return 404,
/history?limit= is capped at 10 records, and active_jobs is null on
/status and always 0 on /jobs.
GET /api/v1/compaction/status
Current compaction status. The manager block counts attempts (one compaction subprocess invocation), not batches.
Response:
{
"manager": {
"active_jobs": 2,
"total_completed": 1894,
"total_failed": 3
},
"schedulers": {
"hourly": {
"enabled": true,
"running": true,
"schedule": "5 * * * *",
"next_run": "2027-01-14T15:05:00Z"
}
}
}active_jobs is null before v27.01.1.
GET /api/v1/compaction/stats
Compaction statistics.
GET /api/v1/compaction/candidates
List files eligible for compaction.
POST /api/v1/compaction/trigger
Manually trigger a compaction cycle. Filters are query parameters, not a
body: tier (comma-separated, defaults to every enabled tier), database,
and measurement (v26.09.2+, requires database). Returns 409 while a
cycle is already running.
curl -X POST "http://localhost:8000/api/v1/compaction/trigger?database=default&measurement=cpu" \
-H "Authorization: Bearer $ARC_TOKEN"GET /api/v1/compaction/jobs
Attempts in flight: active_jobs, unit: "attempts", and an always-empty
jobs array. Returns 0 unconditionally before v27.01.1.
GET /api/v1/compaction/history
Job history, one record per attempt. Query parameters: limit (default 10,
capped at 100; ignored before v27.01.1) and cycle_id to filter to one cycle.
Records carry cycle_id and attempt_depth, the adaptive-splitter level.
GET /api/v1/compaction/cycles
Retained cycle history, newest first. limit defaults to 10, capped at 500.
Carries running_cycle_id while a cycle runs.
GET /api/v1/compaction/cycles/{id}
One cycle by id, including failed_partitions — a deduplicated map of
partition path to failed-batch count, which is the retry list. A 404 names
the retained window rather than claiming the cycle never ran.
Delete operations
POST /api/v1/delete
Delete data matching conditions.
Request:
{
"database": "default",
"measurement": "cpu",
"where": "host = 'server01' AND time < '2024-01-01'",
"confirm": true
}Response:
{
"deleted_rows": 1523,
"deleted_files": 3
}GET /api/v1/delete/config
Get delete operation configuration.
Database management
Endpoints for managing databases programmatically.
The three read endpoints require read permission
GET /api/v1/databases, GET /api/v1/databases/:name and
GET /api/v1/databases/:name/measurements require a token with the read
permission. Where reads are restricted per database, each asks a different
question:
GET /api/v1/databasesrequires a grant covering every database. A token scoped to particular ones is refused rather than given a partial list — the same barSHOW DATABASESapplies, and a scoped caller names its database explicitly.- The two per-database routes require only that the caller can read
something inside the named database, and
/:name/measurementsthen filters the names it returns, so it never discloses a measurement the caller cannot read.
See Telling the refusals apart below.
GET /api/v1/databases
List all databases with measurement counts.
Response:
{
"databases": [
{"name": "default", "measurement_count": 5},
{"name": "production", "measurement_count": 12}
],
"count": 2
}POST /api/v1/databases
Create a new database.
Request:
{
"name": "my_database"
}Response (201 Created):
{
"name": "my_database",
"measurement_count": 0,
"created_at": "2024-12-21T10:30:00Z"
}Validation rules:
- Must start with a letter (a-z, A-Z)
- Can contain letters, numbers, underscores, and hyphens
- Maximum 64 characters
- Reserved names blocked:
system,internal,_internal
Error Response (400):
{
"error": "Invalid database name: must start with a letter and contain only alphanumeric characters, underscores, or hyphens"
}GET /api/v1/databases/:name
Get information about a specific database.
Response:
{
"name": "production",
"measurement_count": 12
}Error Response (404):
{
"error": "Database 'nonexistent' not found"
}GET /api/v1/databases/:database/measurements/:measurement/schema
Registered field schema of a measurement (v26.09.2+): the columns and DuckDB
types every query binds for it regardless of time range. Needs read
permission on the measurement; 404 while no anchor exists. See the
stable field schema guide.
Response:
{
"database": "production",
"measurement": "cpu",
"fields": [
{"name": "time", "type": "TIMESTAMP WITH TIME ZONE"},
{"name": "host", "type": "VARCHAR"},
{"name": "usage", "type": "DOUBLE"}
]
}POST /api/v1/databases/:database/measurements/:measurement/schema/rebuild
Queue a rebuild of the registered schema from the measurement's files
(v26.09.2+, admin token). Returns 202 when queued, 409 while one is
already queued or running, 503 when the queue is full.
GET /api/v1/databases/:name/measurements
List all measurements in a database.
Response:
{
"database": "production",
"measurements": [
{"name": "cpu"},
{"name": "memory"},
{"name": "disk"}
],
"count": 3
}Errors on the read endpoints
| Status | Body | Meaning |
|---|---|---|
401 | {"success": false, "error": "Authentication required"} | No token was sent |
401 | {"success": false, "error": "Invalid or expired token"} | Re-authenticate |
403 | {"success": false, "error": "no permission for read on database 'analytics'"} | The token's grants do not cover that database. Empty name on GET /api/v1/databases, which names none |
403 | {"success": false, "error": "token does not have 'read' permission"} | The token has no read permission; a different token is needed |
403 | {"success": false, "error": "permission data unavailable"} | Arc could not load the token's grants and refused rather than guess. A server-side fault — check the Arc server log |
404 | {"error": "Database 'nonexistent' not found"} | No such database |
Telling the refusals apart
All three 403s share a status code and differ only in message. They need
different responses, so do not collapse them:
- "no permission for … on database" — a normal state for a scoped token, not a failure. Name a database the token is granted. An identical retry is refused identically.
- "token does not have … permission" — a different token is required. Naming a database will not help.
- "permission data unavailable" — nothing about the token or the database caused this.
On GET /api/v1/databases/:name the permission check runs before the
existence check, so a database the token has no grant inside answers 403
rather than 404. A 404 only comes back for a database the token can read
inside, which is what prevents the endpoint being used to enumerate names. A
database whose measurements are all filtered out answers 200 with an empty
list, not 404.
DELETE /api/v1/databases/:name
Delete a database and all its data.
Destructive operation
This operation is destructive and cannot be undone. Requires:
delete.enabled = truein configuration?confirm=truequery parameter
Request:
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
"http://localhost:8000/api/v1/databases/old_data?confirm=true"Response:
{
"message": "Database 'old_data' deleted successfully",
"files_deleted": 47
}Error Responses:
Delete disabled (403):
{
"error": "Delete operations are disabled. Set delete.enabled=true in arc.toml to enable."
}Missing confirmation (400):
{
"error": "Confirmation required. Add ?confirm=true to delete the database."
}Retention policies
POST /api/v1/retention
Create a retention policy.
Request:
{
"name": "30-day-retention",
"database": "default",
"measurement": "cpu",
"duration": "30d",
"schedule": "0 2 * * *"
}GET /api/v1/retention
List all retention policies.
GET /api/v1/retention/:id
Get a specific policy.
PUT /api/v1/retention/:id
Update a retention policy.
DELETE /api/v1/retention/:id
Delete a retention policy.
POST /api/v1/retention/:id/execute
Execute a policy manually.
GET /api/v1/retention/:id/executions
Get policy execution history.
Continuous queries
POST /api/v1/continuous_queries
Create a continuous query.
Request:
{
"name": "hourly-rollup",
"source_database": "default",
"source_measurement": "cpu",
"destination_database": "default",
"destination_measurement": "cpu_hourly",
"query": "SELECT time_bucket('1 hour', time) as time, host, AVG(usage) as avg_usage FROM default.cpu GROUP BY 1, 2",
"schedule": "0 * * * *"
}GET /api/v1/continuous_queries
List all continuous queries.
GET /api/v1/continuous_queries/:id
Get a specific continuous query.
PUT /api/v1/continuous_queries/:id
Update a continuous query.
DELETE /api/v1/continuous_queries/:id
Delete a continuous query.
POST /api/v1/continuous_queries/:id/execute
Execute a continuous query manually.
GET /api/v1/continuous_queries/:id/executions
Get execution history.
MQTT subscriptions
Available since v26.02.1
MQTT subscription management is available starting Arc v26.02.1.
Manage MQTT broker subscriptions for direct IoT data ingestion. See the MQTT Integration Guide for detailed usage.
POST /api/v1/mqtt/subscriptions
Create a new MQTT subscription.
Request:
{
"name": "factory-sensors",
"broker": "tcp://localhost:1883",
"topics": ["sensors/#"],
"database": "iot",
"qos": 1,
"auto_start": true
}Response (201 Created):
{
"id": "sub_abc123",
"name": "factory-sensors",
"broker": "tcp://localhost:1883",
"topics": ["sensors/#"],
"database": "iot",
"status": "running",
"created_at": "2026-02-01T10:00:00Z"
}Full options:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
name | string | Yes | - | Unique subscription name |
broker | string | Yes | - | Broker URL (tcp://, ssl://, ws://) |
topics | array | Yes | - | Topics to subscribe |
database | string | Yes | - | Target Arc database |
qos | int | No | 1 | QoS level: 0, 1, or 2 |
client_id | string | No | auto | MQTT client ID |
username | string | No | - | MQTT username |
password | string | No | - | MQTT password (encrypted at rest) |
tls_enabled | bool | No | false | Enable TLS/SSL |
tls_cert_path | string | No | - | Client certificate path |
tls_key_path | string | No | - | Client key path |
tls_ca_path | string | No | - | CA certificate path |
topic_mapping | object ({string: string}) | No | Per-topic target-database override ({"<topic>": "<database>"}); does not configure measurements or tags | |
auto_start | bool | No | true | Start on creation and server restart |
GET /api/v1/mqtt/subscriptions
List all MQTT subscriptions.
Response:
{
"subscriptions": [
{
"id": "sub_abc123",
"name": "factory-sensors",
"broker": "tcp://localhost:1883",
"status": "running"
}
],
"count": 1
}GET /api/v1/mqtt/subscriptions/:id
Get subscription details.
PUT /api/v1/mqtt/subscriptions/:id
Update a subscription. Subscription must be stopped first.
DELETE /api/v1/mqtt/subscriptions/:id
Delete a subscription. Subscription must be stopped first.
POST /api/v1/mqtt/subscriptions/:id/start
Start a stopped subscription.
Response:
{
"id": "sub_abc123",
"status": "running",
"message": "Subscription started"
}POST /api/v1/mqtt/subscriptions/:id/stop
Stop a running subscription.
POST /api/v1/mqtt/subscriptions/:id/restart
Restart a subscription (stop + start).
GET /api/v1/mqtt/subscriptions/:id/stats
Get statistics for a specific subscription.
Response:
{
"id": "sub_abc123",
"messages_received": 15420,
"bytes_received": 2458320,
"decode_errors": 0,
"last_message_at": "2026-02-01T10:30:15Z",
"topics": {
"sensors/temperature": 8500,
"sensors/humidity": 6920
}
}GET /api/v1/mqtt/stats
Aggregate statistics across all running subscriptions.
Response:
{
"status": "success",
"running_count": 2,
"subscriptions_stats": {
"sub_abc123": { ... },
"sub_def456": { ... }
}
}GET /api/v1/mqtt/health
MQTT service health check.
Response:
{
"status": "healthy",
"healthy": true,
"running_count": 2,
"connected_count": 2,
"disconnected_count": 0,
"service": "mqtt_subscriptions"
}Backup & restore
Admin-only endpoints for backing up and restoring Arc data, metadata, and configuration. Operations run asynchronously with progress tracking.
See Backup & Restore for full documentation.
| Method | Endpoint | Description |
|---|---|---|
POST | /api/v1/backup | Trigger a full backup (async, returns 202) |
GET | /api/v1/backup | List all available backups |
GET | /api/v1/backup/status | Progress of active operation |
GET | /api/v1/backup/:id | Get backup manifest |
DELETE | /api/v1/backup/:id | Delete a backup |
POST | /api/v1/backup/restore | Restore from a backup (async, requires confirm: true) |
# Create backup
curl -X POST "http://localhost:8000/api/v1/backup" \
-H "Authorization: Bearer $ARC_TOKEN"
# Poll progress
curl "http://localhost:8000/api/v1/backup/status" \
-H "Authorization: Bearer $ARC_TOKEN"
# Restore
curl -X POST "http://localhost:8000/api/v1/backup/restore" \
-H "Authorization: Bearer $ARC_TOKEN" \
-H "Content-Type: application/json" \
-d '{"backup_id": "backup-20260211-143022-a1b2c3d4", "confirm": true}'Response formats
Success response
{
"status": "success",
"data": [...],
"count": 10
}Error response
{
"error": "Error message"
}HTTP status codes
200- Success204- No Content (successful write)400- Bad Request401- Unauthorized403- Forbidden (requires admin)404- Not Found500- Internal Server Error
Rate limiting
Arc does not enforce rate limiting by default. For production deployments, consider:
- Reverse proxy rate limiting (Nginx, Traefik)
- API Gateway (AWS API Gateway, Kong)
- Application-level throttling
CORS
CORS is enabled by default with permissive settings. Configure via reverse proxy for production.
Best practices
1. Use MessagePack for writes
MessagePack is considerably faster than Line Protocol:
# Fast: MessagePack columnar
data = {"m": "cpu", "columns": {...}}
requests.post(url, data=msgpack.packb(data))
# Slower: Line Protocol text
data = "cpu,host=server01 usage=45.2"
requests.post(url, data=data)2. Batch your writes
Send multiple records per request:
# Good: Batch write
data = {
"m": "cpu",
"columns": {
"time": [t1, t2, t3, ...],
"host": [h1, h2, h3, ...],
"usage": [u1, u2, u3, ...]
}
}3. Use Arrow for large queries
For 10K+ rows, use the Arrow endpoint:
response = requests.post(url + "/api/v1/query/arrow", ...)
table = pa.ipc.open_stream(response.content).read_all()
df = table.to_pandas() # Zero-copy conversion4. Enable gzip compression
import gzip
compressed = gzip.compress(msgpack.packb(data))
requests.post(
url,
data=compressed,
headers={"Content-Encoding": "gzip", ...}
)Client libraries
Python (official SDK)
pip install arc-tsdb-client[all]import os
from arc_client import ArcClient
ARC_TOKEN = os.environ["ARC_TOKEN"]
with ArcClient(host="localhost", token=os.environ["ARC_TOKEN"]) as client:
client.write.write_columnar(
measurement="cpu",
columns={"time": [...], "host": [...], "usage": [...]},
)
df = client.query.query_pandas("SELECT * FROM default.cpu LIMIT 10")See Python SDK Documentation for full details.
Next steps
- Python SDK - Official Python client
- Getting Started - Quick start guide
- Configuration - Server configuration