vFound homeStart your 14-day free trial
DEVELOPERS

The vFound API and webhooks

Connect the vFound suite to the systems you already run: property management, service desks, HR and student systems, mailroom scanners, data warehouses or your own apps. Read and change records in every product, keep your people list in sync, get a signed webhook within seconds when something happens, and let AI assistants help at the desk.

Want a guided setup? See the vFound integration guides for Cloudbeds, OfficeRnD Flex, Microsoft Entra, Slack, Microsoft Teams, Google Chat, calendars, Zapier and MCP clients.

Authentication

Every venue has the API. An owner or manager creates up to 10 keys in Settings, For developers, choosing what each key may do. The full key is shown once. Send it as a Bearer token:

curl https://vfound.io/api/v1/items \
  -H "Authorization: Bearer vf_live_xxxxxxxxxx_…"
ScopeAllowsEndpoints
items:readRead found items and categoriesGET /items, /items/{id}, /categories
items:writeAdd and update found itemsPOST /items, PATCH /items/{id}
claims:readRead claims, with the claimant’s contact detailsGET /claims, /claims/{id}
lost_reports:readRead guests’ lost reportsGET /lost-reports
analytics:readRead the analytics summaryGET /analytics
parcels:readRead parcels, with each recipient’s name and emailGET /parcels, /parcels/{id}
parcels:writeLog, hand over, return and remindPOST /parcels and its actions
checkroom:readRead tickets, stations, events, the exit queue and reportsGET under /checkroom
checkroom:writeCheck in, hand back, mark ready and manage stations or eventsPOST and PUT under /checkroom
loans:readRead equipment and who has itGET /loans, /loans/items and one of each
loans:writeAdd equipment, hand it out, extend and take backPOST and PATCH under /loans
visitors:readRead visitsGET /visitors, /visitors/{id}
visitors:writeInvite, sign in and out, cancel, send a passPOST /visitors and its actions
people:readRead the people listGET /people, /people/{id}
people:writeAdd, update, sync and remove peoplePOST, PATCH and DELETE under /people
stays:readRead private hotel stay context and its correction historyGET /stays, /stays/{stay}
stays:writeIdempotently add or update stay context from a hotel systemPOST /stays
webhooks:manageSubscribe and unsubscribe the key’s own webhook addresses, for events whose data the key can readGET, POST and DELETE /webhooks

Items, claims, lost reports and analytics need vFound Lost & Found; parcels need vFound Parcels; tickets need vFound Checkroom; equipment needs vFound Equipment; visits need vFound Visitors; the people list needs Parcels, Visitors or Equipment; hotel stays need Lost & Found or Parcels. A product that is not open for the venue answers 403.

A key acts as the person who created it and stops working if that person no longer manages the venue. Changes made with a key go through the same rules, emails and audit trail as the desk. Each key can make 120 requests a minute. Base address: https://vfound.io/api/v1. Lists return 25 records a page (up to 100 with per_page) and accept page; most also filter with updated_since (ISO 8601) and search, as listed below.

Safe retries

Send an Idempotency-Key header (any unique value up to 191 visible characters, such as a UUID) with a POST, PATCH or DELETE. If the connection drops and you send the same request again with the same key within 24 hours, vFound does not repeat the change: it answers with the first response and the header Idempotent-Replayed: true. The same key with a different request is refused (422, idempotency_key_reused); a key whose first request is still running answers 409 (idempotency_in_progress). Server errors are not kept, so retrying after one runs the request again. An answer that carries something shown only once, such as the signing secret from POST /webhooks or a kiosk pairing code, is never stored either: a retry runs the request again, so check GET /webhooks before you subscribe the same address twice.

OpenAPI

The whole API, every event and every object is described in an OpenAPI 3.1 file: vfound.io/openapi/v1.json. Import it into Postman, Insomnia or your own code generator. A test keeps it in step with the API, so it never lists an endpoint that does not exist or misses one that does.

Ready-made operational system connections

Owners and managers configure these read-only provider connections in Settings, Connections, Operational systems. They are separate from the public REST API: the customer creates and controls the provider credential, the connection must pass a test before activation, repeated failures pause only that connection, and manual work remains available.

SystemWhat vFound readsvFound areas
CloudbedsMinimal reservation and room context through a customer-controlled property key limited to read:reservation.Private Stays for Lost & Found and Parcels
OfficeRnD FlexMember ID, name, email and active state through flex.community.members.read, with an optional location filter.The shared people list for Parcels, Equipment and Visitors
Microsoft Entra IDUser ID, display name, email and active state through Microsoft Graph delta updates and administrator-consented User.Read.All.The shared people list for Parcels, Equipment and Visitors

These are customer-configured API connections, not vendor certification claims. In particular, the Entra connection is not a Microsoft Marketplace app.

Oracle OPERA Cloud is not available. The fail-closed server adapter remains disabled until Oracle access, the hotel's approval, exact environment configuration and live acceptance are complete. It is not advertised as certified or customer-validated.

Items

