openapi: 3.1.0
info:
  title: NFTPAYROLL Service API
  version: "1.0.0"
  summary: B2B payroll ingestion & NFT mint pipeline
  description: |
    The NFTPAYROLL Service API is a server-to-server (B2B) API for pushing payroll
    data into the platform and triggering each payroll to be uploaded to IPFS and
    minted as an NFT on the Sepolia network.

    ## Authentication
    Every request is authorized with a **service API key**. Send your key on each
    request using the `X-API-Key` header:

    ```
    X-API-Key: sk_live_xxxxxxxxxxxxxxxxxxxx
    ```

    `Authorization: Bearer <key>` is also accepted. Each key is bound to a single
    company (tenant); all records you submit are created under that company.

    ## Rate limit
    The default limit is **60 requests per minute**. When exceeded, the API returns
    `429 Too Many Requests`. Contact us to raise the limit on a per-key basis.

    ## Workflow
    There are two ways to ingest payrolls:

    1. **Direct import** (`POST /payrolls/bulk`) — records are validated, created,
       and immediately queued for minting.
    2. **Review → Submit** (`POST /payrolls/review-batches` → `.../submit`) —
       records are first validated and previewed (not yet created). After reviewing
       the result you call `submit` to persist them and queue minting. Recommended
       for high-volume or approval-gated integrations.

    ## Idempotency (external_ref)
    Send `payroll.external_ref` to prevent duplicate records. If omitted, the system
    generates a reference in the form `PR-{companyId}-{nationalId}-{YYYYMM}-{documentNo}`.
    Submitting the same `external_ref` a second time skips the record
    (`skipped` / `duplicate`).
  contact:
    name: NFTPAYROLL Integrations
    email: developers@nftpayroll.ai

servers:
  - url: http://127.0.0.1:8000/api/service/v1
    description: Local development
  - url: https://api.nftpayroll.ai/api/service/v1
    description: Production

security:
  - ApiKeyAuth: []

tags:
  - name: Payrolls
    description: Payroll ingestion and batch status
  - name: Review Batches
    description: Preview → validate → submit flow

