> ## Documentation Index
> Fetch the complete documentation index at: https://edgeful.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# manage your settings with the API

> read and change your Edgeful bookmarks, watchlists, account defaults, custom sessions, report customizations and templates from your own code with an API key, and list, run, read, save and delete your saved custom reports.

everything connected Claude can change through the [settings tools](https://edgeful.com/docs/getting-started/manage-settings.md) is also a REST route for your own code. send your API key, and Edgeful writes the same rows the app reads: bookmarks, watchlists, account defaults, custom sessions, report customizations and templates land exactly as if you had edited them by hand, and the app shows them after a reload. six more routes list, run, read, save and delete your saved custom reports; building and revising a report stay with Claude.

## your key and the write permission

1. create a key in the [API dashboard](https://www.edgeful.com/api-dashboard) and send it as `Authorization: Bearer <your-api-key>` on every request, as for [any other call](https://edgeful.com/docs/api-reference/authentication.md). only an API key works here: a browser session does not. every route acts on the account that owns the key; there is no member id to pass.
2. writes are on by default, and one permission covers Claude and your own code. in the API dashboard, under **let Claude or your code write to your account**, **this API key** covers every client that uses the key, whether that is Claude over MCP or your script over REST. turning it off keeps every read working, and `GET /v1/settings` reports `writes_enabled` for the key you sent.
3. the reads work on any API-enabled plan with writes off; the writes need the permission.

## endpoints

each route has the same name as the MCP tool it mirrors. a refusal message that names an operation (for example "list\_report\_catalog with surface screener lists them") refers to this table.

| operation | route | purpose | write permission |
| - | - | - | - |
| `get_account_settings` | `GET /v1/settings` | your defaults, custom sessions, bookmarks, watchlists, customized setting keys and templates, plus whether this key may write | no |
| `list_report_catalog` | `GET /v1/settings/report-catalog` | report pages with their markets and sub-reports, marked with whether your plan can run each; with `surface=wip` or `surface=screener`, what those templates are composed from | no |
| `describe_report_customizations` | `GET /v1/settings/report-customizations` | a report page's customizations by `report` and `sub_report`, or by `setting_key`: each slot's label, domain, default and current value, the pages the key drives and the query a report endpoint needs to apply them | no |
| `update_report_customizations` | `POST /v1/settings/report-customizations` | set slots of one or more setting keys, or reset a key to its defaults | yes |
| `update_bookmarks` | `POST /v1/settings/bookmarks` | add and remove bookmarks by report slug and sub-report | yes |
| `update_watchlist` | `POST /v1/settings/watchlists` | add and remove tickers on one market's watchlist | yes |
| `update_profile_defaults` | `POST /v1/settings/defaults` | change your default market, ticker, session, date range or report | yes |
| `save_custom_session` | `POST /v1/settings/custom-sessions` | create a custom session, or update one by `id` | yes |
| `delete_custom_session` | `DELETE /v1/settings/custom-sessions/{id}` | delete a custom session by id | yes |
| `list_templates` | `GET /v1/settings/templates` | your templates by type and, for report templates, by parent report, with the built-in What's in Play and screener templates as read-only entries | no |
| `save_template` | `POST /v1/settings/templates` | create a report, What's in Play or screener template, or update one by `id` | yes |
| `delete_template` | `DELETE /v1/settings/templates/{id}` | delete a template by id | yes |
| `list_custom_reports` | `GET /v1/custom-reports` | your saved custom reports and their methodology, a page at a time | no |
| `get_custom_report_latest_result` | `GET /v1/custom-reports/{definition_id}/latest-result` | the latest successful stored result of a saved custom report, with every row; never a new run | no |
| `run_custom_report` | `POST /v1/custom-reports/{definition_id}/runs` | run a saved custom report on its saved inputs, or on another ticker, session or date range | yes |
| `get_custom_report_run` | `GET /v1/custom-reports/{definition_id}/runs/{run_id}` | one run's status while it computes, then its result with every row | no |
| `save_custom_report` | `POST /v1/custom-reports` | save a reviewed draft from a build in Claude | yes |
| `delete_custom_report` | `DELETE /v1/custom-reports/{definition_id}` | delete a saved custom report and its stored runs | yes |

the full request and response shapes are in the API reference groups **settings** and **custom reports** in the sidebar.

## try it

```bash curl theme={null}
curl -X POST "https://api.edgeful.com/v1/settings/bookmarks" \
  -H "Authorization: Bearer $EDGEFUL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"add": [{"report": "gap-fill", "sub_report": "by-size"}]}'
```

```python Python theme={null}
import os, requests

headers = {"Authorization": f"Bearer {os.environ['EDGEFUL_API_KEY']}"}
bookmarks = requests.post(
    "https://api.edgeful.com/v1/settings/bookmarks",
    headers=headers,
    json={"add": [{"report": "gap-fill", "sub_report": "by-size"}]},
)
print(bookmarks.json()["current"])

template = requests.post(
    "https://api.edgeful.com/v1/settings/templates",
    headers=headers,
    json={
        "template_type": "report",
        "name": "tight gap",
        "parent_report": "gap-fill",
        "sub_report": "by-size",
        "configuration": {
            "setting_key": "gapFillBySize",
            "customizations": {"value": 50},
            "inputs": {"ticker": {"market": "futures", "ticker": "ES"}},
        },
    },
)
print(template.json()["template"]["url"])
```

## how writes behave

* **the app shows the change after reload.** an Edgeful tab that was open before your write keeps its old copy of your bookmarks and watchlists and overwrites the change the next time it saves. a scheduled integration cannot reload that tab, so expect bookmark and watchlist additions made while you are in the app to be reverted by the tab's next save until you reload it.
* **writes are idempotent change sets.** a `POST` carries what to add and remove, or the fields to change, never a whole list to replace. adding a bookmark or ticker you already have, or removing one you do not, succeeds with `status` `unchanged`; creating a session or template equal to one you already saved converges on it. a write answers `200` with `status` `created`, `updated`, `unchanged` or `deleted`, and a repeated delete is `unchanged`, not `404`, so resending a request after a timeout is always safe.
* **every result carries what undo needs.** a defaults write returns the `previous` defaults, a customization write each key's `previous` values (null when the key had none, so a reset undoes it) and a template update the `previous` template, and sending those back undoes the change. bookmark and watchlist writes return the previous and current lists and what `changed`, so removing what was added and adding what was removed undoes them. deletes return the removed object so it can be saved again under a new id. a custom-session save returns the saved session and the full list.
* **send writes one at a time.** writes to one account serialize on your profile, so parallel requests only wait for each other.
* **Edgeful validates before it saves.** report slugs and sub-reports come from the report catalog, tickers must be known on the market you name, a default session must be a standard session the report allows on that market, and a custom session needs `HH:MM` start and end times that differ and an IANA timezone. an invalid request is refused with `400` and a `detail` that lists the allowed values.
* **defaults are reconciled like the account form.** the report constrains the market, a market change resets the ticker to that market's default, and the session is re-resolved when the report no longer allows it.
* **report customizations land where the page reads them**, in slots (`value`, `value2`, ...) under the setting key, checked the way the page's customization modal checks them; each record carries the key's previous and resulting values and, per page, the query a report endpoint needs to apply them. the [MCP settings page](https://edgeful.com/docs/getting-started/manage-settings.md#how-writes-behave) spells out the slot, template and What's in Play rules in full; the REST routes apply exactly the same ones.
* **templates are applied in Edgeful.** saving a template does not change any report page. it appears in its picker after a reload, or the app's five-minute template refetch, and you apply it from there.
* **every write is logged** with the operation, your member id and the key that made it. settings writes cost no AI credits.

## errors and retries

| status | when | body |
| - | - | - |
| `400` | a value Edgeful cannot use: an unknown slug, a ticker the market does not know, a slot outside its domain, a name that already exists | `{"detail": "<message naming the valid values>"}` |
| `401` | no key, a session token instead of a key, or a revoked key | `{"detail": "..."}` |
| `403` | no API-enabled subscription (`missing_entitlement`), a paused membership (`membership_paused`), or writes turned off for this key (`writes_disabled`) | `{"detail": {"detail": "...", "code": "..."}}` |
| `404` | a custom report that is not yours, a run id that is not one of the report's runs (`run_not_found`), or a build message you cannot save | `{"detail": "Custom report not found."}`, `{"detail": "Custom report message not found."}` or `{"detail": {"code": "...", "message": "..."}}` |
| `409` | a custom report that needs a rebuild in Edgeful (`needs_rebuild`), a run window with no completed sessions (`no_completed_data`), or a build that has not completed | `{"detail": {"code": "...", "message": "..."}}` or `{"detail": "..."}` |
| `422` | a malformed body or query: an unknown field, a list over its cap, a bad clock, an unknown query argument; or run controls a report cannot take (`invalid_controls`, `requires_refine`) | FastAPI's validation body, or `{"detail": {"code": "...", "message": "..."}}` |
| `429` | the key's request limit (no `Retry-After`); the account's settings, custom-report or run budget (`write_budget_exhausted`, with `Retry-After`); the hourly run allowance (`run_limit_hourly`) or a fresh run already computing (`run_limit_concurrent`), both with `retry_after_seconds` | `{"detail": "..."}`, `{"detail": {"detail": "...", "code": "..."}}` or `{"detail": {"code": "...", "message": "...", "retry_after_seconds": ...}}` |
| `503` | the write budget could not be checked (`write_limits_unavailable`), or key verification is temporarily unavailable | `{"detail": {"detail": "...", "code": "..."}}` or `{"detail": "..."}` |

two `400` messages mean retry rather than fix: "Ticker validation is temporarily unavailable; try again shortly." and a customization that "was saved from Edgeful at the same time". everything else under `400` needs a different request. honour `Retry-After` when it is present and back off otherwise; a tight retry loop on `429` burns the hourly budget, because refused attempts count.

## budgets and limits

* settings writes share one budget per account of 60 per minute and 600 per hour across Claude's MCP connections and every API key, so a script writing in a loop can leave Claude refused for the rest of the window, and the reverse. one request counts once however many items it carries, refused attempts count too, and each list is capped at 100 items.
* custom-report runs share a budget of 60 per minute and 600 per hour with Claude's runs, and every run request counts, one that returns an existing result included. saves and deletes share a budget of 10 per minute and 100 per hour with Claude's other custom-report writes.
* a run that needs a fresh computation also uses your run allowance of 200 per hour, shared with builds in Edgeful, and fresh computations run one at a time per account: starting another while one computes is a `429` with `run_limit_concurrent` and `retry_after_seconds`, so start a dashboard's runs one after another. a run already made for the same inputs and completed data uses neither.
* every call also counts against the key's request limits from [authentication](https://edgeful.com/docs/api-reference/authentication.md): 120 per 60 seconds, 30 per 10 seconds and 1,500 per hour.
* settings and custom-report calls do not appear in the API dashboard's usage numbers, which count report calls.
* reads are never budgeted.

## custom reports over REST

building and revising a report stay with Claude over [MCP](https://edgeful.com/docs/getting-started/build-custom-reports.md) and in Edgeful AI. everything you do with a saved report works here.

* **list and read.** `GET /v1/custom-reports` returns your saved reports with `total`, `has_more` and `next_offset`; pass `next_offset` as `offset` for the next page, and `limit` up to 100. `GET /v1/custom-reports/{definition_id}/latest-result` returns the latest successful stored result with the `effective_controls`, `data_through` and `computed_at` it actually used, or `status` `no_result` when no saved run has completed yet. neither starts a run.
* **every row.** a result carries every row the run stored, with `total_rows`; Claude's tools page the same rows a hundred at a time. a run whose result would exceed about 512 KB stores nothing and fails with "result too large - narrow the window."
* **run.** `POST /v1/custom-reports/{definition_id}/runs` runs the saved code on its saved inputs, or on `controls` that change the ticker, session or dates; the market and granularity stay locked, and runs use no AI credits. a run already made for the same inputs and completed data comes back at once with `200`. otherwise the run starts and answers `202` with `status` `running`, its `run_id` and `poll_after_seconds`: read it with `GET /v1/custom-reports/{definition_id}/runs/{run_id}` until `status` is no longer `running`. a failed run with `retryable` true is retried by sending its `run_id` as `retry_of_run_id` with the same controls.
* **save and delete.** `POST /v1/custom-reports` saves a reviewed draft from a build in Claude, named by its `conversation_id` and `message_id`, under the `name` you give it. `DELETE /v1/custom-reports/{definition_id}` deletes a saved report and its stored runs; deleting it again is a `404`.

## the playground sends real requests

the **try it** button on a write route changes your account, and on a delete route removes the template, session or custom report you name. settings results carry what undo needs, but a deleted custom report and its stored runs cannot be restored here. it is not a sandbox.

## versioning

routes under `/v1` only grow: new routes, fields and codes may appear without notice, and a documented route, field, status or code is not removed or renamed under `/v1`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.