{
  "info": {
    "_postman_id": "7d3c1f2e-0a4b-4a5b-9c0d-5a17e2a91c01",
    "name": "Ahlan Hamad Partner API",
    "description": "Generated from openapi.yaml (version 1.0.0-draft.1).\n\nSet the `apiKey` variable to an `ah_test_` key from your sandbox company.\n\nDocs: https://ahlanhamad.com/developers/",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://actions.ahlanhamad.com",
      "description": "Partner API host."
    },
    {
      "key": "apiKey",
      "value": "ah_test_",
      "description": "Your API key. Use an ah_test_ key from a sandbox company while building."
    }
  ],
  "item": [
    {
      "name": "Company",
      "item": [
        {
          "name": "The company this API key belongs to",
          "request": {
            "method": "GET",
            "description": "The company this API key belongs to\n\nAvailable to every valid key; no scope required.\n\nStatus: live. Scope: none (any valid key).",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/company",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "company"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Employees",
      "item": [
        {
          "name": "List employees",
          "request": {
            "method": "GET",
            "description": "List employees\n\nReturns 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\nStatus: ships in v1.0. Scope: `employees:read`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/employees",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "employees"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "active",
                  "description": "`active` — currently employed (every status except `left`).\n`left` — employment has ended. Omit for both.",
                  "disabled": true
                },
                {
                  "key": "entity_id",
                  "value": "<entity_id>",
                  "description": "Only employees of this legal entity (see `Company.entities`).",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "Page size.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "<cursor>",
                  "description": "Opaque `next_cursor` from the previous page. Omit for the first page.",
                  "disabled": true
                }
              ]
            }
          }
        },
        {
          "name": "Get one employee",
          "request": {
            "method": "GET",
            "description": "Get one employee\n\nStatus: ships in v1.0. Scope: `employees:read`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/employees/:employee_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "employees",
                ":employee_id"
              ],
              "variable": [
                {
                  "key": "employee_id",
                  "value": ""
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Payroll",
      "item": [
        {
          "name": "List closed payroll runs",
          "request": {
            "method": "GET",
            "description": "List closed payroll runs\n\nReturns 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\nStatus: ships in v1.0. Scope: `payroll:read`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/payroll-runs",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payroll-runs"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "<from>",
                  "description": "First payroll month to include (inclusive).",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "<to>",
                  "description": "Last payroll month to include (inclusive).",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "finalized",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "Page size.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "<cursor>",
                  "description": "Opaque `next_cursor` from the previous page. Omit for the first page.",
                  "disabled": true
                }
              ]
            }
          }
        },
        {
          "name": "Get a closed payroll run with its components and journal",
          "request": {
            "method": "GET",
            "description": "Get a closed payroll run with its components and journal\n\nRun 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\nStatus: ships in v1.0. Scope: `payroll:read`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/payroll-runs/:payroll_run_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payroll-runs",
                ":payroll_run_id"
              ],
              "variable": [
                {
                  "key": "payroll_run_id",
                  "value": ""
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Attendance",
      "item": [
        {
          "name": "Push clock events (clock in/out, breaks)",
          "request": {
            "method": "POST",
            "description": "Push clock events (clock in/out, breaks)\n\nSubmit 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 employee's attendance time\nzone — their work country's, else the company's configured zone; a\nshift that crosses midnight belongs to the day it started. Break time\nis subtracted from worked time.\n\n**Pairing rules.** Events may arrive late or out of order; the day is\nrecomputed from every event you have sent for that person.\n- A second `clock_in` while a shift is already open is treated as a\n  repeat and ignored (the shift keeps the earlier start).\n- A `clock_out` with no open shift is ignored.\n- A `clock_out` more than 16 hours after its `clock_in` does not pair;\n  the shift stays open with no clock-out.\n- Breaks count only inside an open shift; a break still open at\n  `clock_out` ends there.\n\n**Days recorded elsewhere.** If the employee app or the company's HR\nteam already recorded that employee's day, your events are stored and\nanswered `accepted`, but that day is not overwritten.\n\n**Validation.** A request whose body is malformed (an event without\n`external_id`, `employee_ref` or `source`, a `source` that breaks the\npattern, `meta` over 2 KB, …) is refused whole with `422` and nothing is\nstored. Problems with one event's `type` or `at` reject only that event\n(see `AttendanceEventResult.reason`).\n\nStatus: ships in v1.1. Scope: `attendance:write`.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/attendance/events",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "attendance",
                "events"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"events\": [\n    {\n      \"external_id\": \"qompos-sess-8812-in\",\n      \"employee_ref\": {\n        \"type\": \"external_id\",\n        \"value\": \"cashier-0042\"\n      },\n      \"type\": \"clock_in\",\n      \"at\": \"2026-09-29T08:02:11+04:00\",\n      \"branch_ref\": \"dubai-marina-01\",\n      \"source\": \"qompos\"\n    },\n    {\n      \"external_id\": \"qompos-sess-8812-out\",\n      \"employee_ref\": {\n        \"type\": \"external_id\",\n        \"value\": \"cashier-0042\"\n      },\n      \"type\": \"clock_out\",\n      \"at\": \"2026-09-29T16:31:40+04:00\",\n      \"branch_ref\": \"dubai-marina-01\",\n      \"source\": \"qompos\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Read back daily attendance",
          "request": {
            "method": "GET",
            "description": "Read back daily attendance\n\nOne 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\nStatus: ships in v1.1. Scope: `attendance:read`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/attendance?from=<from>&to=<to>",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "attendance"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "<from>"
                },
                {
                  "key": "to",
                  "value": "<to>",
                  "description": "Inclusive. At most 62 days after `from`."
                },
                {
                  "key": "employee_id",
                  "value": "<employee_id>",
                  "description": "Only this employee's days. An ID that is not an employee of this company returns an empty page.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "50",
                  "description": "Page size.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "<cursor>",
                  "description": "Opaque `next_cursor` from the previous page. Omit for the first page.",
                  "disabled": true
                }
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Webhooks",
      "item": [
        {
          "name": "List webhook endpoints registered by this key",
          "request": {
            "method": "GET",
            "description": "List webhook endpoints registered by this key\n\nStatus: ships in v1.2. Scope: `webhooks:manage`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks"
              ]
            }
          }
        },
        {
          "name": "Register a webhook endpoint",
          "request": {
            "method": "POST",
            "description": "Register a webhook endpoint\n\nThe 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\nStatus: ships in v1.2. Scope: `webhooks:manage`.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"url\": \"https://example.com/webhooks/ahlan-hamad\",\n  \"events\": [\n    \"employee.created\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get a webhook endpoint",
          "request": {
            "method": "GET",
            "description": "Get a webhook endpoint\n\nStatus: ships in v1.2. Scope: `webhooks:manage`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:webhook_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":webhook_id"
              ],
              "variable": [
                {
                  "key": "webhook_id",
                  "value": ""
                }
              ]
            }
          }
        },
        {
          "name": "Change URL, events, or re-enable an endpoint",
          "request": {
            "method": "PATCH",
            "description": "Change URL, events, or re-enable an endpoint\n\nStatus: ships in v1.2. Scope: `webhooks:manage`.",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:webhook_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":webhook_id"
              ],
              "variable": [
                {
                  "key": "webhook_id",
                  "value": ""
                }
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete a webhook endpoint",
          "request": {
            "method": "DELETE",
            "description": "Delete a webhook endpoint\n\nStatus: ships in v1.2. Scope: `webhooks:manage`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:webhook_id",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":webhook_id"
              ],
              "variable": [
                {
                  "key": "webhook_id",
                  "value": ""
                }
              ]
            }
          }
        },
        {
          "name": "Send a `ping` event to the endpoint now",
          "request": {
            "method": "POST",
            "description": "Send a `ping` event to the endpoint now\n\nStatus: ships in v1.2. Scope: `webhooks:manage`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:webhook_id/test",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":webhook_id",
                "test"
              ],
              "variable": [
                {
                  "key": "webhook_id",
                  "value": ""
                }
              ]
            }
          }
        },
        {
          "name": "Recent delivery attempts for an endpoint (self-debugging)",
          "request": {
            "method": "GET",
            "description": "Recent delivery attempts for an endpoint (self-debugging)\n\nThe 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\nStatus: ships in v1.2. Scope: `webhooks:manage`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:webhook_id/deliveries",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":webhook_id",
                "deliveries"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "succeeded",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "webhook_id",
                  "value": ""
                }
              ]
            }
          }
        },
        {
          "name": "Send one past event again now",
          "request": {
            "method": "POST",
            "description": "Send one past event again now\n\nRe-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\nStatus: ships in v1.2. Scope: `webhooks:manage`.",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/webhooks/:webhook_id/deliveries/:delivery_id/redeliver",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "webhooks",
                ":webhook_id",
                "deliveries",
                ":delivery_id",
                "redeliver"
              ],
              "variable": [
                {
                  "key": "webhook_id",
                  "value": ""
                },
                {
                  "key": "delivery_id",
                  "value": ""
                }
              ]
            }
          }
        }
      ]
    }
  ]
}