JouView

AI

JouView for your AI

Point your AI at this page. It can reshape columns and settings, pull trades for analysis, add or update rows, clean up imports, and run formulas. That includes moving an old journal into JouView. Machine schema: /api/journal/openapi.json.

What your AI can do

All of this goes through https://jouview.com/api/journal/* (see the agent sections below and the OpenAPI schema).

JobHow
Add / change / remove columns GET /api/journal/columns then PUT /api/journal/columns. Array order is the full grid order — include system names (Trade, Profit, …) where you want them. replaceCustom: false merges custom by name. true makes custom columns exactly your list (system always kept). Max 800 columns.
Handle settings Column formulas, enums, cell styles on PUT /columns. Whole-row chrome via GET/PUT /api/journal/row-styles.
Retrieve data to analyse GET /api/journal/entries?take=50 (max 500) plus GET /columns for layout. Combine with formula knowledge below for session stats.
Add rows POST /api/journal/entries or POST /api/journal/entries/batch (max 200). Always set a stable externalId.
Update rows Same as add: POST /entries with the same externalId (and import broker key) upserts that trade and rewrites fields / core prices.
Delete rows The API doesn't have any per-row DELETE. Soft-delete all AI-imported rows with POST /api/journal/reset and "removeImportedEntries": true (only brokerKey=ai-transfer). Broker-synced trades stay. Then re-POST the rows that you want to keep.
Modify field data Upsert the entry with the new fields{} map (and/or symbol, quantity, prices, times). Unknown column names are skipped and returned in unknownFields.
Snapshots Prefer TradingView ids or external https URLs. Upload to JouView only when hosting is required (Trader is effectively unlimited).

For you (human)

  1. Sign inAccount with Google.
  2. Create a tokenUse the button below, then paste the token into your AI only if it can call APIs (see the prompt).
  3. Ask your AI to personalizeCopy the prompt, fill in your task, and send it to any AI you use. Or attach CSVs/exports/screenshots to import an existing journal into JouView.
  4. Refresh JouViewAfter the AI finishes, open the JouView menu (bottom tab) and press Refresh so the journal picks up the new columns, styles or changes.
Copy-paste prompt
I want you to modify my JouView journal via the Journal API.

Read https://jouview.com/ai-usage/

My token (keep private): <PASTE_jvtr_TOKEN>

What I want done:
<DESCRIBE_TASK>

Usage recipes

Concrete tasks your AI should follow. Transferring an old journal is only one of them.

0. Before any write

  1. GET firstGET /api/journal/columns and GET /api/journal/row-styles.
  2. Quote what you foundList custom column names and whether row rules already exist. Never say the journal is empty unless those GETs show that.
  3. Merge by defaultAdd columns with replaceCustom: false. For row styles, GET existing rules, then PUT the full merged list.

1. Add or reshape columns

GET /api/journal/columns
PUT /api/journal/columns
{
  "replaceCustom": false,
  "columns": [
    { "name": "Setup", "type": "text", "enumValues": "CBreak, Impulse, Bias" },
    { "name": "Planned", "type": "bool" },
    {
      "name": "Score",
      "type": "number",
      "formula": "([Planned] === \"true\" ? 1 : 0) + ([Retest] === \"true\" ? 1 : 0)"
    }
  ]
}

Default is merge (replaceCustom: false). To drop custom columns that aren't in your list, use "replaceCustom": true and "confirmReplaceCustom": true. Without the confirm flag, the API rejects a replace that would remove existing custom columns. System columns are never removed.

2. Change settings (row styles)

GET /api/journal/row-styles
PUT /api/journal/row-styles
{
  "rules": [
    {
      "BackgroundColor": "[Profit] < 0 ? \"#3d1f1f\" : \"\"",
      "TextColor": "[Profit] > 0 ? \"#26a69a\" : \"#ef5350\"",
      "Bold": "[Score] >= 3"
    }
  ]
}

PUT replaces the whole rules list. Always GET first and keep existing rules you are not changing.

3. Retrieve data to analyse

GET /api/journal/entries?take=200
GET /api/journal/columns

Summarise wins/losses, setups, checklist scores, or session P&L for the user. Do not invent columns that aren't in the GET response.

4. Add a trade

