openapi: 3.1.0
info:
  title: Phonemos - External Data Connector
  description: |-
    The external data connectors allow [https://www.phonemos.com](Phonemos) to integrate structured data from external sources.
    The basic elements are:

    *Connector*: A server that runs in a location that is reachable for the Phonemos backend and implements this API. Connectors are usually instantiated per connected system type (e.g. Jira, YouTrack, Github). A connector makes data sources available.

    *Data Source*: Provides data (or to be more specific data change events) for a specific external system (e.g. the YouTrack instance youtrack.mycompany.com). Each data source is provided by exactly one connector.

    *Entity*: An item inside the external system (e.g. an Issue, a Youtrack project, a git commit). Each entity can have relations to other entities (e.g. an issue belongs to a project).
              There is one special entity type "user", that will be synced to Phonemos users. See the description under DataSourceSchemaEntity.

  contact:
    email: info@phonemos.com
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  version: 1.0.0
servers:
  - url: http://localhost:3004
    description: Local example, see /tests/sample-external-data-connector in Phonemos sources
paths:
  /v1/connector/info:
    get:
      description: Gets information about the connector and its capabilities.
      parameters: []
      responses:
        '200':
          description: successful operation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ConnectorMetadata'
      security:
        - connector: [read]

  /v1/connector/data-sources/{id}:
    parameters:
     - $ref: '#/components/parameters/DataSourceId'
    put:
      description: Creates or reconfigures a data source. The connector is not required to persist this information, Phonemos will retrigger the put if an operation on the data source return 404. But the connector must remember it with a reasonable lifetime (eg until the next restart or so).
      requestBody:
          description: Configuration for the data source
          required: true
          content:
            application/json:
              schema:
                type: object
                properties:
                  config:
                    type: object
                    description: The configuration of the data source
                    additionalProperties: true # Allows an arbitrary JSON object
                    examples:
                    - projectName: Greenhouse
                    - url: https://test.mycompany.com.local/jira
      responses:
        '200':
          description: Data source created or reconfigured successfully
        '400':
          description: Invalid configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DataSourceError'
        '500':
          description: Internal connector error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DataSourceError'
      security:
        - connector: [read]
    delete:
      description: The data source has been deleted in Phonemos, the connector can discard any data associated with it.
      responses:
        '200':
          description: DataSource data has been deleted on the connector
        '404':
          description: DataSource was not known to the connector
      security:
        - connector: [read]

  /v1/connector/data-sources/{id}/info:
    parameters:
     - $ref: '#/components/parameters/DataSourceId'
    get:
      description: Gets meta information about the data source and the items it returns.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DataSourceInfo'
        '404':
          description: The data source is not known. Phonemos will retrigger the put on the data source to provide the config.
      security:
        - connector: [read]

  /v1/connector/data-sources/{id}/status:
    parameters:
     - $ref: '#/components/parameters/DataSourceId'
    get:
      description: Gets the status of the data source
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DataSourceStatus'
        '404':
          description: The data source is not known. Phonemos will retrigger the put on the data source to provide the config.
      security:
        - connector: [read]

  /v1/connector/data-sources/{id}/events:
    parameters:
     - $ref: '#/components/parameters/DataSourceId'
    get:
      parameters:
        - name: afterPosition
          in: query
          required: true
          description: |
            The cursor position where the fetching should start (excluding).
            The format of the position can be freely defined by the connector. Phonemos will always send the cursor position from the last event it has successfully processed to fetch the next items.
          schema:
            type: string
          examples:
            first:
              summary: From the beginning
              value: "0"
            offset:
              summary: In the middle
              value: "3"
            end:
              summary: Where there are none
              value: "999"
      description: |
        Returns data items starting after the given cursor position.
        The events returned have to be ordered by position so that the last event in the array is the one with the highest cursor position.
        If no new events are available the reponse is allowed to wait for new events for up to 1 minute and return immediatly after new events have arrived. It's also ok to just return an empty array immediatly, Phonemos will throttle the next request in that case.
        The number of events returned should be sensible to hold in memory, so the maximum size of the returned json should not exceed 5MB. The connector can make the decision on how many events
        are the right number. It's also possible to return a different number of events on every request based on some logic.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/EntityEvent'
        '404':
          description: The data source is not known. Phonemos will retrigger the put on the data source to provide the config.
      security:
        - connector: [read]

components:
  parameters:
    DataSourceId:
      name: id
      in: path
      required: true
      description: Unique identifier for the data source. Chosen by Phonemos.
      examples:
        uuid:
          summary: Greenhouse example
          value: 795a9b0b-cdff-4443-a10b-cd46e1b31144
      schema:
        type: string

  schemas:
    DataSourceInfo:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier for the data source (from the request parameter).
          examples: ['795a9b0b-cdff-4443-a10b-cd46e1b31144']
        label:
         type: string
         description: The label of the data source
         examples: ['Test Jira (https://jira.test.local)']
        kind:
          type: string
          examples: ["jira", "youtrack"]
        initialPosition:
         type: string
         description: The cursor position that is before in the first ever event from the data source (the "zero" position).
         examples: ['0']
        entites:
          type: array
          items:
            $ref: '#/components/schemas/DataSourceSchemaEntity'
      required:
        - id
        - label
        - kind
        - initialPosition
        - entities
    DataSourceSchemaEntity:
      type: object
      description: |-
        Definition of an entity that is returned by this connector (e.g. an issue, project, account)
        The entity type "user" has a special definition in Phonemos, it is mapped to Phonemos users automatically and has the following (minimal) fields (ids):
          - email (required): Will be used to match users to Phonemos
          - first_name: First name of the user, e.g. "Peter". Will only be used if the user is not already known in Phonemos.
          - last_name: List name of the user, e.g. "Miller". Will only be used if the user is not already known in Phonemos.
          - display_name: Usually something like "Peter Miller" if the application does not have separate first/last name fields.
          - additional fields are allowed
        If you wish to return users that are not mapped to Phonemos user just use a different entity type.
      properties:
        type:
          type: string
          description: Type of the entity
          examples: ["issue", "user"]
        fields:
          type: array
          items:
            $ref: '#/components/schemas/DataSourceSchemaField'
          uniqueItems: true
        references:
          type: array
          items:
            $ref: '#/components/schemas/DataSourceSchemaReference'
          uniqueItems: true
      required:
        - type
        - fields
        - references
    DataSourceSchemaField:
      type: object
      properties:
        id:
          type: string
          description: Unique identifier for the field  (arbitrary, chosen by the connector)
          examples: ["summary", "3b5043d0-e579-419b-9238-e4b3dd4fdb97"]
        name:
          type: string
          description: User presentable name of the field
          examples: ["Summary", "Priority"]
        description:
          type: string
        fieldType:
          $ref: '#/components/schemas/DataSourceSchemaFieldType'
      required:
        - id
        - name
        - description
        - fieldType
      examples:
        - name: Issue Key
          description: User readable id of the issue (e.g. MYPRO-143)
          fieldType:
            type: Text
        - name: Status
          description: Status of the issue (e.g. OPEN)
          fieldType:
            type: Label
            multiple: false
    DataSourceSchemaFieldType:
      type: object
      properties:
        type:
          type: string
          enum:
            - Text
            - Number
            - Date
            - Instant
            - Label
            - Link
            - Other
          description: |
           - Text: JSON-string, no specific format
           - Number: JSON-number
           - Date: JSON-string ISO-8601 date without timezone (2024-12-24)
           - Instant: JSON-string ISO-8601 datetime with timezone, optional milliseconds (2023-04-04T15:20:30+02:00 or 2023-04-04T15:20:30Z)
           - Label: JSON-array of JSON-string (["one", "two"])
           - Link: JSON-object with keys "label" and "uri" (e.g. {"label": "ABC-123", href: "https://issues.local/i/ABC-123"})
           - Other: any JSON
        multiple:
          type: boolean
      required:
        - type
    DataSourceSchemaReference:
      type: object
      properties:
        id:
          type: string
          description: Id of the reference (arbitrary, chosen by the connector)
          example: ["assignee", "project", "defaee9a-9353-43e0-91ca-6573ebca419e"]
        name:
          type: string
          description: User presentable name of the reference
          example: ["Assignee", "Project"]
        description:
          type: string
        type:
          type: string
          description: Entity Type that can be referenced by this relation.
          examples: ["jira-issue", "user"]
        multiple:
          type: boolean
          description: true for a -to-many relation, false for a to-one relation
      required:
        - name
        - description
        - kinds
        - multiple
    DataSourceStatus:
      type: object
      properties:
        status:
          type: string
          enum:
            - Ok
            - Error
            - Unreachable
        details:
          type: string
        lastPosition:
          type: string
      required:
        - status
    ConnectorMetadata:
      type: object
      properties:
        kind:
          type: string
          examples: ["jira", "youtrack"]
        label:
          type: string
          examples: ["Connect to Jira DataCenter instances"]
        version:
          type: string
          examples: ["1.2.3"]
        configSchema:
          $ref: "#/components/schemas/ConnectorConfigSchema"
      required:
        - kind
        - label
        - version
        - configSchema
    ConnectorConfigSchema:
      type: object
      properties:
        options:
          type: array
          items:
            type: object
            properties:
                name:
                    type: string
                    examples: ["url"]
                secret:
                    type: boolean
                    description: If the value is considered sensitive (will not be displayed to the user).
                description:
                    type: string
                    examples: ["The url of the Jira instance"]
                required:
                    type: boolean
                    description: If the value is required in order for the data source to be able to start.
            required:
              - name
              - description
      required: [options]
    DataSourceError:
      type: object
      properties:
        summary:
          type: string
          description: Brief summary of the error for display in the UI
          examples: ["Invalid URL format", "Authentication failed", "Project not found"]
        details:
          type: string
          description: Detailed error information for debugging
          examples: ["The provided URL 'invalid-url' is not a valid HTTP/HTTPS URL", "API key 'abc123' is invalid or expired", "Project 'MYPROJ' does not exist in the specified instance"]
        code:
          type: string
          description: Error code for programmatic handling
          examples: ["parameters", "not-found", "unauthorized", "internal-error", "invalid-config", "authentication-failed", "unreachable", "resource-not-found", "external-system-error", "rate-limited", "permission-denied", "service-unavailable"]
      required:
        - summary
    EntityId:
      type: object
      properties:
        type:
          type: string
          description: Type of the entity
          examples: ["jira-issue", "user"]
        instance:
          type: string
          description: Instance the entity belongs to (e.g. the name of the git repo or the jira server instance id)
          examples:
          - jira:1234-444-333-11
          - git:mycompany/my-repo
        id:
          type: string
          description: Unique identifier of the entity
          examples: ["29bb700d-6674-4636-8f8e-45eca4b2bd37"]
      required: 
      - type
      - instance
      - id
    EntityEvent:
      type: object
      properties:
        type:
          type: string
          description: Event type
          enum: [Upsert, Delete]
        entity:
          $ref: '#/components/schemas/EntityId'
        position:
          type: string
          examples: ['2024-02-24T22:11:00.123456', '1447385']
        fields:
          type: object
          description: |
            All values of the entity. Non present values will be set to 'null' (deleted) by Phonemos.
            The key in the json object is the id of the field, the value is dependend on the type of the field, see
            the description of DataSourceSchemaFieldType for the specification per type
          additionalProperties: true
          examples:
          - summary: My funny issue
            key: ISS-123
            dueDate: "2024-08-12"
            costs: 1234.50
            label: ["feature", "UI"]
        references:
          type: object
          description: |
            All outgoing references from this entity. To remove all references of a type (reference id) set it to an empty array
            or just don't send that reference id in the json object.
            The keys in the json object are the id of the reference, the value an array of EntityId (even for non-multiple
            references, for those the array just is not allowed to contain more than one element).
          additionalProperties: true
          examples:
            assignee:
              - type: user
                instance: jira:1234-444-333-11
                id: peter.miller@local
      required:
        - type
        - entity
        - position
      examples:
        - type: Upsert
          entity:
            type: jira-issue
            instance: jira:1234-444-333-11
            id: 29bb700d-6674-4636-8f8e-45eca4b2bd37
          fields:
            key: ISS-123
            summary: My funny issue
          references:
            assignee:
              - type: user
                instance: jira:1234-444-333-11
                id: d45bd955-4f48-4ac6-b5b5-44aa7747bdd5

  securitySchemes:
    connector:
      type: apiKey
      name: X-Api-Key
      in: header
