Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ FactorialHR api python wrapper

This package provides a python wrapper to the `api of FactorialHR <https://apidoc.factorialhr.com/docs>`_.

The package currently supports the api version `v2026-01-01 <https://apidoc.factorialhr.com/v2026-01-01/docs/getting-started>`_.
The package currently supports the api version `v2026-04-01 <https://apidoc.factorialhr.com/v2026-04-01/docs/getting-started>`_.
The openapi schema can be found at https://api.factorialhr.com/oas

**I derived some types from the examples given. They might be incorrect. If you encounter any problems, please create an issue and/or contribute a fix.**
Expand Down
39 changes: 39 additions & 0 deletions docs/approvals.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
Approvals
=========

Usage
~~~~~

Approve or reject a resource in a materialized approvals flow::

import asyncio
import factorialhr

async def main():
auth = factorialhr.AccessTokenAuth("your_access_token")
async with factorialhr.ApiClient(auth=auth) as api:
flows = factorialhr.MaterializedApprovalsFlowsEndpoint(api)
result = await flows.approve_resource(
data={"resource_id": 123, "resource_type": "Timeoff::Leave"},
)
print(result.status)
rejected = await flows.reject_resource(
data={
"resource_id": 456,
"resource_type": "Timeoff::Leave",
"reason": "Does not comply with policy",
},
)
print(rejected.status)

asyncio.run(main())

Models and endpoints
~~~~~~~~~~~~~~~~~~~~

.. autoclass:: factorialhr.MaterializedApprovalsFlow
:members:
:exclude-members: model_config

.. autoclass:: factorialhr.MaterializedApprovalsFlowsEndpoint
:members:
32 changes: 32 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,38 @@ All notable changes to factorialhr module will be documented in this file.

The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.0.0>`_

[7.0.0] - 2026-06-07
-------------

Changed
^^^^^^^

- support api version ``2026-04-01``. Checkout full `changelog <https://apidoc.factorialhr.com/changelog/changelog_2026-04-01>`_

Added
^^^^^

- ``MaterializedApprovalsFlow``, ``MaterializedApprovalsFlowsEndpoint`` for approvals (``approve_resource``, ``reject_resource``)
- ``BudgetStrategy``, ``BudgetStrategiesEndpoint``, ``BudgetStrategyType`` for project management budget strategies
- ``default_work_area_id`` field to ``Employee`` model
- ``contract_end_date`` field to ``EmployeeTermination`` model
- ``trip_name`` field to ``PerDiem`` model
- ``total``, ``accrued_incidences``, ``available_incidences`` fields to ``AllowanceStatsNew`` model
- ``completed_duration`` field to ``SessionAttendance`` model
- ``gross_cost``, ``net_cost``, ``currency``, ``created_at``, ``payment_status`` fields to ``TrainingClass`` model
- ``TrainingClassPaymentStatus`` enum
- ``de_base_salary_type_id`` field to ``ContractVersion`` and ``ContractVersionHistory`` models

Removed
^^^^^^^

- ``job_catalog_level_id`` field from ``ContractVersion`` model

Note
^^^^

- New query parameters (e.g. ``updated_at_gteq``, ``updated_at_lteq``, ``employee_id``, ``due_date``) and request body fields (e.g. ``source``, ``qr_code``, ``skip_qr``, ``system_created``, ``project_id``, ``send_calendar_invites``, ``completed_duration``, ``payment_status``, ``training_id``, ``all``) are supported by passing them via ``params`` or the request ``data`` to the existing endpoint methods.

[6.0.0] - 2026-02-20
-------------

Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

authentication
api_public
approvals
ats
attendance
banking
Expand Down
12 changes: 12 additions & 0 deletions docs/project_management.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ Enums
:undoc-members:
:show-inheritance:

.. autoclass:: factorialhr.BudgetStrategyType
:members:
:undoc-members:
:show-inheritance:

.. autoclass:: factorialhr.ProjectEmployeeAssignment
:members:
:undoc-members:
Expand Down Expand Up @@ -84,6 +89,13 @@ Models and endpoints
.. autoclass:: factorialhr.PlannedRecordsEndpoint
:members:

