---
openapi: 3.0.1
info:
  title: Kroo API v3
  version: 1.0.0
  description: |
    OAuth 2.0 Client Credentials authenticated API for machine-to-machine
    integrations.

    ## Authentication

    All v3 endpoints require Bearer token authentication. Request a token
    from `/oauth/token` with your client credentials, then send it on
    subsequent requests as `Authorization: Bearer <token>`. Tokens
    expire after 1 hour.

    ## Rate Limiting

    - V3 API endpoints: 60 requests per minute per IP
    - OAuth token endpoint: 10 requests per minute per IP

    Every response carries `X-RateLimit-Limit`, `X-RateLimit-Remaining`,
    and `X-RateLimit-Reset` headers. When the limit is exceeded the
    server returns `429 rate_limit_exceeded`; back off until the
    `X-RateLimit-Reset` Unix timestamp and retry.
  contact:
    name: Kroo API Support
    email: implementations@getkroo.com
servers:
- url: https://api.getkroo.com/api/v3
  description: Production
tags:
- name: Authentication
  description: Exchange client credentials for short-lived Bearer tokens used to authorize
    every other v3 request.
- name: Schedule Files
  description: Upload, list, retrieve, and delete P6 schedule files (XER, XML, PPX).
    Uploads are parsed and ingested asynchronously.
