openapi: 3.0.3
info:
  title: Vertec REST Webservice (VertecREST)
  version: "1.0.0"
  description: |
    OpenAPI description of **VertecREST**, a custom webservice built on top of the
    Vertec Custom Webservice Framework (`vtcweb.WebService`). It exposes generic,
    metadata-driven REST access to Vertec objects: it can query, create, update and
    delete objects of (in principle) any Vertec class, evaluate OCL expressions and
    SQL WHERE-clauses, read and write core and custom attributes/associations,
    retrieve Business Intelligence (BI) measures, and call published Vertec functions.

    Background reading (Vertec Knowledge Base):
    - Custom Webservices framework: https://www.vertec.com/de-ch/kb/custom-webservices/
    - Vertec REST API conventions this service follows: https://www.vertec.com/de-ch/kb/vertec-rest-api/

    ## Authentication and authorization
    Every request must identify itself with an **API token** generated in Vertec, using
    one of two transmission methods:
    - HTTP header: `Authorization: Bearer <apiToken>`
    - Query parameter: `?apiToken=<apiToken>`

    Once identified, the webservice code executes with the rights of the Vertec user the
    API token belongs to. Two distinct layers of denial can occur:
    - **401 Unauthorized** – the caller is not authenticated: the API token is missing,
      invalid, expired, or not associated with any user.
    - **403 Forbidden** – the caller is authenticated, but not authorized for the request.
      This covers several distinct causes: the user lacks the "Execute" right on the
      `Webservicedefinition` for this webservice, or (at the business logic layer) lacks
      the read/write/delete right on the specific Vertec object or member being accessed,
      or lacks the "SQL Query" right for a `sql` search, or lacks read access to the BI
      measure(s) requested.

    ## Response shape conventions
    - Every returned Vertec object always carries `objid`, `stringrepresentation` and
      `classname`. `isvalid`/`hintisvalid` are added whenever the object's class is a
      (subclass of) `Entry` and the caller has read access to those members.
    - Requests that resolve to a **single** Vertec object return that object as a JSON
      object. Requests that resolve to **zero or more than one** object return a JSON
      **array** of objects. Clients must be prepared for either shape when calling
      `GET /objects/{classname}`.
    - Which additional fields appear on an object depends entirely on the
      `result_members`, `result_ocl:<name>`, `functions` and `bi_measures` query
      parameters of the request; this is why object schemas in this document keep
      `additionalProperties: true`.

servers:
  - url: "https://{server}/api/webservice/REST"
    variables:
      server:
        default: yourvertecserver.example.com
        description: Hostname (and port, if applicable) of the Vertec application server that hosts the webservice.

tags:
  - name: Objects
    description: Generic, class-driven CRUD and search operations on Vertec objects.
  - name: Identify
    description: Resolve the concrete class of an object whose class is not known in advance.
  - name: Current User
    description: Convenience access to the object representing the currently authenticated Vertec user.
  - name: Validation
    description: Validate OCL expressions against a Vertec class without evaluating them on real data.

security:
  - BearerAuth: []
  - ApiTokenQuery: []