RequestWhat it doesScope
GET/itemsItems, newest change first. Filters: status, search (words in the title, description, brand, colour or tags), updated_since (ISO 8601), page, per_page (up to 100).items:read
GET/items/{id}One item by its id.items:read
GET/categoriesThe category names an item can use.items:read
POST/itemsAdd a found item. It is published to your guest page, runs alerts and matching, and appears in your audit trail like one added by hand.items:write
PATCH/items/{id}Change details or status.items:write

Add an item

curl https://vfound.io/api/v1/items -X POST \
  -H "Authorization: Bearer $VFOUND_KEY" -H "Content-Type: application/json" \
  -d '{"title":"Black leather wallet","category":"Wallets & Purses","found_date":"2026-09-23",
       "location":"Front desk","colour":"Black","brand":"Fossil","tags":["leather","cards"],
       "photo":"data:image/jpeg;base64,…","photo_public":true}'

Fields: title and category are required. Optional: found_date, found_time (HH:MM), keep_until, location, description, colour, brand, tags, value_band (unknown, low, medium, high), notes (staff only), photo (a base64 data URL up to 8 MB) and photo_public. Photos sent through the API are not checked by vFound AI’s privacy guard, so set photo_public to false for any photo that shows personal details.

The item object

{"id":"9b2f…","title":"Black leather wallet","status":"in_care","category":"Wallets & Purses",
 "found_date":"2026-09-23","found_time":"14:30","keep_until":"2026-10-21","location":"Front desk",
 "description":null,"colour":"Black","brand":"Fossil","tags":["leather","cards"],"value_band":"low",
 "photo_public":true,"photo_url":"https://vfound.io/uploads/…","created_at":"…","updated_at":"…"}

Status is one of in_care, claimed, expired, donated, disposed or trash. A PATCH may set in_care, expired, donated, disposed or trash; returns to a guest happen through claims.

Claims

RequestWhat it doesScope
GET/claimsClaims, newest change first. Filters: status, updated_since, page, per_page.claims:read
GET/claims/{id}One claim by its reference.claims:read
{"id":"k3f9x2a1b7c4d8e6","status":"ready_for_pickup","item":{"id":"9b2f…","title":"Black leather wallet"},
 "claimant":{"first_name":"Ana","last_name":"Ruiz","email":"ana@example.com","phone":null},
 "description":"…","place_lost":"Bar","date_lost":"2026-09-22","language":"es","created_at":"…","updated_at":"…"}

Claim status: unread, open, accepted, awaiting_information, ready_for_pickup, ready_for_shipment, shipped, closed, rejected or expired (accepted but never collected). Claimants’ details are personal data: keep them only as long as you need them.

Return shipping

GET /shipping and GET /shipping/{id} use claims:read to read order status, tracking, postage and the service fee. They return no delivery addresses, payment links or label PDFs. Paid return shipping is optional and is available only for enabled venues and reviewed routes. These reads cannot charge a card, buy a label or record physical carrier handoff.

Lost reports

GET/lost-reports lists what guests reported losing, with status (open or closed), the reporter’s contact details, the language they used and any Find My link they shared. Scope: lost_reports:read.

Analytics

GET/analytics?days=30 returns the same summary as Reports: items logged and returned, return rate, median days to return, where items ended up, categories, hotspots, busiest days, team output, the trend and guest feedback. days is 7, 30, 90 or 365. Scope: analytics:read.

Hotel stays

Stay context is private to the venue and deliberately minimal. It holds a stable stay reference, optional guest reference, guest name, arrival and departure, source timezone, current room, state and source timestamps. It never holds rates, payment details, loyalty data, identity documents, preferences or unrelated reservation notes.

RequestWhat it doesScope
GET/staysList stays. Filters: search (guest, stable reference or current room), state, source, page and per_page. A room match helps navigation only; it never suggests ownership.stays:read
GET/stays/{stay}One stay and its auditable source/correction history.stays:read
POST/staysCreate or update the API source record identified by external_stay_ref. Send source_updated_at; delayed or replayed events no newer than the saved event are acknowledged without replacing newer data.stays:write
curl https://vfound.io/api/v1/stays -X POST \
  -H "Authorization: Bearer $VFOUND_KEY" -H "Idempotency-Key: pms-event-901" \
  -H "Content-Type: application/json" \
  -d '{"external_stay_ref":"BOOK-100","guest_name":"Avery Chen","current_room":"412",
       "stay_state":"in_house","source_timezone":"America/New_York","source_updated_at":"2026-10-03T12:00:00Z"}'

Items and parcels accept optional stay_id and room_at_event. If room at event is omitted, vFound snapshots the stay’s current room. That snapshot does not change after a room move; the record also shows the current room, so the desk sees both.

Parcels

