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

  1. Anis staff send you an invitation — an invitation id and a single-use enrolment token.
  2. You create a P-256 key pair. Save the private half first, where you keep secrets.
  3. You submit the public half and receive a challenge.
  4. You prove you hold the private half by signing a message built from that challenge.
  5. 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 keyPOST /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 proofPOST /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.