openapi: 3.0.3
info:
  title: Memlist Fritidskortet (FK) API
  version: "v3"
  description: |
    Endpoints for reading and updating a company's Fritidskortet (Försäkringskassan)
    data in Memlist: list the whole company register, read a single company's
    enrollment status and details, and update its `fk_status`.

    All FK endpoints are authenticated with an OAuth 2.0 access token (Bearer).
    Obtain a token from `POST /access/user/basic` using HTTP Basic auth with your
    `client_id` and `client_secret`.
  contact:
    name: Memlist support
    email: support@memlist.se

servers:
  - url: https://{instance}.memlist.se/api/v3
    description: Memlist instance
    variables:
      instance:
        default: your-instance
        description: Your Memlist instance subdomain

tags:
  - name: Authentication
    description: Obtain an access token
  - name: Fritidskortet
    description: Company Fritidskortet (FK) data

paths:
  /access/user/basic:
    post:
      tags: [Authentication]
      summary: Obtain an access token
      description: |
        Exchange `client_id` and `client_secret` (HTTP Basic auth) for an access token.
        Use the returned `access_token` as a Bearer token on all FK endpoints.
      operationId: getAccessToken
      security:
        - basicAuth: []
      responses:
        "200":
          description: Access + refresh token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TokenResponse'
        "401":
          description: Missing or invalid credentials
        "429":
          description: Rate limit exceeded (max 10 requests per minute per IP)

  /company/fk:
    get:
      tags: [Fritidskortet]
      summary: List the whole register
      description: |
        Returns every non-deleted company as a minimal record (name, org_number and
        parent). No pagination, no filtering — the entire register in one response.
        The payload intentionally carries no `company_id`, `fk_status`, contact
        details or grants.

        Requires an access group that includes the `company` module.
      operationId: listFkCompanies
      security:
        - bearerAuth: []
      responses:
        "200":
          description: Array of minimal company records
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/CompanyFkListItem'
        "401":
          description: Missing or expired token
        "500":
          description: Unexpected server error

  /company/{company_id}/fk:
    get:
      tags: [Fritidskortet]
      summary: Get a single company's FK data
      description: |
        Returns the Fritidskortet view of one company: name, address, contact info,
        `fk_status` and activated grants.

        Requires an access group that includes the `company` module, and the company
        must be within the caller's company access scope.
      operationId: getCompanyFk
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/CompanyId'
      responses:
        "200":
          description: Company Fritidskortet data
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CompanyFk'
        "400":
          description: Malformed path parameter
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        "401":
          description: Missing or expired token
        "403":
          description: The company exists but is outside the caller's access scope
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "404":
          description: Company not found (or deleted)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "500":
          description: Unexpected server error
    put:
      tags: [Fritidskortet]
      summary: Update fk_status
      description: |
        Updates the company's Fritidskortet enrollment status. **Only** `fk_status`
        is updated — any other field in the body is ignored.

        Requires an access group that includes the `company` module **and** the
        `COMPANY.U` permission, and the company must be within the caller's
        company access scope.
      operationId: updateCompanyFkStatus
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/CompanyId'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateFkStatusRequest'
      responses:
        "200":
          description: Updated company_id and fk_status
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UpdateFkStatusResponse'
        "400":
          description: fk_status missing or not a valid enum value
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        "401":
          description: Missing or expired token
        "403":
          description: Access group lacks the COMPANY.U permission, or the company
            is outside the caller's access scope
        "404":
          description: Company not found (or deleted)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        "500":
          description: Unexpected server error

  /company/fk/status/{company_id}:
    get:
      tags: [Fritidskortet]
      summary: Check utförarstatus
      description: |
        Asks the Fritidskortet service whether the company is an approved utförare.
        The lookup uses the company's org_number and the giro details stored on its
        FRITIDSKORTET api key.

        Requires an access group that includes the `company` module.
      operationId: getFkStatus
      security:
        - bearerAuth: []
      parameters:
        - $ref: '#/components/parameters/CompanyId'
      responses:
        "200":
          description: Utförarstatus as reported by the Fritidskortet service
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/UtforarstatusResponse'
        "400":
          description: Malformed path parameter
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        "401":
          description: Missing or expired token
        "500":
          description: Company or its FRITIDSKORTET api key not found, missing
            org_number/giro details, or the Fritidskortet service call failed

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Access token obtained from POST /access/user/basic
    basicAuth:
      type: http
      scheme: basic
      description: client_id as username, client_secret as password

  parameters:
    CompanyId:
      name: company_id
      in: path
      required: true
      description: The company's 36-character string id
      schema:
        type: string
      example: 5cf693b1-8a20-4bd4-b3b0-5d2f45f2b6a1

  schemas:
    FkStatus:
      type: string
      description: How far along the company is in the Fritidskortet enrollment flow
      enum:
        - NONE
        - ML_ADDED
        - FK_ADDED
        - ACTIVE
        - REJECTED
      x-enum-descriptions:
        NONE: Not enrolled (the default for every company)
        ML_ADDED: Added as an utförare on the Memlist side
        FK_ADDED: Registered and pending approval on the Försäkringskassan side
        ACTIVE: Enrollment complete — active utförare, verified from Ehälsomyndigheten
        REJECTED: Enrollment rejected

    ParentCompany:
      type: object
      properties:
        org_number:
          type: string
          example: "802000-0001"
        name:
          type: string
          example: Exempelförbundet

    CompanyFkListItem:
      type: object
      properties:
        name:
          type: string
          example: IFK Exempelstad
        org_number:
          type: string
          example: "802481-1234"
        parent:
          type: object
          nullable: true
          description: Parent organization, or null for a top-level company
          allOf:
            - $ref: '#/components/schemas/ParentCompany'

    Grant:
      type: object
      properties:
        id:
          type: integer
          example: 1
        uuid:
          type: string
          example: d5a8e9c2-1b3f-4a6d-8e2c-9f1b0a7c4d3e
        name:
          type: string
          example: Projektbidrag
        descr:
          type: string
          example: Bidrag för tidsbegränsade projekt

    CompanyGrant:
      type: object
      properties:
        id:
          type: integer
          example: 3
        created_at:
          type: string
          description: Timestamp, format YYYY-MM-DDTHH:mm:ss (server local time)
          example: "2026-08-14T13:02:11"
        grant:
          $ref: '#/components/schemas/Grant'

    CompanyFk:
      type: object
      properties:
        company_id:
          type: string
          example: 5cf693b1-8a20-4bd4-b3b0-5d2f45f2b6a1
        uuid:
          type: string
          example: 9c1b2f4e-77aa-4f0c-9a41-2f8d0c3e5b21
        name:
          type: string
          example: IFK Exempelstad
        shortname:
          type: string
          nullable: true
          example: IFK EX
        org_number:
          type: string
          example: "802481-1234"
        fk_status:
          $ref: '#/components/schemas/FkStatus'
        street:
          type: string
          nullable: true
          example: Föreningsgatan 12
        co:
          type: string
          nullable: true
          example: null
        zipcode:
          type: string
          nullable: true
          example: "41255"
        post:
          type: string
          nullable: true
          example: Göteborg
        country:
          type: string
          nullable: true
          example: Sverige
        email:
          type: string
          nullable: true
          description: Organization general email
          example: kansli@ifkexempelstad.se
        phone:
          type: string
          nullable: true
          description: Organization general phone
          example: "031-123456"
        landline:
          type: string
          nullable: true
          example: null
        web:
          type: string
          nullable: true
          example: https://ifkexempelstad.se
        c_name:
          type: string
          nullable: true
          description: Contact person name
          example: Anna Andersson
        c_email:
          type: string
          nullable: true
          description: Contact person email
          example: anna@ifkexempelstad.se
        c_phone:
          type: string
          nullable: true
          description: Contact person phone
          example: "0701-234567"
        company_grants:
          type: array
          description: One entry per grant activated on the company (empty if none)
          items:
            $ref: '#/components/schemas/CompanyGrant'

    UpdateFkStatusRequest:
      type: object
      required:
        - fk_status
      properties:
        fk_status:
          $ref: '#/components/schemas/FkStatus'

    UpdateFkStatusResponse:
      type: object
      properties:
        company_id:
          type: string
          example: 5cf693b1-8a20-4bd4-b3b0-5d2f45f2b6a1
        fk_status:
          $ref: '#/components/schemas/FkStatus'

    UtforarstatusResponse:
      type: object
      properties:
        utforarestatus:
          type: string
          description: Whether Fritidskortet has approved the company as utförare
          enum:
            - GODKAND
            - EJ_GODKAND
          example: GODKAND

    TokenResponse:
      type: object
      properties:
        access_token:
          type: string
        token_type:
          type: string
          example: Bearer
        expires_in:
          type: integer
          description: Token lifetime in seconds
          example: 3600
        refresh_token:
          type: string
        refresh_expires_ts:
          type: integer
          format: int64
          description: Unix timestamp when the refresh token expires
          example: 1743465600

    Error:
      type: object
      properties:
        reason:
          type: string
          example: company not found

    ValidationError:
      type: object
      properties:
        reason:
          type: string
        code:
          type: string
          example: validation_error
        errors:
          type: object
          additionalProperties:
            type: array
            items:
              type: string
        form_errors:
          type: array
          items:
            type: string
