payfit
payfit.
The PayFit company the API key belongs to — name, country, registration
setup & usage
log in to PayFit as a company admin, then Integrations → API (app.payfit.com/integrations/hub/api) → "Create a key": give it an explicit label and tick the scopes you need (below), then copy it — it is not shown again afterwards. paste it into your oto connector keys under payfit.
collaborators:read, collaborators:management:read, collaborators:contracts:read, collaborators:personal:read, collaborators:legal-identity:read, contracts:read, contracts:payslips:read, time:read, accounting:read, health-insurance:read, collaborators:meal-vouchers:readcollaborators:social-security:read (NIR), collaborators:bank-info:read (IBAN), payment-files:read (payment file)time:write (record or cancel an absence, sick leave included), collaborators:write (create a collaborator), collaborators:contracts:write (create their contract), health-insurance:write (mutuelle and prévoyance affiliation, regularization). a key without them stays read-only, whatever oto allowsread access level — oto_connector_access_level(op="set", name="payfit", level="read") (scope="group" for a team) — and payfit_act is no longer served, whatever the key carriesa PayFit key opens only one company, and the API has no group view: two companies = two independent keys. create a key in each PayFit company, then set each one as a company of the connector (section "multiple companies").
_account would be the figure of a single company presented as the group'sstart with payfit_company(): its country says whether the French variants apply (FR contracts, meal vouchers, worked time, health insurance), and key_scopes / writable_ops say what the key may do — create_absence is only there if the key carries time:write. read them before a write; a missing scope means a new key, created in PayFit.
payfit_collaborator() (next page: cursor=<next_cursor>)payfit_contract(fr=True) (natureContratDsn: 01 CDI, 02 CDD…; workingTimeModality: forfait_jours…)payfit_absence(begin_date="YYYY-MM-DD", end_date="YYYY-MM-DD"), then link contractId to a collaborator's contractspayfit_payroll(op="accounting", date="202601"): one row per entry, with account, label, debit, credit, employee and analytic codes. this is the only structured numeric data in the API — payroll cost, charges and benefits in kind are read there by account number (641x, 645x, 6417x)payfit_payroll(date="202601") (op status by default) before using any figurespayfit_payroll(op="gross_by_contract", date="202609", date_to="202609"): one row per ACTIVE contract with college (cadre | non_cadre, from the DSN conventional status: 03-04 cadre, 05-07 non-cadre) and gross = debit − credit on the 641x accounts, split in by_account — what payroll booked as remuneration, not the payslip's gross line. never rebuild it from op="accounting" (~30 lines per employee). an "assimilé cadre" (article 36) is not visible through the API. a group of companies = one call per _accountpayfit_payroll(op="accounting_export", date="202601"); "the payment file" → op="payment_file" — documents locked by default, see the note on personal datapayfit_payslip(collaborator_id=…) for the list, then op="download" with the three identifiers of the row: the PDF text comes back readable, the original PDF as raw_url (locked by default, like any document)payfit_payslip(op="overtime", collaborator_id=…, date="202601"): only the overtime / complementary / increased-rate hours lines of the payslip, read server side (the payslip does not go out). the numbers are returned in the line's order, with no role assigned: check the reading against a payslip before totalling, and never add an allegement line (contribution reduction) to the paid amount. for a company, loop over payfit_collaborator()payfit_worked_time(date="202601") · "meal vouchers" → payfit_meal_voucher(date="202601")payfit_insurance() for the company's contracts, kind="provident" for provident cover202601), never 2026-01: it is the only form PayFit acceptsevery write goes through one tool, `payfit_act`, that your MCP client asks you to confirm — the reading tools never write (their old create/cancel/affiliate/regularize ops answer op_moved, naming the payfit_act op). it does what the Partner API allows, nothing more:
payfit_act(op="create_collaborator", first_name=…, last_name=…, personal_email=…) → {collaboratorId}. the collaborator has no contract yet. ⚠️ invite_collaborator=true sends them PayFit's invitation emailpayfit_act(op="create_contract", collaborator_id=…, job_title=…, start_date=…): puts them on payroll; nature, hours and pay are completed in PayFit (the API takes nothing more)payfit_act(op="create_absence", contract_id=…, absence_type=…, begin_date=…, end_date=…) → {id}. ⚠️ created already approved, straight into payroll: there is no request or approval step. sick leave = fr_maladie_ordinaire. no work accident, no maternity or paternity leave in the API's creation list: those are entered in PayFit. op="cancel_absence" (absence_id, optional comment) cancels onepayfit_act(op="set_affiliation", contract_id=…, insurance_contract_ids=[…]) (kind="provident" for prévoyance): ⚠️ replaces the affiliation — a contract left out is unaffiliated. op="request_regularization" (mutuelle only, effective_date) recomputes contributions already paidthese questions come up often and have no endpoint — oto will not fabricate them, and an invented answer would be wrong:
op="overtime" READS from the PDF. to get them as accounting data, isolate them on a dedicated sub-account in PayFit's accounting settings (the API cannot do it): the new account will appear as is in op="accounting"payfit_worked_time)op="overtime") and every amount from the accounting entriespayfit_document) are British; on a French company the list is empty, and that is the right answerthe connector no longer removes anything in a hard-coded way: it serves what the key allows, and protection is an org policy, editable.
absence_type) — sickness, work accident, maternity are health data. a •••• is a masked value, not missing dataabsence_category always stays readable: ordinary_leave (paid leave, RTT, rest, unpaid, remote work, school) or restricted for everything else — enough to plan a workload without reading a reasonoto_org_settings domain=field_filters op=set service=payfit rules=[] unmask=["iban"] (accepted names: those of the server default, under their output name). the org policy is added to the default: without unmask, nothing comes out in clear, and rules: [] alone is refused (floor_lift_must_be_explicit). the response lists the fields now in clear under unmasked. ⚠️ *clearing* the policy (rules: null) restores the server default as isoto_org_settings domain=field_filters op=set service=payfit rules=[] documents=true. this consent lifts no mask from the JSON responses. otherwise the call is refused, saying so, and the refusal is not a bug. if the policy cannot be read, the document is refused too. payfit_payslip(op="overtime") is not locked: it only returns the overtime lines, never the payslip. their numbers (numbers, rates) are filtered by the policy like any data; the raw line (line), a verbatim excerpt of the payslip, follows the documents lock (line_withheld says why otherwise)this connector accepts multiple sociétés: each stored credential becomes a named account (one name per société), at your level, your team's or your org's.
_account="<name>" on the tool; list them: oto_identity(op='list', connector='payfit') (scope='org' or scope='group' for the org's or team's)oto_identity(op='set', connector='payfit', identity_id='<name>'); rename: op='rename' with new_name_account) and adding upoutils
utilisation
claude plugin marketplace add otomata-tech/oto-plugin — mcp + skill configurés.pipx install oto-cli puis oto payfit …