MT2Data Platform
MT2 Data offers customized solutions to apply machine learning in the agricultural sector. Our platform connects your operations to leading agronomic data providers through a unified, secure API.
Products
Colmeia
Colmeia is the integration hub that connects your application to agricultural data providers. It exposes a unified REST API that normalises data from multiple providers behind a single authentication model.
Supported integrations:
- John Deere — equipment, fields, and operations data
- Syngenta Cropwise — properties, crops, and monitoring
- Solinftec — data integration
All endpoints return a standardised JSON envelope:
{ "success": true, "data": { /* ... */ } }
{ "success": false, "error": "<snake_case_code>" }
Base URL: https://colmeia.mt2data.cloud/api
Datalake Gateway
The MT2Data Datalake Gateway provides access to the platform's agronomic data lake via OAuth 2.0 Client Credentials. When using Colmeia, token management is handled automatically on your behalf.
Base URL: https://datalake.mt2data.cloud
Getting started
Access credentials and onboarding details are provided during the integration handoff with the MT2Data team. Once you have your credentials, refer to the product guides in this documentation to start making API calls.
Colmeia Platform
Colmeia is the API middleware layer for the MT2Data agricultural data platform. It provides a unified, tenant-scoped interface for accessing farm management data from multiple integrated providers.
Public base URL: https://colmeia.mt2data.cloud/api
What Colmeia provides
- Provider integrations — John Deere Operations Center, Syngenta Cropwise, Solinftec SCDI, and MT2Data Datalake
- Transparent proxying — all provider authentication is handled server-side; you only need a tenant bearer secret
- Mobile access — WhatsApp/bot interface for field-level data queries
Authentication
Your tenant ID and bearer secret are provisioned by MT2Data. Include the bearer secret in every request:
Authorization: Bearer <tenant-secret>
Quick links
- Middleware API — connection status, response envelope
- John Deere Integration — fields, equipment, operations, telemetry
- Syngenta Cropwise Integration — farm properties, monitoring
- Solinftec Integration — field operations, refueling, weather
- MT2Data Datalake via Colmeia — proxied datalake file access
Custom Integrations
Overview
Custom integrations provide access to client-specific data sources that are not part of the standard provider catalog. Each integration is provisioned individually by MT2Data and requires authentication for access.
Base URL: https://colmeia.mt2data.cloud/api/<CLIENT_SLUG>
Authentication
Two methods are accepted — use whichever fits the context:
Bearer secret (recommended for programmatic access):
Authorization: Bearer <tenant-secret>
The tenant secret is the long-lived key shown on the Integrations page after rotating.
Session cookie (dashboard / browser):
Cookie: colmeia_session=<session-token>
Access is also gated per tenant. If the manager has disabled this integration for a specific tenant, the endpoint returns integration_disabled.
Large numbers and precision
All SQL Server integrations (PIMS, Protheus, Farmtell, and any future bridge built on the same stack) apply the same precision-safe serialisation rule. Read this once and the contract holds for every bridge.
Why
SQL Server columns declared NUMERIC(p, s) / DECIMAL(p, s) with p > 15, and BIGINT values outside the safe-integer range (|v| > 2^53 − 1 = 9007199254740991), do not fit in a JavaScript / JSON number without losing trailing digits. For example, an Oracle-style NUMBER(22) column stored as 30448901015033924 would be silently rounded to 30448901015033920 by a naive JSON serialiser, and the resulting filter WHERE id = 30448901015033920 returns zero rows.
To prevent silent corruption, the bridge returns affected values as JSON strings.
Rules
| SQL Server type / shape | JSON type returned |
|---|---|
NUMERIC(p, s) / DECIMAL(p, s) with p ≤ 15 | number (unchanged) |
NUMERIC(p, s) / DECIMAL(p, s) with p > 15, s = 0 | string — full integer, e.g. "30448901015033924" |
NUMERIC(p, s) / DECIMAL(p, s) with p > 15, s > 0 | string — full decimal, e.g. "3044890101503.3924" (negatives prefixed "-") |
BIGINT (SQLINT8) | string in all cases — the driver already serialises bigints as strings end-to-end |
INT, SMALLINT, TINYINT, FLOAT, REAL, MONEY, SMALLMONEY | number (no change) |
Precision is column metadata, not value-level. A small value like 101 stored in a NUMERIC(22, 0) column still arrives as the string "101". Treat the column as string-typed across the entire result set.
How to handle the values
{
"success": true,
"data": [
{ "ID_UPNIVEL3": "101", "ID_INSUMO": "30448901015033924" }
]
}
Do:
- Keep the value as a string in your application memory. Use it directly in equality / filter round-trips.
- Pass the literal back into SQL Server either as a raw integer literal (
WHERE id = 30448901015033924) or as a quoted string (WHERE CAST(id AS NVARCHAR(40)) = '30448901015033924'). Both work; SQL Server coerces. - For sorting or grouping in the application layer, sort as strings only when every value has the same length and zero-padding; otherwise convert to
BigInt(JavaScript) /decimal.Decimal(Python) /BigInteger(Java) etc.
Do not:
- Call
parseFloat(value),Number(value), or+valueon these strings. That re-introduces the precision loss the bridge just prevented. - Concatenate filter values with
+in a way that coerces to number before the SQL round-trip.
Worked example
Two-step round-trip — fetch an ID, then use it as a filter — demonstrates that no precision is lost in the proxy chain:
# 1. Fetch a bigint ID
curl -s "https://colmeia.mt2data.cloud/api/<CLIENT_SLUG>/data/<TENANT_ID>/query" \
-H "Authorization: Bearer <tenant-secret>" \
-H "Content-Type: application/json" \
-d '{ "query": "SELECT TOP 1 ID_INSUMO FROM <table>" }'
# → { "success": true, "data": [ { "ID_INSUMO": "30448901015033924" } ] }
# 2. Filter by the exact value returned (raw literal)
curl -s "https://colmeia.mt2data.cloud/api/<CLIENT_SLUG>/data/<TENANT_ID>/query" \
-H "Authorization: Bearer <tenant-secret>" \
-H "Content-Type: application/json" \
-d '{ "query": "SELECT COUNT(*) AS n FROM <table> WHERE ID_INSUMO = 30448901015033924" }'
# → { "success": true, "data": [ { "n": 1 } ] } ← matches, full precision preserved
PIMS ERP
Execute SQL queries against a PIMS ERP SQL Server database.
POST /api/<CLIENT_SLUG>/data/:tenantId/query
Request body:
| Field | Required | Type |
|---|---|---|
query | Yes | string — SQL query to execute |
Simple query
curl -s \
"https://colmeia.mt2data.cloud/api/<CLIENT_SLUG>/data/YOUR_TENANT_ID/query" \
-H "Authorization: Bearer <tenant-secret>" \
-H "Content-Type: application/json" \
-d '{ "query": "SELECT TOP 10 * FROM <table_name>" }'
Multiline query
Save the SQL to a file (query.sql), then use jq to build the JSON payload:
-- query.sql
SELECT TOP 10
<columns>
FROM <main_table> M
INNER JOIN <related_table> R ON R.<fk> = M.<pk>
LEFT JOIN <optional_table> O ON O.<fk> = M.<pk>
WHERE M.<date_column> >= '2026-01-01'
ORDER BY M.<date_column> DESC
curl -s \
"https://colmeia.mt2data.cloud/api/<CLIENT_SLUG>/data/YOUR_TENANT_ID/query" \
-H "Authorization: Bearer <tenant-secret>" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile q query.sql '{"query": $q}')"
jq handles newlines, quotes, and special characters automatically. Install with brew install jq or apt install jq.
Consult the PIMS schema documentation provisioned to your tenant for table and column names.
Response:
{
"success": true,
"data": [
{ "<column_a>": "<value_a>", "<column_b>": "<value_b>" }
]
}
Error responses:
| Error | Status | Meaning |
|---|---|---|
unauthorized | 401 | Missing or invalid credentials |
forbidden | 403 | Tenant not owned by this manager |
integration_disabled | 403 | Integration disabled for this tenant |
missing_query | 400 | Request body missing query field |
invalid_json | 400 | Malformed request body |
db_error | 500 | Bridge could not execute the query |
TOTVS Protheus ERP
Execute SQL queries against a TOTVS Protheus SQL Server database.
POST /api/<CLIENT_SLUG>/data/:tenantId/query
Request body:
| Field | Required | Type |
|---|---|---|
query | Yes | string — SQL query to execute |
Simple query
curl -s \
"https://colmeia.mt2data.cloud/api/<CLIENT_SLUG>/data/YOUR_TENANT_ID/query" \
-H "Authorization: Bearer <tenant-secret>" \
-H "Content-Type: application/json" \
-d '{ "query": "SELECT TOP 10 * FROM <table_name>" }'
Multiline query
Save the SQL to a file (query.sql), then use jq to build the JSON payload:
-- query.sql
SELECT TOP 10
<columns>
FROM <main_table> M
INNER JOIN <related_table> R ON R.<fk> = M.<pk>
AND R.D_E_L_E_T_ = ' '
WHERE M.D_E_L_E_T_ = ' '
AND M.<date_column> >= '20260101'
ORDER BY M.<date_column> DESC
curl -s \
"https://colmeia.mt2data.cloud/api/<CLIENT_SLUG>/data/YOUR_TENANT_ID/query" \
-H "Authorization: Bearer <tenant-secret>" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile q query.sql '{"query": $q}')"
jq handles newlines, quotes, and special characters automatically. Install with brew install jq or apt install jq.
Protheus tables follow the pattern
<table_code><company_suffix>(e.g.,<table_code>01). Always filterD_E_L_E_T_ = ' 'to exclude logically deleted records. Consult the Protheus schema documentation provisioned to your tenant for table and column names.
Response:
{
"success": true,
"data": [
{ "<column_a>": "<value_a>", "<column_b>": "<value_b>" }
]
}
Error responses:
| Error | Status | Meaning |
|---|---|---|
unauthorized | 401 | Missing or invalid credentials |
forbidden | 403 | Tenant not owned by this manager |
integration_disabled | 403 | Integration disabled for this tenant |
missing_query | 400 | Request body missing query field |
invalid_json | 400 | Malformed request body |
db_error | 500 | Bridge could not execute the query |
Farmtell
Execute SQL queries against a Farmtell SQL Server database.
POST /api/<CLIENT_SLUG>/data/:tenantId/query
Request body:
| Field | Required | Type |
|---|---|---|
query | Yes | string — SQL query to execute |
Simple query
curl -s \
"https://colmeia.mt2data.cloud/api/<CLIENT_SLUG>/data/YOUR_TENANT_ID/query" \
-H "Authorization: Bearer <tenant-secret>" \
-H "Content-Type: application/json" \
-d '{ "query": "SELECT TOP 10 * FROM <table_name>" }'
Multiline query
Save the SQL to a file (query.sql), then use jq to build the JSON payload:
-- query.sql
SELECT TOP 10
<columns>
FROM <main_table> M
INNER JOIN <related_table> R ON R.<fk> = M.<pk>
LEFT JOIN <optional_table> O ON O.<fk> = M.<pk>
WHERE M.<date_column> >= '2026-01-01'
ORDER BY M.<date_column> DESC
curl -s \
"https://colmeia.mt2data.cloud/api/<CLIENT_SLUG>/data/YOUR_TENANT_ID/query" \
-H "Authorization: Bearer <tenant-secret>" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile q query.sql '{"query": $q}')"
jq handles newlines, quotes, and special characters automatically. Install with brew install jq or apt install jq.
Table and column names may vary across Farmtell deployments. Consult the schema of the specific database provisioned to your tenant.
Response:
{
"success": true,
"data": [
{ "<column_a>": "<value_a>", "<column_b>": "<value_b>" }
]
}
Error responses:
| Error | Status | Meaning |
|---|---|---|
unauthorized | 401 | Missing or invalid credentials |
forbidden | 403 | Tenant not owned by this manager |
integration_disabled | 403 | Integration disabled for this tenant |
missing_query | 400 | Request body missing query field |
invalid_json | 400 | Malformed request body |
db_error | 500 | Bridge could not execute the query |
MT2Data Datalake via Colmeia
Overview
Access MT2Data Datalake files through the Colmeia middleware. Your Datalake credentials are connected to your tenant by MT2Data — you only need your tenant bearer secret to make requests.
Base URL: https://colmeia.mt2data.cloud/api/dl
Authentication
All data routes require your tenant bearer secret:
Authorization: Bearer <tenant-secret>
The middleware handles Datalake OAuth token management automatically.
Catalog
GET /api/dl/data/:tenantId/catalog
Returns all Symbols your Datalake subscription is authorized to access, with descriptions and file paths. Fast (~1s).
curl https://colmeia.mt2data.cloud/api/dl/data/YOUR_TENANT_ID/catalog \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
{
"clientId": "your-account",
"files": [
{
"Symbol": "DatasetSymbol",
"Description": "Human-readable description",
"FileName": "Data/MT2/DatasetSymbol.parquet"
}
],
"count": 1
}
File Listing
GET /api/dl/data/:tenantId/list
Lists files that appear in your catalog and exist in storage — with size and upload timestamp. Uses batched verification (~2s for 500 files).
| Parameter | Required | Default | Description |
|---|---|---|---|
limit | No | 1000 | Max files to return |
curl "https://colmeia.mt2data.cloud/api/dl/data/YOUR_TENANT_ID/list?limit=100" \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
GET /api/dl/data/:tenantId/count
Returns the total count of accessible files (~2s).
curl https://colmeia.mt2data.cloud/api/dl/data/YOUR_TENANT_ID/count \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
File Access
GET /api/dl/data/:tenantId/info/:symbol
Returns metadata for a file by Symbol (~0.5s).
curl https://colmeia.mt2data.cloud/api/dl/data/YOUR_TENANT_ID/info/DatasetSymbol \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
GET /api/dl/data/:tenantId/download/:symbol
Downloads a file by Symbol. Returns binary content.
curl https://colmeia.mt2data.cloud/api/dl/data/YOUR_TENANT_ID/download/DatasetSymbol \
-H "Authorization: Bearer YOUR_TENANT_SECRET" \
-o dataset.parquet
Performance Reference
| Endpoint | Speed | Use Case |
|---|---|---|
/catalog | ~1s | Browse available Symbols |
/info/:symbol | ~0.5s | File metadata |
/download/:symbol | ~0.5s + transfer | Download a file |
/list | ~2s | Verify file existence with metadata |
/count | ~2s | Count accessible files |
Recommended workflow:
GET /catalog— find the SymbolGET /download/:symbol— download directly
Code Examples
Python
import requests
BASE = "https://colmeia.mt2data.cloud/api/dl/data"
TENANT = "YOUR_TENANT_ID"
HEADERS = {"Authorization": "Bearer YOUR_TENANT_SECRET"}
# Browse catalog
catalog = requests.get(f"{BASE}/{TENANT}/catalog", headers=HEADERS).json()
print(f"Symbols available: {catalog['count']}")
# Download a file
with requests.get(f"{BASE}/{TENANT}/download/DatasetSymbol", headers=HEADERS, stream=True) as r:
r.raise_for_status()
with open("dataset.parquet", "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
JavaScript / Node.js
const BASE = 'https://colmeia.mt2data.cloud/api/dl/data/YOUR_TENANT_ID';
const HEADERS = { Authorization: 'Bearer YOUR_TENANT_SECRET' };
const catalog = await fetch(`${BASE}/catalog`, { headers: HEADERS }).then(r => r.json());
console.log(`Symbols: ${catalog.count}`);
const fileData = await fetch(`${BASE}/download/DatasetSymbol`, { headers: HEADERS })
.then(r => r.arrayBuffer());
require('fs').writeFileSync('dataset.parquet', Buffer.from(fileData));
Error Codes
| HTTP | Code | Meaning |
|---|---|---|
| 401 | no_token_found | Your Datalake account is not connected — contact MT2Data |
| 403 | upstream Access denied | Symbol not in your subscription |
| 404 | upstream Not found | File does not exist |
John Deere Integration
Overview
Access agricultural data from John Deere Operations Center through the Colmeia middleware. Your John Deere account is connected to your tenant by MT2Data — you only need your tenant bearer secret to make requests.
Base URL: https://colmeia.mt2data.cloud/api/jd
Authentication
All data routes require your tenant bearer secret:
Authorization: Bearer <tenant-secret>
Pagination
John Deere uses HATEOAS pagination. Responses include a links array where a nextPage entry signals additional pages. The middleware rewrites all uri values in links to point through itself, so you can follow pagination links without a JD token:
{
"total": 153,
"links": [
{ "rel": "self", "uri": "https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/flag-categories" },
{ "rel": "nextPage", "uri": "https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/flag-categories?pageOffset=10&itemLimit=10" }
],
"values": [...]
}
Follow the nextPage URI directly — no additional auth headers required.
Organizations & Farms
Get Organizations
Endpoint: GET /jd/data/:tenantId/organizations
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Get Single Organization
Endpoint: GET /jd/data/:tenantId/organizations/:orgId
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Get Organization Settings
Endpoint: GET /jd/data/:tenantId/organizations/:orgId/settings
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/settings \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Get Farms for Organization
Endpoint: GET /jd/data/:tenantId/organizations/:orgId/farms
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/farms \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Fields & Boundaries
Get Fields for Organization
Endpoint: GET /jd/data/:tenantId/organizations/:orgId/fields
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/fields \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
List Boundaries for Organization
Endpoint: GET /jd/data/:tenantId/organizations/:orgId/boundaries
List Boundaries for Field
Endpoint: GET /jd/data/:tenantId/organizations/:orgId/fields/:fieldId/boundaries
Get Boundary by ID
Endpoint: GET /jd/data/:tenantId/organizations/:orgId/fields/:fieldId/boundaries/:boundaryId
Get Field Operation Boundary
Endpoint: GET /jd/data/:tenantId/field-operations/:operationId/boundary
Equipment
Note: The deprecated
/machinesand/implementsendpoints were removed by John Deere on March 15, 2025. Use the Equipment API routes below. Scopeeq1is required.
Get Equipment for Organization
Endpoint: GET /jd/data/:tenantId/organizations/:orgId/equipment
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/equipment \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Response:
{
"total": 1,
"values": [
{
"@type": "Machine",
"id": "equipment-id-123",
"name": "Tractor Model 2022",
"modelYear": 2022,
"links": [...]
}
]
}
Get Equipment by ID
Endpoint: GET /jd/data/:tenantId/equipment/:equipmentId
Get Field Operations for Organization
Endpoint: GET /jd/data/:tenantId/organizations/:orgId/field-operations
Machine Telemetry
These endpoints require the machine to be a connected JD telematics device (active modem). Machines without an active modem return
404or410— this is expected JD behavior, not a middleware error. Check thetelematicsCapablefield on equipment records as an indicator.
Device State Reports
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/machines/MACHINE_ID/device-state-reports \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Machine Alerts
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/machines/MACHINE_ID/alerts \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Location History
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/machines/MACHINE_ID/location-history \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Breadcrumbs
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/machines/MACHINE_ID/breadcrumbs \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Hours of Operation
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/machines/MACHINE_ID/hours-of-operation \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Engine Hours
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/machines/MACHINE_ID/engine-hours \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Assets
List Assets for Organization
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/assets \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Get Asset by ID
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/assets/ASSET_ID \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Get Asset Locations
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/assets/ASSET_ID/locations \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Asset Catalog
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/asset-catalog \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Flags & Preferences
List Flags for Organization
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/flags \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
List Flag Categories
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/flag-categories \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
List Flags for Field
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/fields/FIELD_ID/flags \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Products & Agronomic Catalog
Chemicals
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/chemicals \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Fertilizers
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/fertilizers \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Varieties
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/varieties \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Active Ingredients
Served by the JD Equipment API (scope
eq1required).
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/active-ingredients \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Crop Types
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/crop-types \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Files & Map Layers
Files
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/files \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Map Layer Summaries
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/fields/FIELD_ID/map-layer-summaries \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Map Layers
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/map-layers/LAYER_ID \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Guidance Lines
curl https://colmeia.mt2data.cloud/api/jd/data/YOUR_TENANT_ID/organizations/YOUR_ORG_ID/fields/FIELD_ID/guidance-lines \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Code Example
const TENANT_ID = 'YOUR_TENANT_ID';
const BASE = 'https://colmeia.mt2data.cloud/api';
const headers = { Authorization: 'Bearer YOUR_TENANT_SECRET' };
// List organizations
const orgs = await fetch(`${BASE}/jd/data/${TENANT_ID}/organizations`, { headers })
.then(r => r.json());
// Get fields for an org
const orgId = orgs.values[0].id;
const fields = await fetch(`${BASE}/jd/data/${TENANT_ID}/organizations/${orgId}/fields`, { headers })
.then(r => r.json());
console.log(`Found ${fields.values?.length} fields`);
Scope Reference
| Scope | Description |
|---|---|
| org1 | View staff, operators, and partners |
| eq1 | View equipment data, machine locations |
| ag1 | View locations, farms, fields |
| ag2 | View + analyze production data, field operations |
| offline_access | Long-term token access |
Error Codes
| Code | Meaning |
|---|---|
no_token_found | Your John Deere account is not connected — contact MT2Data |
org_disabled | This organization has been disabled for your tenant |
Middleware API
Overview
The Colmeia middleware provides a unified API for accessing agricultural data from connected providers. Each tenant has a bearer secret that authenticates all data requests.
Base URL: https://colmeia.mt2data.cloud/api
Authentication
All data routes require a tenant bearer secret in the Authorization header:
Authorization: Bearer <tenant-secret>
Tenant secrets are provisioned by MT2Data and can be rotated through the dashboard. Contact support if you need a new secret.
Connection Status
GET /api/data/:tenantId/connections
Returns the connection state for all providers configured for your tenant. Use this as a pre-flight check before making data requests.
curl https://colmeia.mt2data.cloud/api/data/YOUR_TENANT_ID/connections \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Response:
{
"success": true,
"data": {
"tenant_id": "your-tenant-id",
"connections": [
{ "provider": "john_deere", "connected": true, "expired": false, "expires_at": 1714086400, "auto_renew": true },
{ "provider": "syngenta", "connected": true, "expired": false },
{ "provider": "solinftec", "connected": true, "expired": false, "expires_at": 2092433279, "auto_renew": true },
{ "provider": "datalake", "connected": true, "expired": false, "expires_at": 1714090000, "auto_renew": true },
{ "provider": "pims", "connected": true, "expired": false, "custom": true, "client_slug": "<your_pims_slug>", "display_name": "<your_pims_label>" },
{ "provider": "protheus", "connected": true, "expired": false, "custom": true, "client_slug": "<your_protheus_slug>", "display_name": "<your_protheus_label>" },
{ "provider": "farmtell", "connected": true, "expired": false, "custom": true, "client_slug": "<your_farmtell_slug>", "display_name": "<your_farmtell_label>" },
{ "provider": "solinftec", "connected": true, "expired": false, "snowflake": true, "source_id": "solinftec", "enabled": true, "data_path": "/api/sn/data/your-tenant-id/solinftec/query" }
]
}
}
A connected: false provider means that integration has not been configured for your tenant yet. Contact MT2Data to enable it.
Entries with "custom": true represent custom SQL integrations (PIMS, Protheus, Farmtell, ...). provider is the integration type, client_slug is the per-tenant routing slug used by the data endpoint (/api/<client_slug>/data/<tenant_id>/query). A custom integration only appears here when it is enabled for your tenant.
Entries with "snowflake": true are Snowflake-routed connectors. source_id is the catalog slug for the external system reached over Snowflake (solinftec, …); data_path is the full URL to POST queries against — see snowflake.md. The provider field equals source_id, which can collide with a fixed-provider id (e.g. solinftec is also a first-class provider mounted under /api/sl/data/...). Both rows can appear in the same payload — they represent two distinct integrations and agents disambiguate by the snowflake: true flag plus the data_path URL.
Route Discovery
GET /api/
Returns the full list of available routes. No auth required.
curl https://colmeia.mt2data.cloud/api/
Response Envelope
All endpoints return a consistent JSON envelope:
{ "success": true, "data": { ... } }
{ "success": false, "error": "snake_case_error_code" }
Client-facing error codes
| Code | HTTP | Meaning |
|---|---|---|
unauthorized | 401 | Missing or invalid tenant bearer secret |
unsupported_provider | 400 | Unknown provider ID |
phone_not_linked | 404 | Phone number not provisioned to any tenant |
Snowflake Connectors
Overview
Snowflake connectors let a manager attach one or more known external systems (catalogued by MT2Data) to a tenant and run SQL against them through the Colmeia API. Authentication to Snowflake uses key-pair JWT — Colmeia generates the keypair when the connector is created, displays the public key for one-time enrollment by the Snowflake account administrator, and signs JWTs from the dashboard side so your scripts only ever use your Colmeia tenant bearer.
Each connector is keyed by a Snowflake source slug drawn from a static catalog curated by MT2Data (currently: solinftec). A tenant can attach at most one connector per source slug, with its own keypair, account, role, warehouse, database, and schema defaults. The slug is the path segment used in the data URL.
Base URL: https://colmeia.mt2data.cloud/api/sn
Setup (one-time, in the dashboard)
-
Sign in at
https://colmeia.mt2data.cloud/login. -
Open the tenant page → Integrations.
-
Scroll to the Snowflake Connectors section → click + Add Snowflake connection.
-
Fill the form:
Field Required Description Snowflake source Yes Dropdown of catalogued external systems available for this tenant. Pick the source you want to integrate (currently Solinftec). Sources already connected for this tenant are disabled in the dropdown. The selected source's slug becomes the path segment in the data URL (/api/sn/data/:tenantId/:sourceId/query). Picking a source may pre-fill the Warehouse / Database / Schema / Role fields — those defaults are suggestions you can edit.Account locator Yes Bare Snowflake account locator (not org-account form). Get it by running SELECT CURRENT_ACCOUNT()in Snowflake — returns something likePS07064. TheORG-ACCOUNTform (e.g.SOLINFTEC-EASTUS2) is rejected by JWT auth on non-AWS-US regions; use the locator. For Azure / non-AWS-US accounts you may need region-qualified formLOCATOR.REGION.CLOUD(e.g.PS07064.EAST-US-2.AZURE) — try the bare locator first.Account URL Yes Snowflake URL for your account (e.g. https://ps07064.east-us-2.azure.snowflakecomputing.com).Username (LOGIN_NAME) Yes The user's LOGIN_NAME(not itsNAME), uppercased. Snowflake's JWT verifier maps thesubclaim tologin_name, not the user identifier. To get it, runDESC USER "<your_user>"in Snowflake and copy theLOGIN_NAMErow value verbatim. If the user was created without an explicitLOGIN_NAME, this defaults to the user'sNAMEin uppercase.Warehouse No Default warehouse for queries that don't override it. Must have AUTO_RESUME = TRUE— programmatic access cannot wake a suspended warehouse. Check withSHOW WAREHOUSES LIKE '<wh>'; admin must runALTER WAREHOUSE <wh> SET AUTO_RESUME = TRUEif it'sfalse.Role No Default role. Database / Schema No Default database and schema. -
Submit. Colmeia generates a fresh RSA-2048 keypair tied to this connector and shows you an
ALTER USER … SET RSA_PUBLIC_KEY=…command. The status reads Pending admin enrollment. -
Forward the
ALTER USER …command to the Snowflake account administrator (see the message template below). They run it once. Key-pair JWT authentication is not subject to DUO/MFA, so no human interaction is needed afterwards. -
After the admin confirms enrollment, return to the connector card and click Test connection. Status flips to Connected and the connector is ready for queries.
You can later Disable (pauses queries while keeping the row), Enable, or Revoke (deletes the connector and its private key from Colmeia) a connector at any time. Revoking inside Colmeia does not revoke the public key on the Snowflake side — ask the admin to run ALTER USER "<USER>" UNSET RSA_PUBLIC_KEY; if you also want to remove it there.
Worked example — Solinftec
The dashboard accepts these values:
| Field | Value |
|---|---|
| Snowflake source | Solinftec (catalog entry — pre-fills warehouse/database/schema below) |
| Account locator | PS07064 (from SELECT CURRENT_ACCOUNT()) |
| Account URL | https://ps07064.east-us-2.azure.snowflakecomputing.com |
| Username (LOGIN_NAME) | SAMUEL@MT2.PAGE (the LOGIN_NAME from DESC USER, not the user NAME) |
| Warehouse | SSWH_0307 (catalog default, editable; verify AUTO_RESUME = TRUE) |
| Database | SOLINFTEC_BI (catalog default, editable) |
| Schema | SS (catalog default, editable) |
| Role | (optional — leave blank or use the role Solinftec granted) |
After saving, the dashboard shows the data URL as /api/sn/data/<your-tenant-id>/solinftec/query.
After submit, Colmeia shows a command similar to:
ALTER USER "<YOUR_USER>" SET RSA_PUBLIC_KEY='MIIBIjANBgkqhkiG9w0BAQEFAAOC...';
(The base64 payload is unique to this connector.)
Admin enrollment message (template)
Send to the Snowflake administrator by a secure channel:
Hello,
To allow programmatic access from Colmeia using key-pair JWT authentication (which is not subject to DUO MFA), please run the following SQL once as
SECURITYADMINorACCOUNTADMIN. Note: theALTER USERquoted identifier below must match the user'sNAME(case-sensitive). If the user was created with an explicitLOGIN_NAMEthat differs from theNAME, replace"<YOUR_USER>"with the actualNAMEshown byDESC USER.ALTER USER "<YOUR_USER>" SET RSA_PUBLIC_KEY='<base64 shown in the Colmeia dashboard>';Then confirm the fingerprint and the
LOGIN_NAME:DESC USER "<YOUR_USER>";The
RSA_PUBLIC_KEY_FPvalue should match the fingerprint shown on the Colmeia connector card. Please also reply with the user'sLOGIN_NAMErow value — that's what Colmeia must use in the connector'sUsernamefield (Snowflake's JWT verifier matches thesubclaim againstLOGIN_NAME, notNAME).Additional checks:
- If the warehouse used by this user has
AUTO_RESUME = FALSE, please enable auto-resume so programmatic queries can wake it:SHOW WAREHOUSES LIKE '<WH_NAME>'; -- if auto_resume = false: ALTER WAREHOUSE <WH_NAME> SET AUTO_RESUME = TRUE;- Confirm that any
AUTHENTICATION POLICYattached to this user includes'KEYPAIR_JWT', and that noNETWORK POLICYblocks Cloudflare egress (egress IPs are not fixed). If a network policy is active,NETWORK_POLICY_EVALUATION = NOT_ENFORCEDon the auth policy resolves it.Thanks.
Query API
POST /api/sn/data/:tenantId/:sourceId/query
Execute a free-form SQL statement against the connector's Snowflake account.
Path parameters:
| Parameter | Description |
|---|---|
tenantId | Your Colmeia tenant ID. |
sourceId | Slug from the Snowflake source catalog (e.g. solinftec). The set of valid values per tenant is whatever the manager has connected — see GET /api/data/:tenantId/connections and look for entries with "snowflake": true. |
Authentication — either:
Authorization: Bearer <tenant-secret>
or a dashboard session cookie:
Cookie: colmeia_session=<session-token>
Request body:
| Field | Required | Type | Description |
|---|---|---|---|
query | Yes | string | SQL statement to execute. |
warehouse | No | string | Override the connector's default warehouse. |
database | No | string | Override the connector's default database. |
schema | No | string | Override the connector's default schema. |
role | No | string | Override the connector's default role. |
timeout | No | number | Server-side execution timeout in seconds (default 60). |
Simple query
curl -s \
"https://colmeia.mt2data.cloud/api/sn/data/YOUR_TENANT_ID/YOUR_SOURCE_ID/query" \
-H "Authorization: Bearer <tenant-secret>" \
-H "Content-Type: application/json" \
-d '{ "query": "SELECT CURRENT_TIMESTAMP() AS now, CURRENT_USER() AS user" }'
The body fields warehouse, database, schema, role, and timeout are all optional. Omit them and the query runs with whatever defaults you set on the connector. The Snowflake user's DEFAULT_NAMESPACE, DEFAULT_ROLE, and DEFAULT_WAREHOUSE also fill in any gaps. Most queries don't need any of these fields.
Query with per-request overrides
Use the optional body fields only when a single query needs to run against a non-default warehouse / database / schema / role, or with a custom timeout. The override applies to that one call — the connector's defaults are untouched.
curl -s \
"https://colmeia.mt2data.cloud/api/sn/data/YOUR_TENANT_ID/YOUR_SOURCE_ID/query" \
-H "Authorization: Bearer <tenant-secret>" \
-H "Content-Type: application/json" \
-d '{
"query": "SELECT field_id, name, area_hectares FROM solinftec_bi.ss.fields LIMIT 10",
"warehouse": "SSWH_LARGE",
"database": "SOLINFTEC_BI",
"schema": "SS",
"role": "SSRL_0307",
"timeout": 120
}'
Common reasons to override:
warehouse— heavy query needs a bigger compute (e.g. switch fromX-Smalldefault toLARGEfor this call only).database/schema— query targets tables outside the connector's default namespace.role— need a role with different grants for a specific query.timeout— long-running query that may exceed the default 60s server-side execution timeout.
Multiline query
Save the SQL to a file (query.sql), then use jq to build the JSON payload:
-- query.sql
SELECT
field_id,
name,
crop_type,
area_hectares,
last_visit_at
FROM your_database.public.fields
WHERE last_visit_at >= '2026-01-01'
ORDER BY last_visit_at DESC
LIMIT 100
curl -s \
"https://colmeia.mt2data.cloud/api/sn/data/YOUR_TENANT_ID/YOUR_SOURCE_ID/query" \
-H "Authorization: Bearer <tenant-secret>" \
-H "Content-Type: application/json" \
-d "$(jq -n --rawfile q query.sql '{"query": $q}')"
jq handles newlines, quotes, and special characters automatically. Install with brew install jq or apt install jq.
Database, schema, role, and warehouse fall back to whatever you configured on the connector. Override any of them per request by adding the matching field to the JSON body. Table and column names depend on the schema provisioned to your Snowflake account — consult your data provider for the catalog.
Response:
{
"success": true,
"data": {
"columns": [
{ "name": "NOW", "type": "timestamp_ltz" },
{ "name": "USER", "type": "text" }
],
"data": [
{ "NOW": "1779132580.643000000", "USER": "YOUR_USER" }
]
}
}
Values are returned as a string-typed array from Snowflake; numeric, boolean, and NULL columns are cast to JS-native types in the response.
Date/time encoding (important):
Snowflake's REST API does not return ISO strings for date/time columns. The encoding depends on the column type — be defensive when parsing:
| Snowflake type | Returned as | Parse with |
|---|---|---|
timestamp_ltz, timestamp_tz | Unix seconds with fractional part as a string (e.g. "1779132580.643000000") | new Date(parseFloat(v) * 1000) |
timestamp_ntz | Unix seconds string in UTC (no timezone applied) | new Date(parseFloat(v) * 1000) (treat as UTC) |
date | Days since Unix epoch as a string (e.g. "20023") | new Date(parseInt(v, 10) * 86400 * 1000) |
time | Seconds since midnight with fractional part as a string | Parse manually |
Always branch on the columns[i].type value before parsing.
Error responses:
| Error | Status | Meaning |
|---|---|---|
unauthorized | 401 | Missing or invalid credentials. |
forbidden | 403 | Tenant not owned by this manager, or connector belongs to a different tenant. |
unknown_source | 404 | sourceId is not in the static Snowflake source catalog. |
connector_not_found | 404 | No connector exists for this (tenantId, sourceId) pair. |
integration_disabled | 403 | Connector exists but was disabled. Re-enable it in the dashboard. |
missing_query | 400 | Request body missing query field. |
invalid_json | 400 | Malformed request body. |
snowflake_auth_failed | 502 | Snowflake rejected the JWT. Most often: the public key wasn't enrolled yet, the wrong key was enrolled, an authentication policy excludes KEYPAIR_JWT, or a network policy is blocking the request. The last error is shown on the connector card. |
statement_poll_timeout | 504 | The Snowflake statement didn't finish in time. Tune timeout or simplify the query. |
Listing your connectors
Existing connectors for a tenant are visible in the dashboard's Snowflake Connectors section. Each card shows the catalogued source name, source slug, account, user, warehouse, current status, and the data path — the same slug you put in the URL above.
Programmatic discovery: GET /api/data/:tenantId/connections lists every active connector for the tenant. Snowflake entries carry "snowflake": true, the source_id slug, and the data_path URL. See middleware-api.md for the full payload shape.
A tenant can attach at most one connector per source slug — Colmeia enforces uniqueness of (tenant, source_id). To connect to multiple external systems that all happen to expose Snowflake, those systems each need their own slug in the catalog (contact MT2Data to register additions).
Notes on the keypair
- The private key never leaves Colmeia. You do not download, copy, or paste it anywhere.
- The public key is shown only on the connector card. Re-display by reopening the card while the connector is in Pending admin enrollment status.
- Revoking a connector permanently destroys the private key. Re-creating a connector generates a fresh keypair and requires a new
ALTER USER … SET RSA_PUBLIC_KEY=…from the admin. - One Snowflake user can have only one public key enrolled at a time. If you need to rotate, ask the admin to overwrite the previous one with the new value shown in the dashboard.
Solinftec Integration
Overview
Access field operations, refueling, and meteorological data from the Solinftec SCDI platform through the Colmeia middleware. Your Solinftec account is connected to your tenant by MT2Data — you only need your tenant bearer secret to make requests.
Base URL: https://colmeia.mt2data.cloud/api/sl
Authentication
All data routes require your tenant bearer secret:
Authorization: Bearer <tenant-secret>
The middleware handles Solinftec's rotating short-lived tokens automatically. No manual token management is needed on your side.
Pagination
All three endpoints support page and size query parameters. Each response includes:
| Field | Description |
|---|---|
page | Current page number |
page_size | Records per page |
total_pages | Total pages available |
data | Array of records |
Increment page until page >= total_pages to retrieve all records.
Operation Details
Endpoint: GET /api/sl/data/:tenantId/operations
Returns field operation history with text descriptions alongside numeric codes. Recommended to query one day at a time.
Query Parameters:
| Parameter | Required | Format | Description |
|---|---|---|---|
dateini | Yes | DD/MM/YYYY HH:MM:SS | Start datetime |
datefim | Yes | DD/MM/YYYY HH:MM:SS | End datetime |
page | No | integer | Page number (default: 1) |
size | No | integer | Records per page (default: 1000) |
equipamento | No | string | Filter by equipment code |
operacao | No | string | Filter by operation code |
operador | No | string | Filter by operator code |
unidade | No | string | Filter by unit code |
talhao | No | string | Filter by field (talhão) code |
ordemservico | No | string | Filter by work order |
curl "https://colmeia.mt2data.cloud/api/sl/data/YOUR_TENANT_ID/operations?dateini=10/09/2024%2000:00:00&datefim=10/09/2024%2023:59:59" \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Response:
{
"page": 1,
"page_size": 1000,
"total_pages": 1,
"total_rows": 42,
"data": [
{
"cd_id": 12345,
"dt_movimentacao": "10/09/2024 00:00:00",
"cd_equipamento": "T001",
"desc_equipamento": "Equipment description",
"cd_operacao": "101",
"desc_operacao": "Operation description",
"cd_operador": "OP01",
"desc_operador": "Operator name",
"vl_consumo": 45.2
}
]
}
Train Refueling
Endpoint: GET /api/sl/data/:tenantId/refueling
Returns fuel volume dispensed and hourmeter readings. Returns all active trains — no per-train filtering.
Query Parameters:
| Parameter | Required | Format | Description |
|---|---|---|---|
date | Yes | DD/MM/YYYY | Date to query |
page | No | integer | Page number (default: 1) |
size | No | integer | Records per page (default: 1000, max: 10000) |
curl "https://colmeia.mt2data.cloud/api/sl/data/YOUR_TENANT_ID/refueling?date=10/02/2024" \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Meteorological Data
Endpoint: GET /api/sl/data/:tenantId/weather
Returns hourly climate metrics (rainfall, wind speed, temperature, humidity).
Query Parameters:
| Parameter | Required | Format | Description |
|---|---|---|---|
date | Yes | DD/MM/YYYY | Date to query |
page | No | integer | Page number (default: 1) |
size | No | integer | Records per page (default: 1000) |
curl "https://colmeia.mt2data.cloud/api/sl/data/YOUR_TENANT_ID/weather?date=02/11/2024" \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Pagination Example
PAGE=1
TOTAL=999
while [ $PAGE -le $TOTAL ]; do
RESP=$(curl -s "https://colmeia.mt2data.cloud/api/sl/data/YOUR_TENANT_ID/operations?dateini=10/09/2024%2000:00:00&datefim=10/09/2024%2023:59:59&page=$PAGE" \
-H "Authorization: Bearer YOUR_TENANT_SECRET")
TOTAL=$(echo $RESP | jq '.total_pages')
# process $RESP data...
PAGE=$((PAGE + 1))
done
Error Codes
| HTTP | Code | Meaning |
|---|---|---|
| 400 | missing_date_range | dateini or datefim not provided (operations) |
| 400 | missing_date | date not provided (refueling / weather) |
| 401 | no_token_found | Your Solinftec account is not connected — contact MT2Data |
Syngenta Cropwise Integration
Overview
Access farm properties, fields, and monitoring data from Syngenta Cropwise through the Colmeia middleware. Your Cropwise account is connected to your tenant by MT2Data — you only need your tenant bearer secret to make requests.
Base URL: https://colmeia.mt2data.cloud/api/cw
Authentication
All data routes require your tenant bearer secret:
Authorization: Bearer <tenant-secret>
Organization scoping
Cropwise scopes every request to a single Organization (an internal Syngenta UUID). Cropwise has no API to list the organizations a token can access, so the OrgID is provisioned out-of-band by the Syngenta Digital onboarding team and stored against your tenant when MT2Data connects your account.
You do not pass an orgId query parameter on any data route. The middleware resolves the OrgID from your tenant binding on every request and forwards it to Cropwise. If your tenant has no OrgID on file, data routes return 400 no_syngenta_org — contact MT2Data to provision the org.
Farm Management
List Properties (Farms)
Endpoint: GET /cw/data/:tenantId/properties
curl https://colmeia.mt2data.cloud/api/cw/data/YOUR_TENANT_ID/properties \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Response:
{
"total_elements": 3,
"content": [
{
"id": "property-uuid",
"name": "Property Name",
"field_count": 27,
"total_area": 3864.4776,
"state": "MT",
"city": "City Name",
"reference_point": {
"type": "Point",
"coordinates": [-52.78, -11.31]
}
}
]
}
List Fields
Endpoint: GET /cw/data/:tenantId/properties/:propertyId/fields
curl https://colmeia.mt2data.cloud/api/cw/data/YOUR_TENANT_ID/properties/PROPERTY_ID/fields \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Response:
{
"total_elements": 70,
"content": [
{
"id": "field-id",
"name": "Field Name",
"area": 154.32,
"crop_type": "Soybean",
"boundary": { "type": "Polygon", "coordinates": [[]] }
}
]
}
Monitoring Data
Get Field Indicators
Endpoint: GET /cw/data/:tenantId/monitoring/indicators
Get monitoring indicators (pest pressure, disease risk, etc.) for fields.
curl "https://colmeia.mt2data.cloud/api/cw/data/YOUR_TENANT_ID/monitoring/indicators?propertyId=PROPERTY_ID&start=2024-01-01&end=2024-12-31" \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Query Parameters:
| Parameter | Required | Description |
|---|---|---|
propertyId | Yes | Property ID |
start | No | Start date (YYYY-MM-DD) |
end | No | End date (YYYY-MM-DD) |
Get Geo-Referenced Monitoring Points
Endpoint: GET /cw/data/:tenantId/monitoring/points
curl "https://colmeia.mt2data.cloud/api/cw/data/YOUR_TENANT_ID/monitoring/points?propertyId=PROPERTY_ID" \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Get Monitoring Changes (Incremental Sync)
Endpoint: GET /cw/data/:tenantId/properties/:propertyId/monitoring/changes
Fetch only observations that changed within a date range — efficient for incremental sync.
curl "https://colmeia.mt2data.cloud/api/cw/data/YOUR_TENANT_ID/properties/PROPERTY_ID/monitoring/changes?start=2026-01-25&end=2026-01-31" \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Query Parameters:
| Parameter | Required | Description |
|---|---|---|
start | Yes | Start date (YYYY-MM-DD) |
end | Yes | End date (YYYY-MM-DD) — max 7 days from start |
date_reference | No | "sync_date" or "observation_date" (default: "sync_date") |
Agronomic Context
Get Current Season Crops
Endpoint: GET /cw/data/:tenantId/properties/:propertyId/crops
curl "https://colmeia.mt2data.cloud/api/cw/data/YOUR_TENANT_ID/properties/PROPERTY_ID/crops" \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Query Parameters: date (YYYY-MM-DD, defaults to today)
Get Crops Catalog
Endpoint: GET /cw/data/:tenantId/catalog/crops
curl "https://colmeia.mt2data.cloud/api/cw/data/YOUR_TENANT_ID/catalog/crops?country=BR" \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Field Notes & Observations
Get Field Notes
Endpoint: GET /cw/data/:tenantId/properties/:propertyId/notes
curl "https://colmeia.mt2data.cloud/api/cw/data/YOUR_TENANT_ID/properties/PROPERTY_ID/notes" \
-H "Authorization: Bearer YOUR_TENANT_SECRET"
Code Example
const TENANT_ID = 'YOUR_TENANT_ID';
const BASE = 'https://colmeia.mt2data.cloud/api';
const headers = { Authorization: 'Bearer YOUR_TENANT_SECRET' };
// Get all properties
const properties = await fetch(`${BASE}/cw/data/${TENANT_ID}/properties`, { headers })
.then(r => r.json());
// Get fields for first property
const propertyId = properties.content[0].id;
const fields = await fetch(
`${BASE}/cw/data/${TENANT_ID}/properties/${propertyId}/fields`, { headers }
).then(r => r.json());
console.log(`${properties.total_elements} properties, ${fields.total_elements} fields`);
Error Codes
| Code | Status | Meaning |
|---|---|---|
no_token_found | 401 | Your Cropwise account is not connected — contact MT2Data |
no_syngenta_org | 400 | Tenant has no OrgID on file — contact MT2Data so they can provision it from the OrgID provided by Syngenta Digital |
authentication_failed | 500 | Stored credentials need to be refreshed — contact MT2Data |
Troubleshooting
Common Errors
401 — Missing or invalid tenant secret
Cause: The Authorization: Bearer header is missing, empty, or contains an incorrect secret.
Fix: Verify your tenant secret is correct and included on every request:
Authorization: Bearer <your-tenant-secret>
401 — no_token_found
Cause: Your tenant does not have a connection configured for the provider you are trying to access.
Fix: Contact MT2Data to confirm that the integration (John Deere, Syngenta, Solinftec, or Datalake) has been enabled for your tenant.
403 — forbidden
Cause: Your tenant secret is valid but does not have access to the requested resource.
Fix: Confirm you are using the correct tenant ID in the URL path and that the secret belongs to that tenant.
502 / upstream errors
Cause: The upstream provider (John Deere, Syngenta, Solinftec, or MT2Data Datalake) returned an error or is temporarily unavailable.
Fix: Retry after a short wait. If the issue persists, use GET /api/data/:tenantId/connections to check which providers are currently connected and whether any have expired tokens.
no_token_found on a previously working provider
Cause: The stored provider token may have expired and automatic renewal failed.
Fix: Contact MT2Data support to re-connect the integration for your tenant.
Getting Help
If you encounter an issue not covered here, reach out through any of the following:
- Contact form: mt2data.cloud/#contact
- Email: support@mt2data.cloud
When contacting support, include:
- Your tenant ID
- The endpoint you were calling
- The error code and HTTP status returned
- Approximate time of the request
About Colmeia
Colmeia is the MT2Data middleware platform. It provides a unified API for accessing agricultural data from John Deere Operations Center, Syngenta Cropwise, Solinftec SCDI, and the MT2Data Datalake — all through a single tenant bearer secret, with all provider authentication handled server-side.
For more information about the MT2Data platform, visit mt2data.cloud.
MT2Data Datalake Gateway
Secure, index-based access to the MT2Data agronomic data lake. The gateway provides authenticated access to parquet data files for authorized enterprise clients.
Base URL: https://datalake.mt2data.cloud
What the Datalake Gateway provides
- OAuth 2.0 Client Credentials — enterprise M2M authentication with 1-hour JWT tokens
- Symbol-based file access — files are identified by human-readable Symbols, not raw paths
- Subscription-scoped access — your account defines exactly which files you can retrieve
- Fast catalog browsing — browse all authorized Symbols and descriptions in ~1s
Quick links
- Authentication — OAuth 2.0 flow, credentials, token caching
- API Reference — all endpoints with request/response details
- Code Examples — Python, JavaScript, C# clients
- Performance Guide — endpoint speeds, recommended workflows
- Troubleshooting — common errors, security best practices
Access via Colmeia (recommended for Colmeia tenants)
If you already use the Colmeia platform, you do not need to integrate directly against this gateway. Connect your Datalake credentials once through the Colmeia integration dashboard and access files via:
GET https://colmeia.mt2data.cloud/api/dl/data/:tenantId/download/:symbol
Authorization: Bearer <tenant-secret>
Colmeia manages token refresh automatically.
API Reference
Base URL: https://datalake.mt2data.cloud
All data endpoints require authentication. See Authentication for how to obtain an access token.
POST /oauth/token
Obtain an OAuth 2.0 access token using client credentials.
Request:
POST /oauth/token HTTP/1.1
Content-Type: application/json
{
"grant_type": "client_credentials",
"client_id": "your-client-id",
"client_secret": "your-client-secret"
}
Also accepts application/x-www-form-urlencoded.
Response (200):
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600
}
Response (401):
{
"error": "invalid_client",
"error_description": "Invalid client credentials"
}
GET /catalog
Get metadata for all files you are authorized to access. Returns Symbol identifiers, human-readable descriptions, and R2 file paths.
Response (200):
{
"clientId": "your-account",
"files": [
{
"Symbol": "DatasetSymbol",
"Description": "Human-readable description of the dataset",
"FileName": "path/to/DatasetSymbol.parquet"
}
],
"count": 1
}
Performance: ~1s — use this as your primary way to discover available files.
GET /list
List files that appear in your catalog and physically exist in R2, with size and upload timestamp. Uses batched HEAD verification.
Parameters:
| Parameter | Required | Default | Description |
|---|---|---|---|
limit | No | 1000 | Max files to return |
Response (200):
{
"files": [
{
"symbol": "DatasetSymbol",
"description": "Human-readable description of the dataset",
"size": 1048576,
"uploaded": "2026-01-15T10:30:00Z"
}
],
"count": 1,
"limit": 1000,
"truncated": false
}
Performance: ~2s for a full catalog (~500 files).
GET /count
Returns the total number of accessible files that physically exist in R2.
Response (200):
{ "count": 506 }
Performance: ~2s.
GET /info/
Get metadata for a specific file by its Symbol identifier without downloading it.
GET /info/YOUR_SYMBOL HTTP/1.1
Authorization: Bearer <access_token>
Response (200):
{
"symbol": "YOUR_SYMBOL",
"size": 1048576,
"uploaded": "2026-01-15T10:30:00Z",
"httpMetadata": {},
"customMetadata": {}
}
Performance: ~0.5s.
GET /download/
Download a file by its Symbol identifier. Returns binary content.
GET /download/YOUR_SYMBOL HTTP/1.1
Authorization: Bearer <access_token>
Response (200): File content as binary stream. Content-Type and Content-Disposition headers are forwarded from R2.
Response (403):
{
"error": "Access denied",
"message": "Symbol not found in your subscription"
}
Response (404):
{
"error": "Not found",
"message": "File does not exist"
}
Performance: ~0.5s + file transfer time.
GET /
Direct file download by file path (alternative to Symbol-based access).
curl -H "Authorization: Bearer $TOKEN" \
"https://datalake.mt2data.cloud/path/to/your-file.parquet" -o your-file.parquet
Error Codes
| HTTP | Meaning |
|---|---|
| 400 | Bad request (OAuth: invalid_request, unsupported_grant_type) |
| 401 | Missing or expired credentials (OAuth: invalid_client) |
| 403 | Access denied — file not in client's index, or invalid API key |
| 404 | File not found in R2, or catalog not found |
| 500 | Internal server error |
Authentication
The MT2Data Datalake Gateway uses OAuth 2.0 Client Credentials flow for authentication. You receive a client_id and client_secret from MT2Data, exchange them for a short-lived access token, and include that token in all API requests.
Base URL: https://datalake.mt2data.cloud
Your Credentials
You will receive credentials from MT2Data:
| Field | Description |
|---|---|
| Client ID | Your service account identifier |
| Client Secret | Secret key — keep secure, treat like a password |
| Token Endpoint | https://datalake.mt2data.cloud/oauth/token |
Step 1 — Get an Access Token
curl -X POST https://datalake.mt2data.cloud/oauth/token \
-H "Content-Type: application/json" \
-d '{
"grant_type": "client_credentials",
"client_id": "your-client-id",
"client_secret": "your-client-secret"
}'
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}
Tokens are valid for 1 hour (3600 seconds).
Step 2 — Use the Token
Include the access token in the Authorization header on all API requests:
curl https://datalake.mt2data.cloud/catalog \
-H "Authorization: Bearer <your-access-token>"
Token Expiry & Refresh
When a token expires you will receive a 401. Request a new token via Step 1.
Best practice: track expires_in in your application and refresh the token ~60 seconds before expiry to avoid interruption. See Code Examples for complete implementations with automatic token refresh.
Code Examples
Complete client implementations in Python, JavaScript/Node.js, and C# with automatic token refresh.
Python
import requests
from datetime import datetime, timedelta
class MT2DataClient:
def __init__(self, client_id: str, client_secret: str):
self.base_url = "https://datalake.mt2data.cloud"
self.client_id = client_id
self.client_secret = client_secret
self.access_token = None
self.token_expires_at = None
def _get_token(self) -> str:
"""Get or refresh access token."""
if self.access_token and self.token_expires_at > datetime.now():
return self.access_token
response = requests.post(
f"{self.base_url}/oauth/token",
json={
"grant_type": "client_credentials",
"client_id": self.client_id,
"client_secret": self.client_secret
}
)
response.raise_for_status()
data = response.json()
self.access_token = data["access_token"]
# Refresh 60s before actual expiry
self.token_expires_at = datetime.now() + timedelta(seconds=data["expires_in"] - 60)
return self.access_token
def _headers(self) -> dict:
return {"Authorization": f"Bearer {self._get_token()}"}
def get_catalog(self) -> dict:
"""Get all authorized Symbols and descriptions."""
return requests.get(f"{self.base_url}/catalog", headers=self._headers()).json()
def list_files(self, limit: int = 1000) -> dict:
"""List accessible files with existence verification (~2s)."""
return requests.get(
f"{self.base_url}/list",
headers=self._headers(),
params={"limit": limit}
).json()
def count_files(self) -> int:
"""Count total accessible files."""
return requests.get(f"{self.base_url}/count", headers=self._headers()).json()["count"]
def get_file_info(self, symbol: str) -> dict:
"""Get metadata for a file by Symbol."""
return requests.get(f"{self.base_url}/info/{symbol}", headers=self._headers()).json()
def download_file(self, symbol: str, local_path: str) -> None:
"""Download a file by Symbol to disk."""
with requests.get(
f"{self.base_url}/download/{symbol}",
headers=self._headers(),
stream=True
) as r:
r.raise_for_status()
with open(local_path, "wb") as f:
for chunk in r.iter_content(chunk_size=8192):
f.write(chunk)
# Usage
if __name__ == "__main__":
client = MT2DataClient(
client_id="your-client-id@company.com",
client_secret="your-secret-key-here"
)
# Browse available files
catalog = client.get_catalog()
print(f"Available Symbols: {catalog['count']}")
for f in catalog["files"][:5]:
print(f" {f['Symbol']}: {f['Description']}")
# Download a file by Symbol
client.download_file("YOUR_SYMBOL", "dataset.parquet")
print("Downloaded dataset.parquet")
JavaScript / Node.js
const axios = require('axios');
const fs = require('fs');
class MT2DataClient {
constructor(clientId, clientSecret) {
this.baseUrl = 'https://datalake.mt2data.cloud';
this.clientId = clientId;
this.clientSecret = clientSecret;
this.accessToken = null;
this.tokenExpiresAt = null;
}
async _getToken() {
if (this.accessToken && this.tokenExpiresAt > Date.now()) {
return this.accessToken;
}
const response = await axios.post(`${this.baseUrl}/oauth/token`, {
grant_type: 'client_credentials',
client_id: this.clientId,
client_secret: this.clientSecret
});
this.accessToken = response.data.access_token;
// Refresh 60s before expiry
this.tokenExpiresAt = Date.now() + (response.data.expires_in - 60) * 1000;
return this.accessToken;
}
async _headers() {
return { Authorization: `Bearer ${await this._getToken()}` };
}
async getCatalog() {
return (await axios.get(`${this.baseUrl}/catalog`, { headers: await this._headers() })).data;
}
async listFiles(limit = 1000) {
return (await axios.get(`${this.baseUrl}/list`, {
headers: await this._headers(),
params: { limit }
})).data;
}
async getFileInfo(symbol) {
return (await axios.get(`${this.baseUrl}/info/${symbol}`, { headers: await this._headers() })).data;
}
async downloadFile(symbol, localPath) {
const response = await axios.get(`${this.baseUrl}/download/${symbol}`, {
headers: await this._headers(),
responseType: 'stream'
});
await new Promise((resolve, reject) => {
response.data.pipe(fs.createWriteStream(localPath))
.on('finish', resolve)
.on('error', reject);
});
}
}
// Usage
async function main() {
const client = new MT2DataClient(
'your-client-id@company.com',
'your-secret-key-here'
);
const catalog = await client.getCatalog();
console.log(`Available Symbols: ${catalog.count}`);
await client.downloadFile('YOUR_SYMBOL', 'dataset.parquet');
console.log('Downloaded dataset.parquet');
}
main().catch(console.error);
C# / .NET
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
public class MT2DataClient
{
private readonly string _baseUrl = "https://datalake.mt2data.cloud";
private readonly string _clientId;
private readonly string _clientSecret;
private readonly HttpClient _httpClient = new();
private string _accessToken;
private DateTime _tokenExpiresAt;
public MT2DataClient(string clientId, string clientSecret)
{
_clientId = clientId;
_clientSecret = clientSecret;
}
private async Task<string> GetTokenAsync()
{
if (_accessToken != null && _tokenExpiresAt > DateTime.UtcNow)
return _accessToken;
var content = new StringContent(
JsonSerializer.Serialize(new {
grant_type = "client_credentials",
client_id = _clientId,
client_secret = _clientSecret
}),
Encoding.UTF8, "application/json"
);
var response = await _httpClient.PostAsync($"{_baseUrl}/oauth/token", content);
response.EnsureSuccessStatusCode();
using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync());
_accessToken = doc.RootElement.GetProperty("access_token").GetString();
var expiresIn = doc.RootElement.GetProperty("expires_in").GetInt32();
_tokenExpiresAt = DateTime.UtcNow.AddSeconds(expiresIn - 60);
return _accessToken;
}
public async Task<string> GetCatalogAsync()
{
var token = await GetTokenAsync();
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var response = await _httpClient.GetAsync($"{_baseUrl}/catalog");
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
public async Task DownloadFileAsync(string symbol, string localPath)
{
var token = await GetTokenAsync();
_httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var bytes = await _httpClient.GetByteArrayAsync($"{_baseUrl}/download/{symbol}");
await System.IO.File.WriteAllBytesAsync(localPath, bytes);
}
}
// Usage
var client = new MT2DataClient("your-client-id@company.com", "your-secret-key-here");
Console.WriteLine(await client.GetCatalogAsync());
await client.DownloadFileAsync("YOUR_SYMBOL", "dataset.parquet");
Performance Guide
Endpoint Speed Reference
| Endpoint | Speed | Use Case |
|---|---|---|
GET /catalog | ~1s | Browse all authorized Symbols and descriptions |
GET /info/{symbol} | ~0.5s | Get metadata for a specific file |
GET /download/{symbol} | ~0.5s + transfer | Download a specific file |
GET /list | ~2s | Verify file existence with size and timestamp |
GET /count | ~2s | Count total accessible files |
Recommended Workflows
Downloading a known file
If you already know the Symbol:
# Direct download — fastest path
GET /download/IbgePnadAgricultureStk
Discovering and downloading files
# Step 1: Browse catalog (~1s) — find the Symbol you need
GET /catalog
# Returns: [{ "Symbol": "IbgePnadAgricultureStk", "Description": "..." }, ...]
# Step 2: Download by Symbol (~0.5s + transfer)
GET /download/IbgePnadAgricultureStk
Verifying which files exist
# Use /list when you need to confirm files exist in R2 with metadata
GET /list?limit=1000
# ~2s with batched HEAD calls — returns Symbol, size, upload date
Quick file count
GET /count
# ~2s, returns total number of accessible files
Token Caching
Tokens are valid for 1 hour. Do not request a new token on every API call — cache it and refresh when it is ~60 seconds from expiry.
# Good — reuse token until near-expiry
if not self.access_token or self.token_expires_at < datetime.now():
self.access_token = fetch_new_token()
# Bad — fetches a new token on every request
token = fetch_new_token()
call_api(token)
Parallelism
/list and /count internally make batched parallel HEAD requests against R2 — they are already optimized. For downloading multiple files, parallelize client-side:
from concurrent.futures import ThreadPoolExecutor
symbols = ["SymbolA", "SymbolB", "SymbolC"]
with ThreadPoolExecutor(max_workers=5) as executor:
futures = [executor.submit(client.download_file, s, f"{s}.parquet") for s in symbols]
for f in futures:
f.result()
Troubleshooting
Common Errors
401 — "Invalid client credentials"
Cause: Client ID or secret is incorrect, or the OAuth client does not exist.
Fix:
- Verify credentials are exactly correct (no trailing spaces or newlines)
- Contact MT2Data to confirm your account is active
401 — "Token expired"
Cause: Access token has expired (lifetime is 1 hour).
Fix: Request a new token via POST /oauth/token. Implement token caching with expiry tracking in your application — see the Performance Guide.
403 — "Access denied" / "Symbol not found in your subscription"
Cause: You are trying to access a file that is not in your index.
Fix:
- Use
GET /catalogto see which Symbols you are authorized to access - Contact MT2Data if you need access to additional files
403 — "Invalid API key"
Cause: API key does not exist or has the wrong prefix.
Fix: Verify the key exists and is prefixed with mt2_admin_ or mt2_client_.
404 — File not found
Cause: The file exists in your catalog but has not yet been synced to R2, or the path/Symbol is incorrect.
Fix:
- Use
GET /catalogto verify the Symbol is in your subscription - Use
GET /listto confirm the file actually exists in R2 - Check that the Symbol spelling is exact (case-sensitive)
404 — "Catalog not found"
Cause: Your client does not have an index file configured in R2.
Fix: Contact MT2Data to ensure your index file has been uploaded.
400 — "unsupported_grant_type"
Cause: grant_type is missing or not set to client_credentials.
Fix:
- Set
grant_type=client_credentialsin the request body - Ensure
Content-Typeisapplication/jsonorapplication/x-www-form-urlencoded
Changes not applying immediately
KV-stored credentials (new keys, updated index files) can take up to 60 seconds to propagate globally. Wait and retry.
Getting Help
If you encounter an issue not covered here, reach out through any of the following:
- Contact form: mt2data.cloud/#contact
- Email: support@mt2data.cloud
When contacting support, include:
- Your Client ID (never the secret)
- The exact error message and HTTP status code
- The endpoint you were calling
- Approximate timestamp of the request
Security Best Practices
- Never share your client secret — treat it like a password
- Use environment variables or a secrets manager — do not hardcode secrets in source code
- Cache tokens — request a new token only when the current one is near expiry, not on every call
- Use HTTPS only — all endpoints enforce HTTPS
- Rotate compromised secrets immediately — contact MT2Data if you suspect a secret has been exposed