Roam Research Docs · Developer documentation
roam-graph-token-
X-Authorization header
Authorization header too (would be more secure) but you have to make sure that your code/library handles redirect properly and passes the authorization header when redirect has been followed (and the latter is generally not default behavior for most network libraries)
Bearer
Bearer roam-graph-token-t_OjqgIAH1JZphzP4HxjJNad55lLFKpsqIM7x3bW
curl -X POST "https://api.roamresearch.com/api/graph/MY-GRAPH/q" --location-trusted \
-H "Accept: application/json" \
-H "X-Authorization: Bearer roam-graph-token-XXX" \
-H "Content-Type: application/json" \
-d "{\"query\": \"[:find ?block-uid ?block-str :in \$ ?search-string :where [?b :block/uid ?block-uid] [?b :block/string ?block-str] [(clojure.string/includes? ?block-str ?search-string)]]\", \"args\": [\"apple\"]}"
# =>
# {"result": [["hjt3Bwqp2", "apple pie recipe"], ["e1Xp7HELP", "Bought apples at the farmers market"]]}
--location-trusted is required because your request is redirected to the actual machine doing the work.
https://api.roamresearch.com/ abstracts away this from the developer point of view, by redirecting messages to the actual machine doing the work
Bearer and are passed in the Authorization header
q, pull, pull-many) have a 20 second time limit. If a request exceeds it, it fails with a 500 whose message contains "took too long to run"
/api/graph/{graph-name}/q (POST)
roamAlphaAPI.data.q
query (string, required) — the datalog query, as an EDN string
args (array, optional) — inputs for the query, bound in order to the extra :in variables (the ones after $)
{"result": [...]} — the array of query results
{
"query": "[:find ?block-uid ?block-str :in $ ?search-string :where [?b :block/uid ?block-uid] [?b :block/string ?block-str] [(clojure.string/includes? ?block-str ?search-string)]]",
"args": ["apple"]
}
curl -X POST "https://api.roamresearch.com/api/graph/MY-GRAPH/q" --location-trusted \
-H "Accept: application/json" \
-H "X-Authorization: Bearer roam-graph-token-XXX" \
-H "Content-Type: application/json" \
-d "{\"query\": \"[:find ?block-uid ?block-str :in \$ ?search-string :where [?b :block/uid ?block-uid] [?b :block/string ?block-str] [(clojure.string/includes? ?block-str ?search-string)]]\", \"args\": [\"apple\"]}"
# =>
# {"result": [["hjt3Bwqp2", "apple pie recipe"], ["e1Xp7HELP", "Bought apples at the farmers market"]]}
import { initializeGraph, q } from "@roam-research/roam-api-sdk";
const graph = initializeGraph({
token: "roam-graph-token-XXX",
graph: "MY-GRAPH",
});
const result = await q(
graph,
"[:find ?block-uid ?block-str :in $ ?search-string :where [?b :block/uid ?block-uid] [?b :block/string ?block-str] [(clojure.string/includes? ?block-str ?search-string)]]",
["apple"]
);
// => [["hjt3Bwqp2", "apple pie recipe"], ["e1Xp7HELP", "Bought apples at the farmers market"]]
result value directly and throws on error responses
/api/graph/{graph-name}/pull (POST)
roamAlphaAPI.data.pull
selector (string, required) — the pull pattern, as an EDN string
eid (string, required) — the entity to pull, as an EDN string: an entity id number or a lookup ref like [:block/uid "abc123xyz"]
{"result": {...}} — the pulled map, containing only the attributes the entity actually has
{
"eid": "[:block/uid \"08-30-2022\"]",
"selector": "[:block/uid :node/title :block/string {:block/children [:block/uid :block/string]} {:block/refs [:node/title :block/string :block/uid]}]"
}
curl -X POST "https://api.roamresearch.com/api/graph/MY-GRAPH/pull" --location-trusted \
-H "Accept: application/json" \
-H "X-Authorization: Bearer roam-graph-token-XXX" \
-H "Content-Type: application/json" \
-d "{\"eid\": \"[:block/uid \\\"08-30-2022\\\"]\", \"selector\": \"[:block/uid :node/title :block/string {:block/children [:block/uid :block/string]} {:block/refs [:node/title :block/string :block/uid]}]\"}"
# =>
# {"result": {":block/uid": "08-30-2022", ":node/title": "August 30th, 2022", ":block/children": [{":block/uid": "hjt3Bwqp2", ":block/string": "apple pie recipe"}, ...]}}
import { initializeGraph, pull } from "@roam-research/roam-api-sdk";
const graph = initializeGraph({
token: "roam-graph-token-XXX",
graph: "MY-GRAPH",
});
const result = await pull(
graph,
"[:block/uid :node/title :block/string {:block/children [:block/uid :block/string]} {:block/refs [:node/title :block/string :block/uid]}]",
'[:block/uid "08-30-2022"]'
);
// =>
// {":block/uid": "08-30-2022",
// ":node/title": "August 30th, 2022",
// ":block/children": [{":block/uid": "hjt3Bwqp2", ":block/string": "apple pie recipe"}, ...]}
result value directly and throws on error responses
/api/graph/{graph-name}/pull-many (POST)
/api/graph/{graph-name}/pull (POST)
roamAlphaAPI.data.pull_many
selector (string, required) — the pull pattern, as an EDN string, applied to every eid
eids (required) — the entities to pull
{"result": [...]} — an array of pulled maps, one per eid, in the same order
{
"eids": "[[:block/uid \"08-30-2022\"] [:block/uid \"08-31-2022\"]]",
"selector": "[:block/uid :node/title {:block/children [:block/uid :block/string]}]"
}
curl -X POST "https://api.roamresearch.com/api/graph/MY-GRAPH/pull-many" --location-trusted \
-H "Accept: application/json" \
-H "X-Authorization: Bearer roam-graph-token-XXX" \
-H "Content-Type: application/json" \
-d "{\"eids\": \"[[:block/uid \\\"08-30-2022\\\"] [:block/uid \\\"08-31-2022\\\"]]\", \"selector\": \"[:block/uid :node/title {:block/children [:block/uid :block/string]}]\"}"
# =>
# {"result": [{":block/uid": "08-30-2022", ":node/title": "August 30th, 2022", ":block/children": [...]}, {":block/uid": "08-31-2022", ":node/title": "August 31st, 2022", ":block/children": [...]}]}
import { initializeGraph, pull_many } from "@roam-research/roam-api-sdk";
const graph = initializeGraph({
token: "roam-graph-token-XXX",
graph: "MY-GRAPH",
});
const result = await pull_many(
graph,
"[:block/uid :node/title {:block/children [:block/uid :block/string]}]",
'[[:block/uid "08-30-2022"] [:block/uid "08-31-2022"]]'
);
// =>
// [{":block/uid": "08-30-2022", ":node/title": "August 30th, 2022", ":block/children": [...]},
// {":block/uid": "08-31-2022", ":node/title": "August 31st, 2022", ":block/children": [...]}]
result value directly and throws on error responses
/api/graph/{graph-name}/search (POST)
search-str (string, required) — the text to search for
search-string is accepted as an alias
search-pages (boolean, optional, default true) — include page results
search-blocks (boolean, optional, default true) — include block results
hide-code-blocks (boolean, optional, default false) — exclude code blocks from block results
limit (number, optional, default 300, max 1000) — maximum number of results
pull (string, optional) — pull pattern applied to each result, as an EDN string; default [:block/string :node/title :block/uid]
{"result": [...], "search-str": "..."} — result is an array of pulled maps
{
"search-str": "apple",
"hide-code-blocks": true,
"limit": 20
}
curl -X POST "https://api.roamresearch.com/api/graph/MY-GRAPH/search" --location-trusted \
-H "Accept: application/json" \
-H "X-Authorization: Bearer roam-graph-token-XXX" \
-H "Content-Type: application/json" \
-d "{\"search-str\": \"apple\", \"hide-code-blocks\": true, \"limit\": 20}"
# =>
# {"result": [{":node/title": "apple", ":block/uid": "x83-jseuA"}, {":block/string": "apple pie recipe", ":block/uid": "hjt3Bwqp2"}], "search-str": "apple"}
fetch (this route is not in the SDK yet)
const response = await fetch("https://api.roamresearch.com/api/graph/MY-GRAPH/search", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Authorization": "Bearer roam-graph-token-XXX",
},
body: JSON.stringify({ "search-str": "apple", "hide-code-blocks": true, "limit": 20 }),
});
const { result } = await response.json();
// => [{":node/title": "apple", ":block/uid": "x83-jseuA"}, {":block/string": "apple pie recipe", ":block/uid": "hjt3Bwqp2"}]
/api/graph/{graph-name}/roam-query (POST)
{{[[query]]: ...}} blocks — and returns the matching blocks and pages
roamAlphaAPI.data.roamQuery
query (string, required) — the query string, e.g. {and: [[project]] [[active]]}
current-date (string, required) — the client's current date, as "yyyy-MM-dd"
{between: } clauses; required even if the query has none
block-uid (string, optional) — uid of an existing block to treat as the query's location
{between: } clauses instead of current-date
limit (number, optional) — maximum number of results
pull (string, optional) — pull pattern applied to each result, as an EDN string; default [:block/string :node/title :block/uid]
{"result": [...]} — an array of pulled maps
[[project]] and [[active]]
{
"query": "{and: [[project]] [[active]]}",
"current-date": "2023-07-29",
"limit": 20
}
curl -X POST "https://api.roamresearch.com/api/graph/MY-GRAPH/roam-query" --location-trusted \
-H "Accept: application/json" \
-H "X-Authorization: Bearer roam-graph-token-XXX" \
-H "Content-Type: application/json" \
-d "{\"query\": \"{and: [[project]] [[active]]}\", \"current-date\": \"2023-07-29\", \"limit\": 20}"
# =>
# {"result": [{":block/string": "kick off [[project]] review #active", ":block/uid": "hjt3Bwqp2"}, ...]}
fetch (this route is not in the SDK yet)
const response = await fetch("https://api.roamresearch.com/api/graph/MY-GRAPH/roam-query", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Authorization": "Bearer roam-graph-token-XXX",
},
body: JSON.stringify({
"query": "{and: [[project]] [[active]]}",
"current-date": "2023-07-29",
"limit": 20,
}),
});
const { result } = await response.json();
// => [{":block/string": "kick off [[project]] review #active", ":block/uid": "hjt3Bwqp2"}, ...]
/api/graph/{graph-name}/write (POST)
action key in the JSON request body, alongside that action's own parameters
batch-actions
action (string, required) — one of: create-block, move-block, update-block, delete-block, create-page, update-page, delete-page, batch-actions
create-block
roamAlphaAPI.data.block.create
location (map, required)
parent-uid (string) — uid of the parent block or page the new block goes under
page-title — target a page by title instead of uid; creates the page if it does not exist
{"daily-note-page": "MM-DD-YYYY"} for daily note pages
order (number | "first" | "last", required) — position among the parent's children
block (map, required)
string (string, required) — the block's text
uid (string, optional) — uid for the new block, autogenerated if omitted
open (boolean, optional, default true) — expanded or collapsed
heading (number, optional) — 0, 1, 2, or 3, where 0 means normal text (no heading)
text-align (string, optional) — left, center, right, or justify
children-view-type (string, optional) — bullet, numbered, or document
move-block
roamAlphaAPI.data.block.move
block (map, required)
uid (string, required) — the block to move
location (map, required)
parent-uid (string) — uid of the new parent block or page
page-title — target a page by title instead of uid; creates the page if it does not exist
{"daily-note-page": "MM-DD-YYYY"} for daily note pages
order (number | "first" | "last", required) — position among the new parent's children
update-block
roamAlphaAPI.data.block.update
block (map, required)
uid (string, required) — the block to update
string (string, optional) — the new text
open (boolean, optional) — expanded or collapsed
heading (number, optional) — 0, 1, 2, or 3, where 0 means normal text (no heading)
text-align (string, optional) — left, center, right, or justify
children-view-type (string, optional) — bullet, numbered, or document
delete-block
roamAlphaAPI.data.block.delete
block (map, required)
uid (string, required) — the block to delete
create-page
roamAlphaAPI.data.page.create
page (map, required)
title (string, required) — the page's title
uid (string, optional) — uid for the new page, autogenerated if omitted
children-view-type (string, optional) — bullet, numbered, or document
update-page
roamAlphaAPI.data.page.update
page (map, required)
uid (string, required) — the page to update
title (string, optional) — the new title
children-view-type (string, optional) — bullet, numbered, or document
delete-page
roamAlphaAPI.data.page.delete
page (map, required)
uid (string, required) — the page to delete
batch-actions
uid and parent-uid values and reuse them across the batch — e.g. create a page with uid -1, then create blocks under parent-uid -1
{"tempids-to-uids": {"-1": "EBiw8LzPb"}}
num-actions-successfully-transacted-before-failure tells you how many succeeded, alongside message and batch-error-message
actions (array, required) — a list of action maps, each shaped exactly like the individual actions above
batch-actions responds with {"tempids-to-uids": {...}} when tempids are used
create-block request — creates a new block as the last child of the September 28th, 2022 daily note page
{
"action": "create-block",
"location": {
"parent-uid": "09-28-2022",
"order": "last"
},
"block": {
"string": "new block created via the backend"
}
}
curl -X POST "https://api.roamresearch.com/api/graph/MY-GRAPH/write" --location-trusted \
-H "Accept: application/json" \
-H "X-Authorization: Bearer roam-graph-token-XXX" \
-H "Content-Type: application/json" \
-d "{\"action\":\"create-block\",\"location\":{\"parent-uid\":\"09-28-2022\",\"order\":\"last\"},\"block\":{\"string\":\"new block created via the backend\"}}"
# => 200 OK (empty body)
import { initializeGraph, createBlock } from "@roam-research/roam-api-sdk";
const graph = initializeGraph({
token: "roam-graph-token-XXX",
graph: "MY-GRAPH",
});
const ok = await createBlock(graph, {
location: { "parent-uid": "09-28-2022", order: "last" },
block: { string: "new block created via the backend" },
});
// => true
action for you and resolves to true on success
batch-actions request that uses every action — creates pages and blocks (reusing tempids), captures to a daily note page, then moves, updates, and deletes
{
"action": "batch-actions",
"actions": [
{
"action": "create-page",
"page": { "title": "Batch action test page", "uid": -1 }
},
{
"action": "create-block",
"location": { "parent-uid": -1, "order": "last" },
"block": { "string": "First" }
},
{
"action": "create-block",
"location": { "parent-uid": -1, "order": "last" },
"block": { "string": "Second", "uid": -2, "heading": 2, "open": false }
},
{
"action": "create-block",
"location": { "page-title": {"daily-note-page": "07-29-2023"}, "order": "first" },
"block": { "string": "captured to a daily note page" }
},
{
"action": "move-block",
"block": { "uid": -2 },
"location": { "parent-uid": -1, "order": 0 }
},
{
"action": "update-block",
"block": { "uid": -2, "string": "Second (updated)", "heading": 0 }
},
{
"action": "create-page",
"page": { "title": "Scratch page", "uid": -3, "children-view-type": "numbered" }
},
{
"action": "update-page",
"page": { "uid": -3, "title": "Scratch page (renamed)" }
},
{
"action": "delete-page",
"page": { "uid": -3 }
},
{
"action": "delete-block",
"block": { "uid": -2 }
}
]
}
// =>
// {"tempids-to-uids": {"-1": "EBiw8LzPb", "-2": "X4DelKvsP", "-3": "mR7wJq2Lc"}}
message value
"order": 2 in an update-block request
batch-actions especially, You have to carefully handle this (to know how many requests suceeded)
num-actions-successfully-transacted-before-failure tells you how many succeeded, alongside message and batch-error-message
message value
pull and pull-many requests
q, search, and roam-query requests
write requests
batch-actions request counts as the number of actions in it, not as 1 request
batch-actions request counts as the number of actions in it
X-Authorization header instead of the Authorization header
X-Authorization header
Authorization header too (would be more secure) but you have to make sure that your code/library handles redirect properly and passes the authorization header when redirect has been followed (and the latter is generally not default behavior for most network libraries)
"message": "You are not authenticated" response from the server, which is super confusing unless you go to the console and really look at the requests
fetch or equivalent in my roam/js (i.e. in the browser)
fetch directly from the browser. Just make sure you pass your token in the X-Authorization header, not Authorization (browsers drop the Authorization header when following the cross-origin redirect)
roamAlphaAPI.data.backend.q — a client-side wrapper that makes this same request for you