Docs

Create a visit

Preview· P9
POST
/visits

Creates an expected visit: an invitation from a host when you use a secret key or OAuth token, or a pre-registration when you use a publishable key. Agoo links the visit to an existing visitor record when the phone number (or, without a phone, the email address) matches one, and creates a visitor record otherwise.

  • With send_pass (the default), Agoo sends the visitor a QR pass on the channels in pass_channels, trying them in order and falling back to the next if one fails. Leave pass_channels out to use your organisation's default order. Each message counts towards your SMS and WhatsApp allowance.
  • Fires visit.created, then pass.sent once the pass is sent.
  • The visitor is checked against your watchlist straight away. On a match the visit is held for security (awaiting_approval): no pass is sent, no host is asked, watchlist.matched fires and your security team is alerted. The response shows only the status.
  • type is one of your organisation's visit type keys (see GET /visit-types) and defaults to meeting. A key your organisation doesn't have, or a type that is turned off, returns validation_failed.
  • custom_fields are checked against the visit form for the visit's type (see GET /forms). A photo or file question takes the ID of a file you registered first (POST /files with kind answer_photo or answer_file); the file then belongs to the visit and is deleted with it. A signature takes its strokes, [[[x, y], …], …] in a 1000 × 400 box. An ID scan takes any of document_type, number and image (an id_image file): the answer keeps last4 (the number's last four characters), the full number is kept encrypted on the visitor and never returned by the API, and the visitor can be matched against your watchlist by it.
  • Pre-registrations made with a publishable key have source: pre_registration. If the site requires host approval for pre-registrations, the visit starts as awaiting_approval, the host is asked, and the pass is sent once the host approves (the visit is then expected again).
  • Each visitor new this month counts towards your plan's visitors a month. A new visitor beyond it returns plan_required; visitors already counted this month can always be invited again.
  • An unknown site_id, or a host_id that isn't an active person who can host, returns validation_failed.
  • With a publishable key, type must be open for pre-registration (pre_registration: true in GET /visit-types); any other type, including the default meeting when it isn't open, returns validation_failed at body.type. custom_fields may contain only the fields in that type's public_form; a field your staff fill in returns validation_failed at body.custom_fields.<key>. Publishable keys can't list or search people, so your page must already know the host_id; to let visitors search for their host, use the hosted pre-registration page (the embed script).
  • In test mode, messages go to the console's test outbox instead of the visitor.

Scope: visits:write · Plan: Pro and Enterprise in live mode; every plan in test mode. Publishable keys: allowed, for visit types open for pre-registration.

Authorization

headerAuthorizationBearer <token>

Send Authorization: Bearer <token> on every request. The token is one of:

PrefixWhat it isWhere it may be used
agoo_sk_live_Secret key, live modeYour servers only
agoo_sk_test_Secret key, test modeYour servers only
agoo_pk_live_Publishable key, live modeBrowsers and apps: create pre-registrations and bookings, read public booking types (with their intake questions) and their free slots, read the visit types open for pre-registration with their public forms. Never lists people.
agoo_pk_test_Publishable key, test modeAs above, in test mode

Admins create keys in Console → Developers → API keys and choose each key's scopes. A key is shown once. Never put a secret key in a URL, a browser or a mobile app.

Scope: visits:write

Header Parameters

Idempotency-Key*string

A UUID you generate for this operation. If you retry with the same key within 24 hours, Agoo returns the original response instead of acting twice. While the first request is still running, a retry returns conflict. Reusing a key with a different request returns idempotency_key_reused. Responses with rate_limited, internal_error or unavailable aren't stored, so retry those with the same key.

Formatuuid

Request Body

application/json
  1. body

A new expected visit. Say who it is for with host_id, host_ids or team_id; with none of them it is for no one in particular, and your reception receives it.

site_id*SiteId

A site ID (TypeID with the site prefix).

Match^site_[0-7][0-9a-hjkmnp-tv-z]{25}$
Example"site_01kjpt3yw0fz0v414608h9x65s"
host_id?PersonId

The host. They must be active and able to host (can_host).

Match^person_[0-7][0-9a-hjkmnp-tv-z]{25}$
Example"person_01kjsesxm0e4zbyvkr7bcfxbfg"
host_ids?array<>

Several hosts, each active and able to host. The first is the lead host (or host_id, when you send both). Not with team_id.

Itemsitems <= 10
team_id?TeamId

