API reference
Programmatic access to Uruguayan customs declarations. Base URL https://uytrades.com/api/v1. Requires a Pro account.
Authentication
Create a token at /account and send it as a Bearer token. The token is shown once at creation and cannot be retrieved again — store it like a password.
curl -H "Authorization: Bearer uytr_a1b2c3d4_xxxxxxxxxxxx" \
"https://uytrades.com/api/v1/me"
A token carries its owner's tier, so if a subscription lapses the token stops working on Pro endpoints without needing to be revoked.
Session cookies work too, which is why these endpoints can be opened in a logged-in browser. Scripts should always use a token.
Rate limits
Limits are per account, not per token — minting extra keys does not raise your budget.
The two groups have separate budgets: exhausting the query budget does not block the cheap endpoints. The per-minute figure is a burst guard, not the throughput ceiling — the hourly one is.
Every response carries your current standing, so a client never has to guess:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1755792000
X-RateLimit-Limit-Minute: 20
X-RateLimit-Remaining-Minute: 19
Over the limit, you get a 429 with a truthful Retry-After — the real number of seconds until the window resets, not a fixed guess:
HTTP/1.1 429 Too Many Requests
Retry-After: 412
{
"error": {
"code": "rate_limited",
"message": "60 requests/hour exceeded.",
"limit": 60,
"window": "hour",
"reset": 1755792000
}
}
Endpoints
GET /api/v1/me
Confirms a token works and reports what it buys. The conventional first call from a script, and the cheapest thing to poll.
curl -H "Authorization: Bearer $TOKEN" "https://uytrades.com/api/v1/me"
{"id": 42, "email": "you@example.com", "tier": "pro",
"is_pro": true, "pro_until": "2027-01-14T00:00:00+00:00"}
GET /api/v1/dua/meta
Filter options, row caps and column names. The only endpoint that needs no authentication, so a client can discover the schema before you buy.
curl "https://uytrades.com/api/v1/dua/meta"
{"row_limit_max": 10000, "default_limit": 100,
"regimens": ["ALL", "EXPORT", "IMPORT", "TRANSIT"],
"columns": ["anio", "fecha_dua", "aduana_ing_egr", ...],
"requires_pro": true, "is_pro": false}
GET /api/v1/dua
Filtered declaration rows, paginated. Pro only.
curl -H "Authorization: Bearer $TOKEN" \
"https://uytrades.com/api/v1/dua?year=2026®imen=IMPORT&limit=100"
{"data": [{"anio": 2026, "fecha_dua": "2026-03-04", "nombre": "ACME SA", ...}],
"pagination": {"limit": 100, "offset": 0, "returned": 100, "has_more": true},
"query": {"year": 2026, "aduana": null, "regimen": "IMPORT", "search": null},
"meta": {"elapsed_ms": 84.2}}
has_more is exact: page until it is false and you will never fetch an empty page.
GET /api/v1/dua/summary
Totals for a filter set without transferring the rows. Much faster than paging through everything when you only need a count or a sum.
curl -H "Authorization: Bearer $TOKEN" \
"https://uytrades.com/api/v1/dua/summary?year=2026®imen=IMPORT"
{"row_count": 148230, "total_usd": 2841993022.14,
"by_aduana": [{"aduana": "001", "rows": 91204, "usd": 1922841003.5}],
"by_regimen": [{"regimen_tipo": "I", "rows": 148230}],
"query": {"year": 2026, "aduana": null, "regimen": "IMPORT", "search": null}}
GET
POST
DELETE
/api/v1/keys
List, create and revoke your own tokens, so CI and ETL jobs can rotate credentials without a browser. A created token is returned once in the response body.
curl -X POST -H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"name": "nightly-etl"}' \
"https://uytrades.com/api/v1/keys"
{"id": 7, "name": "nightly-etl", "prefix": "a1b2c3d4",
"created_at": "2026-08-21T16:04:11+00:00", "last_used_at": null,
"token": "uytr_a1b2c3d4_xxxxxxxxxxxx"}
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
"https://uytrades.com/api/v1/keys/7"
Up to 20 active keys per account. Revoke one to make room.
Errors
Every error is JSON with the same envelope — never an HTML page:
{"error": {"code": "unauthorized", "message": "Authentication required. ..."}}
A 401 is a real 401: the API never redirects to a login page, so a script with a dead token gets an error it can detect rather than a page of HTML with a 200 on it.
Bulk downloads
The JSON API is built for scoped queries — one company, one product, one office, one year. At 60 requests an hour and 10,000 rows each, it is not the right tool for pulling an entire year.
For that use the CSV export at /app/export, which streams up to 1,000,000 rows in a single request and accepts the same Bearer token. A whole year is a couple of calls there instead of hundreds here.
curl -H "Authorization: Bearer $TOKEN" \
"https://uytrades.com/app/export?year=2026®imen=IMPORT&search=ACME" \
-o acme-2026.csv
What is stable
The endpoints documented on this page are a stable contract. We will not change their response shapes or remove fields without notice.
Everything else under /api/v1 exists to serve our own web app. It is unversioned, undocumented and may change or disappear at any time:
/api/v1/alerts
/api/v1/watches
/api/v1/views
/api/v1/export-schedules
They are visible to a logged-in browser and to your token, but building against them is at your own risk.