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 emails you an invitation — from no-reply@anis.ly, to the technical contact you gave. It carries the key id (the invitation id the calls below use), the invitation (the single-use enrolment token) and the address to call — the SDK’s authority — and is valid for 24 hours. A sandbox invitation is marked [Sandbox · تجريبي] in its subject; its key never works in production.
  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 read Anis staff the safety code — on the phone. Your software shows it after the key is submitted, and the API returns it as safetyCode. Anis staff phone your technical contact, ask for the code and compare it with the key you submitted. Once they confirm the key it goes live and signs requests, and an email “Your key is live” follows.

The safety code is 16 characters, written XXXX-XXXX-XXXX-XXXX: a short form of the key’s fingerprint. Dashes, spaces and letter case do not matter when you read it out. The call is the check that the key Anis holds is the key you made.

Anis staff never ask for your invitation

The invitation reaches you only by email. Anis staff phone you for the safety code, never for the invitation. If someone asks for the invitation, do not give it — write to support@anis.ly.

The four enrolment calls are authorised with the token — Authorization: Enrollment <token> — not with a signature. Their answers are signed by Anis — every one, success and refusal — and your client requires the signature and verifies it like that of any signed call (see Verifying answers); an enrolment answer without one is a failure. The proof also names your key id: a token that does not belong to that key is answered like an unknown key, as resource_not_found.

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),
}, 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}");
Console.WriteLine($"Your safety code: {submitted.SafetyCode} — Anis staff will call you and ask you to read it.");

submitted.KeyId is the key id every signature will carry. submitted.SafetyCode is the code you read to Anis staff on the phone. SafetyCode and the check below are in the SDK since 1.3.0.

When you submit the key, the .NET SDK also checks the fingerprint the server returns against your own key. If they differ, it stops with EnrollmentKeyMismatchException and sends nothing more: do not continue, and write to support@anis.ly.

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"
}

Send the public members only — a d is refused. Only the length of the window is used, and it starts when Anis staff activate the key. Anis keeps the shorter of the validity you ask for and the validity Anis staff set (never more than two years). The answer carries keyId, thumbprint, safetyCode, challenge and challengeGeneration. Keep the challenge until the proof is accepted: it is returned only here. Show safetyCode to whoever will take the call from Anis staff — it is what they read out.

The key submission has no field for your networks. Tell Anis staff the networks you will call from when you ask for access (see Getting access). A call from any other address is refused as insufficient_scope.

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. More than about 30 minutes after the key submission a proof can no longer succeed: do not prove again — ask Anis staff to restart the enrolment. After five failed proofs the next one is refused as invitation_invalid.

Where the key stands

Read the enrolment status with the same token:

state Meaning
pendingInvitation The invitation is open; no key has been submitted yet
pendingProof Submitted; possession not proved yet
pendingApproval Proved; waiting for Anis staff to check the safety code with you on the phone and confirm the key
active Confirmed — the key signs requests
unavailable The key is being replaced by a newer one: move your signer to the new key id before the overlap ends

active can appear a moment before calls signed with the key work: if your very first call is refused, wait a moment and try the same call again.

keyExpiresAt is the date your active key stops working (absent until the key is active). Ask Anis staff for a replacement weeks before it: a key past its end date is refused as invalid_credentials on every call, including reveals of cards you already bought.

Time limits

  • The invitation is valid for 24 hours. Read the invitation for its exact expiresAt. It limits only the key submission: once your key is submitted, the status read keeps answering with the same token after the invitation’s own expiry, until the key ends.
  • The proof must follow the key submission within about 30 minutes by default; a later proof comes back "failed" and can no longer succeed — do not prove again, ask Anis staff to restart the enrolment. 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 (it is valid for 24 hours): ask Anis staff for a new one
Anis staff say the safety code you read does not match Do not continue. Check that you read the code of the key you submitted, and write to support@anis.ly — Anis staff restart the enrolment if there is any doubt
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" Less than about 30 minutes after the key submission: check the proof message, the 64-byte signature form and the challenge generation, and prove again. Later than that a proof can no longer succeed: do not prove again — ask Anis staff to restart the enrolment
The proof is refused as invitation_invalid Five proofs failed: ask Anis staff to restart the enrolment
The proof is refused as key_duplicate The proof was already accepted (its answer was lost and the same proof was sent again). Read the status before anything else
The proof is refused as resource_not_found The token and the key id do not belong together: send the key id from the key submission with that invitation’s token. If Anis staff restarted the enrolment, the old token no longer works: enrol again with the new invitation
The proof is refused as challenge_expired Anis staff restarted the enrolment: enrol again with the new invitation
The status read is refused as resource_not_found The invitation or token is unknown, the invitation lapsed before any key was submitted, or the key has ended (revoked, retired after a replacement, or past its end date): ask Anis staff
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

Anis staff can restart the enrolment of one key a limited number of times — four by default, five invitations in all. Save the private key before you submit the public half, so that no restart is spent on a lost key.

Replacing and revoking keys

  • Replacing a key. Anis staff start the replacement and a new invitation is emailed to your technical contact. Enrol the new key exactly as above. Once it is confirmed, both keys work for an overlap Anis staff set — up to 30 days, counted from when the new key starts working. When the overlap ends, the old key is refused as invalid_credentials by itself — nobody needs to do anything. Move your signer to the new key id before then. Ask for a replacement weeks before your key’s keyExpiresAt.
  • 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.