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_…"
| Scope | Allows | Endpoints |
|---|---|---|
items:read | Read found items and categories | GET /items, /items/{id}, /categories |
items:write | Add and update found items | POST /items, PATCH /items/{id} |
claims:read | Read claims, with the claimant’s contact details | GET /claims, /claims/{id} |
lost_reports:read | Read guests’ lost reports | GET /lost-reports |
analytics:read | Read the analytics summary | GET /analytics |
parcels:read | Read parcels, with each recipient’s name and email | GET /parcels, /parcels/{id} |
parcels:write | Log, hand over, return and remind | POST /parcels and its actions |
checkroom:read | Read tickets, stations, events, the exit queue and reports | GET under /checkroom |
checkroom:write | Check in, hand back, mark ready and manage stations or events | POST and PUT under /checkroom |
loans:read | Read equipment and who has it | GET /loans, /loans/items and one of each |
loans:write | Add equipment, hand it out, extend and take back | POST and PATCH under /loans |
visitors:read | Read visits | GET /visitors, /visitors/{id} |
visitors:write | Invite, sign in and out, cancel, send a pass | POST /visitors and its actions |
people:read | Read the people list | GET /people, /people/{id} |
people:write | Add, update, sync and remove people | POST, PATCH and DELETE under /people |
stays:read | Read private hotel stay context and its correction history | GET /stays, /stays/{stay} |
stays:write | Idempotently add or update stay context from a hotel system | POST /stays |
webhooks:manage | Subscribe and unsubscribe the key’s own webhook addresses, for events whose data the key can read | GET, 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.
| System | What vFound reads | vFound areas |
|---|---|---|
| Cloudbeds | Minimal reservation and room context through a customer-controlled property key limited to read:reservation. | Private Stays for Lost & Found and Parcels |
| OfficeRnD Flex | Member 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 ID | User 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.
Items
| Request | What it does | Scope |
|---|---|---|
| GET/items | Items, 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/categories | The category names an item can use. | items:read |
| POST/items | Add 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
| Request | What it does | Scope |
|---|---|---|
| GET/claims | Claims, 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.
| Request | What it does | Scope |
|---|---|---|
| GET/stays | List 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/stays | Create 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
| Request | What it does | Scope |
|---|---|---|
| GET/parcels | Parcels, 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/parcels | Log 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}/collect | Hand it over: collected_by (required) and an optional signature (a PNG data URL). | parcels:write |
| POST/parcels/{id}/return | Return it to the sender, with an optional note. | parcels:write |
| POST/parcels/{id}/remind | Email the recipient a reminder to collect it. | parcels:write |
| POST/parcels/tracking | Normalise 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-queue | People who said they are at the desk, with their parcel count and shelves. | parcels:read |
| POST/parcels/pickup-queue/{id}/serve | Mark one desk arrival as served. | parcels:write |
| POST/parcels/bulk-remind | Email reminders for up to 200 selected waiting parcels. | parcels:write |
| POST/parcels/bulk-return | Return up to 200 selected waiting parcels to sender. | parcels:write |
| GET/parcels/shelves | Parcel shelf capacities and current occupancy. | parcels:read |
| POST/parcels/shelves | Create a shelf or change its capacity. | parcels:write |
| GET/parcels/analytics | Volume, carrier mix, collection time, uncollected rate and busiest shelves. | parcels:read |
| GET/parcels/outgoing | Outgoing parcels ready for a courier or handed over. | parcels:read |
| POST/parcels/outgoing | Log an outgoing parcel. | parcels:write |
| POST/parcels/outgoing/{id}/handoff | Record the courier and signature at handoff. | parcels:write |
| GET/parcels/rounds | Delivery rounds and their stops. | parcels:read |
| POST/parcels/rounds | Create a campus or office round from waiting parcels. | parcels:write |
| POST/parcels/rounds/{round}/stops/{stop}/deliver | Complete a stop with the recipient name and signature. | parcels:write |
| GET/parcels/couriers | Couriers 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
| Request | What it does | Scope |
|---|---|---|
| GET/checkroom/tickets | Tickets, 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/tickets | Check 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-back | Hand 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/stations | List the venue's checkroom stations, number ranges and storage locations. | checkroom:read |
| POST/checkroom/stations | Create 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/events | List recent and upcoming checkroom events. | checkroom:read |
| POST/checkroom/events | Create 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-queue | Tickets whose guests asked the desk to get their things ready. | checkroom:read |
| POST/checkroom/tickets/{id}/ready | Mark a waiting ticket ready and notify the guest. | checkroom:write |
| GET/checkroom/report | Ticket volume, peak hours, return time and fees by method and staff. | checkroom:read |
| GET/checkroom/export | Download the checkroom report rows as formula-safe CSV. | checkroom:read |
Equipment
| Request | What it does | Scope |
|---|---|---|
| GET/loans | Equipment 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/loans | Hand 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}/extend | Give more time: a new due_at. The borrower is emailed the new time. | loans:write |
| POST/loans/{id}/return | Take 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/returns | Take 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/items | Everything 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/items | Add 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/checkouts | Hand 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/kits | Saved groups of equipment that are usually handed out together. | loans:read |
| POST/loans/kits | Save 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/reservations | Reservations by start time, optionally filtered by status or from. | loans:read |
| POST/loans/reservations | Reserve a specific item or one item in a category for a person and time window. | loans:write |
| POST/loans/reservations/{id}/approve | Approve a reservation that is waiting for review. | loans:write |
| POST/loans/reservations/{id}/cancel | Cancel a pending, approved or ready reservation. | loans:write |
| POST/loans/reservations/{id}/pickup | Atomically hand over the reserved item and close the reservation as collected. | loans:write |
| GET/loans/rules | Venue defaults and category rules for duration, limits, overdue blocks and approval. | loans:read |
| POST/loans/rules | Create or replace one category rule. | loans:write |
| GET/loans/maintenance | Service, inspection, repair and note history; filter by item or upcoming work. | loans:read |
| POST/loans/maintenance | Record maintenance and the next due time for an item. | loans:write |
| GET/loans/stocktakes | Recent equipment stock-takes with scanned and missing counts. | loans:read |
| POST/loans/stocktakes | Start one open stock-take for the venue. | loans:write |
| POST/loans/stocktakes/{id}/scan | Scan an equipment label, asset tag or serial number into the open stock-take. | loans:write |
| POST/loans/stocktakes/{id}/finish | Finish the stock-take and return the missing-item list. | loans:write |
| GET/loans/transfers | Incoming and outgoing equipment transfers for this venue. | loans:read |
| POST/loans/transfers | Prepare equipment for another authorised venue in the same customer group. | loans:write |
| POST/loans/transfers/{id}/send | Record that prepared equipment left the source venue. | loans:write |
| POST/loans/transfers/{id}/receive | Receive equipment at the destination with its location and arrival state. | loans:write |
| POST/loans/transfers/{id}/cancel | Cancel a prepared transfer before dispatch. | loans:write |
| POST/loans/transfers/{id}/reject | Reject a prepared transfer at the destination before dispatch. | loans:write |
| POST/loans/transfers/{id}/exception | Record a problem while equipment is in transit. | loans:write |
| POST/loans/import | Preview or import up to 5,000 CSV rows using vFound, Snipe-IT, Sortly or Cheqroom headings. | loans:write |
Visitors
| Request | What it does | Scope |
|---|---|---|
| GET/visitors | Visits, 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/visitors | Invite 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-in | Sign an expected visitor in; the host is told. | visitors:write |
| POST/visitors/{id}/sign-out | Sign them out. | visitors:write |
| POST/visitors/{id}/cancel | Cancel an invitation. | visitors:write |
| POST/visitors/{id}/send-pass | Email the pass again. | visitors:write |
| POST/visitors/{id}/approve | Approve a pending visit and send the pass. | visitors:write |
| POST/visitors/{id}/deny | Deny a pending visit. | visitors:write |
| GET/visitors/{id}/badge | Download the browser-printable badge. Choose size 4x3, brother or dymo. | visitors:read |
| GET/visitor-types | List 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-types | Create 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-call | Get 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-call | Start 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}/finish | Finish the active roll call and keep its report. | visitors:write |
| GET/visitor-kiosks | List paired, pending and revoked kiosk devices. | visitors:read |
| POST/visitor-kiosks/pairing | Create an eight-character pairing code that lasts ten minutes. | visitors:write |
| DELETE/visitor-kiosks/{id} | Revoke a kiosk device. | visitors:write |
| GET/visitor-analytics | Totals by day, hour, type and host, plus average stay for the last year. | visitors:read |
| GET/visitor-registration | Get 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.
| Request | What it does | Scope |
|---|---|---|
| GET/people | The list, by name. Filters: search (name, email, unit or reference), updated_since. | people:read |
| GET/people/{id} | One person. | people:read |
| POST/people | Add someone: name (required), email, unit, reference and language for their emails. | people:write |
| POST/people/upsert | Add 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/export | Download the complete people list as a formula-safe CSV. | people:read |
| POST/people/{id}/send-my-vfound | Email one active person the signed page for their linked parcels, visitors and equipment. | people:write |
| GET/people/sync-source | Show the saved CSV directory and the latest sync result. | people:read |
| PUT/people/sync-source | Save a public HTTPS CSV url and mode (upsert or mirror). | people:write |
| DELETE/people/sync-source | Remove the connection and keep the people already imported. | people:write |
| POST/people/sync-source/run | Run 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.
| Event | When | Data |
|---|---|---|
item.created | A found item is added | Item |
item.updated | Its status, title, location, category, keep until date or photo visibility changes | Item |
claim.created | A guest submits a claim | Claim |
claim.status_changed | A claim moves to a new status | Claim |
lost_report.created | A guest reports a loss | Lost report |
parcel.logged | A parcel is logged at the desk or through the API | Parcel |
parcel.updated | Any of its details change at the desk, such as its recipient, tracking number or shelf | Parcel |
parcel.reminded | The recipient is sent a reminder, by the desk or automatically | Parcel |
parcel.collected | A parcel is handed over | Parcel |
parcel.returned | A parcel is returned to the sender | Parcel |
parcel.recipient_arrived | A recipient says they are at the desk | Pickup queue entry |
parcel.reminders_snoozed | A recipient pauses their reminders | Person and parcel count |
parcel.held | A recipient sets a hold date | Person, date and parcel count |
parcel.proxy_created | A recipient emails a delegate a collection code | Person, delegate and expiry |
parcel.pickup_served | The desk marks an arrival served | Pickup queue entry |
parcel.escalated | A waiting parcel reaches the venue's escalation age | Parcel |
parcel.outgoing_logged | An outgoing parcel is logged | Outgoing parcel |
parcel.outgoing_handed_over | An outgoing parcel is handed to a courier | Outgoing handoff |
parcel.round_created | A delivery round is created | Round summary |
parcel.round_delivered | A delivery round stop is completed | Round, stop and parcel ids |
checkroom.checked_in | Something is checked in and given a number | Ticket |
checkroom.handed_back | It is handed back | Ticket |
checkroom.left | The night is closed with it still at the checkroom | Ticket |
checkroom.self_checkin_started | A guest starts a ticket from a paired kiosk | Ticket |
checkroom.ready_requested | A guest asks the desk to get their things ready | Ticket |
checkroom.ready | The desk marks a guest's things ready | Ticket |
checkroom.station_created | A checkroom station is created | Station |
checkroom.station_updated | A checkroom station changes | Station |
checkroom.event_created | A checkroom event is created | Event |
checkroom.event_updated | A checkroom event changes | Event |
loan.checked_out | An item is handed out | Loan |
loan.due_soon | Its due time is less than two hours away, once per due time | Loan |
loan.overdue | Its due time passes and it is not back, once per due time | Loan |
loan.extended | Its due time changes | Loan |
loan.returned | It comes back to the desk | Loan |
loan.lost | It is recorded as not coming back | Loan |
loan.checkout_created | One atomic multi-item checkout is created | Checkout |
loan.self_checkout | A borrower completes kiosk checkout | Checkout |
loan.kit_created | A saved equipment kit is created | Kit |
loan.kit_updated | A saved equipment kit changes | Kit |
loan.reservation_requested | A reservation waits for approval | Reservation |
loan.reservation_created | A reservation is confirmed immediately | Reservation |
loan.reservation_approved | A pending reservation is approved | Reservation |
loan.reservation_ready | Reserved equipment is ready for collection | Reservation |
loan.reservation_cancelled | A reservation is cancelled | Reservation |
loan.reservation_collected | Reserved equipment is handed over | Checkout |
loan.extension_requested | A borrower asks for more time | Loan |
loan.problem_reported | A borrower reports an equipment problem | Loan |
loan.rule_updated | A category checkout rule changes | Rule |
loan.maintenance_logged | Maintenance is recorded | Maintenance |
loan.maintenance_due | Scheduled maintenance becomes due | Maintenance |
loan.stocktake_started | An equipment stock-take starts | Stock-take |
loan.stocktake_completed | An equipment stock-take finishes | Stock-take |
loan.transfer_prepared | An authorised venue prepares equipment to move | Transfer |
loan.transfer_sent | Prepared equipment leaves its source venue | Transfer |
loan.transfer_received | The destination receives the equipment | Transfer |
loan.transfer_rejected | The destination rejects a prepared transfer | Transfer |
loan.transfer_cancelled | The source cancels a prepared transfer | Transfer |
loan.transfer_exception | An in-transit transfer needs attention | Transfer |
loan.transfer_lost | The source closes a transfer that never arrived | Transfer |
loan.items_imported | An equipment CSV import completes | Counts |
visit.expected | A visitor is invited | Visit |
visit.signed_in | A visitor signs in, at the desk or from your QR sign | Visit |
visit.host_confirmed | The desk confirms who a self signed in visitor came to see | Visit |
visit.signed_out | They sign out, at the desk, from their pass or automatically after the day ends | Visit |
visit.cancelled | An invitation is cancelled | Visit |
visit.approval_requested | A visit waits for staff approval | Visit |
visit.approved | A pending visit is approved | Visit |
visit.denied | A pending visit is denied | Visit |
visit.host_replied | A host replies from their private link | Visit |
visit.roll_call_started | A live visitor roll call starts | Roll call summary |
visit.roll_call_completed | A live visitor roll call finishes | Roll call summary |
visitor_type.created | A visitor type is created | Visitor type |
visitor_type.updated | A visitor type changes | Visitor type |
visitor_type.deactivated | A visitor type is deactivated | Visitor type |
kiosk.paired | A visitor kiosk is paired | Kiosk |
kiosk.revoked | A visitor kiosk is revoked | Kiosk |
courier.signed_in | A delivery visitor is logged in Parcels | Courier |
courier.signed_out | A delivery visitor leaves | Courier |
person.created | A person is added | Person |
person.updated | A person is changed | Person |
person.deactivated | Directory provisioning deactivates a person | Person |
person.deleted | A person is removed | Person |
people.sync.completed | A connected CSV sync completes | Counts |
people.sync.failed | A connected CSV sync fails safely | Error |
{"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.
| Request | What it does | Scope |
|---|---|---|
| GET/webhooks | The addresses this key created, and their events. | webhooks:manage |
| POST/webhooks | Add 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_…"}}}}
| Tool | What it does | Scope |
|---|---|---|
search_found_items | Search found items by words such as “black iPhone” | items:read |
list_expected_visitors | Visitors invited and not here yet | visitors:read |
list_visitors_on_site | Visitors signed in now | visitors:read |
list_visitor_types | Visitor types and their rules | visitors:read |
visitor_analytics | Visitor totals, patterns and average stay | visitors:read |
visitor_registration_link | The venue's self-registration link | visitors:read |
list_couriers_on_site | Couriers signed in through Visitors | parcels:read |
list_waiting_parcels | Parcels waiting to be collected | parcels:read |
detect_parcel_tracking | Normalise a tracking number and detect its carrier | parcels:read |
parcel_pickup_queue | People waiting at the parcel desk | parcels:read |
parcel_shelves | Parcel shelf capacity and occupancy | parcels:read |
parcel_analytics | Parcel volume, carriers and collection performance | parcels:read |
list_outgoing_parcels | Outgoing parcels and their handoff state | parcels:read |
list_parcel_rounds | Parcel delivery rounds and stops | parcels:read |
list_checkroom_exit_queue | Tickets whose guests are waiting at the exit | checkroom:read |
mark_checkroom_ready | Mark a waiting ticket ready and notify the guest | checkroom:write |
list_checkroom_stations | Checkroom stations and number ranges | checkroom:read |
list_checkroom_events | Recent and upcoming checkroom events | checkroom:read |
checkroom_report | Checkroom volume, return time and fee totals | checkroom:read |
list_overdue_equipment | Equipment past its due time, and who has it | loans:read |
list_equipment_kits | Saved groups of equipment | loans:read |
create_equipment_kit | Save a reusable equipment kit without handing it over | loans:write |
list_equipment_reservations | Reservations by time and status | loans:read |
reserve_equipment | Reserve an item or category without handing it over | loans:write |
list_equipment_rules | Venue and category checkout rules | loans:read |
save_equipment_rule | Create or replace a category rule | loans:write |
list_equipment_maintenance | Equipment maintenance history and upcoming work | loans:read |
log_equipment_maintenance | Record service, inspection, repair or a note | loans:write |
list_equipment_stocktakes | Recent stock-takes and their counts | loans:read |
start_equipment_stocktake | Start a stock-take | loans:write |
scan_equipment_stocktake | Record one item in an open stock-take | loans:write |
finish_equipment_stocktake | Finish a stock-take and return what is missing | loans:write |
list_equipment_transfers | Incoming and outgoing transfers without borrower details | loans:read |
preview_equipment_import | Validate an equipment CSV without saving it | loans:write |
find_person | Look someone up on the people list | people:read |
export_people | Export the people list as formula-safe CSV | people:read |
directory_sync_status | Check the saved CSV connection and its latest run | people:read |
run_directory_sync | Run the saved CSV connection now | people:write |
email_my_vfound | Email one person their signed private page | people:write |
invite_visitor | Invite a visitor, who gets a pass by email | visitors:write |
approve_visit | Approve a pending visit request | visitors:write |
start_roll_call | Start or return the active roll call | visitors:write |
mark_visitor_safe | Mark one visitor safe | visitors:write |
create_kiosk_pairing | Create a ten-minute kiosk pairing code | visitors:write |
log_parcel | Log a parcel and email the recipient | parcels:write |
remind_parcel | Remind a recipient to collect a parcel | parcels:write |
remind_parcels | Remind up to 200 selected parcel recipients | parcels:write |
log_outgoing_parcel | Add an outgoing parcel to the courier log | parcels:write |
create_parcel_round | Create a delivery round from waiting parcels | parcels:write |
extend_loan | Give a borrower more time | loans: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:
| Code | What it means |
|---|---|
parcel_not_waiting | The parcel is no longer waiting: it was already collected, returned or delivered. |
tracking_duplicate | A parcel with this tracking number is already waiting here. |
no_email | The person or visitor has no email address, so nothing can be sent. |
capacity_full | Every checkroom number is in use. |
already_checked_in | The checkroom ticket is already checked in. |
already_handed_back | The checkroom ticket was already handed back. |
nothing_to_hand_back | The checkroom ticket has nothing to hand back. |
number_in_use | That checkroom number is already in use. |
ticket_closed | Only an open checkroom ticket can be emailed. |
item_out | The equipment is out with someone. Take it back first. |
item_in_repair | The equipment is set aside for repair. |
item_retired | The equipment is retired, so it cannot be handed out or reserved. |
reservation_conflict | Someone has reserved the item, or the last free one in its category, during this time, or it is expected to still be out. |
already_back | The equipment was already taken back. |
not_out | The equipment is not out, so it cannot get more time, a reminder or be marked as not coming back. |
borrower_overdue | The borrower has overdue equipment, and the venue or the category allows nothing more until it comes back. |
borrower_item_limit | This would give the borrower more items than the venue or the category allows at once. |
reservation_not_ready | The reservation is not approved yet, or it was already collected or cancelled. |
reservation_item_mismatch | The checkout does not include the item the reservation is for. |
reservation_category_mismatch | The checkout includes an item outside the reserved category. |
reservation_borrower_mismatch | The reservation belongs to a different borrower. |
reservation_too_long | The reservation is longer than the category can be borrowed. Only reservations made from My vFound are limited this way. |
reservation_daily_limit | The borrower already made 5 reservations from My vFound in the last 24 hours. |
stocktake_open | A stock-take is already open. Finish it first. |
item_unavailable | The equipment is not available or set aside for repair, so it cannot start a transfer. |
transfer_open | The equipment already has an open transfer. |
transfer_not_ready | Only a prepared transfer can be sent. |
transfer_not_in_transit | Only equipment in transit can be received or marked with a problem. |
transfer_already_sent | A sent transfer can no longer be cancelled or rejected. |
already_signed_in | The visitor is already signed in. |
visit_closed | The visit is closed. Sign the visitor in as a new visit. |
not_signed_in | The visitor is not signed in. |
not_expected | Only an expected visit can be cancelled or sent a pass. |
approval_required | The visit needs approval before the visitor can sign in. |
visit_not_valid_today | The pass cannot be used again today, or today is outside its dates. |
Retries with an Idempotency-Key header can also answer with a code:
| Code | What it means |
|---|---|
idempotency_key_invalid | 400: the header is not 1 to 191 visible ASCII characters. |
idempotency_key_reused | 422: this key was already used for a different request. |
idempotency_in_progress | 409: the first request with this key is still running. Try again shortly. |