File Service
The File Service owns all collaborative resources in Typograph: teams, projects, templates, media and fonts, plus sharing, invites, comments, favorites, trash, and search. The gateway routes everything under /v1/file/*.
Base URL: /v1/file/
Scopes: file (full), file:read, file:write, file:delete. All File Service endpoints require a user token.
File Service paths follow a "list-by-parent, item-by-id" pattern. Collections are listed and created under their owning parent (projects under teams, templates under projects, media/uploads/fonts under projects), but individual resources are addressed by their own top-level ID once created. Top-level GET /v1/file/teams is an exception — it's scoped to the authenticated user's organization.
Teams
| Endpoint | Method | Scopes |
|---|---|---|
/v1/file/teams | GET | file / file:read |
/v1/file/teams | POST | file / file:write |
/v1/file/teams/{id} | GET | file / file:read |
/v1/file/teams/{id} | PATCH | file / file:write |
/v1/file/teams/{id} | DELETE | file / file:delete |
/v1/file/teams/{teamId}/contacts | GET | file / file:read |
/v1/file/organizations/{organizationId}/teams | GET | file / file:read |
curl https://api.typograph.nl/v1/file/teams \
-H "Authorization: Bearer $USER_TOKEN"
List responses follow standard pagination.
Projects
Projects are team-scoped — there's no top-level "list all projects" endpoint. Create and list projects on a specific team; once you have a project ID you can read, update, and delete it directly.
| Endpoint | Method | Scopes |
|---|---|---|
/v1/file/teams/{teamId}/projects | GET | file / file:read |
/v1/file/teams/{teamId}/projects | POST | file / file:write |
/v1/file/projects/{id} | GET | file / file:read |
/v1/file/projects/{id} | PATCH | file / file:write |
/v1/file/projects/{id} | DELETE | file / file:delete |
/v1/file/projects/{projectId}/templates | GET, POST | file:read / file:write |
/v1/file/projects/{projectId}/uploads | GET, POST | file:read / file:write |
/v1/file/projects/{projectId}/uploads/{id} | PATCH | file:write |
/v1/file/projects/{projectId}/media | GET | file:read |
/v1/file/projects/{projectId}/media/presigned-urls | POST | file:read |
/v1/file/projects/{projectId}/fonts | GET | file:read |
/v1/file/projects/{projectId}/fonts/presigned-urls | POST | file:read |
curl -X POST https://api.typograph.nl/v1/file/teams/019b.../projects \
-H "Authorization: Bearer $USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "Q4 Campaign" }'
Templates
Templates are the Typograph document files that the editor opens and the publisher renders. Templates are project-scoped on create/list — once you have a template ID you can read and update it directly.
| Endpoint | Method | Scopes |
|---|---|---|
/v1/file/projects/{projectId}/templates | GET | file / file:read |
/v1/file/projects/{projectId}/templates | POST | file / file:write |
/v1/file/templates/{id} | GET | file / file:read |
/v1/file/templates/{id} | PATCH | file / file:write |
/v1/file/templates/{id} | DELETE | file / file:delete |
/v1/file/templates/{id}/touch | PATCH | file:write |
/v1/file/templates/{id}/{fileType}/presigned-url | POST | file:write |
fileType is one of thumbnail or document. The touch endpoint bumps the "last modified" timestamp without changing content — the editor calls it on open to drive recency lists. DELETE /templates/{id} soft-deletes a template (?action=delete) or restores it (?action=undelete); soft-deleted templates surface in the user's trash flow (see Trash) and are permanently removed — including their files in storage — by the scheduled deletion job once the retention window has passed.
Premade templates
The premade template catalog is a global, read-only set of starter templates curated by Typograph. Unlike project templates, the catalog is identical for every user and is not project-scoped. Items are addressed by a URL-safe code (lowercase letters, digits, hyphens) rather than a UUID.
| Endpoint | Method | Scopes |
|---|---|---|
/v1/file/premade-templates | GET | file / file:read |
/v1/file/premade-templates/categories | GET | file / file:read |
/v1/file/premade-templates/{code} | GET | file / file:read |
The list endpoint accepts limit (max 100, default 50), offset, category (filter by category code), and featured (boolean) query parameters. List responses are wrapped as { "results": [...], "total": <int> }.
Template comments
| Endpoint | Method | Scopes |
|---|---|---|
/v1/file/templates/{templateId}/comments | GET | file:read |
/v1/file/templates/{templateId}/comments | POST | file:write |
/v1/file/templates/{templateId}/comments/{commentId} | PUT | file:write |
/v1/file/templates/{templateId}/comments/{commentId} | DELETE | file:delete |
/v1/file/templates/{templateId}/comments/{commentId}/resolve | PATCH | file:write |
/v1/file/templates/{templateId}/comments/{commentId}/replies | GET, POST | file:read / file:write |
/v1/file/templates/{templateId}/comments/{commentId}/replies/{replyId} | PUT | file:write |
/v1/file/templates/{templateId}/comments/{commentId}/replies/{replyId} | DELETE | file:delete |
Sharing: Invites, Roles, Permissions
Sharing is resource-scoped: you invite someone to a specific team, project, or template. The resource type is part of the URL.
| Endpoint | Method | Scopes |
|---|---|---|
/v1/file/{resourceType}/{resourceId}/permissions | GET | file:read |
/v1/file/{resourceType}/{resourceId}/invites | GET, POST | file:read / file:write |
/v1/file/{resourceType}/{resourceId}/invites/{id} | GET | file:read |
/v1/file/{resourceType}/{resourceId}/invites/{id} | PATCH | file:write |
/v1/file/{resourceType}/{resourceId}/invites/{id} | DELETE | file:delete |
/v1/file/{resourceType}/{resourceId}/roles/{id} | PATCH | file:write |
/v1/file/{resourceType}/{resourceId}/roles/{id} | DELETE | file:delete |
resourceType is one of team, project, or template.
curl -X POST https://api.typograph.nl/v1/file/template/019b.../invites \
-H "Authorization: Bearer $USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "email": "colleague@example.com", "role": "editor" }'
The authoritative list of roles and their capabilities lives on the Identity Service — see Identity Service.
Favorites, Shared, Trash, Search, Recents
| Endpoint | Method | Scopes |
|---|---|---|
/v1/file/favos | GET | file / file:read |
/v1/file/favos | PUT | file / file:write |
/v1/file/shared/projects | GET | file / file:read |
/v1/file/shared/templates | GET | file / file:read |
/v1/file/trash | GET | file / file:read |
/v1/file/search | GET | file / file:read |
/v1/file/recent/searched | GET | file / file:read |
/v1/file/recent/templates | GET | file / file:read |
PUT /v1/file/favos toggles the favorite state for a resource (pass the resource type and id in the body).
Users, Roles & Invites (Current User)
| Endpoint | Method | Scopes | Notes |
|---|---|---|---|
/v1/file/users | PATCH | file / file:write | Updates the authenticated user's profile in the file service. |
/v1/file/users/{id} | GET | file / file:read | Fetch a specific user by UUID. |
/v1/file/users/me/invites | GET | file / file:read | List invites addressed to the current user. |
/v1/file/roles | GET | file / file:read | Catalog of resource roles (owner/editor/commenter/viewer). |
Notifications
| Endpoint | Method | Scopes |
|---|---|---|
/v1/file/notifications | GET | file / file:read |
/v1/file/notifications | PATCH | file / file:write |
/v1/file/notifications/{id} | PATCH | file / file:write |
Mark a single notification read/unread with PATCH /notifications/{id}. Mark all notifications read in one call with PATCH /notifications (no body fields required).
Uploads
Large uploads use presigned URLs — you request one from the File Service, then PUT the file bytes directly to cloud storage, bypassing the API. The same pattern applies for project media, project fonts, and template thumbnails/documents (endpoints listed in the relevant sections above).
Flow:
POST /v1/file/projects/{projectId}/media/presigned-urls # get upload URLs
PUT <signed_url> # upload bytes to R2/S3
PATCH /v1/file/projects/{projectId}/uploads/{id} # finalize metadata
Error Responses
Standard envelope (see API Overview → Error Handling):
| Status | Error | Description |
|---|---|---|
401 | unauthorized | Missing or invalid token |
403 | access_denied | Missing scope or resource permission |
404 | not_found | Resource does not exist or not accessible to caller |
409 | conflict | Duplicate slug / concurrent edit |
422 | validation_error | Field validation failed |
{
"error": "validation_error",
"error_description": "Request validation failed",
"request_id": "019397ab-cdef-7890-abcd-ef1234567890",
"details": [
{ "field": "name", "code": "required", "message": "The name field is required." }
]
}