Listings
Facebook Marketplace listings (mock of facebook-camofox-client).
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.
Listings — Facebook Marketplace items
Listings are Marketplace rows the dashboard creates and polls for status. Each has listingId (17-digit marketplace shape), title/price/location, up-to-4 images (first is cover), accountId FK to a Facebook account, locationPoint (lat/lng + radius 1–200km), and status (under-review at creation). The store seeds from MOCK_LISTINGS and persists.
Six routes: list, create (validates title/price/location/photo/account), patch details, patch status (transitions via LISTING_STATUSES), get status, and hard-delete. Code: apps/web/src/lib/listings/server.ts.
GET /listings — List listings
Lists all Marketplace listings — seeded MOCK_LISTINGS plus any user-created rows (persisted to localStorage, migrated for legacy accountId FKs). Each has listingId, title/price/location, 1–4 images (first is cover), accountId FK, locationPoint (lat/lng + radius 1–200km), and status. This is the source for the dashboard Listings table. Code: apps/web/src/lib/listings/server.ts:77
POST /listings — Create listing
Creates a new Marketplace listing. Validates title, price, location (all trimmed non-empty), at least one images string, and a Facebook accountId (resolves legacy label via resolveListingAccountId, falls back to fb-personal). Generates a 17-digit listingId, sets category/condition defaults, locationPoint clamped 1–200km, status: under-review, and listingUrl. Persists and returns 201. Code: apps/web/src/lib/listings/server.ts:79
PATCH /listings/{listingId} — Update listing
Patches a listing's details/photos in place — listingId, listingUrl, publishedAt, and status stay put; only title, price, location, category, condition, images (sliced to 4), locationPoint, and accountId (resolves legacy label or keeps stored FK) move. Validates title/price/location + photo presence, 404 if unknown. Code: apps/web/src/lib/listings/server.ts:123
DELETE /listings/{listingId} — Delete listing
Hard-deletes :listingId — the row leaves the store entirely (unlike the removed status which keeps it visible for record-keeping). Persists and returns the remaining list. 404 if unknown. Code: apps/web/src/lib/listings/server.ts:192
PATCH /listings/{listingId}/status — Update listing status
Transitions a listing's status via the status route — never local state. The dashboard's Sold/Remove actions round-trip here so the mock stays the source of truth and the live facebook-camofox-client can implement the same shape. Validates status against LISTING_STATUSES; 404 if unknown id. Code: apps/web/src/lib/listings/server.ts:166
GET /listings/{listingId}/status — Get listing status
Returns the lightweight status probe for :listingId — { listingId, status, title } (status typed as ListingStatus). Used by the dashboard to poll under-review → active transitions without fetching the full record. Code: apps/web/src/lib/listings/server.ts:183
List listings
Lists all Marketplace listings — seeded MOCK_LISTINGS plus any user-created rows (persisted to localStorage, migrated for legacy accountId FKs). Each has listingId, title/price/location, 1–4 images (first is cover), accountId FK, locationPoint (lat/lng + radius 1–200km), and status. This is the source for the dashboard Listings table. Code: apps/web/src/lib/listings/server.ts:77
/listingsResponse Body
Listings.
TypeScript Definitions
Use the response body type in TypeScript.
listingsRequiredarray<object>curl -X GET "http://localhost:5174/listings"fetch("http://localhost:5174/listings")package mainimport ( "fmt" "net/http" "io/ioutil")func main() { url := "http://localhost:5174/listings" 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/listings"response = requests.request("GET", url)print(response.text){
"listings": [
{
"property1": null,
"property2": null
}
]
}Create listing
Creates a new Marketplace listing. Validates title, price, location (all trimmed non-empty), at least one images string, and a Facebook accountId (resolves legacy label via resolveListingAccountId, falls back to fb-personal). Generates a 17-digit listingId, sets category/condition defaults, locationPoint clamped 1–200km, status: under-review, and listingUrl. Persists and returns 201. Code: apps/web/src/lib/listings/server.ts:79
/listingsRequest Body
application/jsonRequiredtitleRequiredstringListing title.
priceRequiredstringPrice string.
locationRequiredstringHuman location.
imagesRequiredarray<string>Photo URLs, first is cover.
accountIdstringFacebook account id.
categorystringconditionstringlocationPointobjectlat/lng/radiusKm.
Response Body
Created (under-review).
TypeScript Definitions
Use the response body type in TypeScript.
listingRequiredobjectListingRecord — Marketplace listing.
listingsRequiredarray<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/listings" \ -H "Content-Type: application/json" \ -d '{ "title": "string", "price": "string", "location": "string", "images": [ "string" ], "accountId": "string", "category": "string", "condition": "string", "locationPoint": {} }'const body = JSON.stringify({ "title": "string", "price": "string", "location": "string", "images": [ "string" ], "accountId": "string", "category": "string", "condition": "string", "locationPoint": {}})fetch("http://localhost:5174/listings", { body})package mainimport ( "fmt" "net/http" "io/ioutil" "strings")func main() { url := "http://localhost:5174/listings" body := strings.NewReader(`{ "title": "string", "price": "string", "location": "string", "images": [ "string" ], "accountId": "string", "category": "string", "condition": "string", "locationPoint": {} }`) 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/listings"body = { "title": "string", "price": "string", "location": "string", "images": [ "string" ], "accountId": "string", "category": "string", "condition": "string", "locationPoint": {}}response = requests.request("POST", url, json = body, headers = { "Content-Type": "application/json"})print(response.text){
"listing": {
"property1": null,
"property2": null
},
"listings": [
{
"property1": null,
"property2": null
}
]
}{
"error": "string"
}Update listing
Patches a listing's details/photos in place — listingId, listingUrl, publishedAt, and status stay put; only title, price, location, category, condition, images (sliced to 4), locationPoint, and accountId (resolves legacy label or keeps stored FK) move. Validates title/price/location + photo presence, 404 if unknown. Code: apps/web/src/lib/listings/server.ts:123
/listings/{listingId}Request Body
application/jsonRequiredtitlestringpricestringlocationstringimagesarray<string>accountIdstringcategorystringconditionstringPath Parameters
listingIdRequiredstringMarketplace listingId.
Response Body
Updated.
TypeScript Definitions
Use the response body type in TypeScript.
listingRequiredobjectListingRecord — Marketplace listing.
listingsRequiredarray<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".
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 PATCH "http://localhost:5174/listings/string" \ -H "Content-Type: application/json" \ -d '{ "title": "string", "price": "string", "location": "string", "images": [ "string" ], "accountId": "string", "category": "string", "condition": "string" }'const body = JSON.stringify({ "title": "string", "price": "string", "location": "string", "images": [ "string" ], "accountId": "string", "category": "string", "condition": "string"})fetch("http://localhost:5174/listings/string", { body})package mainimport ( "fmt" "net/http" "io/ioutil" "strings")func main() { url := "http://localhost:5174/listings/string" body := strings.NewReader(`{ "title": "string", "price": "string", "location": "string", "images": [ "string" ], "accountId": "string", "category": "string", "condition": "string" }`) req, _ := http.NewRequest("PATCH", 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/listings/string"body = { "title": "string", "price": "string", "location": "string", "images": [ "string" ], "accountId": "string", "category": "string", "condition": "string"}response = requests.request("PATCH", url, json = body, headers = { "Content-Type": "application/json"})print(response.text){
"listing": {
"property1": null,
"property2": null
},
"listings": [
{
"property1": null,
"property2": null
}
]
}{
"error": "string"
}{
"error": "string"
}Delete listing
Hard-deletes :listingId — the row leaves the store entirely (unlike the removed status which keeps it visible for record-keeping). Persists and returns the remaining list. 404 if unknown. Code: apps/web/src/lib/listings/server.ts:192
/listings/{listingId}Path Parameters
listingIdRequiredstringResponse Body
Remaining listings.
TypeScript Definitions
Use the response body type in TypeScript.
listingsRequiredarray<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/listings/string"fetch("http://localhost:5174/listings/string")package mainimport ( "fmt" "net/http" "io/ioutil")func main() { url := "http://localhost:5174/listings/string" 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/listings/string"response = requests.request("DELETE", url)print(response.text){
"listings": [
{
"property1": null,
"property2": null
}
]
}{
"error": "string"
}Get listing status
Returns the lightweight status probe for :listingId — { listingId, status, title } (status typed as ListingStatus). Used by the dashboard to poll under-review → active transitions without fetching the full record. Code: apps/web/src/lib/listings/server.ts:183
/listings/{listingId}/statusPath Parameters
listingIdRequiredstringResponse Body
Status probe.
TypeScript Definitions
Use the response body type in TypeScript.
listingIdRequiredstringstatusRequiredstringtitleRequiredstringFailure 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 GET "http://localhost:5174/listings/string/status"fetch("http://localhost:5174/listings/string/status")package mainimport ( "fmt" "net/http" "io/ioutil")func main() { url := "http://localhost:5174/listings/string/status" 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/listings/string/status"response = requests.request("GET", url)print(response.text){
"listingId": "string",
"status": "string",
"title": "string"
}{
"error": "string"
}Update listing status
Transitions a listing's status via the status route — never local state. The dashboard's Sold/Remove actions round-trip here so the mock stays the source of truth and the live facebook-camofox-client can implement the same shape. Validates status against LISTING_STATUSES; 404 if unknown id. Code: apps/web/src/lib/listings/server.ts:166
/listings/{listingId}/statusRequest Body
application/jsonRequiredstatusRequiredstringNew status.
"active" | "under-review" | "under-review-duplicate" | "sold" | "removed" | "login-wall" | "unknown"Path Parameters
listingIdRequiredstringResponse Body
Status updated.
TypeScript Definitions
Use the response body type in TypeScript.
listingRequiredobjectListingRecord — Marketplace listing.
listingsRequiredarray<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".
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 PATCH "http://localhost:5174/listings/string/status" \ -H "Content-Type: application/json" \ -d '{ "status": "active" }'const body = JSON.stringify({ "status": "active"})fetch("http://localhost:5174/listings/string/status", { body})package mainimport ( "fmt" "net/http" "io/ioutil" "strings")func main() { url := "http://localhost:5174/listings/string/status" body := strings.NewReader(`{ "status": "active" }`) req, _ := http.NewRequest("PATCH", 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/listings/string/status"body = { "status": "active"}response = requests.request("PATCH", url, json = body, headers = { "Content-Type": "application/json"})print(response.text){
"listing": {
"property1": null,
"property2": null
},
"listings": [
{
"property1": null,
"property2": null
}
]
}{
"error": "string"
}{
"error": "string"
}