A team instead of people: its members are told, and the first to take the visit becomes its host. With a publishable key, a team visitors may choose, when the visit type offers teams.

Match^team_[0-7][0-9a-hjkmnp-tv-z]{25}$
Example"team_01m4r6y616e1b973vj50h0643y"
type?VisitType

The visit type's key. It must be turned on for your organisation.

Match^[a-z][a-z0-9_]{0,39}$
Default"meeting"
Example"meeting"
Example"contractor"
Example"parent_pickup"
visitor*

The visitor's details. Agoo links them to an existing visitor record with the same phone number (or, without a phone, the same email address). To send a pass, give a phone number for WhatsApp or SMS, or an email address for email.

purpose?string
Lengthlength <= 300
vehicle_plate?string

The plate of the vehicle they're coming in, so the gate recognises it.

Length2 <= length <= 16
expected_at*string

When the visitor is expected.

Formatdate-time
expected_until?string

When the visitor's pass stops working, for example for a multi-day pass. Must be after expected_at. Defaults to the end of that day at the site.

Formatdate-time
send_pass?boolean

Send the visitor their QR pass now.

Defaulttrue
pass_channels?array<>

Channels to try, in order. Defaults to your organisation's channel order.

Items1 <= items
pass_rules?

When and where the pass admits. Left out: the visit type's defaults, as your plan allows them. Not with a publishable key.

custom_fields?

Answers to your organisation's custom fields, keyed by field key. The keys and allowed values come from the JSON Schema of the matching form (GET /forms). Keys that aren't in the form are rejected. Questions the form marks as required aren't enforced through the API; they're asked when the visitor checks in or completes their pre-registration. Answers to fields an admin marked sensitive or masked are never included for API keys or connected apps, nor in events and webhooks: they stay inside Agoo, seen only by the roles the admin chose.

Example
{  "vehicle_plate": "GR 4521-24",  "bringing_laptop": true}

Response Body

The new visit.

application/json
  1. response

One visit by one visitor to one site.

id*VisitId

A visit ID (TypeID with the visit prefix).

Match^visit_[0-7][0-9a-hjkmnp-tv-z]{25}$
Example"visit_01m4k5z4j0fxbte6sn6e8tpgza"
status*VisitStatus

Where the visit is. A visit usually goes expected → checked_in → checked_out, passing through awaiting_approval when a host must approve. A pre-registration that needs approval starts as awaiting_approval and, once the host approves, becomes expected with decision.outcome: approved. It can also end as denied, cancelled or no_show.

Value in"expected""awaiting_approval""checked_in""checked_out""denied""cancelled""no_show"
type*VisitType

A visit type's key. Every organisation has six built-in visit types, meeting, interview, delivery, contractor, event and other, and can add its own in the console, such as parent_pickup or vendor. Each type has its own visit form, badge and rules. A key is lowercase letters, digits and underscores, starts with a letter and is at most 40 characters. It never changes, even when the type is renamed, and it's the same in live and test mode. List your organisation's types with GET /visit-types.

A key your organisation doesn't have returns validation_failed, and so does a type that is turned off when you create a visit or change a visit's type.

Match^[a-z][a-z0-9_]{0,39}$
Example"meeting"
Example"contractor"
Example"parent_pickup"
source*VisitSource

How the visit started: invitation (a host, the console or a secret key), pre_registration (the visitor, or a publishable key), walk_in (at the kiosk or front desk) or booking (an in-person booking).

Value in"invitation""pre_registration""walk_in""booking"
site_id*SiteId

A site ID (TypeID with the site prefix).

Match^site_[0-7][0-9a-hjkmnp-tv-z]{25}$
Example"site_01kjpt3yw0fz0v414608h9x65s"
gate_id*|

The gate the visitor came in through, once known.

Match^gate_[0-7][0-9a-hjkmnp-tv-z]{25}$
host_id*|

The lead host. null while a visit for a team or for no one in particular hasn't been taken by anyone.

Match^person_[0-7][0-9a-hjkmnp-tv-z]{25}$
Example"person_01kjsesxm0e4zbyvkr7bcfxbfg"
audience*string

Who the visit is for: one person, several people (a lead host and others), a team, or desk (no one in particular, which your reception receives).

Value in"person""people""team""desk"
hosts*array<>

Everyone the visit is for, the lead first, and anyone reception asked to see the visitor, with their answer. Empty while nobody has taken a visit for a team or for no one in particular.