RequestWhat it doesScope
GET/parcelsParcels, newest change first. Filters: status (waiting, collected or returned), search (recipient, unit, tracking number, sender or pickup code), updated_since.parcels:read
GET/parcels/{id}One parcel.parcels:read
POST/parcelsLog a parcel for recipient_email (matched to your people list) or recipient_name, with optional unit, carrier, tracking, sender and shelf. The person is emailed their pickup code unless notify is false.parcels:write
POST/parcels/{id}/collectHand it over: collected_by (required) and an optional signature (a PNG data URL).parcels:write
POST/parcels/{id}/returnReturn it to the sender, with an optional note.parcels:write
POST/parcels/{id}/remindEmail the recipient a reminder to collect it.parcels:write
POST/parcels/trackingNormalise a tracking number and detect UPS, FedEx, USPS, DHL, Amazon or an S10 international carrier. A valid GS1 SSCC is accepted as shipment tracking; a GS1 product identifier is identified but never treated as a parcel number.parcels:read
GET/parcels/pickup-queuePeople who said they are at the desk, with their parcel count and shelves.parcels:read
POST/parcels/pickup-queue/{id}/serveMark one desk arrival as served.parcels:write
POST/parcels/bulk-remindEmail reminders for up to 200 selected waiting parcels.parcels:write
POST/parcels/bulk-returnReturn up to 200 selected waiting parcels to sender.parcels:write
GET/parcels/shelvesParcel shelf capacities and current occupancy.parcels:read
POST/parcels/shelvesCreate a shelf or change its capacity.parcels:write
GET/parcels/analyticsVolume, carrier mix, collection time, uncollected rate and busiest shelves.parcels:read
GET/parcels/outgoingOutgoing parcels ready for a courier or handed over.parcels:read
POST/parcels/outgoingLog an outgoing parcel.parcels:write
POST/parcels/outgoing/{id}/handoffRecord the courier and signature at handoff.parcels:write
GET/parcels/roundsDelivery rounds and their stops.parcels:read
POST/parcels/roundsCreate a campus or office round from waiting parcels.parcels:write
POST/parcels/rounds/{round}/stops/{stop}/deliverComplete a stop with the recipient name and signature.parcels:write
GET/parcels/couriersCouriers logged through Visitors, with status in or out.parcels:read
{"id":412,"status":"waiting","recipient":{"name":"Ana Ruiz","email":"ana@example.com","unit":"Room 214","reference":"S1044"},
 "carrier":"UPS","tracking":"1Z999AA10123456784","sender":null,"size":"small","shelf":"B2","pickup_code":"KQHRER",
 "notified_at":"…","collected_at":null,"collected_by":null,"returned_at":null,"created_at":"…","updated_at":"…"}

The pickup code is shown only while the parcel waits. Recipients’ details are personal data: keep them only as long as you need them.

Checkroom

RequestWhat it doesScope
GET/checkroom/ticketsTickets, newest change first, with status (pending, in, returned, left or moved), the number, what was handed in, individually tagged piece_records, the guest’s details if given, the fee recorded at the desk and the times. Filters: status, search (guest, room, ticket number, piece tag or ticket code), updated_since.checkroom:read
GET/checkroom/tickets/{id}One ticket.checkroom:read
POST/checkroom/ticketsCheck something in and get the next free number: kind (coat, bag, luggage or other, required), pieces, optional piece_details with one kind and storage location for each piece, guest_name, guest_email, guest_room, collect_by for luggage, notes, fee_method (cash or card), station_id, event_id, storage_location, language and photo. The answer also carries guest_ticket (the code and the private ticket address) once, to show as a QR code or print for the guest; reads, lists and webhooks never include it.checkroom:write
POST/checkroom/tickets/{id}/hand-backHand it back once your system has read the guest’s ticket, with optional collected_by and signature. Send piece_ids to return selected pieces and leave the others open; omit it to return every open piece. without_ticket records that the guest lost their ticket and then needs collected_by and signature.checkroom:write
GET/checkroom/stationsList the venue's checkroom stations, number ranges and storage locations.checkroom:read
POST/checkroom/stationsCreate a station with a non-overlapping number range.checkroom:write
PUT/checkroom/stations/{id}Change a station, its number range, storage locations or active state.checkroom:write
GET/checkroom/eventsList recent and upcoming checkroom events.checkroom:read
POST/checkroom/eventsCreate an event with its expected volume and stations.checkroom:write
PUT/checkroom/events/{id}Change an event's times, forecast or stations.checkroom:write
GET/checkroom/exit-queueTickets whose guests asked the desk to get their things ready.checkroom:read
POST/checkroom/tickets/{id}/readyMark a waiting ticket ready and notify the guest.checkroom:write
GET/checkroom/reportTicket volume, peak hours, return time and fees by method and staff.checkroom:read
GET/checkroom/exportDownload the checkroom report rows as formula-safe CSV.checkroom:read

Equipment