security:
- BearerAuth: []
components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: 'Bearer token obtained from `POST /oauth/token`. Send as `Authorization:
        Bearer <token>`. Tokens expire after 1 hour; refresh by requesting a new one
        with the same client credentials.'
  schemas:
    ScheduleFile:
      type: object
      required:
      - id
      - name
      - is_baseline
      - company_id
      - customer_project
      - created_at
      properties:
        id:
          type: integer
          format: int64
          description: Unique identifier for the schedule file.
          example: 482
        name:
          type: string
          description: Human-readable name supplied when the file was uploaded.
          example: October 2026 Baseline
        is_baseline:
          type: boolean
          description: True if the file was marked as a baseline at upload time; baselines
            are preserved for variance reporting.
          example: false
        file_type:
          type: string
          enum:
          - xer
          - xml
          - ppx
          - mpp
          description: Format the file was uploaded as, which also determines the
            scheduling integration it belongs to.
          example: xer
        partner_token_id:
          type: integer
          format: int64
          nullable: true
          description: The scheduling integration the file was uploaded under. Null
            for files uploaded before that link existed, or whose integration was
            disconnected.
        company_id:
          type: integer
          format: int64
          description: ID of the company that owns this file. Always equal to the
            authenticated client's company.
          example: 1042
        customer_project:
          type: object
          nullable: true
          description: Project association if one was provided on upload. `null` for
            unassociated files.
          properties:
            id:
              type: integer
              format: int64
              description: Kroo-side project ID.
            name:
              type: string
              description: Human-readable project name.
            source:
              type: string
              description: Partner source the project was synced from (e.g. `procore`).
            customer_project_id:
              type: string
              description: Project identifier in the source partner system.
        created_at:
          type: string
          format: date-time
          description: ISO 8601 timestamp of when the upload was accepted.
          example: '2026-04-27T15:30:00Z'
    Error:
      type: object
      required:
      - error
      - error_description
      properties:
        error:
          type: string
          description: Machine-readable error code. See the operation description
            for the codes a specific endpoint can return.
        error_description:
          type: string
          description: Human-readable description of the error. Safe to log; do not
            show to end users verbatim.
paths:
  "/oauth/token":
    post:
      summary: Issue an access token
      tags:
      - Authentication
      operationId: createAccessToken
      description: |
        Exchanges client credentials for a short-lived Bearer token used to
        authenticate subsequent v3 API requests.

        Submit `grant_type=client_credentials` together with your
        `client_id` and `client_secret`. The response includes the
        `access_token` to send as `Authorization: Bearer <token>`, the
        `token_type` (always `Bearer`), the lifetime in seconds, and the
        space-delimited `scope` actually granted.

        **Scopes:** omit the `scope` parameter to receive every scope your
        client is registered for, or pass a subset (space-delimited). A
        request for a scope outside your client's registered set returns
        `invalid_scope`.

        **Auth:** the token endpoint itself is unauthenticated (no Bearer
        header). Authentication is performed by the `client_id` /
        `client_secret` pair you send in the body.

        **400 error codes** (RFC 6749 §5.2):
        - `invalid_client` — missing or wrong `client_id` / `client_secret`.
        - `unsupported_grant_type` — `grant_type` is not `client_credentials`.
        - `unauthorized_client` — the client is not registered for the
          `client_credentials` grant.
        - `invalid_scope` — the requested `scope` exceeds the client's
          registered scope set.
      security: []
      parameters: []
      responses:
        '200':
          description: Access token issued
          content:
            application/json:
              schema:
                type: object
                required:
                - access_token
                - token_type
                - expires_in
                - scope
                properties:
                  access_token:
                    type: string
                    description: 'Bearer token to send in `Authorization: Bearer <token>`.
                      Treat it as a secret; tokens are bearer credentials.'
                    example: f4a91d2bce7c4f0a9b8e3d6c5a2f1e8b9d7c6a5b4e3f2d1c0b9a8e7d6c5b4a3f
                  token_type:
                    type: string
                    enum:
                    - Bearer
                    description: Always `Bearer`. Use as the auth scheme in the `Authorization`
                      header.
                    example: Bearer
                  expires_in:
                    type: integer
                    description: Seconds until the token expires (3600 = 1 hour).
                    example: 3600
                  scope:
                    type: string
                    description: Space-delimited list of scopes actually granted on
                      this token. May be narrower than what you requested if some
                      scopes are not registered on the client.
                    example: read:schedule_files write:schedule_files
        '400':
          description: Authentication or request failure (see operation description
            for error codes)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '429':
          description: Rate limit exceeded
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
              description: Requests allowed in the current window.
            X-RateLimit-Remaining:
              schema:
                type: integer
              description: Requests remaining in the current window.
            X-RateLimit-Reset:
              schema:
                type: integer
                format: int64
              description: Unix timestamp when the window resets.
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
      requestBody:
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              required:
              - grant_type
              - client_id
              - client_secret
              properties:
                grant_type:
                  type: string
                  enum:
                  - client_credentials
                  description: OAuth 2.0 grant type. Only `client_credentials` is
                    supported on this endpoint for confidential clients.
                client_id:
                  type: string
                  description: Issued when the API client was created.
                client_secret:
                  type: string
                  description: Issued when the API client was created.
                scope:
                  type: string
                  description: Optional space-delimited list of scopes to request.
                    Defaults to the client's full registered scope set.
                  example: read:schedule_files write:schedule_files
        required: true
  "/schedule_files":
    get:
      summary: List schedule files
      tags:
      - Schedule Files
      operationId: listScheduleFiles
      description: |
        Returns the authenticated company's schedule files in reverse
        chronological order.

        **Required scope:** `read:schedule_files`
      security:
      - BearerAuth: []
      parameters:
      - name: page
        in: query
        required: false
        description: 1-indexed page number. Clamped to [1, 10000].
        schema:
          type: integer
          minimum: 1
          maximum: 10000
          default: 1
      - name: per_page
        in: query
        required: false
        description: Page size. Clamped to [1, 100].
        schema:
          type: integer
          minimum: 1
          maximum: 100
          default: 10
      responses:
        '200':
          description: Paginated list of schedule files
          content:
            application/json:
              schema:
                type: object
                required:
                - data
                - meta
                properties:
                  data:
                    type: array
                    items:
                      "$ref": "#/components/schemas/ScheduleFile"
                  meta:
                    type: object
                    required:
                    - page
                    - per_page
                    - total_count
                    - total_pages
                    properties:
                      page:
                        type: integer
                        description: Current page (1-indexed).
                      per_page:
                        type: integer
                        description: Page size applied to this response.
                      total_count:
                        type: integer
                        description: Total number of schedule files for the authenticated
                          company.
                      total_pages:
                        type: integer
                        description: Total number of pages at the current per_page.
        '401':
          description: Missing or invalid token
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '403':
          description: Token lacks required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '429':
          description: Rate limit exceeded
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
            X-RateLimit-Remaining:
              schema:
                type: integer
            X-RateLimit-Reset:
              schema:
                type: integer
                format: int64
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    post:
      summary: Upload a schedule file
      tags:
      - Schedule Files
      operationId: uploadScheduleFile
      description: |
        Uploads a schedule file (P6 XER/XML, Phoenix PPX, or Microsoft Project
        MPP) and enqueues a sync job to parse and ingest it.

        **Required scope:** `write:schedule_files`

        **Constraints:**
        - `file` must be a multipart/form-data upload.
        - `file_type` must be one of `xer`, `xml`, `ppx`, `mpp`.
        - Within the same partner family (XER/XML share a family; PPX and MPP
          are each their own), the most recent file's `file_type` cannot change
          between uploads. Switching formats inside a partner returns 422
          `file_type_mismatch`. PPX after XER is allowed because they belong
          to different partners.
        - `customer_project_id`, if provided, must reference a customer
          project owned by the authenticated company.

        **422 error codes:**
        - `invalid_request` — missing required parameter (`name`, `file`, or
          `file_type`) or `file` is not a multipart upload.
        - `file_type_mismatch` — the most recent file in the same partner
          family uses a different `file_type`.
        - `validation_error` — the underlying record failed model-level
          validation (including unknown `file_type` values).
      security:
      - BearerAuth: []
      parameters: []
      responses:
        '201':
          description: File uploaded; sync job enqueued
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ScheduleFile"
        '401':
          description: Missing or invalid token
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '403':
          description: Token lacks required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Customer project not found for this company
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '422':
          description: Validation failure (see operation description for error codes)
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '429':
          description: Rate limit exceeded
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
            X-RateLimit-Remaining:
              schema:
                type: integer
            X-RateLimit-Reset:
              schema:
                type: integer
                format: int64
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              required:
              - name
              - file
              - file_type
              properties:
                name:
                  type: string
                  example: October Baseline
                file:
                  type: string
                  format: binary
                  description: The schedule file to upload.
                file_type:
                  type: string
                  enum:
                  - xer
                  - xml
                  - ppx
                  - mpp
                  example: xer
                is_baseline:
                  type: boolean
                  default: false
                customer_project_id:
                  type: integer
                  format: int64
                  nullable: true
                  description: Optional. Associate the file with a customer project
                    owned by the authenticated company.
        required: true
  "/schedule_files/{id}":
    parameters:
    - name: id
      in: path
      required: true
      description: Schedule file ID.
      schema:
        type: integer
        format: int64
    get:
      summary: Get a schedule file
      tags:
      - Schedule Files
      operationId: getScheduleFile
      description: |
        Returns a single schedule file owned by the authenticated company.

        **Required scope:** `read:schedule_files`
      security:
      - BearerAuth: []
      responses:
        '200':
          description: Schedule file
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/ScheduleFile"
        '401':
          description: Missing or invalid token
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '403':
          description: Token lacks required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Schedule file not found for the authenticated company
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '429':
          description: Rate limit exceeded
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
            X-RateLimit-Remaining:
              schema:
                type: integer
            X-RateLimit-Reset:
              schema:
                type: integer
                format: int64
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
    delete:
      summary: Delete a schedule file
      tags:
      - Schedule Files
      operationId: deleteScheduleFile
      description: |
        Deletes a schedule file and enqueues a job to remove its derived data
        downstream.

        **Required scope:** `write:schedule_files`
      security:
      - BearerAuth: []
      responses:
        '200':
          description: File deleted; downstream cleanup enqueued
          content:
            application/json:
              schema:
                type: object
                required:
                - deleted
                - id
                properties:
                  deleted:
                    type: boolean
                    enum:
                    - true
                  id:
                    type: integer
                    format: int64
        '401':
          description: Missing or invalid token
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '403':
          description: Token lacks required scope
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '404':
          description: Schedule file not found for the authenticated company
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
        '429':
          description: Rate limit exceeded
          headers:
            X-RateLimit-Limit:
              schema:
                type: integer
            X-RateLimit-Remaining:
              schema:
                type: integer
            X-RateLimit-Reset:
              schema:
                type: integer
                format: int64
          content:
            application/json:
              schema:
                "$ref": "#/components/schemas/Error"