POST /api/journal/entries
{
  "symbol": "ES",
  "quantity": 2,
  "entryPrice": 5200.25,
  "entryTime": "2024-06-12T14:32:00Z",
  "exitPrice": 5210.5,
  "exitTime": "2024-06-12T15:10:00Z",
  "externalId": "manual-2024-06-12-es-1",
  "fields": { "Setup": "CBreak", "Planned": "1", "Profit": "420", "Notes": "Clean retest" }
}

5. Update a trade

POST again with the same externalId and the corrected payload. Core fields and fields{} replace what you send.

6. Delete AI-imported trades

POST /api/journal/reset
{ "removeImportedEntries": true }

Only rows created through this API (ai-transfer) are removed. To delete a subset: reset imports, then re-POST the keepers with their original externalId values.

7. Example: transfer an existing journal

  1. Read the specsThis page + GET /api/journal/openapi.json.
  2. InspectGET /columns.
  3. LayoutPUT /columns (+ optional PUT /row-styles).
  4. ImportPOST /entries/batch with stable externalId per source row.
  5. SnapshotsTradingView id, external URL, or upload.
  6. VerifyGET /entries?take=10.
  7. Rebuild if neededPOST /reset then recreate.

For AI agents: start here

Base URL: https://jouview.com
Authorization: Bearer jvtr_... (preferred)
Or query: ?access_token=jvtr_... (when you cannot set headers)
Token lifetime: at most 1 hour. Mint a fresh token if yours is older or rejected as legacy.
IP binding: the first IP that successfully uses the token owns it until expiry. Later calls from other IPs get 401.
Spec JSON: GET /api/journal/openapi.json (= /api/journal/schema)

