If you receive applications: pull fields, not files, and decrypt them yourself
Intake pipelines usually begin with an extraction step, and that step is where the
accuracy budget goes: a two column layout, a date range written three ways, a job
title that is really a department. Everything downstream inherits whatever it guessed,
and everything downstream also inherits whatever a stranger wrote into the document on
purpose.
There is no document here. GET /shared/records returns named fields from
one schema for every applicant. What it actually hands you is ciphertext plus the
record keys sealed to the public key you published, and our open library opens them
locally with a private key you keep wherever you keep private keys.
Registering, so that you can read at all
-
Publish two keys
An X25519 key for receiving sealed record keys and an Ed25519 key for signing your
own announcements, posted to
POST /orgs/{id}/relying-party/keys. Several generations may be active
at once, and a grant records the exact key it was sealed to, so rotating never
breaks an outstanding disclosure.
-
Attest on your own domain
Serve the matching signed statement at
/.well-known/verigrant-relying-party.json. An applicant's browser
fetches that directly from you, never through us, and refuses to seal if it
disagrees with what our registry says.
-
Every registration is logged, permanently
Each key becomes a leaf in an append only Merkle log with signed tree heads. That
does not stop us inserting a fraudulent key. It makes doing so public, permanent and
attributable, which is the entire and deliberate claim of a transparency log.
-
Keep the private key away from us
The library supports a key provider backed by a KMS, an HSM or, with a warning on
every use, a file. There is no route on this service that would accept a private
key, and there never will be.
Two doors, and which one to lead with
REST is the production door. It is what Workday, an applicant tracking system, a
student information system and your own software integrate against, with an API key
that belongs to your institution, and it is the only door the sealed record is served
through. Where the people deciding this are the people who run your systems, this is
the door to put in front of them: it is versioned, it can be monitored, and it can go
through a review that ends in a signature.
MCP is the other door onto the same service, and it adds nothing. That is the point of
it. Every tool is a call to a route listed above, so there is no second set of rules
to drift out of step with the first. What it removes is the project: an institution
with no integration budget and nobody to assign can hand an assistant a URL and a
grant token and be reading the same day, which is the version of this that gets used
where procurement is the obstacle rather than the interface.
Two limits come with that door and are better read here than discovered. The tools
wrap the grant check and the profile read, so an institution that wants the encrypted
bundle integrates over REST. And a deployment expecting agent traffic wants its rate
limit raised, because a conversation makes several calls where a REST client makes
one.
preview, then advance, then full
# two credentials. the grant, and proof you are you.
GET https://verigrant.com/api/shared/records?kind=preview
Authorization: Bearer v1.…
X-Verigrant-Relying-Party: Key vgrp_…
# ciphertext, plus record keys sealed to your key.
{ "statement": { "kind": "preview", "entries": [ … ] },
"signature": "…",
"records": [ { "ciphertext": "…", "sealed_dek": "…" } ] }
# you shortlisted. now ask for the rest.
POST https://verigrant.com/api/shared/advance
# until they answer, the full keys do not exist.
GET https://verigrant.com/api/shared/records?kind=full
# -> 409 bundle_not_sealed, which is not an error
# on your side. it is the gate working.
What every refusal means
The response for each state of a delegated read
| State |
Answer |
| Signed, unexpired, unrevoked, in scope, and you are registered |
200 |
| No institution credential presented |
403 relying_party_required |
| Valid, but issued to a different institution |
403 |
| Valid, but that scope was never granted |
403 insufficient_scope |
| Withdrawn by its owner |
403 revoked |
| Asked for a stage the candidate has not sealed |
409 bundle_not_sealed |
| Expired, unknown, tampered with, or absent |
401 |
What you keep, stated once
A record you pulled and decrypted is yours, as a dated snapshot of a specific
version. Withdrawal stops the next read and every further seal on the very next
request. It does not reach what you already opened, and we tell the applicant so at
the moment they agree to advance rather than implying otherwise.
A fresh key is used for every saved version, so a delegation cannot quietly follow a
record forward. What you were given is what you were given.