---
title: "Verifying answers — Anis Developers"
description: "How a client checks that an answer really came from Anis — and why it must throw away any answer it cannot check."
url: https://developers.anis.ly/docs/verifying-answers/
language: en
---

> Every page of this documentation: https://developers.anis.ly/llms.txt

# Verifying answers

> **Using an SDK?**
>
> The SDKs verify every answer before you see it, and throw away any that fail. This page is for writing your own client.

Every answer Anis sends — success or refusal — is signed. **A client throws away any answer it cannot verify**: it does not log it and carry on, it does not act on it. An order answer that cannot be verified is treated like one that never arrived — the outcome is unknown and the order is resumed with the same operation id.

## What Anis signs

Answers are signed with **ECDSA P-256 and SHA-256**, label `sig1`, with parameters `created`, `keyid` and `alg` — and no `expires`. The signed parts, in this order:

| # | Part | Present when |
| --- | --- | --- |
| 1 | `"@status"` | always |
| 2 | `"content-digest"` | always |
| 3 | `"x-request-id"` | always |
| 4 | `"signature-input";req` | you signed the request (absent on enrolment answers) — its value is **your own** `Signature-Input` |
| 5 | `"location"` | the answer carries `Location` |
| 6 | `"retry-after"` | the answer carries `Retry-After` |
| 7 | `"idempotency-replayed"` | the answer carries `Idempotency-Replayed: true` |
| 8 | `"cache-control"` | the answer carries `Cache-Control: no-store` |

The base is built like a request’s: one line per part, then `"@signature-params"` with no newline after it.

```text
"@status": 200
"content-digest": sha-256=:epGQsVay09pY/GXv6MpAjMbsZ7VvobF1nAdylVo/mMk=:
"x-request-id": 01J9R2K8T4V6XQ0M3B7C5D9E1F
"signature-input";req: sig1=("@method" "@authority" "@path" "@query" "x-anis-date");created=1789804800;expires=1789804860;keyid="…";alg="ecdsa-p256-sha256"
"@signature-params": ("@status" "content-digest" "x-request-id" "signature-input";req);created=1789804800;keyid="partner-response-signing/v1-active";alg="ecdsa-p256-sha256"
```

`;req` sits **outside** the quotes, identically in its line and in `@signature-params`.

## The checks, in order

1. Both `Signature` and `Signature-Input` are present; the label is `sig1`; `alg` is `ecdsa-p256-sha256`.
2. **The body matches its `Content-Digest`** — checked before the signature. The signature covers the digest header, not the body, so a client that checks the signature first accepts a swapped body whenever the digest was rewritten too.
3. **Rebuild the list of signed parts from what the answer carries** (the table above) and require the answer’s `Signature-Input` to name exactly that list. Never trust the list it advertises: an answer validly signed over a shorter list leaves its body unprotected.
4. `created` is within **60 seconds** of your clock — an answer has no `expires`, so without this bound a captured answer could be replayed to you forever.
5. The `keyid` names a key in Anis’s published keys (below).
6. The signature is 64 bytes and verifies over the rebuilt base.

Anything else: throw the answer away. The [answer test vectors](https://developers.anis.ly/docs/test-vectors.md#testing-answer-verification) cover every one of these checks.

## Anis’s public keys

[`GET /.well-known/partner-signing-keys.json`](https://developers.anis.ly/reference/signing-keys.md) — the one route that needs no signature — publishes every key that may sign an answer, including the next one before a rotation and the previous one after it. Each has a `kid`, which is what an answer’s `keyid` names. Treat `kid` as an opaque name.

- **Cache the document.** When an answer names a `kid` you do not have, fetch it again **once** — that is the ordinary sign of a rotation. Do not refetch without limit on unknown keys.
- **Refuse the whole document if any key carries a private member (`d`)** — not just that key. A document that leaks a private key is not to be trusted at all.
- **Each coordinate is exactly 32 bytes.** Refuse a key of any other shape.

## The one unsigned answer

If Anis’s own signing is unavailable, it answers `503` without a signature. Treat it like any answer you cannot verify: retry a read, resume an order with the same operation id.
