Uploads a passive sensor batch as Parquet.
POST
/
v1
/
signals:batch
from datetime import datetime, timezone
from olira import OliraClient
client = OliraClient(api_key="YOUR_API_KEY")
handle = client.send_signals(
patient_id="8a4fde23-0f1b-4c2a-9d7e-b36c1a5f0e82",
sensor_type="accelerometer",
source_device="phone-imu-1",
sample_rate_hz=60.0,
records=[
{"ts": datetime(2026, 7, 26, 12, 0, 0, tzinfo=timezone.utc), "x": 0.1, "y": 0.0, "z": 9.8},
],
)
job = handle.wait()
using Olira;
using var client = new OliraClient(apiKey: "YOUR_API_KEY");
var handle = client.SendSignals(
patientId: "8a4fde23-0f1b-4c2a-9d7e-b36c1a5f0e82",
sensorType: "accelerometer",
sourceDevice: "phone-imu-1",
sampleRateHz: 60.0,
records:
[
new Dictionary<string, object?>
{
["ts"] = new DateTimeOffset(2026, 7, 26, 12, 0, 0, TimeSpan.Zero),
["x"] = 0.1,
["y"] = 0.0,
["z"] = 9.8,
},
]);
var job = handle.Wait();
POST /v1/signals:batch?patient_id=8a4fde23-0f1b-4c2a-9d7e-b36c1a5f0e82&sensor_type=accelerometer&source_device=phone-imu-1
Authorization: Bearer YOUR_API_KEY
Content-Type: application/vnd.apache.parquet
X-Content-SHA256: <sha256 of body>
# Large payloads use a presigned upload URL — PUT without Authorization — then commit the manifest.
SignalJob(
job_id="sig-job-001",
status="done",
records_written=1,
records_deduplicated=0,
)
{
"error": true,
"status_code": 401,
"error_type": "authentication_error",
"message": "Could not validate credentials",
"details": [
{
"type": "authentication_error",
"message": "Could not validate credentials"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 403,
"error_type": "authorization_error",
"message": "Insufficient OAuth scope for this endpoint",
"details": [
{
"type": "authorization_error",
"message": "Insufficient OAuth scope for this endpoint"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 404,
"error_type": "not_found_error",
"message": "Patient not found",
"details": [
{
"type": "not_found_error",
"message": "Patient not found"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 422,
"error_type": "validation_error",
"message": "Request validation failed (1 error)",
"details": [
{
"type": "missing",
"message": "Field required",
"field": "patient_id",
"location": ["body", "patient_id"],
"input_value": null
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 429,
"error_type": "server_error",
"message": "Rate limit exceeded",
"details": [
{
"type": "rate_limit",
"message": "Too many requests; retry after backoff"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 500,
"error_type": "internal_server_error",
"message": "An internal server error occurred",
"details": [
{
"type": "internal_server_error",
"message": "An unexpected error occurred while processing your request"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
olira.send_signals(
patient_id: str,
sensor_type: SignalSensorType | str,
source_device: str,
records: list[dict] | None = None,
parquet: bytes | None = None,
schema_version: str | None = None,
sample_rate_hz: float | None = None,
units: dict[str, str] | None = None,
timestamp_unit: str | None = None,
device_timezone: str | None = None,
) -> SignalJobHandle
client.SendSignals(
string patientId,
SignalSensorType | string sensorType,
string sourceDevice,
IReadOnlyList<dict>? records = null,
bytes? parquet = null,
string? schemaVersion = null,
double? sampleRateHz = null,
dict[string, string]? units = null,
string? timestampUnit = null,
string? deviceTimezone = null,
) // -> SignalJobHandle
sdk:event-log
Supported today: accelerometer, gyroscope, gps (more sensors will follow). Provide either records= (list of dicts with ts + measurement fields; requires pip install olira[signals]) or parquet= (pre-serialized bytes). The SDK hashes the payload and routes small bodies synchronously and larger bodies via a presigned upload. Returns a SignalJobHandle — call wait() or poll(). See the passive signals guide for absorb, features, and dedup policy.
Parameters
str
required
Olira patient id that owns the series.
SignalSensorType | str
required
One of “accelerometer”, “gyroscope”, “gps”.
str
required
Stable device id for the series (unique with patient + ts at the sink).
list[dict] | None
Measurement rows (each with ts plus sensor fields). Mutually exclusive with parquet=.
bytes | None
Pre-serialized Parquet bytes. Mutually exclusive with records=.
str | None
Sensor schema id (e.g. accelerometer@1). Defaults to latest for the sensor.
float | None
Nominal device sample rate stored with the batch metadata.
dict[str, str] | None
Per-field source units; server converts to canonical units.
str | None
When ts is epoch-encoded: ‘s’, ‘ms’, or ‘us’.
str | None
IANA timezone name; retains original UTC offset at collection.
Returns
SignalJobHandle — Poll/wait handle; .job is the current SignalJob snapshot.
str
Ingestion job id.
SignalJobStatus
received | processing | done | partial | failed.
int
Rows written after absorb.
int
Overlap skips during absorb (first write wins).
bool
True when the upload was a content-hash no-op.
Guide: /send-data/passive-signals.
On the bulk path, PUT Parquet bytes without the SDK Authorization header.
Raises
| Exception | When |
|---|---|
AuthError | HTTP 401 or 403 — invalid API key or insufficient OAuth scope for this endpoint. |
ValidationError | HTTP 400, 404, or 422 — malformed JSON, unknown patient, or validation failure (message includes response excerpt). |
ServerError | HTTP 409 (e.g. conflicting external identifier) or repeated 5xx after retries; includes status_code when applicable. |
RateLimitError | HTTP 429 — rate limited; check retry_after (seconds). |
NetworkError | Connection timeout, DNS failure, or read error after retries. |
from datetime import datetime, timezone
from olira import OliraClient
client = OliraClient(api_key="YOUR_API_KEY")
handle = client.send_signals(
patient_id="8a4fde23-0f1b-4c2a-9d7e-b36c1a5f0e82",
sensor_type="accelerometer",
source_device="phone-imu-1",
sample_rate_hz=60.0,
records=[
{"ts": datetime(2026, 7, 26, 12, 0, 0, tzinfo=timezone.utc), "x": 0.1, "y": 0.0, "z": 9.8},
],
)
job = handle.wait()
using Olira;
using var client = new OliraClient(apiKey: "YOUR_API_KEY");
var handle = client.SendSignals(
patientId: "8a4fde23-0f1b-4c2a-9d7e-b36c1a5f0e82",
sensorType: "accelerometer",
sourceDevice: "phone-imu-1",
sampleRateHz: 60.0,
records:
[
new Dictionary<string, object?>
{
["ts"] = new DateTimeOffset(2026, 7, 26, 12, 0, 0, TimeSpan.Zero),
["x"] = 0.1,
["y"] = 0.0,
["z"] = 9.8,
},
]);
var job = handle.Wait();
POST /v1/signals:batch?patient_id=8a4fde23-0f1b-4c2a-9d7e-b36c1a5f0e82&sensor_type=accelerometer&source_device=phone-imu-1
Authorization: Bearer YOUR_API_KEY
Content-Type: application/vnd.apache.parquet
X-Content-SHA256: <sha256 of body>
# Large payloads use a presigned upload URL — PUT without Authorization — then commit the manifest.
SignalJob(
job_id="sig-job-001",
status="done",
records_written=1,
records_deduplicated=0,
)
{
"error": true,
"status_code": 401,
"error_type": "authentication_error",
"message": "Could not validate credentials",
"details": [
{
"type": "authentication_error",
"message": "Could not validate credentials"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 403,
"error_type": "authorization_error",
"message": "Insufficient OAuth scope for this endpoint",
"details": [
{
"type": "authorization_error",
"message": "Insufficient OAuth scope for this endpoint"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 404,
"error_type": "not_found_error",
"message": "Patient not found",
"details": [
{
"type": "not_found_error",
"message": "Patient not found"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 422,
"error_type": "validation_error",
"message": "Request validation failed (1 error)",
"details": [
{
"type": "missing",
"message": "Field required",
"field": "patient_id",
"location": ["body", "patient_id"],
"input_value": null
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 429,
"error_type": "server_error",
"message": "Rate limit exceeded",
"details": [
{
"type": "rate_limit",
"message": "Too many requests; retry after backoff"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 500,
"error_type": "internal_server_error",
"message": "An internal server error occurred",
"details": [
{
"type": "internal_server_error",
"message": "An unexpected error occurred while processing your request"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
⌘I
from datetime import datetime, timezone
from olira import OliraClient
client = OliraClient(api_key="YOUR_API_KEY")
handle = client.send_signals(
patient_id="8a4fde23-0f1b-4c2a-9d7e-b36c1a5f0e82",
sensor_type="accelerometer",
source_device="phone-imu-1",
sample_rate_hz=60.0,
records=[
{"ts": datetime(2026, 7, 26, 12, 0, 0, tzinfo=timezone.utc), "x": 0.1, "y": 0.0, "z": 9.8},
],
)
job = handle.wait()
using Olira;
using var client = new OliraClient(apiKey: "YOUR_API_KEY");
var handle = client.SendSignals(
patientId: "8a4fde23-0f1b-4c2a-9d7e-b36c1a5f0e82",
sensorType: "accelerometer",
sourceDevice: "phone-imu-1",
sampleRateHz: 60.0,
records:
[
new Dictionary<string, object?>
{
["ts"] = new DateTimeOffset(2026, 7, 26, 12, 0, 0, TimeSpan.Zero),
["x"] = 0.1,
["y"] = 0.0,
["z"] = 9.8,
},
]);
var job = handle.Wait();
POST /v1/signals:batch?patient_id=8a4fde23-0f1b-4c2a-9d7e-b36c1a5f0e82&sensor_type=accelerometer&source_device=phone-imu-1
Authorization: Bearer YOUR_API_KEY
Content-Type: application/vnd.apache.parquet
X-Content-SHA256: <sha256 of body>
# Large payloads use a presigned upload URL — PUT without Authorization — then commit the manifest.
SignalJob(
job_id="sig-job-001",
status="done",
records_written=1,
records_deduplicated=0,
)
{
"error": true,
"status_code": 401,
"error_type": "authentication_error",
"message": "Could not validate credentials",
"details": [
{
"type": "authentication_error",
"message": "Could not validate credentials"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 403,
"error_type": "authorization_error",
"message": "Insufficient OAuth scope for this endpoint",
"details": [
{
"type": "authorization_error",
"message": "Insufficient OAuth scope for this endpoint"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 404,
"error_type": "not_found_error",
"message": "Patient not found",
"details": [
{
"type": "not_found_error",
"message": "Patient not found"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 422,
"error_type": "validation_error",
"message": "Request validation failed (1 error)",
"details": [
{
"type": "missing",
"message": "Field required",
"field": "patient_id",
"location": ["body", "patient_id"],
"input_value": null
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 429,
"error_type": "server_error",
"message": "Rate limit exceeded",
"details": [
{
"type": "rate_limit",
"message": "Too many requests; retry after backoff"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}
{
"error": true,
"status_code": 500,
"error_type": "internal_server_error",
"message": "An internal server error occurred",
"details": [
{
"type": "internal_server_error",
"message": "An unexpected error occurred while processing your request"
}
],
"timestamp": "2026-05-06T12:00:00+00:00"
}