team*|

The team the visit is with: the one the visitor came to see, or the one receiving a visitor with no one in particular to see.

asked_for*|

The person a walk-in asked for, when that person's visitors go through their office.

needs_someone*boolean

The visitor is waiting and nobody has taken the visit yet.

visitor*

The visitor's details as stored on their visitor record.

purpose*|
Lengthlength <= 300
Example"Quarterly treasury review"
vehicle_plate?|

The plate of the vehicle the visitor comes in, as given; the gate recognises it. Left out when there's none.

Lengthlength <= 16
Example"GR 4521-24"
expected_at*|

When the visitor is expected. null for walk-ins.

Formatdate-time
expected_until*|

When the visitor's pass stops working. null for walk-ins.

Formatdate-time
checked_in_at*|
Formatdate-time
checked_out_at*|
Formatdate-time
cancelled_at*|
Formatdate-time
decision*|

The host's decision, for visits that needed approval.

booking_id*|

The booking this visit came from, if any.

Match^booking_[0-7][0-9a-hjkmnp-tv-z]{25}$
rescheduled_to*|

For a visit closed because the visitor was booked in for another time: what replaced it. null otherwise.

pass*

The visitor's QR pass: where and when it was last sent, and when and where it admits.

custom_fields*

Answers to your organisation's custom fields, keyed by field key. The keys and allowed values come from the JSON Schema of the matching form (GET /forms). Keys that aren't in the form are rejected. Questions the form marks as required aren't enforced through the API; they're asked when the visitor checks in or completes their pre-registration. Answers to fields an admin marked sensitive or masked are never included for API keys or connected apps, nor in events and webhooks: they stay inside Agoo, seen only by the roles the admin chose.

Example
{  "vehicle_plate": "GR 4521-24",  "bringing_laptop": true}
created_at*string
Formatdate-time
updated_at*string
Formatdate-time
curl -X POST 'https://api.agoohq.com/v1/visits' \  -H 'Idempotency-Key: 3f6b2a1e-8c4d-4f7a-9b2e-5d1c0a7e9f64' \  -H 'Content-Type: application/json' \  -d '{  "site_id": "site_01kjpt3yw0fz0v414608h9x65s",  "host_id": "person_01kjsesxm0e4zbyvkr7bcfxbfg",  "type": "meeting",  "visitor": {    "name": "Ama Owusu",    "phone": "+233241234567",    "email": "ama@coastline.example",    "company": "Coastline Consult"  },  "purpose": "Quarterly treasury review",  "expected_at": "2026-10-14T09:30:00Z",  "expected_until": "2026-10-14T11:30:00Z",  "send_pass": true,  "pass_channels": [    "whatsapp",    "sms"  ],  "custom_fields": {    "vehicle_plate": "GR 4521-24",    "bringing_laptop": true  }}'
{  "id": "visit_01m4k5z4j0fxbte6sn6e8tpgza",  "status": "expected",  "type": "meeting",  "source": "invitation",  "site_id": "site_01kjpt3yw0fz0v414608h9x65s",  "gate_id": null,  "host_id": "person_01kjsesxm0e4zbyvkr7bcfxbfg",  "audience": "person",  "hosts": [    {      "id": "person_01kjsesxm0e4zbyvkr7bcfxbfg",      "name": "Kwame Mensah",      "role": "lead",      "state": "addressed"    }  ],  "team": null,  "asked_for": null,  "needs_someone": false,  "visitor": {    "id": "visitor_01krdtb870fxj8t6ntdtcxwdk5",    "name": "Ama Owusu",    "phone": "+233241234567",    "email": "ama@coastline.example",    "company": "Coastline Consult"  },  "purpose": "Quarterly treasury review",  "expected_at": "2026-10-14T09:30:00Z",  "expected_until": "2026-10-14T11:30:00Z",  "checked_in_at": null,  "checked_out_at": null,  "cancelled_at": null,  "decision": null,  "booking_id": null,  "rescheduled_to": null,  "pass": {    "channels": [      "whatsapp"    ],    "last_sent_at": "2026-10-10T15:12:44Z"  },  "custom_fields": {    "vehicle_plate": "GR 4521-24",    "bringing_laptop": true  },  "created_at": "2026-10-10T15:12:40Z",  "updated_at": "2026-10-10T15:12:44Z"}