RequestWhat it doesScope
GET/loansEquipment handed out, newest change first, with status (out, returned or lost), whether it is overdue, the item, the borrower and the times. Filters: status (out, returned, lost or overdue), search (borrower name, email or reference), updated_since.loans:read
GET/loans/{id}One loan.loans:read
POST/loansHand something out: item_id or item_code (the code on its label, or the link its QR code holds), borrower_name (required), optional person_id to link the people list, borrower_email for a receipt, borrower_reference, due_at, note, language and signature.loans:write
POST/loans/{id}/extendGive more time: a new due_at. The borrower is emailed the new time.loans:write
POST/loans/{id}/returnTake it back, with an optional condition (good, damaged or incomplete), note, and set_aside to put the item aside for repair.loans:write
POST/loans/returnsTake several things back together: loan_ids (1 to 50), with the same optional condition, note and set_aside for all of them. All or nothing: if one is already back or not found, nothing changes. A borrower with an email address gets one receipt for each checkout, listing everything that came back, and each loan sends its own loan.returned event.loans:write
GET/loans/itemsEverything the desk hands out, by name. Filters: status (available, out, repair or retired), search (name, asset tag or label code), updated_since.loans:read
GET/loans/items/{id}One item, with the link its label’s QR code holds.loans:read
POST/loans/itemsAdd equipment: name (required), category, tag, location, notes, loan_hours (how many hours it usually goes out for), photo, and copies to add up to 50 identical items, numbered.loans:write
PATCH/loans/items/{id}Change its details, or set status to available, repair or retired.loans:write
POST/loans/checkoutsHand out one to 50 items in one atomic checkout, or hand out a saved kit. It accepts a person, due time, note and optional signature.loans:write
GET/loans/kitsSaved groups of equipment that are usually handed out together.loans:read
POST/loans/kitsSave a kit with at least two item_ids.loans:write
PUT/loans/kits/{id}Change a saved kit and its items.loans:write
GET/loans/reservationsReservations by start time, optionally filtered by status or from.loans:read
POST/loans/reservationsReserve a specific item or one item in a category for a person and time window.loans:write
POST/loans/reservations/{id}/approveApprove a reservation that is waiting for review.loans:write
POST/loans/reservations/{id}/cancelCancel a pending, approved or ready reservation.loans:write
POST/loans/reservations/{id}/pickupAtomically hand over the reserved item and close the reservation as collected.loans:write
GET/loans/rulesVenue defaults and category rules for duration, limits, overdue blocks and approval.loans:read
POST/loans/rulesCreate or replace one category rule.loans:write
GET/loans/maintenanceService, inspection, repair and note history; filter by item or upcoming work.loans:read
POST/loans/maintenanceRecord maintenance and the next due time for an item.loans:write
GET/loans/stocktakesRecent equipment stock-takes with scanned and missing counts.loans:read
POST/loans/stocktakesStart one open stock-take for the venue.loans:write
POST/loans/stocktakes/{id}/scanScan an equipment label, asset tag or serial number into the open stock-take.loans:write
POST/loans/stocktakes/{id}/finishFinish the stock-take and return the missing-item list.loans:write
GET/loans/transfersIncoming and outgoing equipment transfers for this venue.loans:read
POST/loans/transfersPrepare equipment for another authorised venue in the same customer group.loans:write
POST/loans/transfers/{id}/sendRecord that prepared equipment left the source venue.loans:write
POST/loans/transfers/{id}/receiveReceive equipment at the destination with its location and arrival state.loans:write
POST/loans/transfers/{id}/cancelCancel a prepared transfer before dispatch.loans:write
POST/loans/transfers/{id}/rejectReject a prepared transfer at the destination before dispatch.loans:write
POST/loans/transfers/{id}/exceptionRecord a problem while equipment is in transit.loans:write
POST/loans/importPreview or import up to 5,000 CSV rows using vFound, Snipe-IT, Sortly or Cheqroom headings.loans:write

Visitors

RequestWhat it doesScope
GET/visitorsVisits, newest change first, with status (expected, in, out, cancelled or missed), the visitor, the host, the pass code while it is valid, and the times. Filters: status, search (visitor, company, host or pass code), updated_since.visitors:read
GET/visitors/{id}One visit.visitors:read
POST/visitorsInvite a visitor or sign one in. It accepts the visitor, host and time fields plus visitor_type_id, answers, valid dates and a daily or weekday recurrence.visitors:write
POST/visitors/{id}/sign-inSign an expected visitor in; the host is told.visitors:write
POST/visitors/{id}/sign-outSign them out.visitors:write
POST/visitors/{id}/cancelCancel an invitation.visitors:write
POST/visitors/{id}/send-passEmail the pass again.visitors:write
POST/visitors/{id}/approveApprove a pending visit and send the pass.visitors:write
POST/visitors/{id}/denyDeny a pending visit.visitors:write
GET/visitors/{id}/badgeDownload the browser-printable badge. Choose size 4x3, brother or dymo.visitors:read
GET/visitor-typesList visitor types and their questions, terms, badges, approvals and retention (retention_days is null when the type follows the venue default).visitors:read
POST/visitor-typesCreate a visitor type.visitors:write
PATCH/visitor-types/{id}Change the visitor type fields you send.visitors:write
DELETE/visitor-types/{id}Deactivate a visitor type without changing earlier visits.visitors:write
GET/visitor-roll-callGet the latest roll call and its live counts. A running roll call adds anyone who signed in since it started; one left running for 6 hours is closed.visitors:read
POST/visitor-roll-callStart a roll call, optionally including visitors expected today and anyone waiting for approval.visitors:write
PATCH/visitor-roll-call/entries/{entry}Mark one entry safe or missing.visitors:write
POST/visitor-roll-call/{id}/finishFinish the active roll call and keep its report.visitors:write
GET/visitor-kiosksList paired, pending and revoked kiosk devices.visitors:read
POST/visitor-kiosks/pairingCreate an eight-character pairing code that lasts ten minutes.visitors:write
DELETE/visitor-kiosks/{id}Revoke a kiosk device.visitors:write
GET/visitor-analyticsTotals by day, hour, type and host, plus average stay for the last year.visitors:read
GET/visitor-registrationGet the venue's self-registration link and whether it is enabled.visitors:read

