Skip to main content

Basics

OpenAPI specification​

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:00 for 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_id references 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