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.

Endpoints Per minute Per hour
/dua, /dua/summary 20 60
/me, /dua/meta 60 600
Unauthenticated (only /dua/meta is reachable) 10 60

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.

Parameter Required Description
yearyesInteger, 2016 onwards.
regimenyesOne of IMPORT, EXPORT, TRANSIT, ALL.
aduananoThree-digit customs office code. Omit for the whole country.
searchnoMatches company name or NCM prefix. Max 100 characters.
limitnoRows per page, 1 to 10,000. Defaults to 100.
offsetnoRows to skip. Use with limit to page through results.
curl -H "Authorization: Bearer $TOKEN" \
  "https://uytrades.com/api/v1/dua?year=2026&regimen=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&regimen=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. ..."}}
Status code When
400bad_requestA parameter is missing or malformed. The message names the parameter.
401unauthorizedNo token, or a token that is malformed, revoked or belongs to a disabled account.
402Authenticated, but the endpoint needs Pro. The body carries an upgrade_url.
404not_foundNo such endpoint or resource.
429rate_limitedRate limit exceeded. Honour Retry-After.
500server_errorSomething broke on our side.

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&regimen=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.