Memory
The pinned facts and the marketing brief that the roasOS agent reads before it writes.
Memory is what you teach the agent. It has two parts, and both parts sit behind /v1/memory.
| Part | Path | What it is |
|---|---|---|
| Pins | /v1/memory/pins | short facts, one line each, in six categories |
| Brief | /v1/memory/brief | one structured marketing brief |
The chat agent reads the same rows. A pin that you write over the API reaches the agent in the app, and a pin that the agent writes reaches this API.
Every door here costs 0 credits.
A 0-credit door never returns a quote envelope. It needs no Idempotency-Key, it accepts no quote_only, and it accepts no quote_id.
| Operation | Path | Scope | Reference |
|---|---|---|---|
| List the pins | GET /v1/memory/pins | api:read | listMemoryPins |
| Pin a fact | POST /v1/memory/pins | api:write | createMemoryPin |
| Unpin a fact | DELETE /v1/memory/pins/{id} | api:write | deleteMemoryPin |
| Read the brief | GET /v1/memory/brief | api:read | getMemoryBrief |
| Replace the brief | PUT /v1/memory/brief | api:write | updateMemoryBrief |
Pins
List the pins
curl "https://api.roasos.com/v1/memory/pins?limit=20&category=hook" \
-H "Authorization: Bearer roasos_sk_..."{
"data": [
{
"id": "b2a1c0de-...",
"category": "hook",
"content": "Open on the price objection. It beats a product shot for this audience.",
"metadata": {},
"created_at": "2026-08-21T10:20:00.000Z"
}
],
"meta": { "has_more": false }
}The list is newest first. Each timestamp is UTC and ends with Z. limit and cursor work as they do on every list. Refer to Pagination.
Add category to read one category only. A category value outside the six is 400 validation_error.
The marketing brief is not a pin, so this list never returns it.
Pin a fact
curl -X POST https://api.roasos.com/v1/memory/pins \
-H "Authorization: Bearer roasos_sk_..." \
-H "Content-Type: application/json" \
-d '{
"category": "banned",
"content": "Never say clinically proven. Legal refused the claim."
}'The answer is 201 with the new pin.
| Field | Rule |
|---|---|
category | one of brand_voice, audience, hook, banned, win, note |
content | 2 to 1,000 characters |
metadata | an optional object of your own keys |
Pin a confirmed fact. Do not pin a guess. The agent treats a pin as a rule of your brand.
| Category | Use it for |
|---|---|
brand_voice | how the brand talks |
audience | who the ads talk to |
hook | an opening that works |
banned | a word or a claim that the agent must not use |
win | an ad or an angle that performed |
note | anything else that the agent must remember |
The cap is 50 for each category
A category holds 50 rows. roasOS drops the oldest pin of that category when you pass the cap. It never drops the marketing brief.
The brief rides the note category as one row, so a workspace with a brief keeps 49 pins in note and 50 in each other category.
roasOS writes your new pin first and culls the category after it. The answer is therefore the same 201 whether a cull ran or not, and your new pin is never the row that goes.
The cap bounds what the agent reads in one turn. Keep the pins short and true, and the cap is never a problem.
Unpin a fact
curl -X DELETE https://api.roasos.com/v1/memory/pins/b2a1c0de-... \
-H "Authorization: Bearer roasos_sk_..."{ "data": { "id": "b2a1c0de-...", "resource": "memory_pin", "deleted": true } }roasOS scopes the id to your workspace before it deletes. An id of another workspace is 404 memory_pin_not_found, and no row is touched.
The marketing brief is not a pin, so this door cannot delete it. The id of the brief row answers the same 404. Replace the brief on PUT /v1/memory/brief instead.
A connected model cannot unpin
roasos_delete_memory_pin is declared and it is never bound on the MCP server. A connected model may therefore write memory, and it can never erase memory. Every delete door of roasOS follows this rule. Delete a pin over REST, with your own key.
The marketing brief
The brief is one structured record for each workspace, so its path names no id either.
Read the brief
curl https://api.roasos.com/v1/memory/brief \
-H "Authorization: Bearer roasos_sk_..."{
"data": {
"schema_version": "marketing-brief@1",
"kind": "marketing_brief",
"brief": {
"personas": ["women 25 to 40 who buy skincare in a pharmacy"],
"objections": [
{ "objection": "It is too expensive.", "response": "One bottle lasts eight weeks." }
],
"words_to_use": ["barrier repair", "fragrance free"],
"voice_notes": "Direct and warm. Short sentences. No hype."
},
"updated_at": "2026-08-21T10:24:00.000Z"
}
}A workspace that wrote no brief reads as an empty brief object and updated_at: null. That is not an error.
Replace the brief
curl -X PUT https://api.roasos.com/v1/memory/brief \
-H "Authorization: Bearer roasos_sk_..." \
-H "Content-Type: application/json" \
-d '{
"personas": ["women 25 to 40 who buy skincare in a pharmacy"],
"words_to_use": ["barrier repair", "fragrance free"],
"voice_notes": "Direct and warm. Short sentences. No hype."
}'PUT replaces the whole brief. A field that you omit is cleared. Send at least one field: an empty body is 400 validation_error, so a PUT can never empty the brief by accident.
The in-app agent behaves differently on purpose. Its own tool merge-patches the brief, one field at a time, while it talks to a person. This door replaces the brief, because a public write that merged would make "clear this field" impossible. Read the brief first, change the object, then send it back.
The fields
| Field | Rule |
|---|---|
personas | 8 entries at most, each 2 to 300 characters |
jtbd_forces | push, pull, habit and anxiety, each 2 to 500 characters |
objections | 10 entries at most, each with objection and response |
anti_persona | 2 to 500 characters: who the ads must not talk to |
words_to_use | 30 entries at most, each 1 to 120 characters |
words_to_avoid | 30 entries at most, each 1 to 120 characters |
proof_points | 12 entries at most, each with claim and evidence |
voice_notes | 2 to 800 characters |
An unknown field is 400 validation_error. Use the words of your customers. Send confirmed values only.
Errors
| Code | Status | Cause |
|---|---|---|
validation_error | 400 | the body or a query value breaks a rule above |
invalid_cursor | 400 | the cursor value is not one that roasOS gave you |
memory_pin_not_found | 404 | your workspace has no pin with that id |
memory_store_unavailable | 503 | the store did not answer, or the answer did not match the contract |
memory_store_unavailable publishes retryable: false. It is a real published shape and not a transient, so a retry loop is the wrong answer to it.
From an agent
| Tool | Operation |
|---|---|
roasos_list_memory_pins | GET /v1/memory/pins |
roasos_create_memory_pin | POST /v1/memory/pins |
roasos_get_marketing_brief | GET /v1/memory/brief |
roasos_update_marketing_brief | PUT /v1/memory/brief |
Refer to Connect an AI agent (MCP).
Next
- See what roasOS compiled from the pins and the brief: Intelligence documents.
- Fill the memory from your own website: Extract a brand.