Enrol a key
You cannot call the API until a key of yours is active. Enrolment turns an invitation from Anis into that key.
The steps
- Anis staff send you an invitation — an invitation id and a single-use enrolment token.
- You create a P-256 key pair. Save the private half first, where you keep secrets.
- You submit the public half and receive a challenge.
- You prove you hold the private half by signing a message built from that challenge.
- You give Anis staff the key’s fingerprint through the channel you agreed with them — not through the API. They record it and confirm the key. From that moment it signs requests.
The four enrolment calls are authorised with the token — Authorization: Enrollment <token> — not with a
signature. Their answers are still signed by Anis, and your client should verify them like any other.
Save the private key before you submit it
An invitation takes exactly one key. If you submitted the public half and then lost the private half, the invitation is spent on a key nobody holds, and Anis staff must restart the enrolment.
With the .NET SDK
using var key = ECDsa.Create(ECCurve.NamedCurves.nistP256);
await File.WriteAllTextAsync("/secure/partner-key.pem", key.ExportPkcs8PrivateKeyPem(), ct); // first
using var enrollment = AnisEnrollmentClient.Create(authority, invitationId, enrollmentToken);
var submitted = await enrollment.SubmitKeyAsync(new EnrollmentKeyRequest
{
PublicJwk = AnisEnrollmentClient.PublicJwkOf(key),
NotBefore = DateTimeOffset.UtcNow,
ExpiresAt = DateTimeOffset.UtcNow.AddYears(1),
Cidrs = ["203.0.113.0/24"], // the networks you will call from — a proposal
}, ct);
var status = await enrollment.ProveAsync(submitted, key, ct);
if (status.ProofState != "accepted")
throw new InvalidOperationException("The proof failed — check the key and prove again."); // state: pendingApproval
Console.WriteLine($"key id {submitted.KeyId}, fingerprint {submitted.Thumbprint}");
submitted.KeyId is the key id every signature will carry. submitted.Thumbprint is the fingerprint Anis staff
record.
Without an SDK
Submit the key — POST /v1/enrollments/{invitationId}/keys:
{
"publicJwk": { "kty": "EC", "crv": "P-256", "x": "<32 bytes, base64url>", "y": "<32 bytes, base64url>" },
"notBefore": "2026-09-24T08:00:00Z",
"expiresAt": "2027-09-24T08:00:00Z",
"cidrs": ["203.0.113.0/24"]
}
Send the public members only — a d is refused. Only the length of the window is used — kept between 1 day and
2 years by default — and it starts when Anis staff activate the key. The answer carries keyId, thumbprint, challenge and challengeGeneration. Keep the challenge
until the proof is accepted: it is returned only here.
Build the proof message. Not the bare challenge — five values, joined by a single \n, with none after the
last:
anis.partners.v2.credential-proof
<keyId, lower-case with hyphens>
<challengeGeneration, as a decimal number>
<SHA-256 of the challenge's UTF-8 bytes, as lower-case hex>
<thumbprint, exactly as returned>
Sign those bytes with ECDSA P-256 and SHA-256. The signature must be the 64-byte form (r then s, 32 bytes
each), not DER, encoded as base64url without padding.
Submit the proof — POST /v1/enrollments/{invitationId}/proof:
{ "keyId": "<keyId>", "challengeGeneration": 1, "signature": "<86 base64url characters>" }
Check the answer’s proofState. A proof that does not verify is not refused: the answer says "failed", and you
may try again. Only "accepted" moves the key on. After five failed proofs, further proofs are refused as
rate_limited until Anis staff restart the enrolment.
Where the key stands
Read the enrolment status with the same token:
state |
Meaning |
|---|---|
pendingProof |
Submitted; possession not proved yet |
pendingApproval |
Proved; waiting for Anis staff to record the fingerprint and confirm the key |
active |
Confirmed — the key signs requests |
unavailable |
The key left enrolment (revoked, expired or replaced); ask for a new invitation |
Time limits
- The invitation is valid for about a day by default. Read the invitation for its
exact
expiresAt. - The proof must follow the key submission within about 30 minutes by default; a later proof comes back
"failed". If your key lives in a vault or hardware module whose signing needs an approval, arrange that approval before you submit the key.
When something goes wrong
| What happened | What to do |
|---|---|
The invitation is refused as invitation_invalid |
It is unknown, used or expired: ask Anis staff for a new one |
The key is refused as key_proof_invalid |
The public key is not a usable P-256 key: check kty, crv, and 32-byte x and y |
The proof comes back proofState: "failed" |
Check the proof message, the 64-byte signature form and the challenge generation, and prove again — within about 30 minutes of the key submission |
Proofs are refused as rate_limited |
Five proofs failed: ask Anis staff to restart the enrolment |
The proof is refused as challenge_expired |
Anis staff restarted the enrolment: enrol again with the new invitation |
| The key submission got no answer | Submit the same key once more. If that is refused as key_duplicate, the first one was taken — ask Anis staff to restart the enrolment |
Replacing and revoking keys
- Replacing a key. Anis staff start the replacement and send a new invitation. Enrol the new key exactly as above. Once it is confirmed, both keys work for an overlap period Anis sets — switch your signer to the new key id within it.
- Revoking a key. From the moment Anis staff revoke a key, every call signed with it is refused with
invalid_credentials. Revoking a key you no longer use does not affect your current one.