Filling slots in-app

Filling slots in-app

A pending slot does not have to send the user to Geena: with the user present in your app you can render a native picker over what their vault already holds, let their tap grant an existing item, or create the asked-for value directly into their vault — granted to the slot in the same act.

Every route on this page requires the slot’s fill verb: the permission to put data into a slot is what buys the right to see what could go in it.

Slots that ask about a family member (they carry subject in /status) take the same three routes with a person=<alias> parameter — the query string on candidates, a body field on attach and create — naming which person the act is for. See Relatives & subjects.

Three bounds hold everywhere on this surface:

  • Slot-scoped. Enumeration exists only per slot of an active connection, only for what the accepted manifest asked. There is no vault-wide listing.
  • Content rides the listing (documents). A document candidate carries its current data when the user’s key context can open it — so your picker can show which email, and prefill a one-value-per-person form. Whatever cannot be decrypted degrades to a metadata-only row, so listing still works against a sealed vault. Files and ID documents stay metadata-only: their content flows exclusively through a grant and the serving routes.
  • Everything receipted. Listing writes a listed receipt (coalesced per slot per 15 minutes), a pick writes attached, a create writes created — all attributed to your app.

1. List candidates

GET /partner/v1/requests/{requestId}/slots/{slotId}/candidates
{
  "slotId": "7c2b9e11-30cf-4f2e-9f57-b8a4f7f4f2ad",
  "kind": "schema",
  "target": "PersonAddress",
  "candidates": [
    {
      "resourceId": "b7d0…",
      "type": "document",
      "name": "Home address",
      "data": { "streetAddress": "Keizersgracht 1", "city": "Amsterdam" },
      "version": 4,
      "createdAt": "2026-05-02T10:00:00Z",
      "updatedAt": "2026-07-12T08:30:00Z",
      "granted": false
    }
  ]
}

granted: true marks the candidate currently backing the slot — render it as the active pick. A row without data could not be decrypted (sealed vault) — render it by name and let the pick still work.

2. Attach the user’s pick

POST /partner/v1/requests/{requestId}/slots/{slotId}/attach
Content-Type: application/json

{ "resourceId": "b7d0…" }

The user’s tap in your UI is the approval — make the tap unmistakably an act of sharing. The grant is minted with your app recorded as its source; the call is idempotent (alreadyGranted: true on a repeat) and any resource that is not the user’s own slot-matching item answers 404 not_found.

{ "slotId": "7c2b9e11-…", "resourceId": "b7d0…", "granted": true }

3. Create-and-attach

POST /partner/v1/requests/{requestId}/slots/{slotId}

For a schema slot, send JSON — the payload is validated against the slot’s schema (its target names one of the built-in schemas), whatever you send:

{ "name": "Home address", "data": { "street": "Keizersgracht 1", "city": "Amsterdam" } }

For a personal_files slot, send multipart/form-data with a file part and an optional label.

Create is always-create: it mints a fresh resource in the user’s vault and grants it to the slot atomically — it never overwrites, never binds to existing data, and its outcome never depends on what the vault already holds. Requires the slot’s fill verb.

{ "slotId": "7c2b9e11-…", "resourceId": "44e1…", "version": 1, "granted": true }
Warning

Identification documents are never created through this API. An id_document slot answers 400 unsupported_item on create: identity documents come to exist only through Geena’s verification capture ceremony. Offer the pick-existing path; for a fresh capture, send the user to Geena.

Creates need the user’s key context: on Enhanced/Maximum accounts a sealed vault answers 423 sealed — see Errors & step-up.