Authentication¶
Production partner backend requests use Ed25519 request signatures.
Bearer API keys are not used for partner API access.
Required Headers¶
Every signed request includes:
X-Yanez-Partner-Id: ptr_abc123
X-Yanez-Key-Id: partner_key_2026_01
X-Yanez-Timestamp: 2026-06-26T17:00:00Z
X-Yanez-Nonce: req_01JABCDEF456
X-Yanez-Content-SHA256: base64url(sha256(raw_request_body_bytes))
X-Yanez-Signature-Alg: EdDSA
X-Yanez-Signature: base64url(ed25519_signature)
For requests with no body, hash zero bytes.
Canonical String¶
Build the canonical string by joining these lines with \n and no trailing
newline:
YANEZ-SIG-V1
<HTTP_METHOD_UPPERCASE>
<PATH_WITH_QUERY>
<X-Yanez-Partner-Id>
<X-Yanez-Key-Id>
<X-Yanez-Signature-Alg>
<X-Yanez-Timestamp>
<X-Yanez-Nonce>
<X-Yanez-Content-SHA256>
PATH_WITH_QUERY must be the raw path and query exactly as sent. Do not sort
query parameters or normalize percent encoding after signing.
Python Signing Example¶
import base64
import hashlib
import secrets
from datetime import datetime, timezone
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
BASE_URL = "https://ptest.yanez.ai"
PARTNER_ID = "ptr_abc123"
KEY_ID = "partner_key_2026_01"
PRIVATE_KEY_HEX = "replace-with-32-byte-private-key-hex"
def b64url(raw: bytes) -> str:
return base64.urlsafe_b64encode(raw).rstrip(b"=").decode()
def sign_yanez_request(method: str, path_with_query: str, body: bytes = b"") -> dict:
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
nonce = secrets.token_urlsafe(18)
body_hash = b64url(hashlib.sha256(body).digest())
canonical = "\n".join([
"YANEZ-SIG-V1",
method.upper(),
path_with_query,
PARTNER_ID,
KEY_ID,
"EdDSA",
timestamp,
nonce,
body_hash,
]).encode()
private_key = Ed25519PrivateKey.from_private_bytes(bytes.fromhex(PRIVATE_KEY_HEX))
return {
"Content-Type": "application/json",
"X-Yanez-Partner-Id": PARTNER_ID,
"X-Yanez-Key-Id": KEY_ID,
"X-Yanez-Signature-Alg": "EdDSA",
"X-Yanez-Timestamp": timestamp,
"X-Yanez-Nonce": nonce,
"X-Yanez-Content-SHA256": body_hash,
"X-Yanez-Signature": b64url(private_key.sign(canonical)),
}
Serialize a JSON request body once, then use the exact same bytes for hashing, signing, and sending.
Verification¶
Yanez verifies:
- all required
X-Yanez-*headers are present - the partner account is active
- the
kidresolves to an active signing key - the signing key is inside its validity window
X-Yanez-Signature-Algmatches the algorithm of the resolved key- the timestamp is within ±300 seconds of server time
- the body hash matches the raw request body
- the Ed25519 signature matches the canonical string
- the nonce has not been used before for the same partner and key (replay window: 600 seconds)
Authentication failures return a generic 401 Unauthorized — the response does not indicate which check failed.
Key Rotation¶
Recommended rotation process:
- Generate a new Ed25519 key pair on the partner backend.
- Send the new public JWK and
kidto Yanez. - Wait for Yanez to confirm the new key is active.
- Start signing new requests with the new
kid. - Keep the old key available until in-flight requests drain.
- Ask Yanez to revoke the old
kid.