Visitors’ details are personal data: keep them only as long as you need them.

People

The people list holds the residents, staff or students your desk serves. Parcels finds recipients in it, Visitors finds hosts, and Equipment suggests borrowers. Keep it in step with your HR, student or property system through the API.

RequestWhat it doesScope
GET/peopleThe list, by name. Filters: search (name, email, unit or reference), updated_since.people:read
GET/people/{id}One person.people:read
POST/peopleAdd someone: name (required), email, unit, reference and language for their emails.people:write
POST/people/upsertAdd or update up to 500 people in one call: people is a list of the same fields. Someone whose email is already on the list is updated. Answers how many were added, updated and skipped.people:write
PATCH/people/{id}Change only the fields you send.people:write
DELETE/people/{id}Remove someone. Their parcels, visits and loans keep the name they had.people:write
GET/people/exportDownload the complete people list as a formula-safe CSV.people:read
POST/people/{id}/send-my-vfoundEmail one active person the signed page for their linked parcels, visitors and equipment.people:write
GET/people/sync-sourceShow the saved CSV directory and the latest sync result.people:read
PUT/people/sync-sourceSave a public HTTPS CSV url and mode (upsert or mirror).people:write
DELETE/people/sync-sourceRemove the connection and keep the people already imported.people:write
POST/people/sync-source/runRun the saved connection now.people:write

Owners and managers can also connect a public HTTPS CSV link from People. vFound checks it hourly, follows at most three redirects after checking each destination, accepts at most 10 MB, and in mirror mode stops before making changes when the valid row count falls by more than 30 percent. Connected fields are changed at their source.

Automatic directory provisioning

Microsoft Entra has the ready-made customer-configured connection described above. Identity directories including Okta, OneLogin and JumpCloud can instead provision the people list at https://vfound.io/api/scim/v2/Users with a vFound API key that has people:write. Use the bearer key as the secret token. Deactivation removes the person from suggestions; DELETE anonymises links through the same People rules. This endpoint creates people-list entries only and never team logins.

Webhooks

Add up to 25 HTTPS addresses in Settings, For developers, or through the API below, and choose which of the events below each one receives. vFound sends a JSON POST within seconds, retries failures for about three hours, and pauses an address after 15 failed deliveries in a row.

