Docs

Changelog

Every change to the Agoo API, webhooks and developer tools, newest first.

Preview· P9For developers

This is a published preview. Names and fields may still change before general availability; changes will be listed in the changelog.

This page records every change to the developer platform: the REST API, webhooks, OAuth and the developer tools. The newest entry is at the top.

2026-10-20 — People and HR: documents

Added

  • HR documents (hrdoc_…): GET /people/{person_id}/documents (the latest version of each, or every version with versions=true), POST /people/{person_id}/documents (the file as the request body, PDF, JPEG, PNG or WebP up to 20 MB, with type_id, title, issued_on, expires_on, notes, replaces and file_name in the query; send an Idempotency-Key), GET /hr-documents/{document_id} and GET /hr-documents/{document_id}/file. Standard documents only: identity, disciplinary and health documents never leave Agoo through the API. A new document is checking until its virus check passes; its file can't be downloaded until then.
  • Scopes: documents:read and documents:write.
  • Event: hr_document.expiring, at each of the document type's reminder steps, with days_left.
  • A document type, title or file name that names a Ghana Card is refused with validation_failed (L.I. 2523).

Nothing changes for existing integrations.

How changes are announced

  • Every change is listed here, with its date and what you need to do, if anything.
  • During the preview, names and fields may still change before general availability. Changes that could break an integration are marked Breaking and explained, with the reason and how to update your code.
  • After general availability, /v1 only changes in backwards-compatible ways: new endpoints, new optional fields, new enum values and new event types can appear at any time, so ignore anything you don't recognise. Anything that would break existing code waits for a new major version, which runs alongside the old one for at least 12 months. See versioning.
  • Deprecations are announced here before anything is removed, with the date it stops working.

Each entry uses these labels:

LabelMeans
AddedSomething new. Nothing you need to do.
ChangedExisting behaviour changed in a backwards-compatible way.
DeprecatedStill works, but will be removed. The entry says when and what to use instead.
RemovedNo longer available.
BreakingPreview only: a change existing code may need to adapt to.
FixedThe behaviour now matches the documentation.

2026-10-20 — ID documents and Ghana Cards (L.I. 2523)

Ghana's National Identity Register (Amendment) Regulations, 2026 (L.I. 2523) forbid relying on a photocopy, scan or image of a Ghana Card to check who someone is. Agoo records a Ghana Card's number and never keeps an image of one.

Breaking

  • No image with a Ghana Card. An id_scan answer whose document_type is ghana_card, or whose number has the Ghana Card's form (GHA-123456789-0, however it's spaced or hyphenated, in any case), is refused with validation_failed at body.custom_fields.<key>.image if it carries an image, on POST /visits and PATCH /visits/{visit_id}. Send the number alone. A number in the Ghana Card's form is recorded as a Ghana Card whatever document_type says.
  • ID document answers are always protected. id_scan fields are always sensitive and masked, so custom_fields leaves their answers out when you call with an API key or as a connected app, and events and webhooks never include them. You can still write them. Before, an API key could read back document_type, last4 and image.
  • ID images can't be opened with an API key or by a connected app. They open only for Owners, Admins and Security leads in Agoo's own apps. You can still upload one (kind id_image) for a passport or another ID.

Added

  • id_document_type on POST /files for an id_image: the document the photo shows (passport, drivers_licence, other). ghana_card is refused with validation_failed at body.id_document_type, so a photo of a Ghana Card is turned away before anything is uploaded.

Changed

  • The kiosk and the apps never photograph a Ghana Card: they read its number on the device and keep no picture, or have it typed. Passports and other IDs follow the organisation's choice of scan, photo or typed.
  • ID images are kept for 1 day by default and 30 days at most.

2026-10-20 — People and HR: departments, positions, HR profiles, employment history and job changes

Added

  • Departments (dept_…): GET /departments (with archived=true for archived ones too), GET /departments/{department_id}, POST /departments and PATCH /departments/{department_id} (rename, move under another, set the head, code or cost centre, or archive one nobody is in). Reading needs people:read, writing hr:write. A person's department string follows its department's name.
  • Positions (pos_…): GET /positions (filter by department_id or status, vacant for the vacancies), GET /positions/{position_id}, POST /positions and PATCH /positions/{position_id}. Your organisation's plan must include positions (Growth and up); otherwise writes are refused with plan_required.
  • HR profiles: GET /people/{person_id}/hr-profile and PATCH /people/{person_id}/hr-profile with the new scopes hr:read and hr:write. Statutory numbers come by kind and last four only; full numbers, pay accounts, contacts, marital status and a declared disability never leave Agoo through the API.
  • Employment history (jobh_…): GET /people/{person_id}/employment-history, newest first by the date each change took effect, paged with limit and cursor.
  • Job changes (pchg_…): GET /people/{person_id}/job-changes and POST /people/{person_id}/job-changes to change a job from a date: applied at once when it's today or earlier, otherwise on its date. Send an Idempotency-Key.
  • Events: department.created, department.updated, position.created, position.updated and person.job_changed.

