Content API reference
Schema version 4.1.0
casadigital.pt hosts a multi-tenant backoffice where customers manage the content of their own websites. Each customer website fetches its content through the authenticated Site API (documented at /api-docs); only casadigital.pt talks to the database.
This page documents the content formats: the normalized database collections and the per-resource shapes served by the Site API. It exposes field formats only, never content.
Endpoints
- GET /api/v1/integration.md — the full client-site integration contract (start here to build a site).
- GET /api/v1/openapi.json — OpenAPI 3.1 description of every Site API endpoint.
- GET /api/content-schema — versioned JSON Schemas of the Site API resources.
- GET /api/content-schema/entities — versioned JSON Schemas of the normalized collections.
- GET /api/content-schema/docs — this documentation as Markdown (best for AI agents).
- GET /api/v1/docs — the Site API reference as Markdown (human version at /api-docs).
- GET /llms.txt — index of everything an agent should read (/llms-full.txt for one fetch).
Tenant model
Every tenant is one document in the sites collection. Its key field (the siteKey) identifies the tenant; consumer apps authenticate with the site's API key, which resolves to this document.
All other content documents carry a siteId field referencing the owning sites document. companies and siteSettings are one-per-site; legalTexts is one-per-document-key; services, products, caseStudies, locations and values are one-per-slug within a site; assets are one-per-pathname.
All documents also carry MongoDB _id, createdAt and updatedAt fields managed by the database layer; they are omitted from the tables below.
Contract scope
The platform owns design-agnostic business data only: business identity and contacts, opening hours, the services performed, the products sold, the case studies published, locations, company values, and the legal documents the client maintains. Every client website has its own bespoke design, so page structure, layout, section copy and metadata (band headings, stat figures, call-to-action labels, intro blocks) live in the client site's own repository and are deliberately absent from this contract.
Each resource maps to one collection: settings to companies, services/products/caseStudies/locations/values to their per-slug collections (array position stored in each document's order field), and legal to one legalTexts document per document key. Platform-only data never leaves: companies.availability is the schedule designer's working data (client sites read the schedule lines derived from it) and the whole siteSettings document holds backoffice preferences.
A service is work scoped per job and carries no price; a product is something sold at a stated price, stored as an amount plus its currency and billing period so each website can format it in its own style; a case study is work already delivered, told as problem, solution and results.
Resource responses omit siteId and every platform-only field, and they are fully defaulted: missing strings come back as empty strings and missing arrays as empty arrays. A site with no content yet returns valid empty resources rather than an error, so client sites can render an empty state.
Every uploaded file lives in the assets collection, stored in Vercel Blob through the backoffice and served at GET /api/v1/assets/{pathname}. Client sites typically proxy that under their own /api/blob/ route using the pathname. A service or a product embeds the pathname of its single image; a case study references whole asset documents, so its files arrive with their kind, MIME type, name, size and pixel dimensions resolved.
Site API resources
GET /api/v1/settings
The business: identity, contacts, opening hours and social links. One per site.
| Field | Type | Required | Description |
|---|---|---|---|
| name | string | no | Business name as displayed across the site. |
| tagline | string | no | One-line slogan shown near the logo/footer. Portuguese. |
| description | string | no | Short description of the business, 1-2 sentences. Portuguese. |
| phone | string | no | Display phone number, formatted for reading (e.g. "+351 912 345 678"). Empty string if none. Derive a tel: URI from it by stripping everything but + and digits. |
| string | no | Public contact email. Empty string if none. | |
| string | no | WhatsApp chat URL (e.g. "https://wa.me/351912345678"). Empty string if none. | |
| appUrl | string | no | Mobile app store URL if the business has an app. Empty string if none. |
| schedule | array of object | no | Opening schedule rows, in display order. |
| schedule[].days | string | no | Day range label (e.g. "Seg – Sáb"). Portuguese. |
| schedule[].hours | string | no | Opening hours label (e.g. "09h – 19h" or "Fechado"). |
| social | object | no | Social media profile links. |
| social.facebook | string | no | Facebook page URL. Empty string if none. |
| social.instagram | string | no | Instagram profile URL. Empty string if none. |
| social.linkedin | string | no | LinkedIn page URL. Empty string if none. |
| social.x | string | no | X/Twitter profile URL. Empty string if none. |
| social.youtube | string | no | YouTube channel URL. Empty string if none. |
GET /api/v1/services
Services offered by the business, in display order.
| Field | Type | Required | Description |
|---|---|---|---|
| [].slug | string | yes | URL-safe unique identifier, lowercase, hyphen-separated. |
| [].title | string | no | Service name. Portuguese. |
| [].icon | "truck" | "wrench" | "packageCheck" | "hammer" | "boxes" | "warehouse" | "arrowDownToLine" | "zap" | "clock" | "shieldCheck" | yes | Icon key from the fixed icon set (Lucide-style names). |
| [].image | object | no | Illustrative image stored in Vercel Blob. |
| [].image.assetId | string | no | Asset document id, set by the backoffice after upload. |
| [].image.pathname | string | no | Blob storage pathname; client sites serve it through their own /api/blob/ proxy. |
| [].image.alt | string | no | Image alt text. Portuguese. |
| [].image.width | integer | no | Image width in pixels. |
| [].image.height | integer | no | Image height in pixels. |
| [].tier | "primary" | "featured" | "secondary" | yes | Display tier: "primary" for the main highlighted services, "featured" for the secondary highlights, "secondary" for the rest. |
| [].short | string | no | One-sentence summary shown on service cards. Portuguese. |
| [].description | string | no | Full description. Portuguese. |
| [].bullets | array of string | no | Bullet-point highlights. Portuguese. |
GET /api/v1/products
Products sold by the business, in display order.
| Field | Type | Required | Description |
|---|---|---|---|
| [].slug | string | yes | URL-safe unique identifier, lowercase, hyphen-separated. |
| [].title | string | no | Product name. Portuguese. |
| [].icon | "truck" | "wrench" | "packageCheck" | "hammer" | "boxes" | "warehouse" | "arrowDownToLine" | "zap" | "clock" | "shieldCheck" | yes | Icon key from the fixed icon set (Lucide-style names). |
| [].image | object | no | Illustrative image stored in Vercel Blob. |
| [].image.assetId | string | no | Asset document id, set by the backoffice after upload. |
| [].image.pathname | string | no | Blob storage pathname; client sites serve it through their own /api/blob/ proxy. |
| [].image.alt | string | no | Image alt text. Portuguese. |
| [].image.width | integer | no | Image width in pixels. |
| [].image.height | integer | no | Image height in pixels. |
| [].sku | string | no | Internal reference or article number. Empty string if none. |
| [].price | number | no | Price amount in the currency below. 0 means the price is not published. |
| [].currency | "EUR" | "USD" | "GBP" | no | Currency of the price amount. |
| [].pricePeriod | "once" | "month" | "year" | no | Billing period the price refers to: "once" for a one-off price, "month" or "year" for a subscription. |
| [].priceNote | string | no | Short qualifier shown next to the price (e.g. "IVA incluído"). Portuguese. |
| [].short | string | no | One-sentence summary shown on product cards. Portuguese. |
| [].description | string | no | Full description. Portuguese. |
| [].bullets | array of string | no | Bullet-point highlights. Portuguese. |
| [].featured | boolean | no | True for a product the website should highlight. |
| [].available | boolean | no | False for a product kept on record but not currently sold. |
GET /api/v1/caseStudies
Case studies published by the business, in display order.
| Field | Type | Required | Description |
|---|---|---|---|
| [].slug | string | yes | URL-safe unique identifier, lowercase, hyphen-separated. |
| [].client | string | no | Name of the client the work was delivered for. Doubles as the case study's title. |
| [].problem | string | no | The situation the client was in. Portuguese. |
| [].solution | string | no | What the business did about it. Portuguese. |
| [].results | string | no | What the client got out of it. Portuguese. |
| [].links | array of object | no | Related links, in display order. |
| [].links[].title | string | no | Link label. Portuguese. May be empty — fall back to showing the URL. |
| [].links[].url | string | no | Absolute URL, including the scheme. |
| [].assets | array of object | no | Every attached file in one list, in display order. Group them by `kind` if your design shows media separately from documents. |
| [].assets[].kind | "image" | "video" | "document" | yes | What the file is, derived from its MIME type: "image" and "video" are media, everything else is a "document". Split a list on this rather than on the file extension. |
| [].assets[].pathname | string | yes | Blob storage pathname; fetch the file from GET /api/v1/assets/{pathname}, or proxy it through your own route. |
| [].assets[].contentType | string | no | MIME type the file was stored with (e.g. "image/webp", "application/pdf"). |
| [].assets[].name | string | no | Original filename at upload time. Use it as the download label for a document. |
| [].assets[].alt | string | no | Alt text, when one was written. Often empty — fall back to the surrounding context. |
| [].assets[].size | integer | no | File size in bytes, when known. |
| [].assets[].width | integer | no | Intrinsic width in pixels. Present for media, absent for documents. |
| [].assets[].height | integer | no | Intrinsic height in pixels. Present for media, absent for documents. |
GET /api/v1/locations
Physical locations of the business, in display order.
| Field | Type | Required | Description |
|---|---|---|---|
| [].slug | string | yes | URL-safe unique identifier, lowercase, hyphen-separated. |
| [].city | string | no | City or locality name. |
| [].lines | array of string | no | Address lines, in display order. |
| [].mapsSearchUrl | string | no | Google Maps search/share URL for this address. Empty string if none. |
| [].mapEmbedUrl | string | no | Google Maps embed URL (iframe src). Empty string if none. |
| [].primary | boolean | no | True for the main location (at most one per site). |
GET /api/v1/values
Company values, in display order.
| Field | Type | Required | Description |
|---|---|---|---|
| [].slug | string | yes | URL-safe unique identifier, lowercase, hyphen-separated. |
| [].title | string | no | Value name (e.g. Qualidade). Portuguese. |
| [].description | string | no | Short explanation of the value. Portuguese. |
GET /api/v1/legal
Legal documents the client maintains, keyed by document. Only documents that have been written are present.
| Field | Type | Required | Description |
|---|---|---|---|
| <key> | object | no | One legal document. |
| <key>.sections | array of object | no | Ordered sections, as the document should read. |
| <key>.sections[].title | string | no | Section heading. Portuguese. |
| <key>.sections[].body | string | no | Section body prose; plain text, may contain multiple sentences. Portuguese. |
Normalized collections
sites
sites collection: one document per tenant (client website). Sites have no lifecycle state — a client site always serves the latest content.
| Field | Type | Required | Description |
|---|---|---|---|
| key | string | yes | Unique tenant slug (siteKey). Echoed in every Site API response envelope; the tenant itself is selected by the API key, not by this value. |
| name | string | yes | Business name. |
| domain | string | no | Production domain of the client site. |
companies
companies collection: the business itself, one document per site (unique siteId).
| Field | Type | Required | Description |
|---|---|---|---|
| siteId | string | yes | Tenant scope: ObjectId of the owning document in the sites collection. |
| name | string | yes | Business name as displayed across the site. |
| tagline | string | no | One-line slogan. Portuguese. |
| description | string | no | Short business description. Portuguese. |
| phone | string | no | Display phone number, formatted for reading. |
| string | no | Public contact email. | |
| string | no | WhatsApp chat URL. | |
| appUrl | string | no | Mobile app store URL. |
| social | object | no | Social media profile links. |
| social.facebook | string | no | Facebook page URL. |
| social.instagram | string | no | Instagram profile URL. |
| social.linkedin | string | no | LinkedIn page URL. |
| social.x | string | no | X/Twitter profile URL. |
| social.youtube | string | no | YouTube channel URL. |
| availability | array of object | no | Structured weekly opening hours from the backoffice designer. Never served to client sites — they read the derived schedule below. |
| availability[].id | string | yes | Stable slot identifier. |
| availability[].week_day | integer | yes | Day of week, 0 = Sunday. |
| availability[].start_time | string | yes | Start time as "HH:mm". |
| availability[].end_time | string | yes | End time as "HH:mm". |
| availability[].active | boolean | no | False for a slot kept but not in effect. |
| schedule | array of object | no | Display lines derived from availability on save, in display order. |
| schedule[].days | string | yes | Day range label (e.g. "Segunda – Sábado"). |
| schedule[].hours | string | yes | Opening hours label (e.g. "09:00 – 19:00"). |
siteSettings
siteSettings collection: backoffice preferences, one document per site (unique siteId).
| Field | Type | Required | Description |
|---|---|---|---|
| siteId | string | yes | Tenant scope: ObjectId of the owning document in the sites collection. |
| crm | object | no | Backoffice notification preferences. Never served to client sites. |
| crm.smsOnNewLead | boolean | no | Send an SMS when a lead arrives. |
| crm.notificationsPhone | string | no | Number to notify. |
services
services collection: one document per service (unique siteId+slug).
| Field | Type | Required | Description |
|---|---|---|---|
| siteId | string | yes | Tenant scope: ObjectId of the owning document in the sites collection. |
| slug | string | yes | URL-safe identifier, unique per site. |
| title | string | yes | Service name. Portuguese. |
| icon | "truck" | "wrench" | "packageCheck" | "hammer" | "boxes" | "warehouse" | "arrowDownToLine" | "zap" | "clock" | "shieldCheck" | yes | Icon key from the fixed icon set. |
| image | object | no | Optional illustrative image stored in Vercel Blob. |
| image.assetId | string | no | Reference to the assets collection. |
| image.pathname | string | no | Blob storage pathname; client sites serve it via their /api/blob/ proxy. |
| image.alt | string | no | Image alt text. |
| image.width | integer | no | Image width in pixels. |
| image.height | integer | no | Image height in pixels. |
| tier | "primary" | "featured" | "secondary" | yes | Display tier: "primary", "featured", or "secondary". |
| short | string | no | One-sentence summary. Portuguese. |
| description | string | no | Full description. Portuguese. |
| bullets | array of string | no | Bullet-point highlights. Portuguese. |
| order | integer | no | Sort index, ascending. |
products
products collection: one document per product (unique siteId+slug).
| Field | Type | Required | Description |
|---|---|---|---|
| siteId | string | yes | Tenant scope: ObjectId of the owning document in the sites collection. |
| slug | string | yes | URL-safe identifier, unique per site. |
| title | string | yes | Product name. Portuguese. |
| icon | "truck" | "wrench" | "packageCheck" | "hammer" | "boxes" | "warehouse" | "arrowDownToLine" | "zap" | "clock" | "shieldCheck" | yes | Icon key from the fixed icon set. |
| image | object | no | Optional illustrative image stored in Vercel Blob. |
| image.assetId | string | no | Reference to the assets collection. |
| image.pathname | string | no | Blob storage pathname; client sites serve it via their /api/blob/ proxy. |
| image.alt | string | no | Image alt text. |
| image.width | integer | no | Image width in pixels. |
| image.height | integer | no | Image height in pixels. |
| sku | string | no | Internal reference or article number. |
| price | number | no | Price amount; 0 when not published. |
| currency | "EUR" | "USD" | "GBP" | yes | Currency of the price amount. |
| pricePeriod | "once" | "month" | "year" | yes | Billing period: "once", "month" or "year". |
| priceNote | string | no | Short qualifier shown next to the price. |
| short | string | no | One-sentence summary. Portuguese. |
| description | string | no | Full description. Portuguese. |
| bullets | array of string | no | Bullet-point highlights. Portuguese. |
| featured | boolean | no | True for a product the website should highlight. |
| available | boolean | no | False for a product not currently sold. |
| order | integer | no | Sort index, ascending. |
caseStudies
caseStudies collection: one document per case study (unique siteId+slug).
| Field | Type | Required | Description |
|---|---|---|---|
| siteId | string | yes | Tenant scope: ObjectId of the owning document in the sites collection. |
| slug | string | yes | URL-safe identifier, unique per site. |
| client | string | yes | Name of the client the work was delivered for. |
| problem | string | no | The situation the client was in. Portuguese. |
| solution | string | no | What the business did about it. Portuguese. |
| results | string | no | What the client got out of it. Portuguese. |
| links | array of object | no | Related links, in display order. |
| links[].title | string | no | Link label. Portuguese. |
| links[].url | string | no | Absolute URL. |
| assets | array of string | no | References into the assets collection, in display order. One list for media and documents alike; the Site API resolves each reference and reports its kind. |
| order | integer | no | Sort index, ascending. |
locations
locations collection: one document per location (unique siteId+slug).
| Field | Type | Required | Description |
|---|---|---|---|
| siteId | string | yes | Tenant scope: ObjectId of the owning document in the sites collection. |
| slug | string | yes | URL-safe identifier, unique per site. |
| city | string | yes | City or locality name. |
| lines | array of string | yes | Address lines, in display order. |
| mapsSearchUrl | string | no | Google Maps search/share URL. |
| mapEmbedUrl | string | no | Google Maps embed URL (iframe src). |
| primary | boolean | no | True for the main location. At most one per site (enforced by a partial unique index). |
| order | integer | no | Sort index, ascending. |
values
values collection: one document per company value (unique siteId+slug).
| Field | Type | Required | Description |
|---|---|---|---|
| siteId | string | yes | Tenant scope: ObjectId of the owning document in the sites collection. |
| slug | string | yes | URL-safe identifier, unique per site. |
| title | string | yes | Value name. Portuguese. |
| description | string | no | Short explanation. Portuguese. |
| order | integer | no | Sort index, ascending. |
legalTexts
legalTexts collection: one document per site per legal document (unique siteId+key). The only page-shaped content the platform stores.
| Field | Type | Required | Description |
|---|---|---|---|
| siteId | string | yes | Tenant scope: ObjectId of the owning document in the sites collection. |
| key | "terms" | "privacy" | yes | Which legal document. Unique per site. |
| sections | array of object | no | Ordered sections, as the document should read. |
| sections[].title | string | no | Section heading. |
| sections[].body | string | no | Section body prose, plain text. |
assets
assets collection: files uploaded through the backoffice to Vercel Blob (unique siteId+pathname). Services and products embed the pathname of their single image; case studies reference whole documents by _id, which is how they can carry files of mixed kinds with their type and dimensions.
| Field | Type | Required | Description |
|---|---|---|---|
| siteId | string | yes | Tenant scope: ObjectId of the owning document in the sites collection. |
| pathname | string | yes | Blob storage pathname, unique per site. Client sites serve it via their /api/blob/ proxy. |
| url | string | no | Direct Blob URL. |
| originalName | string | no | Original filename at upload time. |
| contentType | string | no | MIME type. |
| size | integer | no | File size in bytes. |
| width | integer | no | Image width in pixels. |
| height | integer | no | Image height in pixels. |
| blurDataURL | string | no | Base64 blur placeholder for images. |
| alt | string | no | Default alt text. |
| deletedAt | string | null | no | Soft-delete timestamp (ISO date), or null while active. |