Docs
Concepts

Passes and pass rules

Set when and where a visit's pass admits with pass_rules, check visitors in despite a rule with override_pass_rules, and let them back in on the same pass with re-enter.

Preview· P9

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

Every expected visit has a pass: the link and QR code the visitor shows at reception, the kiosk or the gate. The visit's pass object says where it was sent, when and where it admits, and, for an entry on a pass that admits again, which visit holds the pass.

Part of a Visit
{
  "id": "visit_01m4k5z4j0fxbte6sn6e8tpgza",
  "status": "expected",
  "expected_at": "2026-10-12T07:00:00Z",
  "expected_until": "2026-10-16T17:00:00Z",
  "pass": {
    "channels": ["sms"],
    "last_sent_at": "2026-10-09T10:14:03Z",
    "rules": {
      "daily": { "from": "07:00", "until": "17:00" },
      "single_use": true,
      "gate_ids": ["gate_01kjpt7m20fpf9vcsaexsxgqr3"]
    },
    "for_visit_id": null
  }
}

Days and pass rules

A pass admits on the visit's days, from expected_at to expected_until, counted in the site's time zone: one day for most visits, up to 92 days for a multi-day pass. pass_rules narrows that:

FieldMeansPlan
daily{ "from": "HH:MM", "until": "HH:MM" }: the hours it admits on each of its days, at the site. from is before until.Starter and above
window{ "before": minutes, "after": minutes } (0–720 each): around expected_at on a one-day pass. With daily, before opens each day early.Growth and above
single_usetrue: one entry (one a day, on a multi-day pass).Growth and above
gate_idsUp to 20 gates at the visit's site.Growth and above

An empty object, {}, means no rules: the pass admits on its days, at any gate, any number of times. Several days without daily need Starter and above too. A rule your plan doesn't include returns plan_required; a gate at another site returns validation_failed at body.pass_rules.gate_ids. Plans are checked when the visit is made or its pass changes, so moving to a lower plan never changes passes already sent.

Setting rules

  • On create (POST/visits): send pass_rules. Leave it out, and the visit takes its visit type's default rules, as your plan allows them, with only the gates at its site. Publishable keys can't set rules: a pre-registration with pass_rules returns validation_failed at body.pass_rules.
  • On update (PATCH/visits/{visit_id}): pass_rules replaces them; {} clears them. Only while the visit is expected (invalid_state otherwise).

Visit types' default rules are set by admins in the Console; they aren't in the API.

Checking in against the rules

POST/visits/{visit_id}/check-in checks a pass that has rules before anything else. When they don't admit the visitor now, the call fails with invalid_state and an error whose location names the reason:

locationMeans
pass_rule.not_yetBefore its window or its first day.
pass_rule.expiredAfter its window or its last day.
pass_rule.outside_hoursOutside its daily hours.
pass_rule.wrong_gategate_id isn't one of its gates.
pass_rule.usedSingle use, and the visitor has already come in (that day).
pass_rule.endedThe visit was cancelled, declined or missed.
409 invalid_state
{
  "type": "https://docs.agoo.ardent.africa/developers/concepts/errors#invalid_state",
  "title": "Invalid state",
  "status": 409,
  "detail": "This pass admits only during its daily hours. Let them in anyway to override it; the audit trail records it.",
  "code": "invalid_state",
  "request_id": "req_01m4k6c2r0e9q7w3t5y8u1i2o4",
  "errors": [{ "location": "pass_rule.outside_hours", "message": "This pass admits only during its daily hours." }]
}

To let the visitor in anyway, send the request again with "override_pass_rules": true. The override is recorded on the visit's timeline and in the audit trail, as your key or app. ended can never be overridden. Branch on the location, not on detail.

A pass without rules is checked in as before, early or late: its days only bound the signed code that kiosks check offline.

Back in on the same pass

When a visitor on a multi-day pass comes back the next day, or anyone steps out and returns while their pass admits, call POST/visits/{visit_id}/re-enter with the pass's visit (or its latest entry):

Let Ama back in
curl -X POST https://api.agoo.ardent.africa/v1/visits/visit_01m4k5z4j0fxbte6sn6e8tpgza/re-enter \
  -H "Authorization: Bearer $AGOO_API_KEY" \
  -H "Idempotency-Key: 6f1c2a9e-1b3d-4c5e-8f7a-9b0c1d2e3f4a" \
  -H "Content-Type: application/json" \
  -d '{ "gate_id": "gate_01kjpt7m20fpf9vcsaexsxgqr3" }'
  • The pass's visit must be checked_out, with no entry already expected, waiting or on site (invalid_state otherwise).
  • The rules must admit now, or override_pass_rules must be true, with the same pass_rule.<reason> errors as check-in.
  • The response is a new visit for this entry: pass.for_visit_id is the visit that holds the pass, and it's approved as the pass was. It's then checked in as usual: the watchlist runs again and the host is told, so it may come back awaiting_approval if your check-in rules need the host.
  • visit.created and visit.checked_in fire for the entry, so each day on site is its own visit in your data.

Scope: visits:write.

The pass's QR code

The QR code on a pass carries a code signed with the organisation's pass key, so Agoo's kiosks and guard apps can check it with no network. Treat it as opaque: its format is for those apps, and there's no endpoint that reads it. Use the visit's pass object instead. See Signed passes for what it means for an organisation.

On this page