EventWhenData
item.createdA found item is addedItem
item.updatedIts status, title, location, category, keep until date or photo visibility changesItem
claim.createdA guest submits a claimClaim
claim.status_changedA claim moves to a new statusClaim
lost_report.createdA guest reports a lossLost report
parcel.loggedA parcel is logged at the desk or through the APIParcel
parcel.updatedAny of its details change at the desk, such as its recipient, tracking number or shelfParcel
parcel.remindedThe recipient is sent a reminder, by the desk or automaticallyParcel
parcel.collectedA parcel is handed overParcel
parcel.returnedA parcel is returned to the senderParcel
parcel.recipient_arrivedA recipient says they are at the deskPickup queue entry
parcel.reminders_snoozedA recipient pauses their remindersPerson and parcel count
parcel.heldA recipient sets a hold datePerson, date and parcel count
parcel.proxy_createdA recipient emails a delegate a collection codePerson, delegate and expiry
parcel.pickup_servedThe desk marks an arrival servedPickup queue entry
parcel.escalatedA waiting parcel reaches the venue's escalation ageParcel
parcel.outgoing_loggedAn outgoing parcel is loggedOutgoing parcel
parcel.outgoing_handed_overAn outgoing parcel is handed to a courierOutgoing handoff
parcel.round_createdA delivery round is createdRound summary
parcel.round_deliveredA delivery round stop is completedRound, stop and parcel ids
checkroom.checked_inSomething is checked in and given a numberTicket
checkroom.handed_backIt is handed backTicket
checkroom.leftThe night is closed with it still at the checkroomTicket
checkroom.self_checkin_startedA guest starts a ticket from a paired kioskTicket
checkroom.ready_requestedA guest asks the desk to get their things readyTicket
checkroom.readyThe desk marks a guest's things readyTicket
checkroom.station_createdA checkroom station is createdStation
checkroom.station_updatedA checkroom station changesStation
checkroom.event_createdA checkroom event is createdEvent
checkroom.event_updatedA checkroom event changesEvent
loan.checked_outAn item is handed outLoan
loan.due_soonIts due time is less than two hours away, once per due timeLoan
loan.overdueIts due time passes and it is not back, once per due timeLoan
loan.extendedIts due time changesLoan
loan.returnedIt comes back to the deskLoan
loan.lostIt is recorded as not coming backLoan
loan.checkout_createdOne atomic multi-item checkout is createdCheckout
loan.self_checkoutA borrower completes kiosk checkoutCheckout
loan.kit_createdA saved equipment kit is createdKit
loan.kit_updatedA saved equipment kit changesKit
loan.reservation_requestedA reservation waits for approvalReservation
loan.reservation_createdA reservation is confirmed immediatelyReservation
loan.reservation_approvedA pending reservation is approvedReservation
loan.reservation_readyReserved equipment is ready for collectionReservation
loan.reservation_cancelledA reservation is cancelledReservation
loan.reservation_collectedReserved equipment is handed overCheckout
loan.extension_requestedA borrower asks for more timeLoan
loan.problem_reportedA borrower reports an equipment problemLoan
loan.rule_updatedA category checkout rule changesRule
loan.maintenance_loggedMaintenance is recordedMaintenance
loan.maintenance_dueScheduled maintenance becomes dueMaintenance
loan.stocktake_startedAn equipment stock-take startsStock-take
loan.stocktake_completedAn equipment stock-take finishesStock-take
loan.transfer_preparedAn authorised venue prepares equipment to moveTransfer
loan.transfer_sentPrepared equipment leaves its source venueTransfer
loan.transfer_receivedThe destination receives the equipmentTransfer
loan.transfer_rejectedThe destination rejects a prepared transferTransfer
loan.transfer_cancelledThe source cancels a prepared transferTransfer
loan.transfer_exceptionAn in-transit transfer needs attentionTransfer
loan.transfer_lostThe source closes a transfer that never arrivedTransfer
loan.items_importedAn equipment CSV import completesCounts
visit.expectedA visitor is invitedVisit
visit.signed_inA visitor signs in, at the desk or from your QR signVisit
visit.host_confirmedThe desk confirms who a self signed in visitor came to seeVisit
visit.signed_outThey sign out, at the desk, from their pass or automatically after the day endsVisit
visit.cancelledAn invitation is cancelledVisit
visit.approval_requestedA visit waits for staff approvalVisit
visit.approvedA pending visit is approvedVisit
visit.deniedA pending visit is deniedVisit
visit.host_repliedA host replies from their private linkVisit
visit.roll_call_startedA live visitor roll call startsRoll call summary
visit.roll_call_completedA live visitor roll call finishesRoll call summary
visitor_type.createdA visitor type is createdVisitor type
visitor_type.updatedA visitor type changesVisitor type
visitor_type.deactivatedA visitor type is deactivatedVisitor type
kiosk.pairedA visitor kiosk is pairedKiosk
kiosk.revokedA visitor kiosk is revokedKiosk
courier.signed_inA delivery visitor is logged in ParcelsCourier
courier.signed_outA delivery visitor leavesCourier
person.createdA person is addedPerson
person.updatedA person is changedPerson
person.deactivatedDirectory provisioning deactivates a personPerson
person.deletedA person is removedPerson
people.sync.completedA connected CSV sync completesCounts
people.sync.failedA connected CSV sync fails safelyError
{"id":"evt_…","type":"claim.created","created":"2026-09-23T14:30:00+00:00",
 "venue":{"code":"harbourhotel","name":"Harbour Hotel"},"data":{ …the claim… }}

Check the signature

Every delivery carries Vfound-Signature: t=<unix time>,v1=<hex>, where the hex is an HMAC SHA256 of t + "." + raw body with your endpoint’s secret. Reject deliveries older than five minutes. After you make a new signing secret, deliveries carry two v1 values for 24 hours, one for each secret, so accept the delivery if any of them matches.

// Node.js
const crypto = require("crypto");
function verified(rawBody, header, secret) {
  const parts = header.split(",").map((p) => p.split("="));
  const t = (parts.find(([k]) => k === "t") || [])[1];
  const expected = crypto.createHmac("sha256", secret).update(t + "." + rawBody).digest("hex");
  const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
  return fresh && parts.some(([k, v]) => k === "v1" && v.length === expected.length
    && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(v)));
}
// PHP
$t = null; $signatures = [];
foreach (explode(',', $header) as $part) {
    [$k, $v] = array_pad(explode('=', $part, 2), 2, '');
    if ($k === 't') { $t = $v; } elseif ($k === 'v1') { $signatures[] = $v; }
}
$expected = hash_hmac('sha256', $t.'.'.$rawBody, $secret);
$ok = abs(time() - (int) $t) < 300
    && count(array_filter($signatures, fn ($s) => hash_equals($expected, $s))) > 0;

Headers also include Vfound-Event and a unique Vfound-Delivery id you can use to ignore repeats. Webhooks are sent only to public HTTPS addresses on port 443.

Subscribe through the API

Tools such as Zapier can add and remove their own addresses with a key that has webhooks:manage. The signing secret is shown once, in the answer to the POST. The response also carries a Location header with the address to call with DELETE when the subscription is removed.

A key can subscribe only to events whose data it could read: each event needs the read scope of its product (items:read for item events, claims:read for claim events, lost_reports:read, parcels:read, checkroom:read, loans:read for equipment events, visitors:read for visit, visitor type, kiosk and courier events, and people:read for person and people list events). A key lists and removes only the addresses it created itself; addresses added in Settings, or by another key, are not visible to it.

