# Conventions

> Response envelope, datetime format, IDs, pagination, body size caps, and write-endpoint conventions for the Tradavity Public API.
> Source: https://help.tradavity.com/api-docs/conventions
> Category: Core Concepts

---

The shape, headers, and parsing rules every endpoint follows. Read this before integrating; everything else assumes it.

## Response envelope

Every response is JSON with this shape:

```json
{
  "ok": true,
  "data": { ... resource-specific payload ... },
  "meta": {
    "request_id": "req_a3b4c5d6e7f8a9b0",
    "rate_limit": {
      "tier": "pro",
      "limit": 300,
      "remaining": 297,
      "reset_at": "2026-05-10T15:30:00Z",
      "user_ceiling": 900,
      "user_remaining": 895
    }
  }
}
```

On error:

```json
{
  "ok": false,
  "error": {
    "code": "validation_error",
    "message": "direction must be 'long' or 'short'.",
    "details": { ... optional field-level info ... }
  },
  "meta": {
    "request_id": "req_a3b4c5d6e7f8a9b0"
  }
}
```

Always check `ok` first. The HTTP status reflects the category (200/201/204/400/401/403/404/409/413/429/500). The `error.code` is a stable snake_case string — switch on this in your client, not the human-readable message.

**Shape of `error.details` on `400 validation_error`:**

- Per-field errors: `details.fields` is an object mapping each rejected input field to a short reason string. Example: `{"fields": {"confidence_level": "must be an integer 1-10"}}`.
- Enum / reference rejections: the offending category lands directly on `details`. Examples: `details.allowed` (list of accepted values for an enum filter or field), `details.unknown_tag_ids` / `details.unknown_setup_ids` / `details.unknown_confirmation_ids` (the unrecognized ids you passed), `details.asset_config_errors` (per-key issues on a nested config object).

## Datetimes

Response times are ISO 8601 UTC with a `Z` suffix: `2026-05-10T14:32:18Z`. Convert to your user’s timezone client-side. Use `YYYY-MM-DD` or ISO 8601 in date filters — the parser is lenient and accepts most reasonable formats, but stick to one of these for clarity. Whatever you send is interpreted in your user’s timezone (set in **Settings → Preferences**) and converted to UTC on the server.

## IDs

Trades are identified by `trade_number` — a per-user counter (your first trade is `1`, the second is `2`, etc.) shown in the journal UI. The internal `trade_id` is never surfaced. Other resources use their numeric primary key (account_id, goal_id, protocol_id, etc.).

## Pagination

- **Trades** use **cursor** pagination (sorted by trade date, newest first). Default page is 50, max is 200. The response’s `meta.next_cursor` — an opaque base64 token — goes into the next request as the `cursor` query param. `meta.has_more` tells you when to stop.
- **All other list endpoints** use **offset** pagination via `limit` and `offset` query params. Default `limit` is 50 or 100 (per endpoint); the max is 200 for high-volume resources (balance events, payouts, protocol responses, autosync log) and 500 for static lists. The response includes `total_count` and `has_more`.
- **Pure catalogs** (brokers, instruments, prop firm presets) return up to 500 rows; use the catalog filters if you hit the cap.

## Conventions for write endpoints

- Body must be valid JSON when the endpoint accepts a body. Always set `Content-Type: application/json`; the server is currently lenient about the header but future hardening may make it strict, so do not rely on its absence working.
- Maximum body size is 1 MB. Multipart uploads (screenshots) are exempt and capped separately at 10 MB per file. There is also a per-trade screenshot cap that depends on your plan — Free: 1 per trade, Pro: 3 per trade — exceeding it returns `413 limit_reached` with `error.details` showing the cap.
- **Unknown fields are rejected.** The server validates the payload against an explicit allow-list per endpoint and returns `400 validation_error` with the offending field names if it sees anything unexpected. This catches typos early.
- Datetime fields accept ISO 8601 in UTC (e.g. `2026-05-10T14:32:18Z`). For datetime fields, a date-only string like `2026-05-10` is also accepted and stored as midnight UTC. Date-only fields use `YYYY-MM-DD`.
- Both raw NUL bytes (`\\x00`) inside JSON strings and escaped NUL sequences (`\u0000`) are silently stripped from incoming string values before validation. The request is not rejected for them. Non-printable control characters are stored as-is.
- Numeric fields reject non-numeric strings: a value like `"quantity": "abc"` returns `400 validation_error` instead of being silently coerced. JSON numbers (`"quantity": 1`) and number-looking strings (`"quantity": "1"`) are both accepted.
- POST returns `201 Created` with the created resource in `data`. PATCH and PUT return `200 OK`. DELETE returns `204 No Content` (no body).
- Optimistic concurrency: there is no `If-Match` header today. Last write wins for concurrent PATCH on the same resource. Concurrent writes that violate a uniqueness constraint return `409 duplicate_name`.
