Appearance
Profile
Your public profile is the link-in-bio style page served at /t/<slug>. The API edits a draft, then publishes it. Editing never changes the live page until you call publish.
| Operation | Endpoint | Scope |
|---|---|---|
| Read draft + publish state | GET /api/v1/profile | api:profile:read |
| Update draft (merge patch) | PATCH /api/v1/profile | api:profile:write |
| Publish draft | POST /api/v1/profile/publish | api:profile:write |
Get the draft
GET /api/v1/profileResponse
json
{
"data": {
"draft": { "slug": "your-slug", "displayName": "…", "bio": "…", "layoutBlocks": [] },
"isPublished": true,
"publishedAt": "2026-06-23T00:00:00.000Z",
"slug": "your-slug"
}
}Update the draft
PATCH /api/v1/profile
Content-Type: application/jsonThe body is a JSON Merge Patch (RFC 7396):
- Keys you include are updated; keys you omit are left unchanged.
nullclears a nullable field (or resets it to its default).layoutBlocksis replaced wholesale when present.
Patchable fields
| Field | Type | Notes |
|---|---|---|
slug | string | Public URL segment. 409 if already taken. |
displayName | string | Up to 100 chars. |
bio | string | Up to 1000 chars. |
themePreset | string | A built-in theme name, or custom. |
customColors | object | null | primaryColor, accentColor, backgroundColor, textColor, surfaceColor, borderColor. |
ctaLabel / ctaUrl | string | null | Primary call-to-action button. |
bookingLabel | string | null | Booking button label. |
joinRequestEnabled | boolean | Show the "request to join" button. |
joinRequestDescription | string | null | Helper text for join requests. |
layoutBlocks | array | Ordered content blocks (see below). Up to 30. |
avatarUrl / bannerUrl / pageBackgroundUrl | string | null | Image URLs only — see note. |
avatarFocalX / avatarFocalY | number | 0–1. |
avatarZoom | number | 1–3. |
bannerFocalX / bannerFocalY | number | 0–1. |
bannerZoom | number | 1–3. |
pageBackgroundFocalX / pageBackgroundFocalY | number | 0–1. |
pageBackgroundZoom | number | 1–3. |
pageBackgroundOverlayOpacity | number | 0–1. |
isPublished and teacher_id are not patchable — publishing is a separate call and the owner is always the key holder.
Images are URL-only
The API does not upload files. Set avatarUrl / bannerUrl / pageBackgroundUrl to an absolute https:// URL you already host. Unsafe URL schemes (javascript:, data:, protocol-relative //…) are rejected.
Layout blocks
layoutBlocks is an array of blocks. Common types:
json
[
{
"id": "block-features",
"type": "features",
"data": { "items": [
{ "title": "Card title", "body": "Card body", "linkUrl": "https://example.com/page" }
] }
},
{
"id": "block-links",
"type": "links",
"data": { "items": [
{ "label": "Browse Quizzes", "url": "/quizzes" }
] }
}
]featurescardlinkUrlmust be an absolutehttps://URL. Internal app paths (e.g./quizzes) are rejected for feature cards — use alinksblock for in-app shortcuts (itsurlaccepts relative paths).- Up to 24 feature items and 50 link items.
Example
bash
curl -X PATCH https://quizzz.techtranslab.com/api/v1/profile \
-H "Authorization: Bearer qz_your_key_here" \
-H "Content-Type: application/json" \
-d '{"bio":"New bio","ctaLabel":"Start now","ctaUrl":"https://example.com"}'Response:
json
{ "data": { "draft": { "…": "…" }, "saved": true } }Publish
POST /api/v1/profile/publishPublishes the current saved draft to the live /t/<slug> page.
json
{ "data": { "published": true, "slug": "your-slug", "publishedAt": "2026-06-23T00:00:00.000Z" } }404— no draft to publish.409— slug taken at publish time.400— branding payload invalid.200with"brandingPartial": true— the profile is live; the branding half failed and is retried automatically.
SEO (page
<title>and meta description) is set via the Branding resource, not the profile.