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

GET/listings

Response 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

POST/listings

Request Body

application/jsonRequired
titleRequiredstring

Listing title.

priceRequiredstring

Price string.

locationRequiredstring

Human location.

imagesRequiredarray<string>

Photo URLs, first is cover.

accountIdstring

Facebook account id.

categorystring
conditionstring
locationPointobject

lat/lng/radiusKm.

Response Body

Created (under-review).

TypeScript Definitions

Use the response body type in TypeScript.

listingRequiredobject

ListingRecord — Marketplace listing.

listingsRequiredarray<object>

Failure envelope returned with 4xx statuses.

TypeScript Definitions

Use the response body type in TypeScript.

errorRequiredstring

Human-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

PATCH/listings/{listingId}

Request Body

application/jsonRequired
titlestring
pricestring
locationstring
imagesarray<string>
accountIdstring
categorystring
conditionstring

Path Parameters

listingIdRequiredstring

Marketplace listingId.

Response Body

Updated.

TypeScript Definitions

Use the response body type in TypeScript.

listingRequiredobject

ListingRecord — Marketplace listing.

listingsRequiredarray<object>

Failure envelope returned with 4xx statuses.

TypeScript Definitions

Use the response body type in TypeScript.

errorRequiredstring

Human-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.

errorRequiredstring

Human-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

DELETE/listings/{listingId}

Path Parameters

listingIdRequiredstring

Response 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.

errorRequiredstring

Human-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

GET/listings/{listingId}/status

Path Parameters

listingIdRequiredstring

Response Body

Status probe.

TypeScript Definitions

Use the response body type in TypeScript.

listingIdRequiredstring
statusRequiredstring
titleRequiredstring

Failure envelope returned with 4xx statuses.

TypeScript Definitions

Use the response body type in TypeScript.

errorRequiredstring

Human-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

PATCH/listings/{listingId}/status

Request Body

application/jsonRequired
statusRequiredstring

New status.

Value in: "active" | "under-review" | "under-review-duplicate" | "sold" | "removed" | "login-wall" | "unknown"

Path Parameters

listingIdRequiredstring

Response Body

Status updated.

TypeScript Definitions

Use the response body type in TypeScript.

listingRequiredobject

ListingRecord — Marketplace listing.

listingsRequiredarray<object>

Failure envelope returned with 4xx statuses.

TypeScript Definitions

Use the response body type in TypeScript.

errorRequiredstring

Human-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.

errorRequiredstring

Human-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"
}