.. autoclass:: factorialhr.BudgetStrategy
:members:
:exclude-members: model_config

.. autoclass:: factorialhr.BudgetStrategiesEndpoint
:members:

.. autoclass:: factorialhr.Project
:members:
:exclude-members: model_config
Expand Down
14 changes: 14 additions & 0 deletions src/factorialhr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
WebhookSubscription,
WebhookSubscriptionEndpoint,
)
from factorialhr._approvals import (
MaterializedApprovalsFlow,
MaterializedApprovalsFlowsEndpoint,
)
from factorialhr._ats import (
Answer,
AnswersEndpoint,
Expand Down Expand Up @@ -287,6 +291,9 @@
PurchaseRequestStatus,
)
from factorialhr._project_management import (
BudgetStrategiesEndpoint,
BudgetStrategy,
BudgetStrategyType,
ExpenseRecord,
ExpenseRecordEndpoint,
ExportableExpense,
Expand Down Expand Up @@ -349,6 +356,7 @@
TrainingCategory,
TrainingClass,
TrainingClassesEndpoint,
TrainingClassPaymentStatus,
TrainingMembership,
TrainingMembershipsEndpoint,
TrainingsEndpoint,
Expand Down Expand Up @@ -400,6 +408,9 @@
'BreakConfigurationsEndpoint',
'BudgetOption',
'BudgetOptionsEndpoint',
'BudgetStrategiesEndpoint',
'BudgetStrategy',
'BudgetStrategyType',
'Candidate',
'CandidateSource',
'CandidateSourceCategory',
Expand Down Expand Up @@ -536,6 +547,8 @@
'Location',
'LocationType',
'LocationsEndpoint',
'MaterializedApprovalsFlow',
'MaterializedApprovalsFlowsEndpoint',
'Membership',
'MembershipsEndpoint',
'Message',
Expand Down Expand Up @@ -685,6 +698,7 @@
'TrainingCategoriesEndpoint',
'TrainingCategory',
'TrainingClass',
'TrainingClassPaymentStatus',
'TrainingClassesEndpoint',
'TrainingMembership',
'TrainingMembershipsEndpoint',
Expand Down
16 changes: 8 additions & 8 deletions src/factorialhr/_api_public.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class CredentialsEndpoint(Endpoint):
async def all(self, **kwargs) -> ListApiResponse[Credentials]:
"""Get all credentials.

Official documentation: `api_public/credentials <https://apidoc.factorialhr.com/reference/get_api-2026-01-01-resources-api-public-credentials>`_
Official documentation: `api_public/credentials <https://apidoc.factorialhr.com/reference/get_api-2026-04-01-resources-api-public-credentials>`_

:return: List of credentials
:rtype: ListApiResponse[Credentials]
Expand All @@ -79,7 +79,7 @@ async def all(self, **kwargs) -> ListApiResponse[Credentials]:
async def get(self, **kwargs) -> MetaApiResponse[Credentials]:
"""Get credentials with pagination metadata.

Official documentation: `api_public/credentials <https://apidoc.factorialhr.com/reference/get_api-2026-01-01-resources-api-public-credentials>`_
Official documentation: `api_public/credentials <https://apidoc.factorialhr.com/reference/get_api-2026-04-01-resources-api-public-credentials>`_

:param kwargs: Optional keyword arguments (e.g. ``params`` for query string) forwarded to the HTTP request.
:type kwargs: optional
Expand Down Expand Up @@ -124,7 +124,7 @@ class WebhookSubscriptionEndpoint(Endpoint):
async def all(self, **kwargs) -> ListApiResponse[WebhookSubscription]:
"""Get all webhooks.

Official documentation: `api_public/webhook_subscriptions <https://apidoc.factorialhr.com/reference/get_api-2026-01-01-resources-api-public-webhook-subscriptions>`_
Official documentation: `api_public/webhook_subscriptions <https://apidoc.factorialhr.com/reference/get_api-2026-04-01-resources-api-public-webhook-subscriptions>`_

:param kwargs: Optional keyword arguments (e.g. ``params`` for query string) forwarded to the HTTP request.
:type kwargs: optional
Expand All @@ -138,7 +138,7 @@ async def all(self, **kwargs) -> ListApiResponse[WebhookSubscription]:
async def get(self, **kwargs) -> MetaApiResponse[WebhookSubscription]:
"""Get webhooks with pagination metadata.

Official documentation: `api_public/webhook_subscriptions <https://apidoc.factorialhr.com/reference/get_api-2026-01-01-resources-api-public-webhook-subscriptions>`_
Official documentation: `api_public/webhook_subscriptions <https://apidoc.factorialhr.com/reference/get_api-2026-04-01-resources-api-public-webhook-subscriptions>`_

:param kwargs: Optional keyword arguments (e.g. ``params`` for query string) forwarded to the HTTP request.
:type kwargs: optional
Expand All @@ -154,7 +154,7 @@ async def get(self, **kwargs) -> MetaApiResponse[WebhookSubscription]:
async def get_by_id(self, webhook_subscription_id: int | str, **kwargs) -> WebhookSubscription:
"""Get a specific webhook by ID.

Official documentation: `api_public/webhook_subscriptions <https://apidoc.factorialhr.com/reference/get_api-2026-01-01-resources-api-public-webhook-subscriptions-id>`_
Official documentation: `api_public/webhook_subscriptions <https://apidoc.factorialhr.com/reference/get_api-2026-04-01-resources-api-public-webhook-subscriptions-id>`_

:param webhook_subscription_id: The unique identifier.
:type webhook_subscription_id: int | str
Expand All @@ -170,7 +170,7 @@ async def get_by_id(self, webhook_subscription_id: int | str, **kwargs) -> Webho
async def create(self, data: Mapping[str, typing.Any], **kwargs) -> WebhookSubscription:
"""Create a new webhook subscription.

Official documentation: `api_public/webhook_subscriptions <https://apidoc.factorialhr.com/reference/post_api-2026-01-01-resources-api-public-webhook-subscriptions>`_
Official documentation: `api_public/webhook_subscriptions <https://apidoc.factorialhr.com/reference/post_api-2026-04-01-resources-api-public-webhook-subscriptions>`_

:param data: Payload for the new record (key-value mapping).
:type data: Mapping[str, typing.Any]
Expand All @@ -191,7 +191,7 @@ async def update(
) -> WebhookSubscription:
"""Update an existing webhook subscription.

Official documentation: `api_public/webhook_subscriptions <https://apidoc.factorialhr.com/reference/put_api-2026-01-01-resources-api-public-webhook-subscriptions-id>`_
Official documentation: `api_public/webhook_subscriptions <https://apidoc.factorialhr.com/reference/put_api-2026-04-01-resources-api-public-webhook-subscriptions-id>`_

:param webhook_subscription_id: The unique identifier of the record to update.
:type webhook_subscription_id: int | str
Expand All @@ -214,7 +214,7 @@ async def update(
async def delete(self, webhook_subscription_id: int | str, **kwargs) -> WebhookSubscription:
"""Delete a webhook subscription.

Official documentation: `api_public/webhook_subscriptions <https://apidoc.factorialhr.com/reference/delete_api-2026-01-01-resources-api-public-webhook-subscriptions-id>`_
Official documentation: `api_public/webhook_subscriptions <https://apidoc.factorialhr.com/reference/delete_api-2026-04-01-resources-api-public-webhook-subscriptions-id>`_

:param webhook_subscription_id: The unique identifier of the record to delete.
:type webhook_subscription_id: int | str
Expand Down
98 changes: 98 additions & 0 deletions src/factorialhr/_approvals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import datetime
import typing
from collections.abc import Mapping, Sequence

import pydantic

from factorialhr._client import Endpoint


class MaterializedApprovalsFlow(pydantic.BaseModel):
"""Model for approvals_materialized_approvals_flow."""

model_config = pydantic.ConfigDict(frozen=True)

#: Materialized approvals flow identifier
id: int = pydantic.Field(description='Materialized approvals flow identifier')
#: Author identifier
author_id: int | None = pydantic.Field(default=None, description='Author identifier')
#: Author employee identifier
author_employee_id: int | None = pydantic.Field(default=None, description='Author employee identifier')
#: Owner identifier
owner_id: int | None = pydantic.Field(default=None, description='Owner identifier')
#: Owner employee identifier
owner_employee_id: int | None = pydantic.Field(default=None, description='Owner employee identifier')
#: Type of the resource (e.g. Timeoff::Leave)
resource_type: str | None = pydantic.Field(default=None, description='Type of the resource (e.g. Timeoff::Leave)')
#: Id of the resource
resource_id: int | None = pydantic.Field(default=None, description='Id of the resource')
#: URL of the resource
resource_url: str | None = pydantic.Field(default=None, description='URL of the resource')
#: Status of the approval flow
status: str | None = pydantic.Field(default=None, description='Status of the approval flow')
#: Expiration timestamp
expires_at: datetime.datetime | None = pydantic.Field(default=None, description='Expiration timestamp')
#: Final decision timestamp
final_decision_at: datetime.datetime | None = pydantic.Field(default=None, description='Final decision timestamp')
#: Approval flow identifier
approval_flow_id: int | None = pydantic.Field(default=None, description='Approval flow identifier')
#: List of approvers
approvers: Sequence[typing.Any] = pydantic.Field(default_factory=list, description='List of approvers')
#: Email detail blocks
email_detail_blocks: Sequence[typing.Any] = pydantic.Field(
default_factory=list,
description='Email detail blocks',
)
#: Override approver identifier
override_approver_id: int | None = pydantic.Field(default=None, description='Override approver identifier')
#: Override approver employee identifier
override_approver_employee_id: int | None = pydantic.Field(
default=None,
description='Override approver employee identifier',
)
#: Rules decision
rules_decision: str | None = pydantic.Field(default=None, description='Rules decision')
#: Auto approval description
auto_approval_description: str | None = pydantic.Field(default=None, description='Auto approval description')
#: Action type
action_type: str | None = pydantic.Field(default=None, description='Action type')
#: Reason for rejection
reason: str | None = pydantic.Field(default=None, description='Reason for rejection')


class MaterializedApprovalsFlowsEndpoint(Endpoint):
"""Endpoint for approvals/materialized_approvals_flows operations."""

endpoint = 'approvals/materialized_approvals_flows'

async def approve_resource(self, data: Mapping[str, typing.Any], **kwargs) -> MaterializedApprovalsFlow:
"""Approve resources in a materialized approvals flow.

Official documentation: `approvals/materialized_approvals_flows <https://apidoc.factorialhr.com/reference/post_api-2026-04-01-resources-approvals-materialized-approvals-flows-approve-resource>`_

:param data: Payload with ``resource_id`` and ``resource_type`` (key-value mapping).
:type data: Mapping[str, typing.Any]
:param kwargs: Optional keyword arguments (e.g. ``params`` for query string) forwarded to the HTTP request.
:type kwargs: optional
:raises httpx.HTTPStatusError: When the API returns an error status code.
:return: The updated approval flow.
:rtype: MaterializedApprovalsFlow
"""
response = await self.api.post(self.endpoint, 'approve_resource', json=data, **kwargs)
return pydantic.TypeAdapter(MaterializedApprovalsFlow).validate_python(response['data'])

async def reject_resource(self, data: Mapping[str, typing.Any], **kwargs) -> MaterializedApprovalsFlow:
"""Reject resources in a materialized approvals flow.

Official documentation: `approvals/materialized_approvals_flows <https://apidoc.factorialhr.com/reference/post_api-2026-04-01-resources-approvals-materialized-approvals-flows-reject-resource>`_

:param data: Payload with ``resource_id``, ``resource_type``, and optional ``reason`` (key-value mapping).
:type data: Mapping[str, typing.Any]
:param kwargs: Optional keyword arguments (e.g. ``params`` for query string) forwarded to the HTTP request.
:type kwargs: optional
:raises httpx.HTTPStatusError: When the API returns an error status code.
:return: The updated approval flow.
:rtype: MaterializedApprovalsFlow
"""
response = await self.api.post(self.endpoint, 'reject_resource', json=data, **kwargs)
return pydantic.TypeAdapter(MaterializedApprovalsFlow).validate_python(response['data'])
Loading
Loading