https://api.genuka.com · Auth Authorization: Bearer <token> + X-Company: <companyId> · Getting Started
Endpoints at a glance
Blogs
Blogs are content containers that group articles. Each blog belongs to a company and has a URL-safe handle that is unique within that company. A blog can have an optional cover image (stored in the Spatie media library). Thearticles relationship can be sideloaded.
GET /2023-11/admin/blogs
List all blogs for the authenticated company. Supports search and sorting.
Query parameters
200 OK):
401— Missing or invalid bearer token.403— Token does not have access to the specified company.
POST /2023-11/admin/blogs
Create a new blog for the authenticated company.
Request body
200 OK):
401— Unauthenticated.403— Unauthorized action.
GET /2023-11/admin/blogs/handle/{blog}
Retrieve a single blog by its handle slug (admin route).
Path parameters
200 OK):
401— Unauthenticated.404— No blog found with that handle.
GET /2023-11/admin/blogs/{blog}
Retrieve a single blog by its ULID (admin route).
Path parameters
200 OK):
401— Unauthenticated.404— Blog not found.
PUT /2023-11/admin/blogs/{blog}
Update a blog’s fields. Only supplied fields are changed.
Path parameters
Request body
200 OK):
401— Unauthenticated.404— Blog not found.
DELETE /2023-11/admin/blogs/{blog}
Permanently delete a blog.
Path parameters
200 OK): empty body (no content).
Error responses
401— Unauthenticated.404— Blog not found.
GET /2023-11/blogs
List all blogs for a storefront (public, no auth required). Accepts the same filter/sort/pagination query parameters as the admin list endpoint.
Query parameters
200 OK):
403— Company header missing or invalid.
GET /2023-11/blogs/count
Return the total number of blogs for the company. No pagination.
200 OK):
GET /2023-11/blogs/handle/{blog}
Retrieve a single blog by handle (public).
Path parameters
200 OK):
404— Blog not found.
GET /2023-11/blogs/{blog}
Retrieve a single blog by ULID (public).
Path parameters
200 OK):
404— Blog not found.
Pages
Static CMS pages (e.g. Terms & Conditions, About Us). Each page belongs to a company, has a unique handle, a rich textcontent field, optional metadata, and an optional cover image. The public endpoints require only X-Company.
GET /2023-11/admin/pages
List all pages for the authenticated company.
Query parameters
200 OK):
401— Unauthenticated.403— Unauthorized.
POST /2023-11/admin/pages
Create a new CMS page.
Request body
200 OK):
401— Unauthenticated.422— Validation failed (e.g. duplicate handle within company).
GET /2023-11/admin/pages/handle/{page}
Retrieve a single page by its handle (admin).
Path parameters
200 OK):
401— Unauthenticated.404— Page not found.
GET /2023-11/admin/pages/{page}
Retrieve a single page by ULID (admin).
Path parameters
200 OK):
401— Unauthenticated.404— Page not found.
PUT /2023-11/admin/pages/{page}
Update a page. Only supplied fields are changed.
Path parameters
Request body
200 OK):
401— Unauthenticated.404— Page not found.
DELETE /2023-11/admin/pages/{page}
Permanently delete a page.
Path parameters
204 No Content): empty body.
Error responses
401— Unauthenticated.404— Page not found.
GET /2023-11/pages
List all pages (public storefront). Same filter/sort/pagination as the admin list.
200 OK):
GET /2023-11/pages/count
Return the total number of pages for the company.
200 OK):
GET /2023-11/pages/handle/{page}
Retrieve a page by handle (public).
Path parameters
200 OK):
404— Page not found for that handle.
GET /2023-11/pages/{page}
Retrieve a page by ULID (public).
Path parameters
200 OK):
404— Page not found.
Domains
Custom domain management. When a domain is created or updated the API provisions it on the Caddy reverse proxy and registers it with Plausible for analytics tracking. Custom (non-.genuka.com) domains must have their DNS A record pointing to the server’s IP before the API will accept them.
GET /2023-11/admin/domains
List all domains for the authenticated company.
Query parameters
200 OK):
401— Unauthenticated.
POST /2023-11/admin/domains
Register a new custom domain. For non-.genuka.com domains the DNS A record must point to the Caddy server IP before the request is made.
Request body
200 OK):
400— Domain is not allowed (excluded list or DNS does not point to server).400— Company already has the maximum number of.genuka.comsubdomains.422—domainis required or already taken.500— Caddy provisioning failed.
GET /2023-11/admin/domains/{domain}
Retrieve a single domain by ULID.
Path parameters
200 OK):
401— Unauthenticated.404— Domain not found.
PUT /2023-11/admin/domains/{domain}
Update a domain’s hostname or main-domain flag. If the hostname changes, the old Caddy site is removed and a new one is provisioned.
Path parameters
Request body
200 OK):
400— Domain not allowed or DNS check failed.422— Validation errors.500— Caddy provisioning failed.
DELETE /2023-11/admin/domains/{domain}
Remove a domain and de-provision it from the Caddy reverse proxy. If this was the main domain, the first remaining domain is automatically promoted.
Path parameters
200 OK):
401— Unauthenticated.404— Domain not found.500— Caddy de-provisioning failed.
Domain Analytics
Analytics are powered by a Plausible instance. All analytics endpoints accept a date range via query parameters; dates default to the last 30 days when omitted.GET /2023-11/admin/domains/{domain}/analytics
Aggregate traffic metrics (visitors, pageviews, bounce rate, visit duration) for a domain.
Path parameters
Query parameters
200 OK):
401— Unauthenticated.404— Domain not found.500— Plausible API error.
GET /2023-11/admin/domains/{domain}/analytics/timeseries
Daily visitor and visit counts for a date range.
Path parameters
Query parameters
200 OK):
401— Unauthenticated.500— Plausible API error.
GET /2023-11/admin/domains/{domain}/analytics/top-countries
Top 10 visitor countries for a domain in a date range.
Path parameters
Query parameters
200 OK):
401— Unauthenticated.500— Plausible API error.
GET /2023-11/admin/domains/{domain}/analytics/top-devices
Top 10 device types (Desktop, Mobile, Tablet) for a domain in a date range.
Path parameters
Query parameters
200 OK):
401— Unauthenticated.500— Plausible API error.
GET /2023-11/admin/domains/{domain}/analytics/top-pages
Top 10 most visited pages on a domain in a date range.
Path parameters
Query parameters
200 OK):
401— Unauthenticated.500— Plausible API error.
GET /2023-11/admin/domains/{domain}/analytics/top-sources
Top 10 referral/traffic sources for a domain in a date range.
Path parameters
Query parameters
200 OK):
401— Unauthenticated.500— Plausible API error.
Theme Configurations
Theme configurations link a storefront template to a company and hold a JSONconfiguration blob (colors, fonts, layout settings, etc.). Setting is_active to true promotes that theme and re-provisions all company domains in Caddy. Only one theme can be active at a time; activating a new one automatically deactivates all others.
GET /2023-11/admin/theme-configurations
List all theme configurations for the authenticated company.
Query parameters
200 OK):
401— Unauthenticated.
GET /2023-11/admin/theme-configurations/{themeConfiguration}
Retrieve a single theme configuration by ULID. Eager-loads template and company.
Path parameters
200 OK):
401— Unauthenticated.404— Theme configuration not found.
PUT /2023-11/admin/theme-configurations/{themeConfiguration}
Update a theme configuration. Send either configuration to update the theme settings JSON, or is_active to switch the active theme. Mixing both in a single request is not supported — the handler checks configuration first.
Path parameters
Request body
200 OK):
401— Unauthenticated.404— Theme configuration not found.500— Caddy re-provisioning failed when activating a theme.
Media Library
Uploaded files are stored via Spatie Media Library and served from S3. Each media record belongs to a model (product, blog, page, company, etc.) and has image conversion variants (micro, thumb, large, webp). Access is permission-gated: users with manage_media_files see all company media; others see only the media associated with models they have permission to view.
GET /2023-11/admin/medias
List media files for the authenticated company.
Query parameters
200 OK):
401— Unauthenticated.404— Company not found.
POST /2023-11/admin/medias
Upload one or more media files to the company library. Supports two modes:
- Traditional multipart upload — send files in a
files[]form-data field. - S3 presigned registration — after uploading directly to S3 via
POST /upload/presign, register the uploaded keys by sending amediasJSON array.
multipart/form-data)
Request body — S3 key registration (
application/json)
200 OK): array of MediaResource objects (same shape as list items).
400— Error registering S3 key or uploading file.401— Unauthenticated.404— Company not found orfilesfield missing.500— Internal upload error.
GET /2023-11/admin/medias/stats
Return aggregate usage statistics for the company media library: total file count, total storage in bytes, a breakdown by model type, and a breakdown by S3 folder (top 50 folders by file count).
200 OK):
401— Unauthenticated.404— Company not found.
DELETE /2023-11/admin/medias/{media}
Permanently delete a media record (and its associated S3 file / conversions).
Path parameters
200 OK):
401— Unauthenticated.404— Media record not found.
S3 Upload
These endpoints facilitate direct client-to-S3 uploads, bypassing the API server for file transfer. The recommended flow for large files is:POST /upload/presign → PUT to the presigned URL → POST /admin/medias (S3 key registration). For files over 5 MB, use the multipart flow: POST /upload/multipart/create → PUT each part → POST /upload/multipart/complete → POST /admin/medias.
S3 keys are scoped to company/<companyId>/<model>/<collection>/ to enforce tenant isolation. The DELETE /upload/by-key endpoint enforces the same prefix restriction.
POST /2023-11/upload/presign
Generate a presigned S3 PUT URL for single-file direct upload. The client uploads the file directly to S3 using the returned URL, then registers the object with POST /admin/medias.
Request body
200 OK):
401— Unauthenticated.422— Missing required field (content_type,ext,model, orcollection).
POST /2023-11/upload/multipart/create
Initiate an S3 multipart upload and receive presigned PUT URLs for each part. Use for files larger than 5 MB. Parts must be at least 5 MB each (except the last).
Request body
200 OK):
401— Unauthenticated.422— Validation failed (e.g.part_countout of range).
POST /2023-11/upload/multipart/complete
Complete a multipart upload after all parts have been PUT to S3. The client must supply the ETag header value returned by S3 for each part.
Request body
200 OK): the AWS S3 CompleteMultipartUpload result object, typically including the final object Location, Bucket, Key, and ETag.
401— Unauthenticated.422— Missing or invalidkey,upload_id, orpartsfields.
POST /2023-11/upload/multipart/abort
Abort an in-progress multipart upload, freeing any partially uploaded parts from S3.
Request body
200 OK):
401— Unauthenticated.422— Missingkeyorupload_id.
DELETE /2023-11/upload/by-key
Delete an S3 object directly by its key. Only keys under the authenticated company’s prefix (company/<companyId>/) are permitted.
Request body
200 OK):
401— Unauthenticated.403— Key does not start with the authenticated company’s prefix ({ "message": "Unauthorized key prefix" }).422—keyfield missing.