RequestWhat it doesScope
GET/webhooksThe addresses this key created, and their events.webhooks:manage
POST/webhooksAdd an address: url (public HTTPS on port 443) and events, a list of the events above that the key can read.webhooks:manage
DELETE/webhooks/{id}Remove an address this key created, and its delivery history.webhooks:manage

Delivery history

Settings, For developers, lists every delivery to each address from the last 30 days, with how it went, and sends any of them again with the same Vfound-Delivery id. There you can also edit an address, switch a paused one back on, and make a new signing secret. An address that fails 15 deliveries in a row is paused, and the venue owner gets an email.

Shipping reads: list_return_shipping and return_shipping_status.

AI assistants (MCP)

vFound is a Model Context Protocol server at https://vfound.io/api/mcp, so AI assistants can look things up and help at the desk. Connect one in either of two ways: sign in with vFound, for assistants whose connectors sign in with OAuth, or an API key, for clients that send a header, such as Claude Code, Cursor and Visual Studio Code. Either way it sees only the tools its scopes allow, for the products your venue has open.

# Claude Code
claude mcp add --transport http vfound https://vfound.io/api/mcp \
  --header "Authorization: Bearer $VFOUND_KEY"

// Cursor (.cursor/mcp.json)
{"mcpServers":{"vfound":{"url":"https://vfound.io/api/mcp","headers":{"Authorization":"Bearer vf_live_…"}}}}

// Visual Studio Code (.vscode/mcp.json)
{"servers":{"vfound":{"type":"http","url":"https://vfound.io/api/mcp","headers":{"Authorization":"Bearer vf_live_…"}}}}
ToolWhat it doesScope
search_found_itemsSearch found items by words such as “black iPhone”items:read
list_expected_visitorsVisitors invited and not here yetvisitors:read
list_visitors_on_siteVisitors signed in nowvisitors:read
list_visitor_typesVisitor types and their rulesvisitors:read
visitor_analyticsVisitor totals, patterns and average stayvisitors:read
visitor_registration_linkThe venue's self-registration linkvisitors:read
list_couriers_on_siteCouriers signed in through Visitorsparcels:read
list_waiting_parcelsParcels waiting to be collectedparcels:read
detect_parcel_trackingNormalise a tracking number and detect its carrierparcels:read
parcel_pickup_queuePeople waiting at the parcel deskparcels:read
parcel_shelvesParcel shelf capacity and occupancyparcels:read
parcel_analyticsParcel volume, carriers and collection performanceparcels:read
list_outgoing_parcelsOutgoing parcels and their handoff stateparcels:read
list_parcel_roundsParcel delivery rounds and stopsparcels:read
list_checkroom_exit_queueTickets whose guests are waiting at the exitcheckroom:read
mark_checkroom_readyMark a waiting ticket ready and notify the guestcheckroom:write
list_checkroom_stationsCheckroom stations and number rangescheckroom:read
list_checkroom_eventsRecent and upcoming checkroom eventscheckroom:read
checkroom_reportCheckroom volume, return time and fee totalscheckroom:read
list_overdue_equipmentEquipment past its due time, and who has itloans:read
list_equipment_kitsSaved groups of equipmentloans:read
create_equipment_kitSave a reusable equipment kit without handing it overloans:write
list_equipment_reservationsReservations by time and statusloans:read
reserve_equipmentReserve an item or category without handing it overloans:write
list_equipment_rulesVenue and category checkout rulesloans:read
save_equipment_ruleCreate or replace a category ruleloans:write
list_equipment_maintenanceEquipment maintenance history and upcoming workloans:read
log_equipment_maintenanceRecord service, inspection, repair or a noteloans:write
list_equipment_stocktakesRecent stock-takes and their countsloans:read
start_equipment_stocktakeStart a stock-takeloans:write
scan_equipment_stocktakeRecord one item in an open stock-takeloans:write
finish_equipment_stocktakeFinish a stock-take and return what is missingloans:write
list_equipment_transfersIncoming and outgoing transfers without borrower detailsloans:read
preview_equipment_importValidate an equipment CSV without saving itloans:write
find_personLook someone up on the people listpeople:read
export_peopleExport the people list as formula-safe CSVpeople:read
directory_sync_statusCheck the saved CSV connection and its latest runpeople:read
run_directory_syncRun the saved CSV connection nowpeople:write
email_my_vfoundEmail one person their signed private pagepeople:write
invite_visitorInvite a visitor, who gets a pass by emailvisitors:write
approve_visitApprove a pending visit requestvisitors:write
start_roll_callStart or return the active roll callvisitors:write
mark_visitor_safeMark one visitor safevisitors:write
create_kiosk_pairingCreate a ten-minute kiosk pairing codevisitors:write
log_parcelLog a parcel and email the recipientparcels:write
remind_parcelRemind a recipient to collect a parcelparcels:write
remind_parcelsRemind up to 200 selected parcel recipientsparcels:write
log_outgoing_parcelAdd an outgoing parcel to the courier logparcels:write
create_parcel_roundCreate a delivery round from waiting parcelsparcels:write
extend_loanGive a borrower more timeloans:write