paths:
  /payrolls/bulk:
    post:
      tags: [Payrolls]
      operationId: bulkStorePayrolls
      summary: Bulk import payrolls
      description: |
        Validates and creates a list of payroll records, then queues each one for
        NFT minting. Employees are matched by `national_id`; if not found, the
        employee is created automatically (this requires `name`+`surname` or
        `full_name`). Per-record results are returned in the `created` and `failed`
        arrays; a failure on one record does not stop the others.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BulkPayrollRequest"
            examples:
              singleItem:
                summary: A single payroll
                value:
                  external_batch_ref: ACME-2026-06
                  items:
                    - employee:
                        national_id: "11111111111"
                        name: Jane
                        surname: Doe
                        employee_code: EMP-001
                        position: Software Engineer
                        department: Engineering
                        wallet_address: "0x1111111111111111111111111111111111111111"
                      payroll:
                        period_start: "2026-06-01"
                        period_end: "2026-06-30"
                        payment_date: "2026-07-05"
                        currency: TRY
                        gross_salary: 95000
                        net_salary: 71250
                        bonus: 5000
                        deductions_total: 23750
                        payroll_document_no: PR-2026-06-001
                        employer_sign_name: John Smith
                        employer_sign_title: HR Director
                        external_ref: PR-ACME-001
      responses:
        "200":
          description: Import completed (may be partially successful)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BulkPayrollResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"

  /payrolls/batches/{externalBatchRef}:
    get:
      tags: [Payrolls]
      operationId: getBatchStatus
      summary: Get batch status
      description: |
        Returns the mint status of every payroll under an `external_batch_ref`. Each
        record's blockchain transaction hash (`tx_hash`), NFT `token_id`, and
        `ipfs_cid` are filled in as minting completes.
      parameters:
        - name: externalBatchRef
          in: path
          required: true
          description: The batch reference you provided (or the one the system generated) during import
          schema:
            type: string
          example: ACME-2026-06
      responses:
        "200":
          description: Batch status
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/BatchStatusResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /payrolls/review-batches:
    post:
      tags: [Review Batches]
      operationId: createReviewBatch
      summary: Create review batch (preview)
      description: |
        Validates the records and creates a preview (`review`) batch — payrolls are
        **not created yet**. Each record's status is returned as `ready`, `duplicate`,
        or `failed`. After reviewing the result, persist them with the `submit`
        endpoint.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/BulkPayrollRequest"
      responses:
        "201":
          description: Review batch created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReviewRun"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"
    get:
      tags: [Review Batches]
      operationId: listReviewBatches
      summary: List review batches
      description: Returns the last 100 review batches for your company (without item details).
      responses:
        "200":
          description: List of review batches
          content:
            application/json:
              schema:
                type: object
                properties:
                  company_id:
                    type: integer
                  items:
                    type: array
                    items:
                      $ref: "#/components/schemas/ReviewRun"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"

  /payrolls/review-batches/{runId}:
    get:
      tags: [Review Batches]
      operationId: getReviewBatch
      summary: Get review batch detail
      description: Returns a single review batch with all record details (`items`).
      parameters:
        - name: runId
          in: path
          required: true
          schema:
            type: integer
          example: 4821
      responses:
        "200":
          description: Review batch detail
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReviewRun"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"

  /payrolls/review-batches/{runId}/submit:
    post:
      tags: [Review Batches]
      operationId: submitReviewBatch
      summary: Submit a review batch
      description: |
        Converts the records in a previously created review batch into permanent
        payrolls and queues them for minting. `ready` records are created;
        `duplicate` records are skipped. A batch cannot be submitted twice (`409`).
      parameters:
        - name: runId
          in: path
          required: true
          schema:
            type: integer
          example: 4821
      responses:
        "200":
          description: Submit completed
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReviewSubmitResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "409":
          description: Batch already submitted
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "422":
          $ref: "#/components/responses/ValidationError"
        "429":
          $ref: "#/components/responses/RateLimited"

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: "Service API key. The `Authorization: Bearer <key>` format is also accepted."

  schemas:
    Employee:
      type: object
      required: [national_id]
      properties:
        national_id:
          type: string
          description: 11-digit national ID number
          minLength: 11
          maxLength: 11
          example: "11111111111"
        name:
          type: string
          description: First name (for a new employee, required together with surname, or use full_name)
          example: Jane
        surname:
          type: string
          example: Doe
        full_name:
          type: string
          description: Full name as a single field instead of name+surname
          example: Jane Doe
        employee_code:
          type: string
          maxLength: 50
          example: EMP-001
        position:
          type: string
          maxLength: 100
          example: Software Engineer
        department:
          type: string
          maxLength: 100
          example: Engineering
        start_date:
          type: string
          format: date
          example: "2024-01-15"
        status:
          type: string
          enum: [active, inactive]
          default: active
        wallet_address:
          type: string
          description: Sepolia/Ethereum wallet address. If empty, the default mint wallet is used.
          pattern: "^0x[0-9a-fA-F]{40}$"
          example: "0x1111111111111111111111111111111111111111"

    PayrollData:
      type: object
      required: [period_start, period_end, currency, gross_salary, net_salary, payroll_document_no]
      properties:
        period_start:
          type: string
          format: date
          example: "2026-06-01"
        period_end:
          type: string
          format: date
          description: Must be greater than or equal to period_start
          example: "2026-06-30"
        payment_date:
          type: string
          format: date
          example: "2026-07-05"
        currency:
          type: string
          maxLength: 10
          default: TRY
          example: TRY
        gross_salary:
          type: number
          example: 95000
        net_salary:
          type: number
          example: 71250
        bonus:
          type: number
          example: 5000
        deductions_total:
          type: number
          example: 23750
        employer_sign_name:
          type: string
          maxLength: 255
          example: John Smith
        employer_sign_title:
          type: string
          maxLength: 255
          example: HR Director
        payroll_document_no:
          type: string
          maxLength: 100
          example: PR-2026-06-001
        batch_id:
          type: string
          maxLength: 100
        external_ref:
          type: string
          description: |
            Idempotency key. Format: starts with an uppercase letter/digit and may
            contain `._:-` (6-100 characters). Auto-generated if left empty.
          maxLength: 100
          example: PR-ACME-001

    PayrollItem:
      type: object
      required: [employee, payroll]
      properties:
        employee:
          $ref: "#/components/schemas/Employee"
        payroll:
          $ref: "#/components/schemas/PayrollData"

    BulkPayrollRequest:
      type: object
      required: [items]
      properties:
        payroll_group_id:
          type: string
          format: uuid
          description: Optional group ID. A UUID is generated if omitted.
        external_batch_ref:
          type: string
          description: Reference to query the batch later. Auto-generated if omitted.
          example: ACME-2026-06
        items:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/PayrollItem"

    BulkPayrollResponse:
      type: object
      properties:
        bulk_operation_run_id:
          type: integer
        company_id:
          type: integer
        payroll_group_id:
          type: string
          format: uuid
        external_batch_ref:
          type: string
        created_count:
          type: integer
        created_employees_count:
          type: integer
        created_employees:
          type: array
          items:
            type: object
            properties:
              id: { type: integer }
              national_id: { type: string }
              name: { type: string }
              surname: { type: string }
        failed_count:
          type: integer
        created:
          type: array
          description: Created payroll records
          items:
            type: object
            additionalProperties: true
        failed:
          type: array
          items:
            type: object
            properties:
              index: { type: integer }
              errors:
                type: array
                items: { type: string }
              data:
                type: object
                additionalProperties: true

    BatchStatusResponse:
      type: object
      properties:
        company_id:
          type: integer
        external_batch_ref:
          type: string
        summary:
          type: object
          properties:
            total: { type: integer }
            pending: { type: integer }
            queued: { type: integer }
            sent: { type: integer }
            failed: { type: integer }
        items:
          type: array
          items:
            type: object
            properties:
              id: { type: integer }
              employee_id: { type: integer }
              external_ref: { type: string }
              status:
                type: string
                description: Mint status
                enum: [pending, queued, sent, failed]
              tx_hash:
                type: [string, "null"]
                description: Sepolia transaction hash (filled once minting completes)
              token_id:
                type: [string, "null"]
              ipfs_cid:
                type: [string, "null"]

    ReviewItem:
      type: object
      properties:
        index: { type: integer }
        status:
          type: string
          enum: [ready, duplicate, failed]
        reason:
          type: [string, "null"]
        external_ref:
          type: string
        employee:
          type: object
          properties:
            national_id: { type: string }
            full_name: { type: string }
            employee_code: { type: [string, "null"] }
        payroll:
          type: object
          properties:
            period_start: { type: string, format: date }
            period_end: { type: string, format: date }
            payment_date: { type: [string, "null"], format: date }
            currency: { type: string }
            gross_salary: { type: number }
            net_salary: { type: number }
            payroll_document_no: { type: string }
        submit_status:
          type: string
          description: Filled only after submit
          enum: [success, failed, skipped]

    ReviewRun:
      type: object
      properties:
        id: { type: integer }
        company_id: { type: integer }
        type: { type: string, example: service_payroll_review }
        name: { type: string }
        status:
          type: string
          example: completed
        stage:
          type: string
          enum: [review_pending, submitted]
        payroll_group_id: { type: [string, "null"] }
        external_batch_ref: { type: [string, "null"] }
        submitted_at: { type: [string, "null"], format: date-time }
        total_items: { type: integer }
        processed_items: { type: integer }
        failed_items: { type: integer }
        summary:
          type: object
          properties:
            total: { type: integer }
            ready: { type: integer }
            duplicate: { type: integer }
            failed: { type: integer }
            submitted_success: { type: integer }
            submitted_failed: { type: integer }
            submitted_skipped: { type: integer }
        started_at: { type: [string, "null"], format: date-time }
        finished_at: { type: [string, "null"], format: date-time }
        created_at: { type: [string, "null"], format: date-time }
        items:
          type: array
          description: Returned only by the detail endpoint
          items:
            $ref: "#/components/schemas/ReviewItem"

    ReviewSubmitResponse:
      type: object
      properties:
        message: { type: string }
        created_count: { type: integer }
        failed_count: { type: integer }
        created_employees_count: { type: integer }
        created_employees:
          type: array
          items:
            type: object
            properties:
              id: { type: integer }
              national_id: { type: string }
              name: { type: string }
              surname: { type: string }
        run:
          $ref: "#/components/schemas/ReviewRun"

    Error:
      type: object
      properties:
        message:
          type: string
          example: Invalid service API key.

  responses:
    Unauthorized:
      description: API key missing or invalid
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            message: Invalid service API key.
    NotFound:
      description: Record not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            message: Batch not found.
    ValidationError:
      description: Invalid request body
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            message: The items field is required and must be an array.
    RateLimited:
      description: Rate limit exceeded (60 requests per minute)
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            message: Too Many Attempts.
