Basics
OpenAPI specification​
- View in browser: /openapi
- Download: OpenAPI YAML
API endpoints​
- Production:
https://api.tourfold.com/api/v2/
Naming conventions​
The REST API uses consistent naming across payloads and URLs.
JSON keys​
All JSON keys use snake_case:
{
"id": "a1b2c3d4-e5f6-4a1b-8c9d-123456789abc",
"created_at": "2024-01-15T10:30:00Z",
"phone_number": "+43 1 2345678",
"location_coordinates": {
"latitude": 48.2038,
"longitude": 16.3698
}
}
URL paths​
URL path segments use kebab-case. Path parameter placeholders use descriptive
snake_case names. An opaque resource ID uses {resource_id}; other identifiers name their
actual meaning, such as {resource_slug}, {resource_key}, or {resource_number}. Generic
placeholders such as {id}, {slug}, and {type} are not used:
# Resource endpoints
GET /api/v2/brands
GET /api/v2/brands/{brand_id}
GET /api/v2/brands/{brand_id}/custom-domains/{custom_domain_id}
# Nested resources
GET /api/v2/users/{user_id}/skills
GET /api/v2/users/{user_id}/tags
# Action endpoints
POST /api/v2/devices/{device_id}/activate
POST /api/v2/devices/{device_id}/deactivate
POST /api/v2/users/{user_id}/deactivate
# A slug is not labelled as an ID
GET /api/v2/custom-objects/definitions/{definition_slug}
Query parameters​
All query parameters use descriptive snake_case names; camelCase query names are not used:
# Pagination
GET /api/v2/areas?page=0&size=20
# Filtering
GET /api/v2/users?status=ACTIVE&role_ids=b2c3d4e5-f6a1-4b2c-9d0e-234567890bcd
# Free-text search and a bounded non-paginated result
GET /api/v2/comments/mention-suggestions?search=ali&target_type=user&limit=10
# Multiple values repeat the same plural key
GET /api/v2/comments/counts?target_type=tour&target_ids=a1b2c3d4-e5f6-4a1b-8c9d-123456789abc&target_ids=b2c3d4e5-f6a1-4b2c-9d0e-234567890bcd
# Sorting
GET /api/v2/areas?sort=created_at:asc&sort=updated_at:desc
Array-valued query parameters use OpenAPI style: form with explode: true. Send one value per
occurrence of the same plural parameter name, as in the target_ids example above. Do not send a
comma-separated value (target_ids=a,b) or repeat a singular key (target_id=a&target_id=b).
Generated clients accept an array and serialize it in this repeated-key form. Established query
controls such as sort and include keep their semantic names even when repeated.
Commas have no structural meaning in Tourfold-owned REST query parameters. Sort criteria use
field:direction and repeat sort when an operation supports multiple fields. Structured scalar
values use separate named parameters rather than positional tuples, for example
target_lat=48.2082&target_lon=16.3738. If an individual string array value contains a comma,
percent-encode it as %2C; a literal comma in an array parameter is rejected with 422. Commas
are never data in sort, so both literal and percent-encoded comma forms are rejected with 422.
The externally compatible BW EBA API is the sole exception and retains its separately documented wire format.
Lifecycle actions​
Public resource lifecycle actions use activate and deactivate. unblock is reserved for
security recovery, such as clearing a user's failed-login block. enable and disable describe
capabilities, configuration switches, and provider internals rather than a resource's public
lifecycle.
Request body (JSON)​
When sending data in request bodies, use snake_case for all field names:
{
"name": "Vienna Central",
"description": "Inner-city delivery zone covering districts 1–9",
"color": "#3B82F6",
"zip_ranges": [
{
"from": "1010",
"to": "1090"
}
]
}
Content-Type: Use application/json for POST/PUT bodies. For PATCH, use
application/merge-patch+json (RFC 7386); application/json is accepted as an alias with the
same omit/null semantics. See Creating & Updating Resources.
curl -X POST "https://api.tourfold.com/api/v2/areas" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"name": "Vienna Central",
"description": "Inner-city delivery zone",
"color": "#3B82F6"
}'
For the write model — partial updates with PATCH, clearing fields with null, whole-set
replacement with PUT, and optimistic concurrency via lock_version — see
Creating & Updating Resources.
OpenAPI schema names​
OpenAPI component names describe the public concept, not an implementation class. Resource schemas
use a singular noun (Area), write bodies mirror the operation verb (CreateArea, UpdateArea),
and list envelopes use the singular <Resource>List form (AreaList, DeletedEventList). Public
schema names never expose implementation suffixes such as Request, Response, DTO, or Dto.
Closed value sets​
Finite Tourfold-owned choices are published as OpenAPI enums rather than unrestricted strings with the valid values mentioned only in prose. Generated clients therefore provide a corresponding enum or string-union type:
GET /api/v2/users?status=ACTIVE
For example, the user status filter accepts only ACTIVE, BLOCKED, INACTIVE, or ALL.
Unsupported values return a field-level 422 response; they are never silently interpreted as a
default.
Values designed to be extensible remain strings, including registered resource/type slugs, MIME types, and provider-owned statuses that can evolve independently. A deployment-specific choice is not exposed as a closed set unless the API also provides a discovery contract clients can consume.
Phone numbers​
Tourfold-owned phone fields require an international number with an explicit + country calling
code. Common presentation formatting is accepted and normalized before lookup or storage:
+43 (664) 123-45-67 → +436641234567
Responses always use canonical E.164. National values such as 06641234567 and international
dial-prefix values such as 00436641234567 are rejected with a field-level 422; the API never
assumes an Austrian or other default country. The separately documented BW EBA contract and
BW-owned contact data are not covered by this rule.
Date and time formats​
The REST API uses standardized date and time formats following RFC specifications.
DateTime fields​
All datetime fields use RFC 3339 format (ISO 8601 subset):
{
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T11:45:30.123Z",
"scheduled_start": "2024-01-15T14:00:00+01:00",
"completed_at": "2024-01-15T16:30:00Z"
}
Format: YYYY-MM-DDTHH:mm:ss.sssZ or YYYY-MM-DDTHH:mm:ss.sss±HH:mm
- Z: UTC timezone (e.g.,
2024-01-15T10:30:00Z) - ±HH:mm: Timezone offset (e.g.,
2024-01-15T10:30:00+01:00for Central European Time)
Default timezone: The Tourfold API uses UTC (Z) as the default timezone for all datetime fields. When no timezone is specified, times are interpreted as UTC.
Date fields​
Date-only fields use RFC 3339 date format:
{
"valid_from": "2024-01-01",
"valid_until": "2024-12-31"
}
Format: YYYY-MM-DD
Time fields​
Time-only fields use RFC 3339 time format:
{
"departure_time": "14:30:00",
"arrival_time": "16:45:00",
"break_duration": "00:30:00"
}
Format: HH:mm:ss or HH:mm:ss.sss
Duration fields​
Duration fields use ISO 8601 duration format:
{
"estimated_duration": "PT2H30M",
"processing_time": "PT45M",
"break_time": "PT15M"
}
Format: PTnHnMnS (Period of Time: Hours, Minutes, Seconds)
Examples​
# Request with datetime parameters
curl "https://api.tourfold.com/api/v2/audit-logs?created_from=2024-01-15T00:00:00Z&created_to=2024-01-16T23:59:59Z" \
-H "Authorization: Bearer YOUR_TOKEN"
# Response with datetime fields
{
"id": "a1b2c3d4-e5f6-4a1b-8c9d-123456789abc",
"name": "Vienna Central",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T11:45:30.123Z"
}
Common patterns​
ID fields​
All resource IDs are typically UUIDs (Universally Unique Identifiers) and use consistent naming patterns:
Example - User Resource:
{
"id": "a1b2c3d4-e5f6-4a1b-8c9d-123456789abc",
"brand_id": "b2c3d4e5-f6a1-4b2c-9d0e-234567890bcd",
"role_id": "c3d4e5f6-a1b2-4c3d-0e1f-345678901cde",
"skill_id": "d4e5f6a1-b2c3-4d4e-1f2a-456789012def"
}
Format: UUID format (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
Naming Convention:
- Primary ID:
id- the resource's own identifier (e.g., the user's ID) - Foreign Keys:
{entity}_id- references to other entities (e.g.,brand_idreferences a brand)
Boolean fields​
Boolean fields use descriptive names:
{
"is_active": true,
"is_default": true,
"requires_confirmation": false,
"can_resend_set_password_email": true
}
Array fields​
Array fields use plural nouns:
{
"skills": [...],
"roles": [...],
"tags": [...],
"zip_ranges": [...]
}
Optional fields and null​
Responses omit properties that have no value — the API never emits "field": null on the wire.
An absent key therefore means "no value", and a present key always carries a real value. Fields
that are always populated (identifiers, timestamps, status) are never omitted.
{
"id": "a1b2c3d4-e5f6-4a1b-8c9d-123456789abc",
"name": "Vienna Depot",
"created_at": "2024-01-15T10:30:00Z"
}
Here the resource has no description, so the key is simply absent rather than returned as
"description": null. Clients should treat a missing key as "not set".
An explicit null is meaningful only in a PATCH (merge-patch) request body, where it clears a
clearable field — see Creating & Updating Resources → Clearing an optional field.
An empty or whitespace-only string is not a way to say "no value". Omitting the key expresses
absence, and an explicit null clears a clearable field; a blank string is neither, so sending one
returns 422 Unprocessable Entity with
field-required for that field rather than being silently treated as
absent. If your form submits "" for an untouched optional input, drop the key (on a create) or send
null (to clear on a PATCH) instead.
Single resource responses​
Single resources are returned directly in the root object without wrapping:
{
"id": "a1b2c3d4-e5f6-4a1b-8c9d-123456789abc",
"name": "Vienna Central",
"color": "#3B82F6",
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-15T10:30:00Z"
}
List responses​
Every list response wraps resources in an items array. Paginated responses additionally include
page; non-paginated lists omit it:
{
"items": [
{
"id": "a1b2c3d4-e5f6-4a1b-8c9d-123456789abc",
"name": "Vienna Central",
"color": "#3B82F6",
"created_at": "2024-01-15T10:30:00Z"
},
{
"id": "b2c3d4e5-f6a1-4b2c-9d0e-234567890bcd",
"name": "Salzburg Region",
"color": "#10B981",
"created_at": "2024-01-15T11:00:00Z"
}
],
"page": {
"size": 20,
"total_elements": 150,
"total_pages": 8,
"number": 0
}
}
Note: Single resources are returned directly, while list responses always expose their
collection under items, never under a resource-specific key.
API versioning​
The REST API uses URL-based versioning to ensure stability and backward compatibility:
Version strategy​
- Never remove, only add: New versions maintain full backward compatibility
- Breaking changes: Only introduced in new major versions (v2, v3, etc.)
- Current version:
/api/v2/- stable and fully supported
Version URLs​
# Current stable version
GET https://api.tourfold.com/api/v2/areas
# Future versions (when needed)
GET https://api.tourfold.com/api/v3/areas
GET https://api.tourfold.com/api/v4/areas