Do not call api.jouview.com with raw userIds for this API. Only these /api/journal/* routes. Prefer Bearer. Use ?access_token= only when your tool cannot send Authorization. Keep all calls for one token on the same egress IP.

Concepts

System columns (read-only identity)

Created automatically. isSystem: true. You cannot rename, retype, or delete them via this API. You can reorder them by including their names in PUT /columns. Do not create custom columns with these names.

NameTypeDefault visibleImport note
TradeactionsyesReserved UI. Never invent.
Date/TimedatetimeyesFrom entryTime
DurationnumberyesUsually computed
PricenumberyesFrom entry/exit price
SL/TPnumberyesStops/targets UI
SymboltextnoSet symbol on entry
Lots/QtynumberyesSet quantity
RRnumbernofields.RR or formula
Risknumberyesfields.Risk
Profitnumberyesfields.Profit (common for history)
Cumulative profitnumbernoUsually UI-computed
Run-up / Drawdown / BE Run-up / Virtual*numbermixedOptional fields
SnapshotsactionsyesReserved
Notesrichtextyesfields.Notes

Column types & allowed JSON values

typeAliases acceptedField value examplesEnums?
textstring, note"CBreak"yes
richtextnotes"Clean retest"no
numbernumeric, float, int"420", "-180.5"yes
boolcheckbox, boolean, yesno"1"/"0" (also true/false/yes/no)no
dateN/A"2024-06-12"yes
timeN/A"14:32"yes
datetimeN/A"2024-06-12T14:32:00Z"yes
starsrating"0"..."5" in 0.5 stepsno
groupN/Acontainer: use groupMembersno
actionsN/Asystem only. You can't create these.no

Enums

enumValues: comma-separated choices, e.g. "CBreak, Impulse, Bias". Set enumMultiValue: true only if the user needs multi-select. Not allowed on bool / richtext / stars / group / actions.

Column JSON properties (PUT /columns)

{
  "name": "Setup",
  "type": "text",
  "visible": true,
  "sortable": true,
  "formula": null,
  "enumValues": "CBreak, Impulse, Bias",
  "enumMultiValue": false,
  "textColor": null,
  "backgroundColor": null,
  "bold": null,
  "italic": null,
  "underline": null,
  "fontSize": null,
  "textAlign": null,
  "displayFormat": null,
  "groupMembers": null,
  "groupSummaryColumn": null
}

Row styles & cell styles

Style channels: BackgroundColor, TextColor, FontSize, Bold, Italic, Underline, TextAlign.

PUT /api/journal/row-styles
{
  "rules": [
    {
      "BackgroundColor": "[Profit] < 0 ? \"#3d1f1f\" : \"\"",
      "TextColor": "[Profit] > 0 ? \"#26a69a\" : \"#ef5350\"",
      "Bold": "[Score] >= 3"
    }
  ]
}

Groups (optional: one level)

Prefer flat columns for imports. If the user wants a packed checklist cell:

{
  "name": "Checklist pack",
  "type": "group",
  "groupMembers": ["Planned", "Retest", "Liquidity"],
  "groupSummaryColumn": "Planned",
  "formula": "[Planned] + [Retest] + [Liquidity]"
}

Chart snapshots

JouView's snapshot viewer stores a SnapshotUrl per image on a trade. Choose the lightest option that works:

1. Upload the image file (only if hosting on JouView)

POST /api/journal/snapshots/upload
Authorization: Bearer jvtr_...
Content-Type: multipart/form-data

[email protected]

→ {
  "url": "https://jouview.com/media/snapshots/{userId}/{fileId}.png",
  "path": "/media/snapshots/{userId}/{fileId}.png",
  "snapshotUrl": "https://jouview.com/media/snapshots/...",
  "uploadedCount": 12,
  "remainingUploads": 9988,
  "maxUploadsPerUser": …
}

Max 10 MB per file. Types: png, jpg, jpeg, webp, gif. Public GET at the returned URL. TradingView ids and non-jouview.com https URLs do not consume quota. Trader hosted uploads are effectively unlimited.

2. Attach to a trade

PUT /api/journal/entries/{positionId}/snapshots
{
  "replace": true,
  "snapshots": [
    "AbCdEfGh",
    "https://some-other-cdn.example/chart.jpg",
    "https://jouview.com/media/snapshots/.../....png"
  ]
}

Or include "snapshots": [ "..." ] on POST /api/journal/entries. replace: false appends. Attaching never increments the upload quota.

Endpoints

GET /api/journal/columns

List system + custom columns (full parameter dump).

PUT /api/journal/columns

replaceCustom: false merges custom by name. true makes custom columns exactly your list (system always kept). Array order is the journal column order for system + custom — include system names where you want them; omitted system columns stay and are appended. Max 800 columns.

{
  "replaceCustom": false,
  "columns": [
    { "name": "Trade" },
    { "name": "Setup", "type": "text", "enumValues": "CBreak, Impulse, Bias" },
    { "name": "Planned", "type": "bool" },
    { "name": "Retest", "type": "bool" },
    { "name": "Liquidity", "type": "bool" },
    {
      "name": "Score",
      "type": "number",
      "formula": "[Planned] + [Retest] + [Liquidity]",
      "textColor": "[Score] >= 3 ? \"#26a69a\" : \"#ef5350\""
    },
    { "name": "Grade", "type": "stars" },
    {
      "name": "Profit",
      "textColor": "[Profit] > 0 ? \"#26a69a\" : \"#ef5350\"",
      "displayFormat": "0.00"
    },
    { "name": "Notes" }
  ]
}

Including a system name (e.g. Profit) reorders that column and can update visibility/styles — it does not create a second custom Profit. You cannot rename, retype, or delete system columns. Write values via fields.Profit.

GET /api/journal/row-styles · PUT /api/journal/row-styles

See Styles section. PUT body { "rules": [] } clears.

POST /api/journal/entries

Create or upsert one trade (same externalId updates).

{
  "symbol": "ES",
  "quantity": 2,
  "entryPrice": 5200.25,
  "entryTime": "2024-06-12T14:32:00Z",
  "exitPrice": 5210.5,
  "exitTime": "2024-06-12T15:10:00Z",
  "accountCurrency": "USD",
  "externalId": "source-row-42",
  "fields": {
    "Setup": "CBreak",
    "Planned": "1",
    "Retest": "1",
    "Liquidity": "0",
    "Grade": "4",
    "Profit": "420",
    "Risk": "100",
    "Notes": "Clean retest"
  }
}

POST /api/journal/entries/batch

{ "entries": [ ... ] } max 200. Response includes created, failed, results, errors[{index,error,symbol}].

POST /api/journal/snapshots/upload

multipart field file. Hosts the image on jouview.com (counts toward the plan upload quota; Trader is effectively unlimited). Prefer TradingView ids or external https URLs instead when possible.

PUT /api/journal/entries/{positionId}/snapshots

{ "snapshots": ["id-or-url",...], "replace": true }. TradingView /x/ id, external https URL, or JouView upload url. Attaching does not use upload quota.

GET /api/journal/entries?take=50

Verification / analysis list (max take 500).

POST /api/journal/reset

Scoped cleanup. See Start over.

HTTP status & edge cases

CodeWhenBody
401Missing/invalid/expired token, IP mismatch, or deleted/merged token usererror + code (ip_mismatch / user_not_found)
400Validation (missing symbol, empty reset, >800 cols, >2000 batch, plan upload quota)Full error object: error, code, type, path, method, traceId, stackTrace, innerExceptions
500Unhandled server/database failureSame full error object (never an empty body)
200Batch partial failurecreated + errors[] (each item includes type/stack)
200Unknown field namesunknownFields[] skipped

Limits & security

Enforced server-side. Full numbers live in limits on /api/journal/openapi.json.

Start again from scratch

POST /api/journal/reset
{
  "removeCustomColumns": true,
  "removeImportedEntries": true,
  "clearRowStyles": true
}

Then PUT /columns with replaceCustom: true and re-import entries. Does not delete the JouView account. Set only the flags you need.

Formulas

Full formula language for computed cells, style channels, and row rules. Humans edit these in Settings → Journal columns → pen (or Row styles). AI agents set the same strings on PUT /columns / PUT /row-styles.

What a formula is

A formula is one JavaScript expression. It runs in a sandbox for a single journal row (and, when you ask for it, across other rows via ROWS). You attach formulas in Settings → Journal columns → pen:

Row styles use the same language for whole-trade chrome. Styles never change stored values.

Syntax

Empty cells count as 0 when they look numeric to the engine. That keeps [A] + [B] arithmetic when one side is blank. When joining text that may be blank, write ([Notes] || "") + " - done".

[Profit] > 0 ? "Win" : [Profit] < 0 ? "Loss" : "Flat"
Math.round(([RR1] + [RR2] + [RR3]) / 3 * 100) / 100

Fields you can read

Any journal column as [Name], plus these always-available names (even without a matching column):

ReferenceWhat it gives you
[Symbol]Traded symbol (doesn't include an exchange prefix)
[Trade]"Long" or "Short"
[Lots/Qty]Size, signed (negative on a short)
[Date/Time] / [Entry time]Entry on the charts' clock
[Exit time]Exit on the charts' clock. Blank while open.
[Entry date] / [Exit date] / [Today]Calendar day YYYY-MM-DD on the charts (not UTC)
[Entry hour] / [Entry minutes]Hour 0-23, or minutes since midnight
[Price] / [Entry price] / [Exit price]Prices
[SL] / [TP] / [SL/TP]Stop, target, or both as text
[Risk] / [RR] / [Profit]Money risk, reward/risk, result (live while open)
[Cumulative profit]Running total down the journal
[Run-up] / [Drawdown] / [BE Run-up]Excursion money
[Duration]Seconds open
[Row number]1-based index in the current sort

Times use the chart timezone. Prefer [Entry date] / [Today] for "same day" bands instead of slicing a UTC stamp.

Style formulas

Same language as value formulas. Typical patterns:

[Profit] > 0 ? "#26a69a" : [Profit] < 0 ? "#ef5350" : ""
[Profit] < 0

The second example is a Bold/Italic condition: truthy turns the style on. Leave a style field blank to inherit the row rule (or the grid default). false / 0 turns Bold/Italic/Underline off.

Display format (numbers/dates) is a mask such as 0.00 or dd/mm/yyyy HH:mm. It changes how the value is shown, not what is stored.

Yes/No, dropdowns, multivalue

Yes/No columns compare as the text "true" / "false":

[Planned] === "true" ? 1 : 0

Dropdown values are set in the pen panel (comma-separated). Compare with === "A+". Tick Multivalue to allow several checked options. Then treat the cell as a comma list:

String([Pros]).split(",").map(s => s.trim()).includes("Backtested") ? 1 : 0

Groups and stars

A Group column collapses several members into one journal cell (double-click to edit members). Its Formula can sum the members into a points total. A separate Stars column often maps that total onto 0-5:

// Points group ([Planned] === "true" ? 2 : 0) + ([Retest] === "true" ? 2 : 0) + ([Liquidity] === "true" ? 1 : 0)
// Stars: map min..max points onto 0..5 Math.max(0, Math.min(5, Math.round(([Checklist points] - 0) / 5 * 10) / 2))

Tune the min/max in the map to your real score range so a typical "good" trade lands around 4-5 stars.

Cross-row work with ROWS

ROWS is an array of every trade in the journal. Each element is an object keyed by column name (same names as [Brackets]). Use it for averages, streaks, session reviews, and account stats.

// Average Checklist score of real trades today (list => list.length ? Math.round(list.reduce((a, r) => a + (Number(r["Checklist score"]) || 0), 0) / list.length * 2) / 2 : "")( ROWS.filter(r => r["Entry date"] === [Entry date] && String(r["Setup"]).trim().toLowerCase() !== "bias" && (Number(r["Checklist score"]) || 0) !== 0 ) )

Arrow functions and .filter / .map / .reduce are the usual tools. Keep review rows out of averages with Setup tags such as session review / day review.

Build a checklist (power-user pattern)

Many small inputs → points → stars → optional review averages. Start small. Grow the point list over time.

1. Inputs: Yes/No or dropdown columns for each criterion (e.g. Planned, Retest, Liquidity). Optional Yes/No Checklist Done if you only want a score after finishing the list.

2. Direction helper

// Column "Bias" [Trade] === "Long" ? 1 : -1

3. Points group: Group (or number) column whose Formula adds weighted signals:

([Planned] === "true" ? 2 : 0) + ([Retest] === "true" ? 2 : 0) + ([Liquidity] === "true" ? 1 : 0) + ([Session bias] === "Bullish" ? [Bias] * 3 : [Session bias] === "Bearish" ? -[Bias] * 3 : 0) + (String([Pros]).split(",").map(s => s.trim()).includes("Backtested") ? 1 : 0) + (String([Cons]).split(",").map(s => s.trim()).includes("Weak retest") ? -3 : 0) + (m => m === "" || !isFinite(m) ? 0 : m < 540 ? -3 : m < 720 ? 4 : m < 900 ? 3 : -1)([Entry minutes])

4. Stars score: map the raw total onto 0-5 when the checklist is finished:

String([Checklist Done] ?? "") === "true" ? Math.max(0, Math.min(5, Math.round(([Checklist] - (-20)) / 40 * 10) / 2)) : ""

Replace -20 / 40 with your real min score and the span from max to min after a week of trades.

5. Skip non-trades and average reviews: use a Setup dropdown with values like Bias, Session review, Day review:

(setup => setup === "bias" ? "" : setup === "session review" ? (list => list.length ? Math.round(list.reduce((a, r) => a + (Number(r["Checklist score"]) || 0), 0) / list.length * 2) / 2 : "")( ROWS.filter(r => r["Entry date"] === [Entry date] && r["Entry time"] < [Entry time] && String(r["Checklist Done"] ?? "") === "true" && !/^(bias|session review|day review)$/i.test( String(r["Setup"]).trim()) )) : /* real trade */ ( String([Checklist Done] ?? "") === "true" ? Math.max(0, Math.min(5, Math.round(([Checklist] - (-20)) / 40 * 10) / 2)) : "" ) )(String([Setup]).trim().toLowerCase())

