Brand
Onboarding record: identity, voice, offerings, sources & intelligence.
This is not the live API. This page documents an earlier, in-browser mock API (routes like /accounts, /messaging and /brand), kept for reference. The real, read-only API that runs today is described in The API.
Brand — the single workspace record
The brand is the onboarding record the reveal step builds: identity, location, voice, offerings, channels per platform, memory rules, intelligence (competitors + targetCommunities), sources (indexed pages), and sourceUrl. Every route here reads or mutates the same in-memory BrandEntity (persisted to brand-entity in localStorage, migrated on load). The dashboard Brand tab, onboarding, and AI reply drafts all read this one record.
Seven routes: read the brand, upsert it (the workhorse), append intelligence (deduped), list/index/delete sources, and clear the whole record. Code: apps/web/src/lib/brand/server.ts and apps/web/src/lib/brand/types.ts (isBrandEntity, migrateBrandEntity).
GET /brand — Get brand
Returns the single workspace BrandEntity or null when onboarding was skipped or the brand was cleared. This is the source-of-truth the Brand tab, onboarding reveal, and AI reply drafts all read — polling here is how the dashboard knows whether to show the brand form. No parameters. Code: apps/web/src/lib/brand/server.ts:67
PUT /brand — Upsert brand
Creates the brand if none exists or deep-merges the Partial<BrandEntity> patch into the existing record. Merges identity, location, voice (formality/dos/donts/examples), channels per platform (facebook/x/reddit), offerings filtered by isBrandOffering, memory.rules, intelligence, and sources by URL. Stamps updatedAt, persists to brand-entity in localStorage, and validates the merged result with isBrandEntity — returns 400 Invalid brand payload if validation fails. Code: apps/web/src/lib/brand/server.ts:68
DELETE /brand — Delete brand
Clears the entire workspace brand record — identity, location, voice, offerings, sources, channels, memory, and intelligence are discarded (sets brand to null). Persists the cleared state so the dashboard returns to the onboarding prompt. Idempotent. Code: apps/web/src/lib/brand/server.ts:186
POST /brand/intelligence — Append brand intelligence
Appends reveal discoveries to the brand's intelligence block. competitors are deduped by exact string (trimmed), targetCommunities are merged by id via isCommunityPick, and selectedKeyword overwrites if provided. Returns 404 No brand yet if onboarding hasn't created a record yet. Stamps updatedAt and persists. Code: apps/web/src/lib/brand/server.ts:113
GET /brand/sources — List brand sources
Returns the indexed BrandPage[] for the brand's website — each page has url, title, headings, text, status (indexed/pending/failed), and fetchedAt. Returns an empty array when no brand exists yet (the brand itself is the guard, not the sources). Code: apps/web/src/lib/brand/server.ts:142
DELETE /brand/sources — Delete brand source
Removes a single indexed page by exact url string match. Filters brand.sources by URL, stamps updatedAt, and persists. Returns 404 if no brand exists — the URL itself is not validated beyond string match (unknown URLs are a no-op that still returns the filtered list). Code: apps/web/src/lib/brand/server.ts:178
POST /brand/index — Index brand site
Mock sitemap indexer. With { urls: string[] } it registers each new URL as a pending BrandPage (deduped by URL). Without urls (or with { sitemap: true } / empty body) it re-resolves the brand site (identity.website or sourceUrl) into the deterministic seed pages via resolveSitemap and merges them by URL as indexed. Always stamps and persists. Returns 404 if no brand exists. Code: apps/web/src/lib/brand/server.ts:143
Get brand
Returns the single workspace BrandEntity or null when onboarding was skipped or the brand was cleared. This is the source-of-truth the Brand tab, onboarding reveal, and AI reply drafts all read — polling here is how the dashboard knows whether to show the brand form. No parameters. Code: apps/web/src/lib/brand/server.ts:67
/brandResponse Body
Brand record (or null).
TypeScript Definitions
Use the response body type in TypeScript.
brandRequiredobject | null | nullcurl -X GET "http://localhost:5174/brand"fetch("http://localhost:5174/brand")package mainimport ( "fmt" "net/http" "io/ioutil")func main() { url := "http://localhost:5174/brand" req, _ := http.NewRequest("GET", url, nil) res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body))}import requestsurl = "http://localhost:5174/brand"response = requests.request("GET", url)print(response.text){
"brand": {
"property1": null,
"property2": null
}
}Delete brand
Clears the entire workspace brand record — identity, location, voice, offerings, sources, channels, memory, and intelligence are discarded (sets brand to null). Persists the cleared state so the dashboard returns to the onboarding prompt. Idempotent. Code: apps/web/src/lib/brand/server.ts:186
/brandResponse Body
Cleared (brand is null).
TypeScript Definitions
Use the response body type in TypeScript.
brandRequiredobject | null | nullcurl -X DELETE "http://localhost:5174/brand"fetch("http://localhost:5174/brand")package mainimport ( "fmt" "net/http" "io/ioutil")func main() { url := "http://localhost:5174/brand" req, _ := http.NewRequest("DELETE", url, nil) res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body))}import requestsurl = "http://localhost:5174/brand"response = requests.request("DELETE", url)print(response.text){
"brand": {
"property1": null,
"property2": null
}
}Upsert brand
Creates the brand if none exists or deep-merges the Partial<BrandEntity> patch into the existing record. Merges identity, location, voice (formality/dos/donts/examples), channels per platform (facebook/x/reddit), offerings filtered by isBrandOffering, memory.rules, intelligence, and sources by URL. Stamps updatedAt, persists to brand-entity in localStorage, and validates the merged result with isBrandEntity — returns 400 Invalid brand payload if validation fails. Code: apps/web/src/lib/brand/server.ts:68
/brandRequest Body
application/jsonRequiredResponse Body
Saved brand.
TypeScript Definitions
Use the response body type in TypeScript.
brandRequiredobject | null | nullFailure envelope returned with 4xx statuses.
TypeScript Definitions
Use the response body type in TypeScript.
errorRequiredstringHuman-readable failure reason, e.g. "A key needs a name".
curl -X PUT "http://localhost:5174/brand" \ -H "Content-Type: application/json" \ -d '{}'const body = JSON.stringify({})fetch("http://localhost:5174/brand", { body})package mainimport ( "fmt" "net/http" "io/ioutil" "strings")func main() { url := "http://localhost:5174/brand" body := strings.NewReader(`{}`) req, _ := http.NewRequest("PUT", url, body) req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body))}import requestsurl = "http://localhost:5174/brand"body = {}response = requests.request("PUT", url, json = body, headers = { "Content-Type": "application/json"})print(response.text){
"brand": {
"property1": null,
"property2": null
}
}{
"error": "string"
}Append brand intelligence
Appends reveal discoveries to the brand's intelligence block. competitors are deduped by exact string (trimmed), targetCommunities are merged by id via isCommunityPick, and selectedKeyword overwrites if provided. Returns 404 No brand yet if onboarding hasn't created a record yet. Stamps updatedAt and persists. Code: apps/web/src/lib/brand/server.ts:113
/brand/intelligenceRequest Body
application/jsonRequiredcompetitorsarray<string>Competitor domains to append (deduped).
targetCommunitiesarray<object>CommunityPick[] merged by id.
selectedKeywordstringKeyword chosen in reveal.
Response Body
Updated brand.
TypeScript Definitions
Use the response body type in TypeScript.
brandRequiredobject | null | nullFailure envelope returned with 4xx statuses.
TypeScript Definitions
Use the response body type in TypeScript.
errorRequiredstringHuman-readable failure reason, e.g. "A key needs a name".
Failure envelope returned with 4xx statuses.
TypeScript Definitions
Use the response body type in TypeScript.
errorRequiredstringHuman-readable failure reason, e.g. "A key needs a name".
curl -X POST "http://localhost:5174/brand/intelligence" \ -H "Content-Type: application/json" \ -d '{ "competitors": [ "string" ], "targetCommunities": [ {} ], "selectedKeyword": "string" }'const body = JSON.stringify({ "competitors": [ "string" ], "targetCommunities": [ {} ], "selectedKeyword": "string"})fetch("http://localhost:5174/brand/intelligence", { body})package mainimport ( "fmt" "net/http" "io/ioutil" "strings")func main() { url := "http://localhost:5174/brand/intelligence" body := strings.NewReader(`{ "competitors": [ "string" ], "targetCommunities": [ {} ], "selectedKeyword": "string" }`) req, _ := http.NewRequest("POST", url, body) req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body))}import requestsurl = "http://localhost:5174/brand/intelligence"body = { "competitors": [ "string" ], "targetCommunities": [ {} ], "selectedKeyword": "string"}response = requests.request("POST", url, json = body, headers = { "Content-Type": "application/json"})print(response.text){
"brand": {
"property1": null,
"property2": null
}
}{
"error": "string"
}{
"error": "string"
}List brand sources
Returns the indexed BrandPage[] for the brand's website — each page has url, title, headings, text, status (indexed/pending/failed), and fetchedAt. Returns an empty array when no brand exists yet (the brand itself is the guard, not the sources). Code: apps/web/src/lib/brand/server.ts:142
/brand/sourcesResponse Body
Indexed pages.
TypeScript Definitions
Use the response body type in TypeScript.
sourcesRequiredarray<object>curl -X GET "http://localhost:5174/brand/sources"fetch("http://localhost:5174/brand/sources")package mainimport ( "fmt" "net/http" "io/ioutil")func main() { url := "http://localhost:5174/brand/sources" req, _ := http.NewRequest("GET", url, nil) res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body))}import requestsurl = "http://localhost:5174/brand/sources"response = requests.request("GET", url)print(response.text){
"sources": [
{
"property1": null,
"property2": null
}
]
}Delete brand source
Removes a single indexed page by exact url string match. Filters brand.sources by URL, stamps updatedAt, and persists. Returns 404 if no brand exists — the URL itself is not validated beyond string match (unknown URLs are a no-op that still returns the filtered list). Code: apps/web/src/lib/brand/server.ts:178
/brand/sourcesRequest Body
application/jsonRequiredurlRequiredstringExact URL of the page to remove.
Response Body
Remaining sources.
TypeScript Definitions
Use the response body type in TypeScript.
brandRequiredobjectBrandEntity — single workspace brand record (see docs: Brand).
sourcesRequiredarray<object>Failure envelope returned with 4xx statuses.
TypeScript Definitions
Use the response body type in TypeScript.
errorRequiredstringHuman-readable failure reason, e.g. "A key needs a name".
curl -X DELETE "http://localhost:5174/brand/sources" \ -H "Content-Type: application/json" \ -d '{ "url": "string" }'const body = JSON.stringify({ "url": "string"})fetch("http://localhost:5174/brand/sources", { body})package mainimport ( "fmt" "net/http" "io/ioutil" "strings")func main() { url := "http://localhost:5174/brand/sources" body := strings.NewReader(`{ "url": "string" }`) req, _ := http.NewRequest("DELETE", url, body) req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body))}import requestsurl = "http://localhost:5174/brand/sources"body = { "url": "string"}response = requests.request("DELETE", url, json = body, headers = { "Content-Type": "application/json"})print(response.text){
"brand": {
"property1": null,
"property2": null
},
"sources": [
{
"property1": null,
"property2": null
}
]
}{
"error": "string"
}Index brand site
Mock sitemap indexer. With { urls: string[] } it registers each new URL as a pending BrandPage (deduped by URL). Without urls (or with { sitemap: true } / empty body) it re-resolves the brand site (identity.website or sourceUrl) into the deterministic seed pages via resolveSitemap and merges them by URL as indexed. Always stamps and persists. Returns 404 if no brand exists. Code: apps/web/src/lib/brand/server.ts:143
/brand/indexRequest Body
application/jsonOptionalurlsarray<string>Explicit URLs to register as pending.
sitemapbooleanWhen true (or no urls), re-resolve the sitemap.
Response Body
Indexed pages.
TypeScript Definitions
Use the response body type in TypeScript.
brandRequiredobjectBrandEntity — single workspace brand record (see docs: Brand).
sourcesRequiredarray<object>Failure envelope returned with 4xx statuses.
TypeScript Definitions
Use the response body type in TypeScript.
errorRequiredstringHuman-readable failure reason, e.g. "A key needs a name".
curl -X POST "http://localhost:5174/brand/index" \ -H "Content-Type: application/json" \ -d '{ "urls": [ "string" ], "sitemap": true }'const body = JSON.stringify({ "urls": [ "string" ], "sitemap": true})fetch("http://localhost:5174/brand/index", { body})package mainimport ( "fmt" "net/http" "io/ioutil" "strings")func main() { url := "http://localhost:5174/brand/index" body := strings.NewReader(`{ "urls": [ "string" ], "sitemap": true }`) req, _ := http.NewRequest("POST", url, body) req.Header.Add("Content-Type", "application/json") res, _ := http.DefaultClient.Do(req) defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body) fmt.Println(res) fmt.Println(string(body))}import requestsurl = "http://localhost:5174/brand/index"body = { "urls": [ "string" ], "sitemap": true}response = requests.request("POST", url, json = body, headers = { "Content-Type": "application/json"})print(response.text){
"brand": {
"property1": null,
"property2": null
},
"sources": [
{
"property1": null,
"property2": null
}
]
}{
"error": "string"
}