Nothing changes for existing integrations.

Added

  • How a booking type's video meeting is made: video_provider on booking types: link (the type's video_url, else the host's own link, as before), google_meet, teams or zoom. Set it when you create or update a booking type. The three providers need Starter or above; on Free they're refused with validation_failed on video_provider. Existing booking types are link.
  • The meeting on each booking: meeting_provider (google_meet, teams, zoom, or null for a pasted link or the host's own) and meeting_state (none, pending, ready or failed). Agoo makes the meeting through the host's connected account when a video booking is confirmed; meeting_url is null while it's pending. A host who hasn't connected the provider gets their own link, with meeting_provider null.

Nothing changes for existing integrations: both booking fields are new, and booking events carry them. No event announces a link that's made after a booking event was sent yet; get the booking to read it.

2026-10-16 — The deliveries log: richer deliveries, corrections, sending back and two events

Added

  • Who a delivery is for: recipient_type (person, team or front_desk) and team_id. A delivery for a team tells every member, and any of them can collect it; one for the front desk tells nobody. recipient_id is null unless it's for a person. GET /deliveries takes team_id too.
  • More about each delivery: for (who the label or courier said it's for, in words), kind (parcel, envelope, food or other), size (small, medium, large or null), location (where reception keeps it), rider_name and rider_phone, source (api, console, workspace, kiosk, web_kiosk or drop_off), notified_at and reminders_sent, collection_method (code, signature, staff or api), collected_by (a person ID, when it's known) and handed_over_by, returned_at and return_reason, and its photos, label_file_id and photo_file_ids (up to four).
  • Statuses: notified (waiting, and the recipient has been told), returned (sent back to the sender) and refused (turned away at the door), beside received and collected. GET /deliveries?status=waiting lists received and notified together.
  • Get, correct and send back: GET /deliveries/{delivery_id} returns one delivery. PATCH /deliveries/{delivery_id} corrects a waiting one (who it's for, where it's kept, what it is); a new recipient gets a new collection code and is told unless notify_recipient is false. POST /deliveries/{delivery_id}/return records it as sent back, or with refused: true as turned away, with an optional reason. Both return the delivery and take an Idempotency-Key; a delivery that's no longer waiting returns invalid_state.
  • Collecting: POST /deliveries/{delivery_id}/collect takes method (api by default; code with the recipient's six-digit code, signature with the strokes of a drawn signature, or staff with a note saying why), collected_by (a person ID) and collected_by_name, as your organisation's rule for collecting allows. A wrong code returns validation_failed.
  • Creating: POST /deliveries takes all of the above. Leave out recipient_id and team_id and the delivery goes to your organisation's default recipient. Upload photos first with POST /files, kind delivery_label (new: the label) or delivery_photo (the parcel). With notify_recipient (the default) the recipient is told with their collection code and reminded until it's collected, as your organisation's settings say.
  • Two events: delivery.updated (a waiting delivery corrected) and delivery.returned (sent back or refused), with the delivery as data.object. Endpoints subscribed to * start receiving them the first time a parcel is corrected or sent back; ignore them if you don't need them.

Changed

  • delivery.received and delivery.collected carry the richer delivery. A delivery logged for a person is usually notified by the time you fetch it.

The collection code itself is never in the API or an event: only the person or team a parcel is for ever sees it. The deliveries endpoints are on Pro and Enterprise in live mode, and every plan in test mode, as before; webhooks are on Growth and above. Nothing is removed or renamed, so the API stays at /v1.

Nothing you need to do: ignore fields, statuses and events you don't recognise. See webhook events and the API reference.

2026-10-15 — Agoo Reviews in the API, and review webhooks

Added

  • Three scopes: reviews:read (your page's reviews, scores and review invitations on Agoo Reviews), reviews:write (reply to reviews and flag them for a moderator) and reviews:invite (invite customers to review and make after-sale review links). They match the Agoo Reviews permissions in the Console; existing keys don't have them.
  • Reviews (review_…): GET /reviews lists your page's published reviews, newest first, filtered by place_id, stars, label, published_after and unanswered; GET /reviews/{review_id} gets one. Each has its place, status, stars, title, body, visit_month, language, label, proof_seen, reviewer_name, your reply, its url, published_at and edited_at. A review never carries the reviewer's contact details.
  • Scores: GET /review-scores returns your page's score and every branch's, with how many reviews each counts, whether the score is shown yet (from three reviews), and how the stars and labels are spread.
  • Reply and flag: POST /reviews/{review_id}/reply with a body publishes your reply, or changes it, after the same checks as reviews. POST /reviews/{review_id}/flag with a reason (one of the published reasons) and an optional note returns 201 with the flag; a flag never hides a review.
  • Review invitations (rinv_…): GET /review-invitations lists your invitations with their state (never which review came of one); POST /review-invitations invites up to 500 customers (name, email or phone) for a branch (place_id), with declared: true to confirm you invite customers whatever their experience, and returns the batch with how many were invited and why any were left out; send an Idempotency-Key. POST /review-invitations/links with place_id and count makes up to 500 single-use after-sale links, valid 30 days. Both return 201.
  • Four events: review.published, review.updated, review.removed and review.replied, with the review as data.object. Endpoints subscribed to * start receiving them when your page gets its first review after this change; ignore them if you don't need them.

All eight operations come with Reviews Business: Growth, Pro and Enterprise in live mode, every plan in test mode; an organisation without it gets plan_required. There is no way to hide, edit, reorder or delete a review through the API. Nothing is removed or renamed, so the API stays at /v1.

Nothing you need to do. See webhook events and authentication.

2026-10-15 — Payroll exports in the API, and the payroll export webhook

Added

  • The scope attendance:payroll: payroll exports: make them and download their files, with every kind of leave by its pay code. Payroll files name private kinds of leave, such as sick and maternity leave, which every other scope shows as away, so they have a scope of their own. It matches the payroll permission Owners, Admins and HR hold in the Console; a key or app needs it granted explicitly, and existing keys don't have it.
  • Payroll exports (payroll_export_…), a new resource. GET /payroll-exports lists your organisation's payroll exports, newest first, filtered by period_id; GET /payroll-exports/{payroll_export_id} gets one. Each has its number, its period (id, starts_on, ends_on, payroll_on), site_ids (null for the whole organisation), format (csv or xlsx), layout (long, wide or custom), how many people, lines and corrections it holds, superseded (how many of its people a later file has corrected), its file (name, content_type, byte_size, sha256), created_by (a person ID, or null when your system made it), created_via (person or api) and created_at.
  • Make a payroll export: POST /payroll-exports with period_id, and optionally site_ids (up to 200), format (csv by default, or xlsx), layout (long by default, or wide) and a note (up to 300 characters); send an Idempotency-Key. It returns 201 with the export. Only approved, locked timesheets go in; people whose figures changed since an earlier file for the same period get correction lines, and people with nothing new aren't repeated. It returns invalid_state while the period is still running, while a leave type in it has no pay code or two pay codes clash, and when nothing has changed since the last file for the period.
  • Download the file: GET /payroll-exports/{payroll_export_id}/file returns the file as it was made, matching its sha256, or, with format or layout, a copy in another format or layout made from the same lines.
  • The event payroll_export.created, with the payroll export as data.object, whenever a payroll file is made, in the Console or through the API. It carries the export's details, never its lines. Endpoints subscribed to * start receiving it when your organisation makes its first payroll file; ignore it if you don't need it.

All four operations need attendance:payroll and the payroll export, which is on Pro and Enterprise: an organisation on another plan gets plan_required, in test mode too. The webhook is on Growth and above, so it fires only for organisations whose plan has the payroll export. Pay codes and your own file layouts are set in the Console. Nothing is removed or renamed, so the API stays at /v1.

Nothing you need to do. See webhook events and HR and payroll.

2026-10-15 — Timesheets in the API, and timesheet webhooks

Added

  • Timesheet periods: GET /timesheet-periods lists your organisation's periods (tsp_…), filtered by from and to, each with its dates, kind (weekly, fortnightly, monthly), label (such as "September 2026"), payroll_on, status (open, closed, approved) and how many of its timesheets are open, to confirm, to approve and approved; GET /timesheet-periods/{period_id} gets one.
  • Timesheets: GET /timesheets lists timesheets (ts_…), filtered by period_id, person_id and status (open, to_confirm, to_approve, approved); GET /timesheets/{timesheet_id} gets one with each day of its period. A timesheet carries its period, round, when it was confirmed, submitted and approved, by whom and how (approved_via: person, api or auto), whether it's locked, the last reopening and its reason, changed_after_lock, the period's totals and where its approval stands.
  • Approve and reopen: POST /timesheets/{timesheet_id}/approve finishes every remaining step at once (approved_via: api) and locks the timesheet; a timesheet still waiting for the person to confirm is sent for approval first. One with something left to fix (a missing clock-out, a break not ended, extra time or leave waiting for a decision, a clock-in waiting for review) returns invalid_state, saying what's left. POST /timesheets/{timesheet_id}/reopen takes a reason (3 to 500 characters) and sends it back to its first approval step.
  • The events timesheet.submitted, timesheet.approved and timesheet.reopened, with the timesheet as data.object. Endpoints subscribed to * start receiving them at the end of your organisation's first period; ignore them if you don't need them.

Changed

  • Locked days. Once a person's timesheet is approved, changes to their days in that period are refused with invalid_state until it's reopened: recording, deciding or cancelling leave, and reviewing a punch. Punches you send with POST /attendance/events are never refused: one on a locked day is kept, not counted until the timesheet is reopened, and the timesheet's changed_after_lock becomes true. No new error code.

Reads need attendance:read; approving and reopening need attendance:manage. The API is on Pro and above and the webhooks on Growth and above, in live mode; every plan in test mode. Private kinds of leave appear in totals.leave as away, as everywhere else in the API.

Nothing you need to do. See webhook events.

2026-10-15 — Rosters, swaps and open shifts in the API, and roster webhooks

Added

  • Roster shifts: GET /roster-shifts lists the shifts on your rosters (roster_shift_…), filtered by site, person and dates; GET /roster-shifts/{roster_shift_id} gets one. Each has its site, person (null for an open shift), the day it belongs to (a night shift belongs to the evening it starts), its start and end as UTC instants and as local times at the site, its name, label, break and note.
  • Plan from your own system: POST /roster-shifts adds a shift, for a person or open; PATCH /roster-shifts/{roster_shift_id} changes one (its times, person or break, or makes it open); DELETE /roster-shifts/{roster_shift_id} removes one. Like a manager's changes in the Console, they wait as drafts until the site's roster is published: POST /rosters/publish publishes everything waiting at a site and tells each person once. Publishing is refused with conflict while two of someone's shifts would overlap, and with validation_failed when a change to a shift starting within 48 hours has no note.
  • Swaps: GET /shift-swaps lists swaps (shift_swap_…) between your staff, with the shift, any shift asked for in return, who offered it, who it was offered to, who took it and where its approval stands; GET /shift-swaps/{shift_swap_id} gets one; POST /shift-swaps/{shift_swap_id}/decide (approve or decline, with a note both people see, needed to decline) decides a swap waiting for approval, whichever step it's on. Approving moves the shift at once, and is refused with conflict if the person taking it now has another shift at that time.
  • Claims for open shifts: GET /shift-claims lists claims (shift_claim_…); GET /shift-claims/{shift_claim_id} gets one; POST /shift-claims/{shift_claim_id}/decide decides a waiting claim the same way. Approving one gives the shift to that person and closes the other claims on it as taken.
  • The events roster.published, shift_swap.requested, shift_swap.approved, shift_swap.declined, shift_claim.requested, shift_claim.approved and shift_claim.declined. Endpoints subscribed to * start receiving them once your organisation publishes a roster or someone swaps or claims a shift; ignore them if you don't need them.

Reads need attendance:read; adding, changing, removing, publishing and deciding need attendance:manage. There are no new scopes. Rosters come with Growth and above; the API is on Pro and above and the webhooks on Growth and above, in live mode; every plan in test mode. Roster patterns and rotations stay in the Console for now.

The Labour Act consent warnings for night work and overtime, and the records of consent, are never in the API or webhooks.

Nothing you need to do. See webhook events.

2026-10-14 — Leave in the API, and leave webhooks

Added

  • Leave types: GET /leave-types lists your organisation's leave types (ltype_…), with each one's kind, unit, allowance, whether it's paid, whether it needs approval and whether it's private.
  • Leave requests: GET /leave-requests lists leave (leave_…), filtered by person_id, status (pending, approved, declined, cancelled), type_id, from and to; GET /leave-requests/{leave_id} gets one, with where its approval stands (approval: its status, the step it's on and how many there are).
  • Record leave for someone: POST /leave-requests records leave from your own HR system, approved at once, as HR does in the Console. Agoo counts the days by the person's working days, holidays and closed days, and refuses leave that overlaps theirs or goes beyond their balance.
  • Decide and cancel: POST /leave-requests/{leave_id}/decide (approve or decline, with a note the person sees, needed to decline) decides a waiting request: a decision through the API finishes its approval, whichever step it's on; POST /leave-requests/{leave_id}/cancel cancels a waiting request or approved leave that hasn't started, and the days go back to the balance.
  • Balances: GET /people/{person_id}/leave-balances returns a person's balance for each kind of leave counted by the year (private kinds left out) this leave year: entitled, carried over, taken, adjusted, expired, booked, waiting and what's left.
  • The events leave.requested, leave.approved, leave.declined and leave.cancelled, with the leave request as data.object. Endpoints subscribed to * start receiving them once someone asks for leave; ignore them if you don't need them.

Reads need attendance:read; recording, deciding and cancelling need attendance:manage. The API is on Pro and above and the webhooks on Growth and above, in live mode; every plan in test mode.

Private kinds of leave stay private. For a type your organisation marks private (sick and maternity leave by default), the API and webhooks give kind as away, type_name as "Away" and no type_id, reason or decision_note, so an integration never learns what the leave is, and a person's balances leave private types out. Certificates are never in the API or webhooks.

Nothing you need to do. See webhook events.

2026-10-14 — Leave counted in working days

Changed

  • on_leave in GET /attendance/summary counts someone on leave only on the days that leave covers. Leave counted in working days (annual leave, by default) no longer covers a weekend or another day the person doesn't work, so on such a day they aren't counted as on leave. Calendar-day leave (maternity, by default) still covers every day. Sickness recorded during annual leave counts as leave, as before.

Nothing you need to do. Leave types, balances and requests aren't in the API yet; they come with a later update.

2026-10-14 — Break punches

Added

  • Break punches. Where an organisation switches them on, people punch Start break and End break: AttendanceEvent.kind can be break_start or break_end, with break_type rest or nursing. A break sits inside a clock-in and never ends it.
  • break_punches in GET and PATCH /attendance/settings, and in a site's GET and PATCH /sites/{site_id}/attendance-settings (null follows the organisation), with effective.break_punches. Off by default.
  • POST /attendance/events records break_start and break_end from your terminal where break punches are on at the site, with optional break_type. invalid_state when the person isn't clocked in, is already on a break, or isn't on one to end.
  • include_breaks on GET /attendance/events, and kind=break_start or kind=break_end.
  • The events attendance.break_started and attendance.break_ended. Endpoints subscribed to * start receiving them once someone punches a break; ignore them if you don't need them.

Nothing you need to do: GET /attendance/events keeps returning only clock-ins and clock-outs unless you ask for breaks. Worked time in timesheet exports is net of the unpaid breaks people punch, as it is of the automatic one. See webhook events.

2026-10-14 — A shift's own break

Added

  • break_kind and break_minutes on Shift (GET /shifts). A shift can carry its own break: paid (counted as worked), unpaid (taken off worked time once the day's work reaches your organisation's threshold; a break the person clocks out for replaces it) or none, with its length in minutes. Both are null for a shift that follows your organisation's standard day, which is every shift until you set one.

Nothing you need to do. Worked time in your timesheet exports is now net of unpaid breaks where your organisation runs a day in two parts; on Ghana's defaults (a paid 30-minute break) nothing comes off.

2026-10-14 — Absence after the shift, and attendance.absent

Changed

  • GET /attendance/summary counts someone absent only once their rostered shift has ended with no clock-in, and never on approved leave or a public holiday your organisation observes. Before, it counted them absent once the shift's start plus grace had passed. Absences a manager authorised in the Console still count as absent.

Added

  • not_in_yet in each day's counts and the totals of GET /attendance/summary: rostered, past the shift's start plus grace, the shift still running, and no clock-in yet.
  • The event attendance.absent. Once per person and day, a few minutes after a rostered shift ends with no clock-in: person_id, date, site_id, shift_id, shift_starts_at and shift_ends_at. Endpoints subscribed to * start receiving it; ignore it if you don't need it.

Nothing you need to do, unless your code reads absent during the working day: those people are now in not_in_yet. See webhook events.

2026-10-14 — Punches a manager added or corrected

Added

  • The method manual. A manager can now add a clock-in or clock-out someone missed, or correct a punch's time, in the Console, always with a reason. Those punches have method manual in GET /attendance/events (and as a method filter) and in attendance.clocked_in, attendance.clocked_out and attendance.late, like any other punch, so your payroll sees them.
  • AttendanceEvent.added. On manual punches only: by (the manager's person ID), their reason, and replaces, the punch a correction replaced (null for a missed punch). recorded_at is when it was added; occurred_at is the time it stands for.
  • The event attendance.voided. A punch stopped counting: voided by a manager, rejected after a failed face match, or replaced by a corrected time. Its object carries voided (at, by, reason, replaced_by). A correction is never an edit: you get attendance.voided for the wrong punch and attendance.clocked_in or attendance.clocked_out for the right one. Endpoints subscribed to * start receiving it; ignore it if you don't keep punches.

Nothing you need to do unless your code assumes the methods it knows. See webhook events.

2026-10-14 — Line managers and employment details on people

Added

  • Five fields on Person: manager_id (the ID of the person's line manager, or null), started_on (their first day, a date, or null), left_on (the day they left, a date, or null), employment_type (permanent, temporary, casual or part_time) and contracted_hours (hours a week, from 0.5 to 80 with one decimal, or null). They're always in the response. left_on is set to the day someone is deactivated if it's empty, and cleared if they're reactivated.
  • Setting them. POST /people takes manager_id, started_on, employment_type and contracted_hours; PATCH /people/{person_id} takes those and left_on, and null clears any of them except employment_type (which defaults to permanent).
  • manager_id on GET /people lists only the people whose line manager is that person (their direct reports).

A manager must be an active person in the organisation, and can't be the person themselves or anyone who reports to them, directly or further down: those requests return validation_failed. Nothing you need to do: these are new fields, so existing code keeps working. See Line managers and HR.

2026-10-14 — Kiosks in a web browser

Added

  • Device.platform can be web. An organisation can now set up a front-desk computer as a kiosk in its web browser, beside the Kiosk apps. A site's devices (GET /sites and GET /sites/{site_id}) list it like any paired kiosk, with platform web where a tablet or phone has ios or android; its app_version is the kiosk page's build. Nothing else about the object changes, and there's nothing you need to do unless your code assumes only ios and android. See Set up a kiosk on a computer.

2026-10-13 — Icons on visit types

Added

  • VisitType.icon. GET /visit-types and GET /visit-types/{visit_type} return each type's icon: the key of one icon in Agoo's set for visit types, such as handshake, package or backpack, which an admin picks in Console → Settings → Forms. Publishable keys get it too, so a pre-registration form can show the same tile as the kiosk. Built-in types start with their own icon and added types with user. New keys can be added later, so draw user for one you don't know. See Icons. Nothing you need to do.

2026-10-12 — Visits for several people, a team, or no one in particular

Added

  • A visit can be for more than one person. Visit has audience (person, people, team or desk for no one in particular), hosts (everyone the visit is for, each with a role and a state), team, asked_for, needs_someone and rescheduled_to. POST /visits takes host_ids (the first leads) or team_id instead of host_id; with none of them the visit is for no one in particular.
  • Teams. GET and POST /teams, GET, PATCH and DELETE /teams/{team_id}, POST /teams/{team_id}/members and DELETE /teams/{team_id}/members/{person_id}, with the people:read and people:write scopes.
  • Working on a waiting visit. POST /visits/{visit_id}/hosts and DELETE /visits/{visit_id}/hosts/{person_id}, ask, answer, take, hand-over and reschedule, and needs_someone and team_id as filters on GET /visits.
  • Events visit.needs_someone and visit.host_changed.
  • Person.visitors_via and Person.kiosk_listed. Who receives a person's walk-in visitors for them, and whether visitors can find the person at the kiosk and on your visitor pages. Read only: they are set in Console → People.
  • A visit type's host. ask and offer say what a visitor may answer to "Who are you here to see?", so a form built with a publishable key knows whether to ask and what to send. With a secret key or an OAuth app, answer, wait and receiver say who decides and who receives a visitor with no one to see.

Breaking

  • Visit.host_id can be null. A visit for a team or for no one in particular has no host until someone takes it. If your code reads host_id, handle null, or read hosts and team instead. Visits for one person are unchanged.

2026-10-09 — API keys, webhooks and test mode in the Console, and vehicle plates

Added

  • Keys and endpoints in the Console. Owners and Admins make and revoke API keys in Console → Settings → Developers → API keys, manage webhook endpoints under Webhooks, and read what test mode would have sent in the Test outbox. Test keys work on every plan. See authentication and environments.
  • vehicle_plate on POST /visits, PATCH /visits/{visit_id} and the Visit object, and the file kind vehicle_photo.
  • Person.works_gate (2026-10-08): whether the person signs in on a device in guard mode.

2026-10-07 — Photos, files, signatures and ID scans in custom fields

Added

  • Capture fields can be answered. A visit form's photo, file, signature and ID scan questions now appear in its JSON Schema with x-agoo-type photo, file, signature or id_scan, and POST /visits and PATCH /visits/{visit_id} accept their answers with a secret key. See Custom fields. They're never in a public form.
  • File kinds answer_photo and answer_file on POST /files, for the photo and file answers. A file used by an answer belongs to the visit and is deleted with it.

Changed

  • ID scan answers read back as document_type, last4 and image. The number you send is kept encrypted with the visitor and never returned through the API.

2026-10-06 — Pass rules, multi-day passes and re-entry

Added

  • pass_rules on POST /visits and PATCH /visits/{visit_id}, and pass.rules on the Visit object: daily hours (Starter and above), an entry window, single use and allowed gates (gate_ids, Growth and above). Left out on create, a visit takes its visit type's defaults as your plan allows them. Not with a publishable key. A visit's days, from expected_at to expected_until, can now span up to 92 days. See Passes and pass rules.
  • override_pass_rules on POST /visits/{visit_id}/check-in. A check-in the pass's rules don't allow now is invalid_state with an error at pass_rule.<reason>; the override lets the visitor in and is recorded in the audit trail.
  • POST /visits/{visit_id}/re-enter: a visitor back in on a pass that admits again (another day of a multi-day pass, or back after leaving). It returns a new visit for the entry, with pass.for_visit_id naming the visit that holds the pass, and fires visit.created and visit.checked_in.

Changed

  • Passes are signed. The QR code on a visitor's pass now carries a code signed with the organisation's key, for Agoo's kiosks and guard apps to check offline. Treat it as opaque; nothing in the API reads it.

2026-10-05 — person.reactivated

Added

  • person.reactivated. Sent when a deactivated person is reactivated in the console, with the same Person object as person.deactivated. There are now 30 event types. Endpoints subscribed to * receive it; others can add it.

2026-10-02 — Files and attendance evidence

Added

  • Files. POST /files, GET /files/{file_id} and GET /files/{file_id}/download, with the scopes files:read and files:write. See Files and uploads.
  • Attendance evidence and settings. GET and PATCH /attendance/settings and /sites/{site_id}/attendance-settings, GET /attendance/evidence, GET and POST /people/{person_id}/attendance-evidence, POST /attendance-evidence/{evidence_id}/withdraw and POST /attendance/events/{clock_id}/review, with the new scope attendance:manage. There are now 25 scopes.
  • Error codes consent_required (403) and location_check_failed (422). draft_conflict (409) followed on 2026-10-06.
  • entered_while_offline on a watchlist match, when a kiosk let the visitor in before it could know of the match.

2026-10-02 — Form builder and protected answers

Admins now build forms in Console → Settings → Forms, so the schemas behind custom_fields change when they publish.

Added

  • Screen order. Form schemas (GET /forms, and public_form on visit types and booking types) list their properties in screen order in x-agoo-order. JSON objects don't keep key order, so use it rather than the order of properties.
  • Field annotations. Properties carry x-agoo-type (the builder's field type), and where they apply x-agoo-labels (choice labels by value), x-agoo-unit, x-agoo-consent-text, x-agoo-show-if (asked only after an earlier answer) and x-agoo-sites (asked only at those sites). Conditional requirements are allOf if/then rules.
  • Yes or no questions are boolean properties, like consent. A Person question is a string holding a person ID (person_...).

Changed

  • Protected answers aren't sent to integrations. Answers to fields an admin marked sensitive or masked are left out of custom_fields for API keys and connected apps, and out of every event and webhook. Signed-in people see them only in the roles the admin chose. See Custom fields.

2026-10-01 — Public forms for pre-registration and bookings, and plan history

Changes to the preview contract, made before anything ships.

Breaking

  • Publishable keys pre-register only for open types. POST /visits with a publishable key now needs a type an admin has opened for pre-registration; any other type, including the default meeting when it isn't open, returns validation_failed at body.type. Its custom_fields may contain only the fields asked of the visitor (those in the type's public_form); a field your staff fill in returns validation_failed at body.custom_fields.<key>. See public forms.
  • Publishable keys book with the public intake questions only. POST /bookings with a publishable key accepts only the custom_fields in the booking type's public_form; a field your staff fill in returns validation_failed at body.custom_fields.<key>.

Added

  • VisitTypeObject gains pre_registration (true when an admin has opened the type for pre-registration; off by default) and public_form ({ version, schema }: the type's visit form with only the fields asked of the visitor, or null unless pre_registration is true).
  • Publishable keys can call GET/visit-types and GET/visit-types/{visit_type}. They see only types that are turned on and open for pre-registration; any other type is not_found. GET /forms stays closed to publishable keys. The planned React <PreRegistrationForm> will use this to fetch its types and questions itself.
  • BookingType gains public_form (the same { version, schema } shape): the intake questions asked of the visitor on its visit_type's form, for public booking types; null for private ones. Publishing a booking type makes these questions public, whether or not the visit type is open for pre-registration. The planned React <BookingWidget> will render them.

Changed

  • Plan history in the API. In live mode, your plan's visitor history decides which visits and visitor records the API shows, and its audit-trail period which audit events. Moving to a lower plan never deletes data: older records are kept, hidden, and come back when you move up. Lists leave them out; reading or acting on one by ID returns plan_required, not not_found. Visits that are still expected, awaiting_approval or checked_in are never hidden, and DELETE /visitors/{visitor_id} covers hidden records too. There's no API export of hidden records; an organisation's Owner or Admin can request a full export, including them, from the console on any plan.
  • <PreRegistrationForm> needs host. Publishable keys never list or search people, so the planned React component takes the host from your page and shows a message instead of the form without one. For host search, use the embed script, whose hosted page has it.

2026-10-01 — Custom visit types, booking confirmation and MCP client registration

Changes to the preview contract, made before anything ships. If you generated a client from the first preview, regenerate it.

Breaking

  • Visit types are now per organisation. VisitType is no longer a fixed enum. It's a key: the six built-in keys (meeting, interview, delivery, contractor, event, other) plus the keys of types your organisation adds, such as parent_pickup. Keys are lowercase letters, digits and underscores, start with a letter and are at most 40 characters. This applies to type on visits and the visits filter, visit_type on booking types, forms and the forms filter, and every webhook payload that carries them. Treat them as strings. An unknown key, or a type that is turned off, returns validation_failed when you create a visit or change its type. See custom fields.
  • Bookings can wait for the host. BookingStatus adds pending, declined and expired to confirmed and cancelled. A booking made on a booking type that requires confirmation starts as pending, holds its slot, and has no visit_id until the host confirms it. Cancel and reschedule work only on confirmed bookings. Bookings gain confirm_by, confirmed_at, declined_at and decline_reason.

Added

  • GET/visit-types and GET/visit-types/{visit_type}, scope forms:read, to list your organisation's visit types with their names, whether they're turned on, and their forms.
  • Booking types gain requires_confirmation and confirmation_window_hours (default 24).
  • POST/bookings/{booking_id}/confirm and POST/bookings/{booking_id}/decline, scope bookings:write. A decline reason is shown to the attendee only with share_reason_with_attendee.
  • Events booking.confirmed, booking.declined and booking.expired, for 29 event types in all. booking.created still fires for every booking, with its status.
  • Client ID Metadata Documents for MCP clients, as the MCP authorization specification (revision 2026-07-28) recommends: the client's client_id is an HTTPS URL serving its metadata. The server metadata advertises client_id_metadata_document_supported: true. See authentication.

Changed

  • Dynamic client registration stays available for MCP clients that don't support metadata documents. The MCP specification now marks it deprecated; Agoo keeps it, and any change will be announced here first.
  • Plan access for website embeds, stated precisely: the embed script and the WordPress plugin's blocks work on every plan that has booking pages, because they show hosted pages in a frame. The React components call the API directly, so they need Pro or Enterprise in live mode, and work on every plan in test mode.

2026-10-01 — Preview contract published (v1.0.0-preview)

The first public preview of the Agoo developer platform. This contract lets you design and build integrations now.

Added

  • REST API v1 at https://api.agoohq.com/v1, described in an OpenAPI 3.1 document and in the API reference. Resources: organisation, sites, people, visits (including check-in, check-out, approve, deny, cancel and resend pass), visitors (including erasure under Act 843), deliveries, booking types and availability, bookings, attendance events and summaries, shifts, watchlist, roll calls, webhook endpoints, events, audit events and forms.
  • Authentication with secret keys (agoo_sk_live_…, agoo_sk_test_…) and browser-safe publishable keys (agoo_pk_live_…, agoo_pk_test_…), each with scopes. 22 scopes, from organisation:read to forms:read. See authentication.
  • OAuth 2.1 for third-party apps and MCP clients: authorization code with PKCE (S256), rotating refresh tokens, dynamic client registration, and admin approval per organisation.
  • Test mode on every plan: _test_ keys work on a sandbox copy of your organisation, with messages held in the console's test outbox. See environments.
  • Conventions: TypeID identifiers, RFC 3339 timestamps in UTC, E.164 phone numbers, money in minor units, cursor pagination, RFC 9457 problem details with stable error codes, Idempotency-Key on every POST, per-organisation rate limits with RateLimit- headers, and an Agoo-Request-Id on every response. See concepts.
  • Custom fields on visits and people, defined per organisation as JSON Schema and readable with GET /forms. See custom fields.
  • Webhooks with Standard Webhooks signing, retries for about 27 hours and 26 event types across visits, passes, deliveries, bookings, attendance, the watchlist, roll calls, people and visitors. See webhooks and event types.
  • Plan access: webhooks on Growth; the full API, OAuth apps and MCP on Pro; higher limits, custom scopes and IP allow-lists on Enterprise.
  • Documentation for planned tools, so you can plan integrations with them: the MCP server, the TypeScript SDK, the webhooks helper, React components, the embed script, the CLI, the WordPress plugin and other languages. Each will be announced here when it's published.

On this page

2026-10-20 — People and HR: documentsHow changes are announced2026-10-20 — ID documents and Ghana Cards (L.I. 2523)2026-10-20 — People and HR: departments, positions, HR profiles, employment history and job changes2026-10-19 — Meet, Teams and Zoom links on bookings2026-10-16 — The deliveries log: richer deliveries, corrections, sending back and two events2026-10-15 — Agoo Reviews in the API, and review webhooks2026-10-15 — Payroll exports in the API, and the payroll export webhook2026-10-15 — Timesheets in the API, and timesheet webhooks2026-10-15 — Rosters, swaps and open shifts in the API, and roster webhooks2026-10-14 — Leave in the API, and leave webhooks2026-10-14 — Leave counted in working days2026-10-14 — Break punches2026-10-14 — A shift's own break2026-10-14 — Absence after the shift, and attendance.absent2026-10-14 — Punches a manager added or corrected2026-10-14 — Line managers and employment details on people2026-10-14 — Kiosks in a web browser2026-10-13 — Icons on visit types2026-10-12 — Visits for several people, a team, or no one in particular2026-10-09 — API keys, webhooks and test mode in the Console, and vehicle plates2026-10-07 — Photos, files, signatures and ID scans in custom fields2026-10-06 — Pass rules, multi-day passes and re-entry2026-10-05 — person.reactivated2026-10-02 — Files and attendance evidence2026-10-02 — Form builder and protected answers2026-10-01 — Public forms for pre-registration and bookings, and plan history2026-10-01 — Custom visit types, booking confirmation and MCP client registration2026-10-01 — Preview contract published (v1.0.0-preview)Related