6. Color the score: background formula on the stars column:

[Checklist score] >= 4 ? "#3d8c5a" : [Checklist score] >= 2.5 ? "#7f6000" : [Checklist score] > 0 ? "#783f04" : ""

More examples

Reward vs risk

[Risk] ? [Profit] / [Risk]: ""

Mood average (Pre / Management / Post)

([Stress - Happy] + [FOMO - Collected] + [Nervous - Confident] + [Detached - Focused] + [Tired - Rested]) / 5

Timeframe quality band

[TF] === "15m" ? 0 : [TF] === "15m + 5m" ? 1: [TF] === "5m" ? -2 : [TF] === "1m" ? -4 : 0

Alternate row banding

Row style background:

[Row number] % 2 === 0 ? "rgba(231,239,232,0.04)" : ""

Same-day net (closed trades)

ROWS .filter(r => r["Entry date"] === [Entry date] && r["Exit time"]) .reduce((a, r) => a + (Number(r["Profit"]) || 0), 0)

Hold duration in minutes

[Duration] ? Math.round([Duration] / 60): ""

When a formula breaks

A cell that cannot be evaluated shows ERR. Hover it: the tooltip names the problem, lists every referenced value with its type, and suggests a fix. Common causes:

After you save a formula it can take a moment to appear. Open JouView → How to on the chart for the live field list of your columns.

Live machine schema

Agents should fetch and follow this JSON (includes OpenAPI-style paths, components.schemas, catalogs, workflows):

Open /api/journal/openapi.json

Click "Preview below" to load the live schema...

After the AI finishes

Open the JouView menu (bottom tab) and press Refresh so the journal picks up the new columns, styles or changes. Then confirm the journal and adjust anything in Settings.