{
  "openapi": "3.1.0",
  "info": {
    "title": "Ahlan Hamad Partner API",
    "version": "1.0.0-draft.1",
    "summary": "Read company, employee and payroll data; push attendance; receive signed webhooks.",
    "description": "The Ahlan Hamad Partner API lets approved partners (accounting, point-of-sale,\nbanking) integrate with a company's HR and payroll data in Ahlan Hamad.\n\n**Status: DRAFT.** This contract is published ahead of the implementation so\npartners can build in parallel. Every operation carries an `x-release` marker:\n\n| Release | Contents |\n|---|---|\n| `v1.0` | Company, employees, payroll runs (read) |\n| `v1.1` | Attendance ingest + read-back |\n| `v1.2` | Webhook endpoints + events |\n\nShapes may still change before each release ships; after it ships, v1 only\nchanges additively (new fields, new optional parameters, new enum values,\nnew event types). Clients must ignore unknown fields and tolerate unknown\nenum values.\n\n**Data protection.** v1 never returns salaries, allowances per employee,\nbank details (IBAN), civil ID / passport numbers, dates of birth, social\ninsurance numbers, or documents. Payroll data is available only as run-level\ntotals and journal lines.\n\n**Conventions**\n- JSON over HTTPS, UTF-8. Field names are `snake_case`.\n- Timestamps are RFC 3339 with an explicit offset (`2026-09-29T08:02:11+04:00`).\n  Timestamps we return are UTC (`Z`).\n- Calendar dates are `YYYY-MM-DD`; payroll months are `YYYY-MM`.\n- Money is a **decimal string** in the currency's minor-unit precision\n  (`\"1250.500\"` KWD/BHD/OMR, `\"1250.50\"` SAR/AED/QAR), always paired with an\n  ISO 4217 `currency`. Never parse money as a float.\n- IDs are opaque strings. Do not infer structure from them.\n",
    "contact": {
      "name": "Ahlan Hamad Partner Support",
      "email": "support@ahlanhamad.com",
      "url": "https://ahlanhamad.com/developers"
    },
    "termsOfService": "https://ahlanhamad.com/developers/terms"
  },
  "servers": [
    {
      "url": "https://actions.ahlanhamad.com",
      "description": "Production (live and sandbox companies)"
    },
    {
      "url": "https://actions.ahlanhamad.dev",
      "description": "Ahlan Hamad internal development — not for partner use"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "tags": [
    {
      "name": "Company"
    },
    {
      "name": "Employees"
    },
    {
      "name": "Payroll"
    },
    {
      "name": "Attendance"
    },
    {
      "name": "Webhooks"
    }
  ],
  "paths": {
    "/v1/company": {
      "get": {
        "tags": [
          "Company"
        ],
        "operationId": "getCompany",
        "summary": "The company this API key belongs to",
        "description": "Available to every valid key; no scope required.",
        "x-release": "v1.0",
        "responses": {
          "200": {
            "description": "The company.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Company"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/employees": {
      "get": {
        "tags": [
          "Employees"
        ],
        "operationId": "listEmployees",
        "summary": "List employees",
        "description": "Returns partner-safe employee records, oldest first. Archived records are\nnever returned. To follow changes, subscribe to the `employee.*` webhooks\n(v1.2) instead of polling; there is no `updated_since` filter in v1.\n",
        "x-release": "v1.0",
        "x-required-scope": "employees:read",
        "security": [
          {
            "apiKey": [
              "employees:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "`active` — currently employed (every status except `left`).\n`left` — employment has ended. Omit for both.\n",
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "left"
              ]
            }
          },
          {
            "name": "entity_id",
            "in": "query",
            "description": "Only employees of this legal entity (see `Company.entities`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of employees.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Employee"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/employees/{employee_id}": {
      "get": {
        "tags": [
          "Employees"
        ],
        "operationId": "getEmployee",
        "summary": "Get one employee",
        "x-release": "v1.0",
        "x-required-scope": "employees:read",
        "security": [
          {
            "apiKey": [
              "employees:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/EmployeeId"
          }
        ],
        "responses": {
          "200": {
            "description": "The employee.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Employee"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/payroll-runs": {
      "get": {
        "tags": [
          "Payroll"
        ],
        "operationId": "listPayrollRuns",
        "summary": "List closed payroll runs",
        "description": "Returns only runs that are `finalized` or `paid`, newest month first.\nDraft runs are never exposed. A company that pays in several countries\nhas one run per country (and legal entity) per month.\n",
        "x-release": "v1.0",
        "x-required-scope": "payroll:read",
        "security": [
          {
            "apiKey": [
              "payroll:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "description": "First payroll month to include (inclusive).",
            "schema": {
              "$ref": "#/components/schemas/Month"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Last payroll month to include (inclusive).",
            "schema": {
              "$ref": "#/components/schemas/Month"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "finalized",
                "paid"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of payroll runs (summary form).",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/X-RateLimit-Limit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/X-RateLimit-Remaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/X-RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PayrollRunSummary"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/payroll-runs/{payroll_run_id}": {
      "get": {
        "tags": [
          "Payroll"
        ],
        "operationId": "getPayrollRun",
        "summary": "Get a closed payroll run with its components and journal",
        "description": "Run totals, a breakdown by pay component (each with the number of\nemployees it applies to), and the double-entry journal Ahlan Hamad\nposted for the run. No per-employee amounts are returned.\n",
        "x-release": "v1.0",
        "x-required-scope": "payroll:read",
        "security": [
          {
            "apiKey": [
              "payroll:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PayrollRunId"
          }
        ],
        "responses": {
          "200": {
            "description": "The payroll run.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PayrollRun"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/attendance/events": {
      "post": {
        "tags": [
          "Attendance"
        ],
        "operationId": "ingestAttendanceEvents",
        "summary": "Push clock events (clock in/out, breaks)",
        "description": "Submit up to 500 events per request. Each event is processed\nindependently and the response lists one result per event, **in the\nsame order** as the request.\n\n**Idempotent on `external_id`** (per company and `source`): resending an\nevent you already sent returns `duplicate` and changes nothing, so it is\nalways safe to retry a request that timed out.\n\n**Employee matching.** `employee_ref` identifies the person. Events whose\nemployee cannot be matched are **stored, not dropped**, with result\n`unmatched_employee`: the company's HR team links the person once in\nAhlan Hamad and every stored event for them is then processed\nautomatically. You do not need to resend.\n\n**Branches.** `branch_ref` is your identifier for the location. An\nunknown branch never rejects an event — it is stored as given and HR can\nmap it later.\n\nClock events are paired into shifts in the company's timezone; a shift\nthat crosses midnight belongs to the day it started. Break time is\nsubtracted from worked time.\n",
        "x-release": "v1.1",
        "x-required-scope": "attendance:write",
        "security": [
          {
            "apiKey": [
              "attendance:write"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "events"
                ],
                "properties": {
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 500,
                    "items": {
                      "$ref": "#/components/schemas/AttendanceEventInput"
                    }
                  }
                }
              },
              "example": {
                "events": [
                  {
                    "external_id": "qompos-sess-8812-in",
                    "employee_ref": {
                      "type": "external_id",
                      "value": "cashier-0042"
                    },
                    "type": "clock_in",
                    "at": "2026-09-29T08:02:11+04:00",
                    "branch_ref": "dubai-marina-01",
                    "source": "qompos"
                  },
                  {
                    "external_id": "qompos-sess-8812-out",
                    "employee_ref": {
                      "type": "external_id",
                      "value": "cashier-0042"
                    },
                    "type": "clock_out",
                    "at": "2026-09-29T16:31:40+04:00",
                    "branch_ref": "dubai-marina-01",
                    "source": "qompos"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "207": {
            "description": "Per-event results, in request order.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "results"
                  ],
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AttendanceEventResult"
                      }
                    }
                  }
                },
                "example": {
                  "results": [
                    {
                      "external_id": "qompos-sess-8812-in",
                      "status": "accepted",
                      "event_id": "ae_7k2…"
                    },
                    {
                      "external_id": "qompos-sess-8812-out",
                      "status": "duplicate",
                      "event_id": "ae_7k3…"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "description": "More than 500 events, or body larger than 1 MB.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/attendance": {
      "get": {
        "tags": [
          "Attendance"
        ],
        "operationId": "listAttendanceDays",
        "summary": "Read back daily attendance",
        "description": "One record per employee per day, as Ahlan Hamad holds it after pairing\nclock events. Includes days recorded by other sources (the employee app,\nmanual HR entries) — check `sources`.\n",
        "x-release": "v1.1",
        "x-required-scope": "attendance:read",
        "security": [
          {
            "apiKey": [
              "attendance:read"
            ]
          }
        ],
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": true,
            "description": "Inclusive. At most 62 days after `from`.",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "employee_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "A page of attendance days.",
            "headers": {
              "X-Request-Id": {
                "$ref": "#/components/headers/X-Request-Id"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "required": [
                        "data"
                      ],
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/AttendanceDay"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookEndpoints",
        "summary": "List webhook endpoints registered by this key",
        "x-release": "v1.2",
        "x-required-scope": "webhooks:manage",
        "security": [
          {
            "apiKey": [
              "webhooks:manage"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "Endpoints owned by the calling key.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookEndpoint"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "createWebhookEndpoint",
        "summary": "Register a webhook endpoint",
        "description": "The response includes the endpoint's signing `secret` **once**. Store it;\nit cannot be retrieved again (rotate by deleting and re-creating).\nThe URL must be `https://` on a public host.\n",
        "x-release": "v1.2",
        "x-required-scope": "webhooks:manage",
        "security": [
          {
            "apiKey": [
              "webhooks:manage"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEndpointInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/WebhookEndpoint"
                    },
                    {
                      "type": "object",
                      "required": [
                        "secret"
                      ],
                      "properties": {
                        "secret": {
                          "type": "string",
                          "description": "Signing secret (`whsec_…`). Shown only in this response."
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      }
    },
    "/v1/webhooks/{webhook_id}": {
      "parameters": [
        {
          "name": "webhook_id",
          "in": "path",
          "required": true,
          "schema": {
            "type": "string"
          }
        }
      ],
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "getWebhookEndpoint",
        "summary": "Get a webhook endpoint",
        "x-release": "v1.2",
        "x-required-scope": "webhooks:manage",
        "security": [
          {
            "apiKey": [
              "webhooks:manage"
            ]
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      },
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "updateWebhookEndpoint",
        "summary": "Change URL, events, or re-enable an endpoint",
        "x-release": "v1.2",
        "x-required-scope": "webhooks:manage",
        "security": [
          {
            "apiKey": [
              "webhooks:manage"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "events": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/WebhookEventType"
                    }
                  },
                  "active": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/ValidationFailed"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "deleteWebhookEndpoint",
        "summary": "Delete a webhook endpoint",
        "x-release": "v1.2",
        "x-required-scope": "webhooks:manage",
        "security": [
          {
            "apiKey": [
              "webhooks:manage"
            ]
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted. Pending deliveries are cancelled."
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/webhooks/{webhook_id}/test": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "sendTestWebhook",
        "summary": "Send a `ping` event to the endpoint now",
        "x-release": "v1.2",
        "x-required-scope": "webhooks:manage",
        "security": [
          {
            "apiKey": [
              "webhooks:manage"
            ]
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Queued for immediate delivery."
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/webhooks/{webhook_id}/deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listWebhookDeliveries",
        "summary": "Recent delivery attempts for an endpoint (self-debugging)",
        "description": "The last 100 deliveries to this endpoint, newest first: what we sent,\nwhen, what your server answered, and whether we will retry. The same\nlog is visible to the company in Ahlan Hamad under Integrations → API\nkeys. Deliveries older than 30 days are removed.\n",
        "x-release": "v1.2",
        "x-required-scope": "webhooks:manage",
        "security": [
          {
            "apiKey": [
              "webhooks:manage"
            ]
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "succeeded",
                "failed",
                "pending"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Up to 100 deliveries.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "maxItems": 100,
                      "items": {
                        "$ref": "#/components/schemas/WebhookDelivery"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/v1/webhooks/{webhook_id}/deliveries/{delivery_id}/redeliver": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "redeliverWebhook",
        "summary": "Send one past event again now",
        "description": "Re-sends the original event (same event `id`, same payload, fresh\nsignature and timestamp) as a new delivery attempt. Works for any\ndelivery in the log, including successful ones, and on an endpoint that\nwas auto-disabled — re-enable it first with `PATCH`.\n",
        "x-release": "v1.2",
        "x-required-scope": "webhooks:manage",
        "security": [
          {
            "apiKey": [
              "webhooks:manage"
            ]
          }
        ],
        "parameters": [
          {
            "name": "webhook_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "delivery_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Queued. The new attempt appears in the deliveries log.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDelivery"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthenticated"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "description": "The endpoint is disabled.",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {
    "employee.created": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "An employee was added",
        "x-release": "v1.2",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "employee.created"
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "employee": {
                            "$ref": "#/components/schemas/Employee"
                          }
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged. Any other status (or no answer within 10 s) is retried."
          }
        }
      }
    },
    "employee.updated": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "A partner-visible field of an employee changed",
        "description": "Sent when any field in the `Employee` schema changes. Delivered at least\nonce and possibly late (a nightly reconciliation catches changes made\nthrough bulk tools); always re-read the embedded object rather than\ndiffing.\n",
        "x-release": "v1.2",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "employee.updated"
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "employee": {
                            "$ref": "#/components/schemas/Employee"
                          }
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "employee.left": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "An employee's employment ended",
        "x-release": "v1.2",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "employee.left"
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "employee": {
                            "$ref": "#/components/schemas/Employee"
                          }
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "payroll_run.finalized": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "A payroll run was closed and its journal posted",
        "x-release": "v1.2",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "payroll_run.finalized"
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "payroll_run": {
                            "$ref": "#/components/schemas/PayrollRunSummary"
                          }
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "payroll_run.paid": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "A payroll run was marked paid and its payment journal posted",
        "x-release": "v1.2",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "payroll_run.paid"
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "payroll_run": {
                            "$ref": "#/components/schemas/PayrollRunSummary"
                          }
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "payroll_run.corrected": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "A closed payroll run's amounts changed",
        "description": "A correction was applied to a run that was already `finalized` or\n`paid`. Totals and journal lines may have changed — re-fetch the run\nwith `GET /v1/payroll-runs/{id}`.\n",
        "x-release": "v1.2",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "payroll_run.corrected"
                      },
                      "data": {
                        "type": "object",
                        "properties": {
                          "payroll_run": {
                            "$ref": "#/components/schemas/PayrollRunSummary"
                          }
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "payroll_run.deleted": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "A finalized payroll run was deleted and its journal reversed",
        "x-release": "v1.2",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "payroll_run.deleted"
                      },
                      "data": {
                        "type": "object",
                        "required": [
                          "payroll_run_id"
                        ],
                        "properties": {
                          "payroll_run_id": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "attendance.rejected": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "A stored clock event could not become attendance",
        "description": "Sent for events that were accepted but later could not be paired into a\nvalid shift (e.g. a clock-out with no clock-in, or a shift over 16 hours),\nand for events still unmatched to an employee after 72 hours.\n",
        "x-release": "v1.2",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "attendance.rejected"
                      },
                      "data": {
                        "type": "object",
                        "required": [
                          "external_id",
                          "reason"
                        ],
                        "properties": {
                          "external_id": {
                            "type": "string"
                          },
                          "event_id": {
                            "type": "string"
                          },
                          "reason": {
                            "type": "string",
                            "enum": [
                              "orphan_clock_out",
                              "orphan_clock_in",
                              "shift_too_long",
                              "overlapping_shift",
                              "unmatched_employee_expired"
                            ]
                          },
                          "detail": {
                            "type": "string"
                          }
                        }
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    },
    "ping": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Test event sent by the \"send test event\" action",
        "x-release": "v1.2",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/WebhookEvent"
                  },
                  {
                    "type": "object",
                    "properties": {
                      "type": {
                        "const": "ping"
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Acknowledged."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ah_live_… | ah_test_…",
        "description": "`Authorization: Bearer <key>`. Keys are created in Ahlan Hamad under\n**Integrations → API keys** by a company user with the\n`integrations.manage` permission (CEO or HR admin by default). A key\nbelongs to exactly one company, carries a fixed set of scopes, and is\nshown once at creation.\n\n- `ah_live_…` keys work only for live companies.\n- `ah_test_…` keys work only for sandbox companies (fake data you can\n  build against safely). Using the wrong kind returns `401`.\n\n**Sandbox, self-serve.** Anyone with an Ahlan Hamad login can press\n**Create sandbox company** (on `/developers` or Integrations → API\nkeys) to get a private sandbox company pre-filled with fake employees\nand one finalized payroll run, then issue `ah_test_` keys for it. No\ncontact with Ahlan Hamad is needed to start building.\n\nScopes: `employees:read`, `payroll:read`, `attendance:read`,\n`attendance:write`, `webhooks:manage`. `GET /v1/company` needs none.\n"
      }
    },
    "parameters": {
      "Limit": {
        "name": "limit",
        "in": "query",
        "description": "Page size.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 50
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "description": "Opaque `next_cursor` from the previous page. Omit for the first page.",
        "schema": {
          "type": "string"
        }
      },
      "EmployeeId": {
        "name": "employee_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      },
      "PayrollRunId": {
        "name": "payroll_run_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      }
    },
    "headers": {
      "X-Request-Id": {
        "description": "Unique ID for this request. Quote it when contacting support.",
        "schema": {
          "type": "string"
        }
      },
      "X-RateLimit-Limit": {
        "description": "Requests allowed per minute for this key (default 600).",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Remaining": {
        "description": "Requests left in the current window.",
        "schema": {
          "type": "integer"
        }
      },
      "X-RateLimit-Reset": {
        "description": "Seconds until the allowance is fully restored.",
        "schema": {
          "type": "integer"
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying.",
        "schema": {
          "type": "integer"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Malformed request (bad query parameter, invalid JSON, unknown cursor).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Unauthenticated": {
        "description": "Missing, malformed, revoked or wrong-environment API key.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://ahlanhamad.com/developers/errors#unauthenticated",
              "title": "Invalid API key",
              "status": 401,
              "code": "unauthenticated",
              "request_id": "req_01J9…"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The key is valid but lacks the required scope (`insufficient_scope`), or\nthe Partner API is not enabled for this company (`api_not_enabled`).\n",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://ahlanhamad.com/developers/errors#insufficient_scope",
              "title": "This key does not have the payroll:read scope",
              "status": 403,
              "code": "insufficient_scope",
              "request_id": "req_01J9…"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such resource in this company (or not visible to partners, e.g. a draft payroll run).",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "ValidationFailed": {
        "description": "The body is well-formed JSON but fails validation.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            },
            "example": {
              "type": "https://ahlanhamad.com/developers/errors#validation_failed",
              "title": "Request body failed validation",
              "status": 422,
              "code": "validation_failed",
              "request_id": "req_01J9…",
              "errors": [
                {
                  "path": "events[3].at",
                  "message": "must include a UTC offset"
                }
              ]
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests for this key.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/Retry-After"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/X-RateLimit-Limit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/X-RateLimit-Remaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/X-RateLimit-Reset"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    },
    "schemas": {
      "Problem": {
        "type": "object",
        "description": "RFC 9457 (formerly 7807) problem details.",
        "required": [
          "type",
          "title",
          "status",
          "code",
          "request_id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable error code. Branch on this, not on `title`.",
            "enum": [
              "invalid_request",
              "unauthenticated",
              "insufficient_scope",
              "api_not_enabled",
              "not_found",
              "conflict",
              "payload_too_large",
              "validation_failed",
              "rate_limited",
              "internal_error"
            ]
          },
          "request_id": {
            "type": "string"
          },
          "errors": {
            "type": "array",
            "description": "Field-level problems (for `validation_failed`).",
            "items": {
              "type": "object",
              "required": [
                "path",
                "message"
              ],
              "properties": {
                "path": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Page": {
        "type": "object",
        "required": [
          "next_cursor"
        ],
        "properties": {
          "next_cursor": {
            "type": [
              "string",
              "null"
            ],
            "description": "Pass as `cursor` to get the next page. `null` on the last page."
          }
        }
      },
      "Month": {
        "type": "string",
        "pattern": "^[0-9]{4}-(0[1-9]|1[0-2])$",
        "examples": [
          "2026-09"
        ]
      },
      "Money": {
        "type": "string",
        "pattern": "^-?[0-9]+(\\.[0-9]{1,3})?$",
        "description": "Decimal string in the currency's minor-unit precision.",
        "examples": [
          "1250.500"
        ]
      },
      "CountryCode": {
        "type": "string",
        "enum": [
          "KW",
          "SA",
          "AE",
          "QA",
          "BH",
          "OM"
        ]
      },
      "CurrencyCode": {
        "type": "string",
        "description": "ISO 4217.",
        "examples": [
          "KWD",
          "SAR",
          "AED",
          "QAR",
          "BHD",
          "OMR"
        ]
      },
      "LocalizedText": {
        "type": "object",
        "required": [
          "en"
        ],
        "properties": {
          "en": {
            "type": "string"
          },
          "ar": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Company": {
        "type": "object",
        "required": [
          "id",
          "name",
          "country",
          "currency",
          "timezone",
          "sandbox",
          "entities"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "$ref": "#/components/schemas/LocalizedText"
          },
          "country": {
            "$ref": "#/components/schemas/CountryCode"
          },
          "currency": {
            "$ref": "#/components/schemas/CurrencyCode"
          },
          "timezone": {
            "type": "string",
            "description": "IANA timezone, e.g. `Asia/Kuwait`."
          },
          "sandbox": {
            "type": "boolean",
            "description": "True for sandbox companies (fake data, `ah_test_` keys)."
          },
          "entities": {
            "type": "array",
            "description": "Legal establishments. A company operating in several GCC countries\nhas one per country; each has its own currency and payroll runs.\n",
            "items": {
              "$ref": "#/components/schemas/Entity"
            }
          }
        }
      },
      "Entity": {
        "type": "object",
        "required": [
          "id",
          "country",
          "name",
          "currency",
          "primary"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "country": {
            "$ref": "#/components/schemas/CountryCode"
          },
          "name": {
            "$ref": "#/components/schemas/LocalizedText"
          },
          "currency": {
            "$ref": "#/components/schemas/CurrencyCode"
          },
          "primary": {
            "type": "boolean"
          }
        }
      },
      "Employee": {
        "type": "object",
        "description": "Partner-safe employee record. Salary, bank, ID-document, date-of-birth,\ninsurance-number and exit-reason data are never included.\n",
        "required": [
          "id",
          "employee_code",
          "name",
          "job_title",
          "department",
          "status",
          "employment_type",
          "country",
          "hire_date"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "employee_code": {
            "type": [
              "string",
              "null"
            ],
            "description": "The company's own staff number, if it uses one."
          },
          "name": {
            "$ref": "#/components/schemas/LocalizedText"
          },
          "job_title": {
            "$ref": "#/components/schemas/LocalizedText"
          },
          "department": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "`notice_period` — resignation or termination recorded, last day not\nyet reached. `left` — employment has ended (the reason is not\nexposed).\n",
            "enum": [
              "active",
              "probation",
              "on_leave",
              "suspended",
              "notice_period",
              "left"
            ]
          },
          "employment_type": {
            "type": "string",
            "enum": [
              "full_time",
              "part_time",
              "contract"
            ]
          },
          "country": {
            "$ref": "#/components/schemas/CountryCode"
          },
          "entity_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "work_location": {
            "description": "The branch / site the employee is assigned to, if any.",
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "required": [
                  "id",
                  "name"
                ],
                "properties": {
                  "id": {
                    "type": "string"
                  },
                  "name": {
                    "$ref": "#/components/schemas/LocalizedText"
                  }
                }
              }
            ]
          },
          "manager_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "description": "Contact email on file (usually the work address)."
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Contact phone on file, E.164 where available."
          },
          "hire_date": {
            "type": "string",
            "format": "date"
          },
          "termination_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "external_ids": {
            "type": "object",
            "description": "Identifiers this person has in partner systems, keyed by partner app\n(e.g. `{\"qompos\": \"cashier-0042\"}`). Only the calling key's own app\nis included. Available from v1.1.\n",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "PayrollRunSummary": {
        "type": "object",
        "required": [
          "id",
          "month",
          "period_start",
          "period_end",
          "status",
          "country",
          "currency",
          "employee_count",
          "totals"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "month": {
            "$ref": "#/components/schemas/Month"
          },
          "period_start": {
            "type": "string",
            "format": "date"
          },
          "period_end": {
            "type": "string",
            "format": "date"
          },
          "pay_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "status": {
            "type": "string",
            "enum": [
              "finalized",
              "paid"
            ]
          },
          "country": {
            "$ref": "#/components/schemas/CountryCode"
          },
          "entity_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "currency": {
            "$ref": "#/components/schemas/CurrencyCode"
          },
          "employee_count": {
            "type": "integer"
          },
          "totals": {
            "type": "object",
            "required": [
              "base_salary",
              "allowances",
              "deductions",
              "net"
            ],
            "properties": {
              "base_salary": {
                "$ref": "#/components/schemas/Money"
              },
              "allowances": {
                "$ref": "#/components/schemas/Money"
              },
              "deductions": {
                "$ref": "#/components/schemas/Money"
              },
              "net": {
                "$ref": "#/components/schemas/Money"
              },
              "employer_social_insurance": {
                "$ref": "#/components/schemas/Money"
              },
              "employer_dews": {
                "$ref": "#/components/schemas/Money",
                "description": "DIFC employee workplace savings (employer share). UAE DIFC only."
              }
            }
          },
          "last_corrected_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Set if a correction changed this run after it was closed."
          }
        }
      },
      "PayrollRun": {
        "allOf": [
          {
            "$ref": "#/components/schemas/PayrollRunSummary"
          },
          {
            "type": "object",
            "required": [
              "components",
              "journal"
            ],
            "properties": {
              "components": {
                "type": "array",
                "description": "Run totals broken down by pay component. Components with a zero total are omitted.",
                "items": {
                  "$ref": "#/components/schemas/PayrollComponent"
                }
              },
              "journal": {
                "type": "array",
                "description": "The journal entries Ahlan Hamad posted for this run: the\n`accrual` leg when the run was finalized, and the `payment` leg\nonce it is marked paid. Each entry balances (sum of debits =\nsum of credits).\n",
                "items": {
                  "$ref": "#/components/schemas/JournalEntry"
                }
              }
            }
          }
        ]
      },
      "PayrollComponent": {
        "type": "object",
        "required": [
          "type",
          "total",
          "employee_count"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "New types may be added; treat unknown values as \"other\".\n`eosb_accrual` and `wps_bank_fees` appear once end-of-service\naccrual and wage-protection fees are included in the payroll journal.\n",
            "enum": [
              "basic_salary",
              "housing_allowance",
              "transport_allowance",
              "social_allowance",
              "other_allowances",
              "overtime",
              "additions",
              "absence_deductions",
              "leave_deductions",
              "loan_deductions",
              "other_deductions",
              "employee_social_insurance",
              "employer_social_insurance",
              "employer_dews",
              "income_tax",
              "eosb_accrual",
              "wps_bank_fees",
              "net_pay"
            ]
          },
          "total": {
            "$ref": "#/components/schemas/Money"
          },
          "employee_count": {
            "type": "integer",
            "description": "Number of employees with a non-zero amount for this component."
          }
        }
      },
      "JournalEntry": {
        "type": "object",
        "required": [
          "id",
          "leg",
          "reference",
          "date",
          "currency",
          "status",
          "lines"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "leg": {
            "type": "string",
            "enum": [
              "accrual",
              "payment"
            ]
          },
          "reference": {
            "type": "string",
            "examples": [
              "PAY-KW-2026-09"
            ]
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "currency": {
            "$ref": "#/components/schemas/CurrencyCode"
          },
          "status": {
            "type": "string",
            "enum": [
              "posted",
              "reversed"
            ]
          },
          "description": {
            "$ref": "#/components/schemas/LocalizedText"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/JournalLine"
            }
          }
        }
      },
      "JournalLine": {
        "type": "object",
        "required": [
          "account_code",
          "account_name",
          "debit",
          "credit"
        ],
        "properties": {
          "account_code": {
            "type": "string",
            "description": "Account code in the company's Ahlan Hamad chart of accounts (e.g. `5100`)."
          },
          "account_name": {
            "$ref": "#/components/schemas/LocalizedText"
          },
          "account_type": {
            "type": "string",
            "enum": [
              "asset",
              "liability",
              "equity",
              "revenue",
              "expense"
            ]
          },
          "component": {
            "type": [
              "string",
              "null"
            ],
            "description": "The `PayrollComponent.type` this line comes from, when it maps to one."
          },
          "debit": {
            "$ref": "#/components/schemas/Money"
          },
          "credit": {
            "$ref": "#/components/schemas/Money"
          },
          "memo": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "EmployeeRef": {
        "type": "object",
        "required": [
          "type",
          "value"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "- `external_id` — your own identifier for the person (recommended).\n  HR links it to the employee once; after that it always matches.\n- `employee_id` — Ahlan Hamad's `Employee.id`.\n- `employee_code` — the company's staff number.\n- `email`, `phone` — matched against the employee's contact details.\n",
            "enum": [
              "external_id",
              "employee_id",
              "employee_code",
              "email",
              "phone"
            ]
          },
          "value": {
            "type": "string"
          }
        }
      },
      "AttendanceEventInput": {
        "type": "object",
        "required": [
          "external_id",
          "employee_ref",
          "type",
          "at",
          "source"
        ],
        "properties": {
          "external_id": {
            "type": "string",
            "maxLength": 128,
            "description": "Your unique ID for this event. Makes the call idempotent."
          },
          "employee_ref": {
            "$ref": "#/components/schemas/EmployeeRef"
          },
          "type": {
            "type": "string",
            "enum": [
              "clock_in",
              "clock_out",
              "break_start",
              "break_end"
            ]
          },
          "at": {
            "type": "string",
            "format": "date-time",
            "description": "When it happened, RFC 3339 **with** offset. Must not be more than 5 minutes in the future or 35 days in the past."
          },
          "branch_ref": {
            "type": "string",
            "maxLength": 128,
            "description": "Your identifier for the location."
          },
          "source": {
            "type": "string",
            "description": "Your system's name, fixed per integration (e.g. `qompos`).",
            "pattern": "^[a-z0-9_-]{2,32}$"
          },
          "meta": {
            "type": "object",
            "description": "Free-form, stored for support (max 2 KB). Never interpreted.",
            "additionalProperties": true
          }
        }
      },
      "AttendanceEventResult": {
        "type": "object",
        "required": [
          "external_id",
          "status"
        ],
        "properties": {
          "external_id": {
            "type": "string"
          },
          "event_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "- `accepted` — stored and matched to an employee.\n- `duplicate` — already received; nothing changed.\n- `unmatched_employee` — stored; will be processed once HR links the person.\n- `rejected` — not stored; see `reason`.\n",
            "enum": [
              "accepted",
              "duplicate",
              "unmatched_employee",
              "rejected"
            ]
          },
          "reason": {
            "type": "string",
            "description": "Present when `status` is `rejected`.",
            "enum": [
              "invalid_timestamp",
              "timestamp_out_of_range",
              "invalid_type",
              "employee_left",
              "period_locked"
            ]
          },
          "detail": {
            "type": "string"
          }
        }
      },
      "AttendanceDay": {
        "type": "object",
        "required": [
          "employee_id",
          "date",
          "status",
          "worked_minutes",
          "break_minutes",
          "sources"
        ],
        "properties": {
          "employee_id": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "status": {
            "type": "string",
            "enum": [
              "present",
              "late",
              "half_day",
              "absent",
              "on_leave",
              "holiday"
            ]
          },
          "first_in": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "last_out": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "worked_minutes": {
            "type": "integer"
          },
          "break_minutes": {
            "type": "integer"
          },
          "sources": {
            "type": "array",
            "description": "Where the day's records came from.",
            "items": {
              "type": "string",
              "examples": [
                "qompos",
                "employee_app",
                "manual"
              ]
            }
          }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "employee.created",
          "employee.updated",
          "employee.left",
          "payroll_run.finalized",
          "payroll_run.paid",
          "payroll_run.corrected",
          "payroll_run.deleted",
          "attendance.rejected"
        ]
      },
      "WebhookEndpointInput": {
        "type": "object",
        "required": [
          "url",
          "events"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "`https://` only; private and loopback addresses are refused."
          },
          "events": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "description": {
            "type": "string",
            "maxLength": 200
          }
        }
      },
      "WebhookEndpoint": {
        "type": "object",
        "required": [
          "id",
          "url",
          "events",
          "active",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "active": {
            "type": "boolean",
            "description": "False after 7 days of failed deliveries, or when the API key that\nowns it is revoked. Re-enable with `PATCH` once fixed.\n"
          },
          "disabled_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "delivery_failures",
              "key_revoked",
              "manual",
              null
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_delivery": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "properties": {
                  "at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "status_code": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "ok": {
                    "type": "boolean"
                  }
                }
              }
            ]
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "required": [
          "id",
          "event_id",
          "event_type",
          "attempt",
          "status",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "event_id": {
            "type": "string"
          },
          "event_type": {
            "type": "string"
          },
          "attempt": {
            "type": "integer",
            "description": "1 for the first try; increments on each retry or redelivery."
          },
          "status": {
            "type": "string",
            "enum": [
              "succeeded",
              "failed",
              "pending"
            ]
          },
          "response_status_code": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Your server's HTTP status, or null if the connection failed or timed out."
          },
          "response_body_excerpt": {
            "type": [
              "string",
              "null"
            ],
            "description": "First 1 KB of your response body."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Our side of a failure (timeout, DNS, TLS, connection refused)."
          },
          "duration_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "next_attempt_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "When we will retry, if we will."
          },
          "request": {
            "type": "object",
            "description": "Exactly what we sent.",
            "required": [
              "headers",
              "body"
            ],
            "properties": {
              "headers": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              },
              "body": {
                "type": "string"
              }
            }
          }
        }
      },
      "WebhookEvent": {
        "type": "object",
        "description": "Envelope for every webhook delivery.\n\n**Verify every delivery.** Each request carries\n`X-AH-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256>` where the HMAC\nis computed with the endpoint's secret over `<t>.<raw request body>`.\nReject if the signature does not match or `t` is more than 5 minutes\nfrom your clock.\n\n**Delivery.** At least once, not necessarily in order. Deduplicate on\n`id`. Respond `2xx` within 10 seconds; otherwise we retry after 1 min,\n5 min, 30 min, 2 h, 12 h and 24 h. After 7 days of failures the\nendpoint is disabled and the company owner is emailed.\n",
        "required": [
          "id",
          "type",
          "created_at",
          "company_id",
          "livemode",
          "data"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique event ID."
          },
          "type": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "company_id": {
            "type": "string"
          },
          "livemode": {
            "type": "boolean",
            "description": "False for sandbox companies."
          },
          "data": {
            "type": "object"
          }
        }
      }
    }
  }
}