paths:
  /functions/currentuser:
    get:
      operationId: getCurrentUser
      tags: [Current User]
      summary: Get the currently logged-in user
      description: |
        Returns the Vertec `User` object of the login associated with the API token used to
        authenticate the request. The response is built the same way as a single-object
        response from `GET /objects/{classname}`, so `result_members`, `result_ocl:<name>`,
        `functions` and `bi_measures` query parameters can be used to enrich it.
      parameters:
        - $ref: '#/components/parameters/resultMembers'
        - $ref: '#/components/parameters/resultOcl'
        - $ref: '#/components/parameters/functions'
        - $ref: '#/components/parameters/biMeasures'
        - $ref: '#/components/parameters/biDateFrom'
        - $ref: '#/components/parameters/biDateTill'
        - $ref: '#/components/parameters/biCurrency'
        - $ref: '#/components/parameters/projectionDimension'
      responses:
        "200":
          description: The currently authenticated Vertec user.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VertecObject'
        "400":
          $ref: '#/components/responses/BadRequest'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "403":
          $ref: '#/components/responses/Forbidden'
        "405":
          $ref: '#/components/responses/MethodNotAllowed'

  /identify/{objid}:
    get:
      operationId: identifyObject
      tags: [Identify]
      summary: Identify the class of an object by its internal id
      description: |
        Returns just the class name and string representation of the Vertec object with the
        given internal id (`objid`). Intended for situations where the caller only holds an
        object reference (e.g. the value of an `ObjectReference` member) and does not know
        which concrete Vertec class it belongs to, so it cannot yet call
        `GET /objects/{classname}/{ids}` directly.
      parameters:
        - name: objid
          in: path
          required: true
          schema:
            type: integer
          description: Internal Vertec object id (`bold_id`).
      responses:
        "200":
          description: Class metadata of the identified object.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/IdentifyResponse'
        "400":
          description: |
            No object id was found in the path, the id is not an integer, or no Vertec
            object exists with the given `objid`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "403":
          $ref: '#/components/responses/Forbidden'
        "405":
          $ref: '#/components/responses/MethodNotAllowed'

  /objects/{classname}:
    get:
      operationId: queryObjects
      tags: [Objects]
      summary: Query Vertec objects of a class
      description: |
        Performs a global search for objects of `classname` and shapes the result. Since
        Vertec 6.8.0.20 the class name is always mandatory; loading "all objects of a class"
        without any search criteria is not supported.

        **Search modes** (the initial object list is built by exactly one of these; they
        cannot be combined with each other in the same request):
        - `sql` – a raw SQL WHERE-clause, evaluated with `getwithsql`. Requires the "SQL
          Query" right; otherwise a `403` is returned.
        - `query_member0`…`query_member9` + `query_value0`…`query_value9` – search by up to
          ten persistent core or custom members/associations, each pair naming a member and
          the value/condition to match (see the `query_memberN`/`query_valueN` parameters
          below for operators and per-datatype value formats). Combine several pairs with
          AND (default) or OR via `useor=true`. This mode works even without the "SQL Query"
          right: it is executed with elevated rights internally, and the result list is
          filtered afterwards to the objects the caller may actually read, so that "nothing
          found" and "access denied" cannot be told apart.
        - `ids` (query parameter, alternative to specifying id(s) in the path on
          `GET /objects/{classname}/{ids}`) – one internal id, one entry id, or a
          comma-separated list of internal ids.

        Note that `sql`, `query_memberN` and `ids` cannot be combined with each other in the
        same request (e.g. `sql` together with `query_member0` is rejected with `400`).

        **OCL refinement** (`ocl`, optional, can be combined with any of the above): an OCL
        (Object Constraint Language) expression. If a search mode above already produced an
        object list, the OCL expression is evaluated on that list (e.g. to run further
        selects); otherwise it is evaluated globally, without root object. The final result
        must again be Vertec object(s), not a primitive value.

        **Association traversal** (`association`, optional): once the object list is
        determined, travel a *multi-link* association (core or custom) from every object in
        the list, replacing the list with the linked target objects. Single-link
        associations are rejected.

        **Result shaping**, applied per resulting object:
        - `result_members` – comma-separated list of additional attribute/association names
          to include.
        - `result_ocl:<name>` – one or more OCL expressions evaluated on the object, each
          exposed in the response under the key `<name>`.
        - `functions` – comma-separated list of published Vertec functions to call on the
          object (see the `functions` parameter below).
        - `bi_measures` (+ `bi_date_from`, `bi_date_till`, `bi_currency`,
          `projection_dimension`) – Business Intelligence figures for the object.
      parameters:
        - $ref: '#/components/parameters/classname'
        - $ref: '#/components/parameters/idsQuery'
        - $ref: '#/components/parameters/ocl'
        - $ref: '#/components/parameters/sql'
        - $ref: '#/components/parameters/useor'
        - $ref: '#/components/parameters/queryMember0'
        - $ref: '#/components/parameters/queryValue0'
        - $ref: '#/components/parameters/queryMember1'
        - $ref: '#/components/parameters/queryValue1'
        - $ref: '#/components/parameters/queryMember2'
        - $ref: '#/components/parameters/queryValue2'
        - $ref: '#/components/parameters/queryMember3'
        - $ref: '#/components/parameters/queryValue3'
        - $ref: '#/components/parameters/queryMember4'
        - $ref: '#/components/parameters/queryValue4'
        - $ref: '#/components/parameters/queryMember5'
        - $ref: '#/components/parameters/queryValue5'
        - $ref: '#/components/parameters/queryMember6'
        - $ref: '#/components/parameters/queryValue6'
        - $ref: '#/components/parameters/queryMember7'
        - $ref: '#/components/parameters/queryValue7'
        - $ref: '#/components/parameters/queryMember8'
        - $ref: '#/components/parameters/queryValue8'
        - $ref: '#/components/parameters/queryMember9'
        - $ref: '#/components/parameters/queryValue9'
        - $ref: '#/components/parameters/association'
        - $ref: '#/components/parameters/resultMembers'
        - $ref: '#/components/parameters/resultOcl'
        - $ref: '#/components/parameters/functions'
        - $ref: '#/components/parameters/biMeasures'
        - $ref: '#/components/parameters/biDateFrom'
        - $ref: '#/components/parameters/biDateTill'
        - $ref: '#/components/parameters/biCurrency'
        - $ref: '#/components/parameters/projectionDimension'
      responses:
        "200":
          description: |
            The matching object(s). A JSON object is returned when exactly one object
            matches; a JSON array is returned otherwise (including the empty-result case,
            which yields `[]`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VertecObjectOrList'
        "400":
          $ref: '#/components/responses/BadRequest'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "403":
          $ref: '#/components/responses/Forbidden'
        "405":
          $ref: '#/components/responses/MethodNotAllowed'
    post:
      operationId: createObject
      tags: [Objects]
      summary: Create a new Vertec object
      description: |
        Creates a new object of class `classname` and writes the members supplied in the
        JSON request body onto it (see `WriteMembersRequest`). If member validation, member
        writing, or a rights check fails after the object was created, the newly created
        object is deleted again ("create" is not left half-written) and an error is
        returned instead.
      parameters:
        - $ref: '#/components/parameters/classname'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WriteMembersRequest'
      responses:
        "200":
          description: |
            The newly created object, shaped like a single-object `GET` response (member
            values are re-read after writing; if re-reading them fails for any reason, an
            empty body is returned instead, but the object has still been created).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VertecObject'
        "400":
          $ref: '#/components/responses/BadRequest'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "403":
          description: |
            Creating an object of this class was denied, or a member/association in the
            request body could not be written due to insufficient rights. In both cases the
            partially created object is deleted before the error is returned.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "405":
          $ref: '#/components/responses/MethodNotAllowed'

  /objects/{classname}/{ids}:
    get:
      operationId: getObjects
      tags: [Objects]
      summary: Retrieve one or more specific Vertec objects by id
      description: |
        Retrieves the object(s) identified by `ids` in the path instead of via a search.
        `ids` may be a single internal object id, a single entry id (e.g. a user
        abbreviation), or a comma-separated list of internal ids. An optional `ocl`
        expression is then evaluated on this object list (e.g. to filter or project it
        further), `association` can travel a multi-link association from it, and
        `result_members`, `result_ocl:<name>`, `functions` and `bi_measures` shape the
        resulting object(s) exactly as described for `GET /objects/{classname}`. `sql` and
        `query_memberN` search cannot be combined with an id-based lookup.
      parameters:
        - $ref: '#/components/parameters/classname'
        - $ref: '#/components/parameters/idsPath'
        - $ref: '#/components/parameters/ocl'
        - $ref: '#/components/parameters/association'
        - $ref: '#/components/parameters/resultMembers'
        - $ref: '#/components/parameters/resultOcl'
        - $ref: '#/components/parameters/functions'
        - $ref: '#/components/parameters/biMeasures'
        - $ref: '#/components/parameters/biDateFrom'
        - $ref: '#/components/parameters/biDateTill'
        - $ref: '#/components/parameters/biCurrency'
        - $ref: '#/components/parameters/projectionDimension'
      responses:
        "200":
          description: |
            The matching object(s), in the same single-object-vs-array shape as
            `GET /objects/{classname}`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VertecObjectOrList'
        "400":
          $ref: '#/components/responses/BadRequest'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "403":
          $ref: '#/components/responses/Forbidden'
        "405":
          $ref: '#/components/responses/MethodNotAllowed'
    put:
      operationId: updateObject
      tags: [Objects]
      summary: Update an existing Vertec object
      description: |
        Resolves `ids` to exactly one object of class `classname` and writes the members
        supplied in the JSON request body onto it (see `WriteMembersRequest`). `ids` must
        resolve to precisely one object; if it resolves to zero or several objects, the
        request fails with `400`.
      parameters:
        - $ref: '#/components/parameters/classname'
        - $ref: '#/components/parameters/idsPath'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/WriteMembersRequest'
      responses:
        "200":
          description: |
            The updated object, shaped like a single-object `GET` response. If re-reading
            the object for the response fails for any reason after a successful write, an
            empty body is returned instead.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VertecObject'
        "400":
          $ref: '#/components/responses/BadRequest'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "403":
          $ref: '#/components/responses/Forbidden'
        "405":
          $ref: '#/components/responses/MethodNotAllowed'
    delete:
      operationId: deleteObject
      tags: [Objects]
      summary: Delete an existing Vertec object
      description: |
        Resolves `ids` to exactly one object of class `classname` and deletes it. `ids`
        must resolve to precisely one object; if it resolves to zero or several objects,
        the request fails with `400`.
      parameters:
        - $ref: '#/components/parameters/classname'
        - $ref: '#/components/parameters/idsPath'
      responses:
        "200":
          description: The object was deleted successfully. No response body is returned.
        "400":
          $ref: '#/components/responses/BadRequest'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "403":
          description: The "Delete" right was denied for the object.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "405":
          $ref: '#/components/responses/MethodNotAllowed'

  /validateocl:
    post:
      operationId: validateOcl
      tags: [Validation]
      summary: Validate an OCL expression for a class
      description: |
        Checks whether an OCL (Object Constraint Language) expression is syntactically and
        semantically valid for the given Vertec class, using `vtcapp.validateexpression`,
        without evaluating it against any real object. Useful for validating an expression
        intended for `ocl`, `result_ocl:<name>` or similar parameters before using it.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/ValidateOclRequest'
      responses:
        "200":
          description: |
            Validation result. A syntactically or semantically invalid expression is still
            reported with `200` and `isvalid: false` in the body, not as an error response.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidateOclResponse'
        "400":
          description: |
            The request body is missing `classname`/`expression` or has the wrong type,
            `classname` is not a valid Vertec class, or validating the expression against
            the Vertec model failed for another reason. Unlike other endpoints in this API,
            this operation does not distinguish access-rights failures from other errors:
            any internal failure (including insufficient rights) is reported as `400`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "403":
          description: |
            The user lacks the "Execute" right on the `Webservicedefinition` for this
            webservice. (Unlike other endpoints, access-rights failures raised while
            validating the expression itself are reported as `400`, not `403`, because this
            operation does not distinguish them from other internal errors.)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        "405":
          $ref: '#/components/responses/MethodNotAllowed'

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: |
        Send the Vertec API token as `Authorization: Bearer <apiToken>`.
    ApiTokenQuery:
      type: apiKey
      in: query
      name: apiToken
      description: |
        Alternative to the `Authorization` header: send the Vertec API token as the
        `apiToken` query parameter.

  parameters:
    classname:
      name: classname
      in: path
      required: true
      schema:
        type: string
      description: |
        Vertec class name (default English or German designation, case-insensitive), e.g.
        `Project`, `User`, `Address`, `Invoice`. Must be a valid Vertec classname; the class
        name is mandatory since Vertec 6.8.0.20.
    idsPath:
      name: ids
      in: path
      required: true
      schema:
        type: string
      description: |
        Either the internal object id (`objid`) of a single object, the entry id of a
        single object (e.g. a user abbreviation), or a comma-separated list of internal
        object ids.
    idsQuery:
      name: ids
      in: query
      schema:
        type: string
      description: |
        Comma-separated list of internal object ids to retrieve, used instead of putting
        the id(s) in the path. Cannot be combined with `sql` or `query_memberN` search.
    ocl:
      name: ocl
      in: query
      schema:
        type: string
      description: |
        An OCL (Object Constraint Language) expression. Evaluated on the object list
        already determined by `sql`/`query_memberN`/`ids`/path id(s), or globally if none
        of those were used. Must yield Vertec object(s), not a simple datatype such as a
        string or number.
    sql:
      name: sql
      in: query
      schema:
        type: string
      description: |
        A raw SQL WHERE-clause used to search for objects of `classname` (`getwithsql`).
        Requires the "SQL Query" right. Cannot be combined with `query_memberN` search or
        an id-based lookup.
    useor:
      name: useor
      in: query
      schema:
        type: boolean
        default: false
      description: |
        When several `query_memberN`/`query_valueN` pairs are given, combine them with OR
        instead of the default AND.
    queryMember0:
      name: query_member0
      in: query
      schema:
        type: string
      description: |
        Name of the persistent core or custom attribute/association to search on (paired
        with `query_value0`). Up to ten independent pairs are supported
        (`query_member0`/`query_value0` … `query_member9`/`query_value9`).
    queryValue0:
      name: query_value0
      in: query
      schema:
        type: string
      description: |
        Search value (or condition) for the member named in `query_member0`. For numeric,
        date, datetime and currency attributes the value may be prefixed with a comparison
        operator (`<`, `>`, `<=`, `>=`); without a prefix, equality is used. For string
        attributes, a leading and/or trailing `%` is interpreted as a wildcard (translated
        to `LIKE`/`STARTING WITH`/`CONTAINING` depending on the database), all other
        characters are matched literally. For associations, the value must be the internal
        object id to match. Booleans accept `true`/`1` or `false`/`0`.
    queryMember1:
      name: query_member1
      in: query
      schema:
        type: string
      description: Same as `query_member0`, paired with `query_value1`.
    queryValue1:
      name: query_value1
      in: query
      schema:
        type: string
      description: Same conversion/operator rules as `query_value0`, for the member named in `query_member1`.
    queryMember2:
      name: query_member2
      in: query
      schema:
        type: string
      description: Same as `query_member0`, paired with `query_value2`.
    queryValue2:
      name: query_value2
      in: query
      schema:
        type: string
      description: Same conversion/operator rules as `query_value0`, for the member named in `query_member2`.
    queryMember3:
      name: query_member3
      in: query
      schema:
        type: string
      description: Same as `query_member0`, paired with `query_value3`.
    queryValue3:
      name: query_value3
      in: query
      schema:
        type: string
      description: Same conversion/operator rules as `query_value0`, for the member named in `query_member3`.
    queryMember4:
      name: query_member4
      in: query
      schema:
        type: string
      description: Same as `query_member0`, paired with `query_value4`.
    queryValue4:
      name: query_value4
      in: query
      schema:
        type: string
      description: Same conversion/operator rules as `query_value0`, for the member named in `query_member4`.
    queryMember5:
      name: query_member5
      in: query
      schema:
        type: string
      description: Same as `query_member0`, paired with `query_value5`.
    queryValue5:
      name: query_value5
      in: query
      schema:
        type: string
      description: Same conversion/operator rules as `query_value0`, for the member named in `query_member5`.
    queryMember6:
      name: query_member6
      in: query
      schema:
        type: string
      description: Same as `query_member0`, paired with `query_value6`.
    queryValue6:
      name: query_value6
      in: query
      schema:
        type: string
      description: Same conversion/operator rules as `query_value0`, for the member named in `query_member6`.
    queryMember7:
      name: query_member7
      in: query
      schema:
        type: string
      description: Same as `query_member0`, paired with `query_value7`.
    queryValue7:
      name: query_value7
      in: query
      schema:
        type: string
      description: Same conversion/operator rules as `query_value0`, for the member named in `query_member7`.
    queryMember8:
      name: query_member8
      in: query
      schema:
        type: string
      description: Same as `query_member0`, paired with `query_value8`.
    queryValue8:
      name: query_value8
      in: query
      schema:
        type: string
      description: Same conversion/operator rules as `query_value0`, for the member named in `query_member8`.
    queryMember9:
      name: query_member9
      in: query
      schema:
        type: string
      description: Same as `query_member0`, paired with `query_value9`.
    queryValue9:
      name: query_value9
      in: query
      schema:
        type: string
      description: Same conversion/operator rules as `query_value0`, for the member named in `query_member9`.
    association:
      name: association
      in: query
      schema:
        type: string
      description: |
        Name of a multi-link association (core or custom) to travel from every object in
        the currently resolved object list, replacing the list with the linked target
        objects. Single-link associations are rejected with `400`.
    resultMembers:
      name: result_members
      in: query
      schema:
        type: string
      description: |
        Comma-separated list of additional core/custom attribute or association names to
        include in each returned object. `objid`, `stringrepresentation` and `classname`
        are always included and must not be repeated here.
    resultOcl:
      name: "result_ocl:<name>"
      in: query
      schema:
        type: string
      description: |
        Evaluates an OCL expression on each returned object and exposes its result under
        the key `<name>` in the response. The parameter name must follow the pattern
        `result_ocl:<name>`; repeat the parameter with a different `<name>` suffix for
        multiple computed fields.
      allowReserved: true
    functions:
      name: functions
      in: query
      schema:
        type: string
      description: |
        Comma-separated list of Vertec functions to call on each returned object; every
        function must be published for the object's class in the Vertec metadata
        (`vtcmetadata.class_functions`), otherwise `400` is returned. Functions that
        require parameters expect them as additional query parameters named after the
        parameter (their name and type - `string`, `date` or `object` - are declared in the
        metadata); a missing required parameter yields `400`. Each function's result is
        exposed in the response under a key named after the function. Functions known to
        this webservice include:
          - Generic OCL-published functions evaluated directly via `evalocl`, e.g.
            `getStdHours`, `getWorkingHours`, `getVacationBalance`, `getVacationCarryover`,
            `getOvertimeBalance`, `getOvertimeCarryover`, `getEmploymentLevel`, `getSalary`,
            `getOverheads`, `isBlockedDate` (typically on `User`).
          - `enteringprojects` (on `User`) – list of `Project` objects the user may book
            time on, each carrying `code`, `description`, `regarding` and `customer`.
          - `enteringphases` (on `Project`; requires a `user` parameter identifying a
            `User`) – list of `Phase` objects the given user may book on, each carrying
            `code` and `description`.
          - `enteringservicetypes` / `enteringexpensetypes` (on `Phase`; requires a `user`
            parameter identifying a `User`) – list of service/expense entry types the given
            user may book on the phase, each carrying `code` and `text`.
          - `currentAbsences` (on `User`; requires a `date` parameter) – list of absences
            of the user on the given date, each carrying `type`, `active`, `description`
            and `minutesabsent`.
          - `getTrackingUsers` (on `User`) – list of `User` objects being tracked, each
            carrying `name` and `abbreviation`.
          - `documentplaintext` (on any class holding a `document` attribute, e.g.
            `Activity`, `Invoice`) – plain text extracted from the stored document via
            `vtcapp.extractplaintext`; no additional parameters required.
    biMeasures:
      name: bi_measures
      in: query
      schema:
        type: string
      description: |
        Comma-separated list of active BI (Business Intelligence) measure internal names to
        retrieve for each returned object.
    biDateFrom:
      name: bi_date_from
      in: query
      schema:
        type: string
        format: date
      description: |
        Start date (ISO 8601, `YYYY-MM-DD`) of the period for `bi_measures`. Optional; if
        omitted, `bi_date_till` is used as a cut-off date instead of a period.
    biDateTill:
      name: bi_date_till
      in: query
      schema:
        type: string
        format: date
      description: End date (ISO 8601, `YYYY-MM-DD`) of the period for `bi_measures`.
    biCurrency:
      name: bi_currency
      in: query
      schema:
        type: string
      description: Optional ISO currency code to convert `bi_measures` values into.
    projectionDimension:
      name: projection_dimension
      in: query
      schema:
        type: string
      description: |
        Optional grouping/projection dimension for `bi_measures` (e.g. group revenue by
        another class such as `Project`). When set, each BI measure is returned as a list
        of `{objid, stringrepresentation, value}` entries, one per distinct value of the
        dimension, instead of a single aggregated value.

  responses:
    BadRequest:
      description: |
        The request was malformed or semantically invalid for the Vertec data model,
        for example: an unknown or invalid class name, an object id/entry id that does not
        exist, more than two path segments, an unparsable JSON request body, a value that
        cannot be converted to the target member's datatype, an unknown attribute or
        association name, an attempt to write a non-persistent member, combining `sql`,
        `query_memberN` and id-based search in the same request, resolving `ids` to more
        than one object for `PUT`/`DELETE`, a custom attribute/association name containing
        non-ASCII characters (custom field names must be ASCII-only), or a `bi_measures`
        name that does not exist or is not active.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: |
        The caller is not authenticated: the API token is missing, invalid, expired, or
        not associated with any user.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Forbidden:
      description: |
        The caller is authenticated but not authorized for this request. Possible causes
        include: the user lacks the "Execute" right on the `Webservicedefinition` for this
        webservice, lacks the Vertec read/write/delete right on the specific object or
        member being accessed, lacks the "SQL Query" right for a `sql` search, or lacks
        read access to the requested BI measure(s).
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    MethodNotAllowed:
      description: |
        This HTTP method is not implemented for this path. The Vertec Custom Webservice
        Framework routes a request only to the method decorated for it (`@get`, `@post`,
        `@put`, `@delete`, or `@http` for all four); calling a path with a verb that has no
        matching decorator - e.g. `PUT`/`DELETE` on `/identify/{objid}` or
        `/functions/currentuser`, which only implement `@get`, or `PATCH` anywhere - is
        rejected with `405` before any business logic runs.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'

  schemas:
    VertecObject:
      type: object
      description: |
        A Vertec object as returned by the REST webservice. `objid`, `stringrepresentation`
        and `classname` are always present and readable by every user. `isvalid` and
        `hintisvalid` are added only when the object's class is a (subclass of) `Entry` and
        the caller can read them. Any further properties correspond one-to-one to the names
        requested via `result_members`, `result_ocl:<name>`, `functions` and `bi_measures`;
        their JSON types depend on the underlying Vertec member/expression (string, integer,
        boolean, ISO date/datetime string, base64 string for documents/images, an `objid`
        integer for single-link associations, `-1` for an unset single-link, or a nested
        list of `LinkedObjectSummary` for multi-link associations and list-returning
        functions), which is why this schema does not enumerate them exhaustively.
      properties:
        objid:
          type: integer
          description: Internal Vertec object id (`bold_id`).
        stringrepresentation:
          type: string
          description: Human-readable display text of the object, as shown in the Vertec client.
        classname:
          type: string
          description: Default Vertec class name of the object, e.g. `Project`, `User`.
        isvalid:
          type: boolean
          nullable: true
          description: |
            Present only for objects whose class inherits from `Entry`. `true` if the entry
            currently satisfies all of Vertec's validation rules.
        hintisvalid:
          type: string
          nullable: true
          description: |
            Present only for `Entry` objects. English-translated explanation of why the
            entry is invalid; empty string when `isvalid` is `true`.
      required:
        - objid
        - stringrepresentation
        - classname
      additionalProperties:
        description: |
          Value of one dynamically requested field (from `result_members`,
          `result_ocl:<name>`, `functions` or `bi_measures`). A scalar attribute value
          (string, number, boolean, ISO date/datetime string, base64 string - empty string
          `""` if the bytearray attribute is unset -, or the `objid` of a single-link
          association, `-1` if unset), or a list of `LinkedObjectSummary` for a multi-link
          association or a list-returning function.
        oneOf:
          - type: string
          - type: integer
          - type: number
          - type: boolean
          - type: array
            items:
              $ref: '#/components/schemas/LinkedObjectSummary'

    LinkedObjectSummary:
      type: object
      description: |
        Compact representation of an object reached through a multi-link association or
        returned as part of a list by a function (e.g. `enteringprojects`). Always carries
        `objid`, `stringrepresentation` and `classname`; further properties are the members
        listed for that association/function.
      properties:
        objid:
          type: integer
        stringrepresentation:
          type: string
        classname:
          type: string
      required:
        - objid
        - stringrepresentation
        - classname
      additionalProperties: true

    VertecObjectOrList:
      description: |
        Exactly one object is returned as a JSON object; zero or several objects are
        returned as a JSON array. Clients must inspect whether the response root is an
        object or an array.
      oneOf:
        - $ref: '#/components/schemas/VertecObject'
        - type: array
          items:
            $ref: '#/components/schemas/VertecObject'

    WriteMembersRequest:
      type: object
      description: |
        JSON object mapping Vertec member names to the values to write on the target
        object, used by `POST /objects/{classname}` and `PUT /objects/{classname}/{ids}`.
        Only persistent core or custom attributes/associations can be written; unknown or
        non-persistent member names cause `400`.

        Value conversion per attribute datatype:
        - `string` – any JSON value, converted with `str()`.
        - `boolean` – a JSON boolean, or the strings `"True"`/`"true"`.
        - `date` – an ISO date string `YYYY-MM-DD`.
        - `datetime` – an ISO 8601 UTC string `YYYY-MM-DDTHH:MM:SSZ`.
        - `integer` – an integer (or a string that parses as one).
        - `currency` – a number (or a string that parses as one).
        - `object` (single-link core attribute of reference type) – the internal `objid`
          (integer) of the object to link.
        - `bytearray` – a base64-encoded string (used for images and documents).

        Association handling:
        - Single-link core or custom association – provide the internal `objid` (integer)
          of the object to link, or `-1` to remove the existing link. Writing a role of a
          `WrapperLinkType` is rejected with `403`; write the underlying members/
          associations instead.
        - Multi-link core or custom association – provide either a single `objid` or a JSON
          array of `objid`s; each is *added* to the association. There is no way to remove
          a member from a multi-link association through this endpoint.

        Document handling – on classes that can hold a document (`Activity`, subclasses of
        `AbstractExpenditure`, `Creditor`, `Invoice`), the `document`/`content`/`receipt`
        member (depending on class) accepts a base64-encoded byte string. When writing
        `content` or `document` on an `Activity`, a `documentfullname` field (the file name)
        must also be present in the same request body.
      additionalProperties: true

    IdentifyResponse:
      type: object
      description: Minimal class metadata for an object whose class was not known in advance.
      properties:
        classname:
          type: string
          description: Default Vertec class name of the identified object.
        stringrepresentation:
          type: string
          description: Human-readable display text of the identified object.
      required:
        - classname
        - stringrepresentation

    ErrorResponse:
      type: object
      description: |
        Error payload as raised by the webservice via `WebException(status, message)`.
      properties:
        message:
          type: string
          description: Human-readable description of what went wrong, in English.

    ValidateOclRequest:
      type: object
      description: Request body for `POST /validateocl`.
      properties:
        classname:
          type: string
          description: |
            Vertec class name the expression is evaluated against. The field must be
            present, but may be an empty string (`""`); in that case the expression is
            validated as a *global* expression, without being bound to a specific class -
            the same way `ocl` can be evaluated without a root object on
            `GET /objects/{classname}`.
        expression:
          type: string
          description: The OCL (Object Constraint Language) expression to validate.
      required:
        - classname
        - expression

    ValidateOclResponse:
      type: object
      description: Result of validating an OCL expression for a class.
      properties:
        isvalid:
          type: boolean
          description: Whether the expression is syntactically and semantically valid for `classname`.
        resulttype:
          type: string
          description: |
            Type of the expression's result. For core/primitive result types this uses the
            same simplified names as elsewhere in this API (`string`, `boolean`, `integer`,
            `date`, `datetime`, `currency`, `object`, `bytearray`); for object-typed results
            that are not one of these, the actual Vertec class name is returned instead.
        islist:
          type: boolean
          description: Whether the expression's result is a list rather than a single value.
        issimpletype:
          type: boolean
          description: |
            Whether the expression's result is a simple/primitive datatype rather than a
            Vertec object reference.
        errormessage:
          type: string
          description: Error text if the expression is invalid; empty string if `isvalid` is `true`.
        errorlocation:
          type: string
          description: |
            Location indicator for the error if the expression is invalid; empty string if
            `isvalid` is `true`.
      required:
        - isvalid
        - resulttype
        - islist
        - issimpletype
        - errormessage
        - errorlocation
