Fourteen tools manage custom structured content types — think "define your own content model" (case studies, FAQs, team bios) with entries you can draft and publish.
All endpoints follow the pattern POST https://mcp.vlozi.app/tools/collections.<name> with Authorization: Bearer ls_xxx. All responses use this envelope:
{ "data": <payload>, "error": null } // success
{ "data": null, "error": "..." } // failure (with HTTP 4xx/5xx)WARNING
Entry data is validated against the collection's declared field schema at write time — types,
required, min/max (length/value/item caps), pattern, enum options, and email/url
format are all enforced, and a violation returns 422 with a Field "<path>": <message> error.
Richer handling on top of that: richtext fields are sanitized, relation fields are checked to
point at a real, non-deleted entry in the right target collection, and group fields are
truncated to their configured max item count. Unknown (undeclared) keys are still accepted and
stored unchanged, so extra fields don't block a write — but anything the schema declares is
enforced.
Collections
collections.list_collections
List collections in the workspace.
Scope: collections:read
Input: none
Response: { collections: { id, name, slug, cardinality, status, entryCount, publishedCount, createdAt, updatedAt }[] }
collections.get_collection
Get a collection's full definition.
Scope: collections:read
Input: { id: string } (required)
Response: { collection: { id, name, slug, cardinality, schema: { fields: [] }, settings, allowedOrigins, status, version, createdAt, updatedAt } }
NOTE
Field types: text, textarea, richtext, number, boolean, date, select, multiselect, tags, url, email, image, file, relation, group. group fields nest up to 3 levels.
collections.create_collection
Create a new collection.
Scope: collections:write
Input: { name: string, cardinality?: "single" | "many", schema?: { fields: [] }, settings?: object, allowedOrigins?: string[] } (name required; cardinality default many)
Response: { collection: object }
NOTE
cardinality: "single" means the collection can only ever hold one entry (e.g. a "Homepage settings" collection). A relation field's target collection is validated to exist (self-reference is fine) — 422 if not. Slug is auto-derived from the name and made unique.
collections.update_collection
Update a collection. Pass only the fields to change.
Scope: collections:write
Input: { id: string, expectedVersion?: string, name?, schema?, settings?, allowedOrigins?, status?: "active" | "archived" }
Response: { collection: object }
NOTE
cardinality is immutable — not accepted here at all, only at creation. Requires at least one field beyond id.
Optimistic concurrency: pass expectedVersion = the collection's version (a positive integer from a prior get) to guard against clobbering a concurrent edit. If it no longer matches, the write is refused with 409 and the message includes the current version — re-fetch and retry. expectedVersion: "*" means "must exist"; a malformed value is a 400.
collections.delete_collection
Delete a collection and everything in it.
Scope: collections:write
Input: { id: string, expectedVersion?: string } (id required)
Response: { id: string, status: "deleted", strandedRelationFields?: { collectionId, fieldName }[] }
CAUTION
Soft-delete, and every entry inside is soft-deleted along with it. No restore tool exists for collections — treat this as irreversible from the API's point of view.
Stranded references: if another live collection still has a relation field targeting this collection, the response includes strandedRelationFields (the referencing collectionId + fieldName). Those references now resolve to nothing on public reads — re-point or remove them as intended.
expectedVersion is accepted as in update_collection for optimistic-concurrency control.
Entries
collections.list_entries
List entries in a collection.
Scope: collections:read
Input: { collectionId: string, status?: "draft" | "published", page?: number, limit?: number } (collectionId required; page default 1; limit default 25, max 100)
Response: { entries: object[], total: number, page: number, limit: number }
collections.get_entry
Get one entry.
Scope: collections:read
Input: { id: string } (required)
Response: { entry: { id, collectionId, data, status, sortOrder, version, publishedAt, createdAt, updatedAt } }
collections.create_entry
Create a new entry in a collection.
Scope: collections:write
Input: { collectionId: string, data?: object } (collectionId required; data defaults to {})
Response: { entry: object } — always created as status: "draft"
NOTE
Returns 409 if the collection is cardinality: "single" and already has a live entry — edit the existing one instead. New entries are appended to the end of the sort order. If data violates the collection's field schema (type, required, range, options, format), the write is refused with 422 and a Field "<path>": <message> error.
curl -X POST https://mcp.vlozi.app/tools/collections.create_entry \
-H "Authorization: Bearer $VLOZI_API_KEY" \
-d '{"collectionId": "col_xxx", "data": {"question": "Do you offer refunds?", "answer": "Yes, within 30 days."}}'collections.update_entry
Update an entry's data.
Scope: collections:write
Input: { id: string, data: object, expectedVersion?: string } (id, data required)
Response: { entry: object }
WARNING
data replaces the entire object — this is not a partial merge, so send the full field set even if only one value changed. Doesn't touch publish status. A schema violation returns 422.
expectedVersion provides optimistic-concurrency control as in update_collection; a stale value returns 409 with the current version.
collections.delete_entry
Delete one entry.
Scope: collections:write
Input: { id: string, expectedVersion?: string } (id required)
Response: { id: string, status: "deleted" } — soft delete
NOTE
expectedVersion provides optimistic-concurrency control as in update_collection; a stale value returns 409 with the current version.
collections.reorder_entries
Set the display order of entries in a collection.
Scope: collections:write
Input: { collectionId: string, ids: string[] } (both required; ids 1–500, an ordered list — array position becomes the new sort order)
Response: { updated: number, missingIds: string[] }
NOTE
Entries you omit from ids keep their existing position. Any id that doesn't belong to this collection (or is already deleted) is not reordered and is listed in missingIds — compare it against ids to detect ids that were skipped.
collections.bulk_delete_entries
Delete multiple entries in one call.
Scope: collections:write
Input: { ids: string[] } (required, 1–200)
Response: { deleted: number, requested: number }
NOTE
Not scoped to one collection — entries from different collections can be deleted together as long as they're all yours. Unmatched or already-deleted ids are silently skipped; compare deleted to requested to see if any were skipped.
collections.publish_entry
Publish a draft entry, making it visible via the public read API and embed widget.
Scope: collections:entries.publish
Input: { id: string } (required)
Response: { entry: object }
NOTE
Idempotent if already published. Returns 404 if deleted/missing, 409 on an unexpected concurrent state change.
collections.unpublish_entry
Revert a published entry to draft, immediately hiding it from public reads.
Scope: collections:entries.publish
Input: { id: string } (required)
Response: { entry: object }
NOTE
Idempotent if already draft. Returns 404 if deleted/missing, 409 on an unexpected concurrent state change.