The Nominal Public API exposes the same JSON↔CSV conversion the web app runs, callable from curl, JavaScript, Python, or any HTTP client. Authentication is via API keys minted from your account's /settings page. The endpoint accepts up to 1 MB per request and returns the converted output as JSON.
Quickstart
Three steps to your first successful call:
- Subscribe to Pro from /pricing — the Public API is a Pro-only feature.
- Mint an API key from /settings by clicking Generate new key. Copy the raw token — the dashboard only shows the first 16 characters.
- Call
POST /api/v1/convertwith the raw key as aBearertoken.
One-line call from your terminal. Replace <YOUR_API_KEY>.
# Convert a JSON array to CSV. curl -sX POST https://nominal-vrkp.polsia.app/api/v1/convert \ -H "Authorization: Bearer <YOUR_API_KEY>" \ -H "Content-Type: application/json" \ -d '{"input":"[{\"a\":1},{\"a\":2}]","direction":"json_to_csv"}'
{
"success": true,
"direction": "json_to_csv",
"output": "a\n1\n2\n",
"bytes_in": 21,
"bytes_out": 6
}
Browser- or Node-compatible fetch. Replace <YOUR_API_KEY>.
const res = await fetch( 'https://nominal-vrkp.polsia.app/api/v1/convert', { method: 'POST', headers: { 'Authorization': 'Bearer <YOUR_API_KEY>', 'Content-Type': 'application/json' }, body: JSON.stringify({ input: '[{"a":1},{"a":2}]', direction: 'json_to_csv' }) } ); const json = await res.json();
{
"success": true,
"direction": "json_to_csv",
"output": "a\n1\n2\n",
"bytes_in": 21,
"bytes_out": 6
}
Single requests.post. Replace <YOUR_API_KEY>.
import requests r = requests.post( "https://nominal-vrkp.polsia.app/api/v1/convert", headers={ "Authorization": "Bearer <YOUR_API_KEY>", "Content-Type": "application/json", }, json={"input": '[{"a":1},{"a":2}]', "direction": "json_to_csv"}, timeout=30, ) r.raise_for_status() print(r.json()["output"])
{
"success": true,
"direction": "json_to_csv",
"output": "a\n1\n2\n",
"bytes_in": 21,
"bytes_out": 6
}
Authentication
Every request must include an Authorization header of the form:
Authorization: Bearer nk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keys are sha256-hashed server-side; only the prefix and the hash live in our database. The raw key is shown exactly once at mint time — store it somewhere safe immediately. If you lose it, revoke the old key and mint a new one from /settings.
- Prefix:
nk_+ 64 hex characters (total 67 chars). - Dashboard display: only the first 16 characters (
nk_+ 13 hex chars). - Revocation: any time from /settings — immediate, no confirmation email.
POST
/api/v1/convert
Convert a JSON array to CSV, or CSV back to a JSON array. The endpoint uses dot-notation flattening for nested objects and arrays are serialized as JSON strings within a single cell.
Request body schema
| Field | Type | Required | Notes |
|---|---|---|---|
input | string | yes | Source payload — JSON array (for json_to_csv/json_to_yaml), CSV text (for csv_to_json/csv_to_yaml), or YAML text (for yaml_to_json/yaml_to_csv). Max 1 MB. |
direction | string | yes | One of json_to_csv, csv_to_json, json_to_yaml, yaml_to_json, csv_to_yaml, or yaml_to_csv. |
Response schema
On success, the response is JSON with the converted output as a string field. The byte counts let you size requests:
| Field | Type | Notes |
|---|---|---|
success | boolean | Always true on a 200 response. |
direction | string | Echoes the direction you sent. |
output | string | Converted payload, encoded as a JSON string. |
bytes_in | integer | Size of input in UTF-8 bytes. |
bytes_out | integer | Size of output in UTF-8 bytes. |
Status codes
| Code | Meaning | When |
|---|---|---|
200 | OK | Conversion succeeded. Body contains the converted output. |
400 | Bad request | Missing input/direction, invalid direction, or unparseable input. Body has { "error": "..." }. |
401 | Unauthenticated | No Authorization header or the key is invalid. Body: { "error": "auth_required" | "invalid_key" }. |
403 | Forbidden | Key is valid but the account is not on Pro. Body: { "error": "pro_required" }. |
413 | Payload too large | The input field exceeds 1 MB. Body: { "error": "payload_too_large", "max_bytes": 1048576 }. |
Code samples
Each tab is a real, copy-pasteable example. Replace <YOUR_API_KEY> with the raw key from /settings; replace the example payload with your own data.
# Convert a JSON array to CSV. curl -sX POST https://nominal-vrkp.polsia.app/api/v1/convert \ -H "Authorization: Bearer <YOUR_API_KEY>" \ -H "Content-Type: application/json" \ -d '{"input":"[{\"a\":1},{\"a\":2}]","direction":"json_to_csv"}' # Convert a CSV back to JSON. curl -sX POST https://nominal-vrkp.polsia.app/api/v1/convert \ -H "Authorization: Bearer <YOUR_API_KEY>" \ -H "Content-Type: application/json" \ -d "{\"input\":\"a,b\\n1,2\\n3,4\",\"direction\":\"csv_to_json\"}"
const API_KEY = '<YOUR_API_KEY>'; const BASE = 'https://nominal-vrkp.polsia.app'; async function convert(input, direction) { const res = await fetch(`${BASE}/api/v1/convert`, { method: 'POST', headers: { 'Authorization': `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: '{"input":"[{\\"a\\":1},{\\"a\\":2}]","direction":"json_to_csv"}' }); const json = await res.json(); if (!res.ok) throw new Error(json.error || 'convert failed'); return json.output; } convert('[{"a":1},{"a":2}]', 'json_to_csv') .then(csv => console.log(csv)); // 'a\n1\n2\n'
import json import requests API_KEY = "<YOUR_API_KEY>" BASE = "https://nominal-vrkp.polsia.app" def convert(text: str, direction: str) -> str: r = requests.post( f"{BASE}/api/v1/convert", headers={ "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", }, json={"input": text, "direction": direction}, timeout=30, ) r.raise_for_status() return r.json()["output"] print(convert('[{"a":1},{"a":2}]', "json_to_csv")) # 'a\n1\n2\n'
Rate limits
Pro subscribers have no daily cap on API usage. The 2/day limit enforced for free web usage does not apply to the Public API — your API key must belong to an active Pro subscription, and once it does, conversion counts share the same UTC-day window only for diagnostics.
The response headers advertise the current quota state so you can surface it in your own monitoring:
X-Conversions-Used: 0 X-Conversions-Limit: unlimited
For free accounts, when an API key is somehow minted before the subscription is active, the endpoint returns 403 { "error": "pro_required" } — never a partial quota. There is no per-minute rate limit enforced in v1; abusive traffic should be raised with support@nominal.app.
Errors
All errors are returned with a JSON body of { "error": "<code>", ... }. Stable error codes so your retry logic can branch on them.
Error code reference
| Code | HTTP | Fix |
|---|---|---|
auth_required | 401 | Add an Authorization: Bearer <YOUR_API_KEY> header. The endpoint does not accept query-param auth. |
invalid_key | 401 | The key is unknown or revoked. Mint a new key from /settings. |
pro_required | 403 | The account isn't on Pro. Subscribe at /pricing, then call again. |
missing_field | 400 | You omitted input or direction. Both are required. |
direction_invalid | 400 | direction must be json_to_csv or csv_to_json. |
invalid_input | 400 | The input field could not be parsed as the requested source format. The error message includes the parser reason. |
payload_too_large | 413 | input exceeded 1 MB. Split the payload and call again. |
internal_error | 500 | Server-side failure. Safe to retry with exponential backoff. |
<YOUR_API_KEY>, the curl example at the top of this page should round-trip [{"a":1},{"a":2}] → a\n1\n2\n. If you see anything else, the Errors table above maps to remediation.