Each tool runs the API request above with the same checks, so an assistant can never do more than its key or connection. There are no tools that hand anything over, sign anyone in or out, decide a claim or delete. Each answer counts toward the key’s or connection’s 120 requests a minute.

Sign in with vFound

Add https://vfound.io/api/mcp as a connector in your own assistant. The assistant finds how to sign in by itself and opens a vFound page, where an owner or manager signs in to their workspace as usual, chooses the venue and sees in plain words what the assistant may do, and can untick anything. Staff and view-only members cannot connect an assistant. The assistant is your venue’s own: your subscription with its provider pays for its AI, and vFound receives only the tool requests it sends.

A connection acts as the person who allowed it, with the same checks as an API key: it stops working if that person no longer manages the venue or the venue is not active, it changes nothing while the subscription is paused, and it sees tools only for open products. Its access token lasts an hour and the assistant renews it by itself; a connection unused for 30 days signs in again. See each one, who connected it and when it was last used in Settings, For developers, where Disconnect stops it at once. The token works only on the MCP endpoint, not on the REST API.

For client developers: OAuth 2.1 as the MCP authorization specification describes, with the authorization code flow and PKCE (S256) only. Discovery: /.well-known/oauth-protected-resource (RFC 9728, resource https://vfound.io/api/mcp) and /.well-known/oauth-authorization-server (RFC 8414); a request without a token gets a WWW-Authenticate header that names the first. Clients register themselves at /oauth/register (RFC 7591) as public clients with no secret; redirect addresses must use https, or http only for localhost or 127.0.0.1, and must then match exactly. Then /oauth/authorize, /oauth/token (codes last 60 seconds and work once; refresh tokens are replaced on each use, and reusing an old one disconnects the assistant) and /oauth/revoke (RFC 7009). Scopes use the API key names above; send resource=https://vfound.io/api/mcp (RFC 8707).

Errors

Errors are JSON with a message; validation errors add errors by field. 401 means no valid key, 403 a missing scope, a key whose creator no longer manages the venue, a venue that is not active, or a change sent while the subscription is paused, 404 not found in your venue, 422 invalid input and 429 too many requests.

When a change cannot happen because of the record’s current state, the answer is 422 with a stable code as well. The codes never change name:

CodeWhat it means
parcel_not_waitingThe parcel is no longer waiting: it was already collected, returned or delivered.
tracking_duplicateA parcel with this tracking number is already waiting here.
no_emailThe person or visitor has no email address, so nothing can be sent.
capacity_fullEvery checkroom number is in use.
already_checked_inThe checkroom ticket is already checked in.
already_handed_backThe checkroom ticket was already handed back.
nothing_to_hand_backThe checkroom ticket has nothing to hand back.
number_in_useThat checkroom number is already in use.
ticket_closedOnly an open checkroom ticket can be emailed.
item_outThe equipment is out with someone. Take it back first.
item_in_repairThe equipment is set aside for repair.
item_retiredThe equipment is retired, so it cannot be handed out or reserved.
reservation_conflictSomeone has reserved the item, or the last free one in its category, during this time, or it is expected to still be out.
already_backThe equipment was already taken back.
not_outThe equipment is not out, so it cannot get more time, a reminder or be marked as not coming back.
borrower_overdueThe borrower has overdue equipment, and the venue or the category allows nothing more until it comes back.
borrower_item_limitThis would give the borrower more items than the venue or the category allows at once.
reservation_not_readyThe reservation is not approved yet, or it was already collected or cancelled.
reservation_item_mismatchThe checkout does not include the item the reservation is for.
reservation_category_mismatchThe checkout includes an item outside the reserved category.
reservation_borrower_mismatchThe reservation belongs to a different borrower.
reservation_too_longThe reservation is longer than the category can be borrowed. Only reservations made from My vFound are limited this way.
reservation_daily_limitThe borrower already made 5 reservations from My vFound in the last 24 hours.
stocktake_openA stock-take is already open. Finish it first.
item_unavailableThe equipment is not available or set aside for repair, so it cannot start a transfer.
transfer_openThe equipment already has an open transfer.
transfer_not_readyOnly a prepared transfer can be sent.
transfer_not_in_transitOnly equipment in transit can be received or marked with a problem.
transfer_already_sentA sent transfer can no longer be cancelled or rejected.
already_signed_inThe visitor is already signed in.
visit_closedThe visit is closed. Sign the visitor in as a new visit.
not_signed_inThe visitor is not signed in.
not_expectedOnly an expected visit can be cancelled or sent a pass.
approval_requiredThe visit needs approval before the visitor can sign in.
visit_not_valid_todayThe pass cannot be used again today, or today is outside its dates.

Retries with an Idempotency-Key header can also answer with a code:

CodeWhat it means
idempotency_key_invalid400: the header is not 1 to 191 visible ASCII characters.
idempotency_key_reused422: this key was already used for a different request.
idempotency_in_progress409: the first request with this key is still running. Try again shortly.
Questions or a use case we should support? Email us. We read every message.