{
  "openapi": "3.1.0",
  "info": {
    "title": "Feranor PQC Sidecar",
    "version": "1",
    "summary": "Post-quantum encryption and signature over HTTP, for applications in any language.",
    "description": "The API of the Feranor PQC sidecar, version 1. Content goes in and out as raw bodies; messages are CMS (RFC 5652) in binary form, with the media types of RFC 8551; errors are problem details (RFC 9457) with a stable code. In JSON answers, members without a value are left out. The sidecar listens on a Unix socket by default (no server URL applies: send any Host, for example http://localhost), or on a loopback TCP port with an access token. Every route, query parameter, header, media type, problem code and JSON member here is checked against the sidecar by its tests. The guide's sidecar chapter tells how to deploy it."
  },
  "servers": [
    {
      "url": "http://localhost",
      "description": "The Unix socket (Sidecar:UnixSocket, the default) or the TCP port (Sidecar:Port)."
    }
  ],
  "security": [
    {},
    {
      "accessToken": []
    }
  ],
  "tags": [
    {
      "name": "Operations",
      "description": "Encryption, decryption, signature, verification and inspection: each is recorded in the audit journal when one is configured."
    },
    {
      "name": "Keys",
      "description": "The public half of the key store, for senders and verifiers."
    },
    {
      "name": "Administration",
      "description": "Version, operations and logs, for the console and the administrator."
    },
    {
      "name": "Health",
      "description": "Probes for systemd, Docker and Kubernetes."
    }
  ],
  "paths": {
    "/v1/encrypt": {
      "post": {
        "operationId": "encrypt",
        "tags": [
          "Operations"
        ],
        "summary": "Encrypt content for one or more recipients",
        "description": "Encrypts the body, of any size, as an AuthEnvelopedData (RFC 5083) for the recipients of the Feranor-Recipient headers. The message is written to a temporary file and sent once the whole request is read; only ciphertext touches the disk. With sign=true, the content is signed first with the current signing key, in memory within Sidecar:MaxInMemorySize, and co-signed with the classical key as Sidecar:HybridSignature says.",
        "parameters": [
          {
            "name": "sign",
            "in": "query",
            "required": false,
            "description": "Sign the content before encrypting it. Absent means false; any other value than true or false is refused with invalid-request.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "$ref": "#/components/parameters/Feranor-Recipient"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The content. Any Content-Type is accepted; the body is read as bytes.",
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary",
                "contentMediaType": "application/octet-stream"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The encrypted message, CMS in binary form (DER, or BER for large content).",
            "headers": {
              "Feranor-License": {
                "$ref": "#/components/headers/Feranor-License"
              }
            },
            "content": {
              "application/pkcs7-mime; smime-type=authEnveloped-data": {
                "schema": {
                  "type": "string",
                  "format": "binary",
                  "contentMediaType": "application/pkcs7-mime"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/decrypt": {
      "post": {
        "operationId": "decrypt",
        "tags": [
          "Operations"
        ],
        "summary": "Decrypt a message with the key store",
        "description": "Decrypts an AuthEnvelopedData of any size with the sidecar's key store. A signed message is verified too, in memory within Sidecar:MaxInMemorySize, and its signer reported in the Feranor-Signer headers. A message that is not encrypted, or a classical one (an EnvelopedData, or RSA or ECDH recipients), is refused with invalid-request: fpqc reencrypt brings a classical message to post-quantum cryptography. An unsigned message proves nothing about its sender: use requireSignature=true, or the Sidecar:RequireSignature setting, where a signature is expected.",
        "parameters": [
          {
            "name": "requireSignature",
            "in": "query",
            "required": false,
            "description": "Refuse an unsigned message with signature-required (422). The Sidecar:RequireSignature setting does the same for every request. Absent means false; any other value than true or false is refused with invalid-request.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "An AuthEnvelopedData, as encrypt answers it. Any Content-Type is accepted.",
          "content": {
            "application/pkcs7-mime; smime-type=authEnveloped-data": {
              "schema": {
                "type": "string",
                "format": "binary",
                "contentMediaType": "application/pkcs7-mime"
              }
            },
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary",
                "contentMediaType": "application/octet-stream"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The decrypted content. Feranor-Signed is false for an unsigned message; for a signed one, the signer headers describe the verified signer.",
            "headers": {
              "Feranor-License": {
                "$ref": "#/components/headers/Feranor-License"
              },
              "Feranor-Signed": {
                "$ref": "#/components/headers/Feranor-Signed"
              },
              "Feranor-Signer": {
                "$ref": "#/components/headers/Feranor-Signer"
              },
              "Feranor-Signer-Thumbprint": {
                "$ref": "#/components/headers/Feranor-Signer-Thumbprint"
              },
              "Feranor-Signature-Algorithm": {
                "$ref": "#/components/headers/Feranor-Signature-Algorithm"
              },
              "Feranor-Hybrid": {
                "$ref": "#/components/headers/Feranor-Hybrid"
              },
              "Feranor-Co-Signer": {
                "$ref": "#/components/headers/Feranor-Co-Signer"
              },
              "Feranor-Co-Signature-Algorithm": {
                "$ref": "#/components/headers/Feranor-Co-Signature-Algorithm"
              }
            },
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary",
                  "contentMediaType": "application/octet-stream"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/sign": {
      "post": {
        "operationId": "sign",
        "tags": [
          "Operations"
        ],
        "summary": "Sign content",
        "description": "Signs the body with the current post-quantum signing key, co-signed with the classical key as Sidecar:HybridSignature says. By default the content goes inside the SignedData, in memory within Sidecar:MaxInMemorySize. With detached=true, the body is read as a stream, of any size, and the answer is the signature alone, once the whole body is read.",
        "parameters": [
          {
            "name": "detached",
            "in": "query",
            "required": false,
            "description": "Answer with a detached signature (application/pkcs7-signature) rather than a SignedData holding the content. Absent means false; any other value than true or false is refused with invalid-request.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "requestBody": {
          "required": true,
          "description": "The content. Any Content-Type is accepted.",
          "content": {
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary",
                "contentMediaType": "application/octet-stream"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A SignedData holding the content, or with detached=true the detached signature.",
            "headers": {
              "Feranor-License": {
                "$ref": "#/components/headers/Feranor-License"
              }
            },
            "content": {
              "application/pkcs7-mime; smime-type=signed-data": {
                "schema": {
                  "type": "string",
                  "format": "binary",
                  "contentMediaType": "application/pkcs7-mime"
                }
              },
              "application/pkcs7-signature": {
                "schema": {
                  "type": "string",
                  "format": "binary",
                  "contentMediaType": "application/pkcs7-signature"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/verify": {
      "post": {
        "operationId": "verify",
        "tags": [
          "Operations"
        ],
        "summary": "Verify a signature",
        "description": "Verifies a SignedData against the sidecar's trust settings (Sidecar:TrustedCertificates, Sidecar:Revocation, Sidecar:RequireHybridSignature and the others). Either the body is a SignedData holding its content, answered with the content; or the body is multipart/form-data (RFC 7578) with one signature part and one content part, answered with 204. The content part is verified as a stream, of any size, when it follows the signature part; before it, it is read in memory within Sidecar:MaxInMemorySize. The signer is reported in the Feranor-Signer headers.",
        "requestBody": {
          "required": true,
          "description": "A SignedData holding its content (any Content-Type but multipart/form-data), or the detached signature and its content as multipart/form-data.",
          "content": {
            "application/pkcs7-mime; smime-type=signed-data": {
              "schema": {
                "type": "string",
                "format": "binary",
                "contentMediaType": "application/pkcs7-mime"
              }
            },
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary",
                "contentMediaType": "application/octet-stream"
              }
            },
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "signature",
                  "content"
                ],
                "properties": {
                  "signature": {
                    "type": "string",
                    "format": "binary",
                    "contentMediaType": "application/pkcs7-signature",
                    "description": "The detached signature. Put it first, so that the content is verified as a stream."
                  },
                  "content": {
                    "type": "string",
                    "format": "binary",
                    "contentMediaType": "application/octet-stream",
                    "description": "The signed content."
                  }
                }
              },
              "encoding": {
                "signature": {
                  "contentType": "application/pkcs7-signature"
                },
                "content": {
                  "contentType": "application/octet-stream"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The signature is valid: the signed content, for a SignedData body.",
            "headers": {
              "Feranor-License": {
                "$ref": "#/components/headers/Feranor-License"
              },
              "Feranor-Signed": {
                "$ref": "#/components/headers/Feranor-Signed"
              },
              "Feranor-Signer": {
                "$ref": "#/components/headers/Feranor-Signer"
              },
              "Feranor-Signer-Thumbprint": {
                "$ref": "#/components/headers/Feranor-Signer-Thumbprint"
              },
              "Feranor-Signature-Algorithm": {
                "$ref": "#/components/headers/Feranor-Signature-Algorithm"
              },
              "Feranor-Hybrid": {
                "$ref": "#/components/headers/Feranor-Hybrid"
              },
              "Feranor-Co-Signer": {
                "$ref": "#/components/headers/Feranor-Co-Signer"
              },
              "Feranor-Co-Signature-Algorithm": {
                "$ref": "#/components/headers/Feranor-Co-Signature-Algorithm"
              }
            },
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary",
                  "contentMediaType": "application/octet-stream"
                }
              }
            }
          },
          "204": {
            "description": "The signature is valid, for a multipart body.",
            "headers": {
              "Feranor-License": {
                "$ref": "#/components/headers/Feranor-License"
              },
              "Feranor-Signed": {
                "$ref": "#/components/headers/Feranor-Signed"
              },
              "Feranor-Signer": {
                "$ref": "#/components/headers/Feranor-Signer"
              },
              "Feranor-Signer-Thumbprint": {
                "$ref": "#/components/headers/Feranor-Signer-Thumbprint"
              },
              "Feranor-Signature-Algorithm": {
                "$ref": "#/components/headers/Feranor-Signature-Algorithm"
              },
              "Feranor-Hybrid": {
                "$ref": "#/components/headers/Feranor-Hybrid"
              },
              "Feranor-Co-Signer": {
                "$ref": "#/components/headers/Feranor-Co-Signer"
              },
              "Feranor-Co-Signature-Algorithm": {
                "$ref": "#/components/headers/Feranor-Co-Signature-Algorithm"
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/inspect": {
      "post": {
        "operationId": "inspect",
        "tags": [
          "Operations"
        ],
        "summary": "Describe a message without any key",
        "description": "Reads a message without any key and verifies nothing: its type, algorithms (as OIDs), recipients or signers, and sizes read from the encoding. An encrypted message of any size is read as a stream; a SignedData is read in memory within Sidecar:MaxInMemorySize. The whole body is read before the answer, a refused one included. Served even while the audit journal cannot be written.",
        "requestBody": {
          "required": true,
          "description": "A CMS message: AuthEnvelopedData, SignedData or a classical EnvelopedData. Any Content-Type is accepted.",
          "content": {
            "application/pkcs7-mime": {
              "schema": {
                "type": "string",
                "format": "binary",
                "contentMediaType": "application/pkcs7-mime"
              }
            },
            "application/octet-stream": {
              "schema": {
                "type": "string",
                "format": "binary",
                "contentMediaType": "application/octet-stream"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "What the message is. Nothing in it is verified.",
            "headers": {
              "Feranor-License": {
                "$ref": "#/components/headers/Feranor-License"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InspectionReport"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "413": {
            "$ref": "#/components/responses/PayloadTooLarge"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/recipient": {
      "get": {
        "operationId": "getRecipient",
        "tags": [
          "Keys"
        ],
        "summary": "What senders encrypt to",
        "description": "The current encryption key's certificate, or its public key when it has none, in PEM (RFC 7468). The key is first opened with the store's secret: a key the store does not hold is never published.",
        "responses": {
          "200": {
            "description": "The certificate or the public key, in PEM.",
            "headers": {
              "Feranor-License": {
                "$ref": "#/components/headers/Feranor-License"
              }
            },
            "content": {
              "application/x-pem-file": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NoSuchKey"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/signer": {
      "get": {
        "operationId": "getSigner",
        "tags": [
          "Keys"
        ],
        "summary": "What verifiers trust",
        "description": "The current post-quantum signing certificate, in PEM, once its key opens with the store's secret.",
        "responses": {
          "200": {
            "description": "The certificate, in PEM.",
            "headers": {
              "Feranor-License": {
                "$ref": "#/components/headers/Feranor-License"
              }
            },
            "content": {
              "application/x-pem-file": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NoSuchKey"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/co-signer": {
      "get": {
        "operationId": "getCoSigner",
        "tags": [
          "Keys"
        ],
        "summary": "What verifiers of hybrid signatures trust too",
        "description": "The current classical (ECDSA) co-signing certificate, in PEM, once its key opens with the store's secret; 404 when the store has none, signatures being post-quantum alone.",
        "responses": {
          "200": {
            "description": "The certificate, in PEM.",
            "headers": {
              "Feranor-License": {
                "$ref": "#/components/headers/Feranor-License"
              }
            },
            "content": {
              "application/x-pem-file": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NoSuchKey"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/v1/version": {
      "get": {
        "operationId": "getVersion",
        "tags": [
          "Administration"
        ],
        "summary": "Product, version, profile, platform and license",
        "description": "For clients that show them, such as the console. The license key is checked at each call; its state never changes what the sidecar does.",
        "responses": {
          "200": {
            "description": "The version report.",
            "headers": {
              "Feranor-License": {
                "$ref": "#/components/headers/Feranor-License"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionReport"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/operations": {
      "get": {
        "operationId": "getOperations",
        "tags": [
          "Administration"
        ],
        "summary": "The last operations",
        "description": "The last cryptographic operations (a POST to a route of /v1), most recent first, from memory, emptied by a restart. Nothing about the content. Needs the administration token; without the Sidecar:AdministrationTokenFile setting, the answer is 403. The audit journal keeps the same operations on disk, chained.",
        "security": [
          {
            "administrationToken": []
          },
          {
            "administrationToken": [],
            "accessToken": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many operations, at most. Brought within 1 and the capacity.",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The operations.",
            "headers": {
              "Feranor-License": {
                "$ref": "#/components/headers/Feranor-License"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationsReport"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/v1/logs": {
      "get": {
        "operationId": "getLogs",
        "tags": [
          "Administration"
        ],
        "summary": "The last log entries",
        "description": "The last log entries at level or above, most recent first, from memory, emptied by a restart: the events of the container's output and of the telemetry, kept from Information up whatever the output's level (from Warning up for the framework's own categories). Needs the administration token, as /v1/operations.",
        "security": [
          {
            "administrationToken": []
          },
          {
            "administrationToken": [],
            "accessToken": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "How many entries, at most. Brought within 1 and the capacity.",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 200
            }
          },
          {
            "name": "level",
            "in": "query",
            "required": false,
            "description": "The lowest level returned, case ignored. Any other value is refused with invalid-request.",
            "schema": {
              "type": "string",
              "enum": [
                "Information",
                "Warning",
                "Error",
                "Critical"
              ],
              "default": "Information"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The log entries.",
            "headers": {
              "Feranor-License": {
                "$ref": "#/components/headers/Feranor-License"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LogsReport"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/health/live": {
      "get": {
        "operationId": "getLiveness",
        "tags": [
          "Health"
        ],
        "summary": "Liveness",
        "description": "Healthy as long as the process answers. No access token; served on Sidecar:HealthPort when one is set, the only path there with /health/ready.",
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "Alive.",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/HealthStatus"
                }
              }
            }
          }
        }
      }
    },
    "/health/ready": {
      "get": {
        "operationId": "getReadiness",
        "tags": [
          "Health"
        ],
        "summary": "Readiness",
        "description": "Ready when the current encryption key opens with the store's secret (or no key store is configured), the Unix socket is still the sidecar's, and the audit journal can be written. No access token.",
        "security": [
          {}
        ],
        "responses": {
          "200": {
            "description": "Ready.",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/HealthStatus"
                }
              }
            }
          },
          "503": {
            "description": "Not ready: the cause is in the logs.",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/HealthStatus"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "accessToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "The access token of Sidecar:AccessTokenFile, on TCP: sent with every request but the health checks, in exactly one Authorization header, and compared in constant time. Any address beyond loopback needs it. On the Unix socket, the default, the socket's file permissions decide who may call, and no token is sent."
      },
      "administrationToken": {
        "type": "apiKey",
        "in": "header",
        "name": "Feranor-Administration-Token",
        "description": "The administration token of Sidecar:AdministrationTokenFile, for /v1/operations and /v1/logs only. It differs from the access token, which is sent too on TCP."
      }
    },
    "parameters": {
      "Feranor-Recipient": {
        "name": "Feranor-Recipient",
        "in": "header",
        "required": true,
        "description": "The recipients, each the base64 of a DER certificate or SubjectPublicKeyInfo, separated by commas; or one per header, the header repeated. At most 1024 recipients, and the headers may total 1 MiB (a certificate signed with SLH-DSA is about 67 KB in base64).",
        "schema": {
          "type": "string",
          "minLength": 1
        }
      }
    },
    "headers": {
      "Feranor-License": {
        "description": "The state of the sidecar's license key, on every answer under /v1/, errors included, but the refusals that come before it (Origin, Host, access token). The details are in GET /v1/version. It changes nothing in the answer: the sidecar works whatever the license.",
        "schema": {
          "type": "string",
          "enum": [
            "valid",
            "missing",
            "invalid",
            "expired",
            "not-verifiable"
          ]
        }
      },
      "Feranor-Signed": {
        "description": "Whether the content was signed.",
        "schema": {
          "type": "string",
          "enum": [
            "true",
            "false"
          ]
        }
      },
      "Feranor-Signer": {
        "description": "The signer's subject, percent-encoded UTF-8.",
        "schema": {
          "type": "string"
        }
      },
      "Feranor-Signer-Thumbprint": {
        "description": "SHA-256 of the signer's certificate, in hexadecimal.",
        "schema": {
          "type": "string",
          "pattern": "^[0-9A-F]{64}$"
        }
      },
      "Feranor-Signature-Algorithm": {
        "description": "The OID of the post-quantum signature algorithm.",
        "schema": {
          "type": "string",
          "pattern": "^[0-2](\\.[0-9]+)+$"
        }
      },
      "Feranor-Hybrid": {
        "description": "true when a classical co-signer was verified next to the post-quantum signer.",
        "schema": {
          "type": "string",
          "enum": [
            "true",
            "false"
          ]
        }
      },
      "Feranor-Co-Signer": {
        "description": "The classical co-signer's subject, percent-encoded UTF-8; for a hybrid signature only.",
        "schema": {
          "type": "string"
        }
      },
      "Feranor-Co-Signature-Algorithm": {
        "description": "The OID of the classical co-signature algorithm, such as 1.2.840.10045.4.3.3 (ecdsa-with-SHA384) for P-384; for a hybrid signature only.",
        "schema": {
          "type": "string",
          "pattern": "^[0-2](\\.[0-9]+)+$"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "invalid-request: a missing or malformed header, part or parameter, or a message sent to the wrong operation; invalid-input: not a valid message, key or certificate. On TCP, a Host header that is neither localhost, an IP address nor a name of Sidecar:AllowedHosts is refused too, without a code.",
        "headers": {
          "Feranor-License": {
            "$ref": "#/components/headers/Feranor-License"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "The access token is missing or wrong (TCP with Sidecar:AccessTokenFile).",
        "headers": {
          "WWW-Authenticate": {
            "schema": {
              "type": "string",
              "const": "Bearer"
            }
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Forbidden": {
        "description": "A request with an Origin header (from a web page), or /v1/operations and /v1/logs without the administration token. No code.",
        "headers": {
          "Feranor-License": {
            "$ref": "#/components/headers/Feranor-License"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "NoSuchKey": {
        "description": "invalid-request: the key store has no such key.",
        "headers": {
          "Feranor-License": {
            "$ref": "#/components/headers/Feranor-License"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "PayloadTooLarge": {
        "description": "payload-too-large: beyond Sidecar:MaxInMemorySize (100 MiB by default) for an operation in memory, or Sidecar:MaxStreamSize (16 GiB by default) for any request.",
        "headers": {
          "Feranor-License": {
            "$ref": "#/components/headers/Feranor-License"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "recipient-not-found, authentication-failed, signature-invalid, signature-required or algorithm-not-allowed.",
        "headers": {
          "Feranor-License": {
            "$ref": "#/components/headers/Feranor-License"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Too many operations at once (Sidecar:MaxConcurrentRequests, by default the number of processors and at least 4, with a queue four times as long): retry later. No code.",
        "headers": {
          "Feranor-License": {
            "$ref": "#/components/headers/Feranor-License"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "key-store-unavailable: no key store, no signing key with a certificate (nor a co-signing key when Sidecar:HybridSignature is Required), or a store that cannot be opened, the cause being in the logs; audit-journal-unavailable: the audit journal cannot be written, and every operation but inspect is refused until the sidecar restarts.",
        "headers": {
          "Feranor-License": {
            "$ref": "#/components/headers/Feranor-License"
          }
        },
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/Problem"
            }
          }
        }
      }
    },
    "schemas": {
      "ProblemCode": {
        "type": "string",
        "description": "The stable error codes of the API.",
        "enum": [
          "invalid-request",
          "invalid-input",
          "payload-too-large",
          "recipient-not-found",
          "authentication-failed",
          "signature-invalid",
          "signature-required",
          "algorithm-not-allowed",
          "key-store-unavailable",
          "audit-journal-unavailable"
        ]
      },
      "Problem": {
        "type": "object",
        "description": "Problem details (RFC 9457). The errors of the API carry code; the refusals before the API (Origin, Host, tokens, concurrency) and the framework's own (404, 405) do not.",
        "properties": {
          "type": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "detail": {
            "type": "string"
          },
          "instance": {
            "type": "string"
          },
          "traceId": {
            "type": "string"
          },
          "code": {
            "$ref": "#/components/schemas/ProblemCode"
          }
        }
      },
      "HealthStatus": {
        "type": "string",
        "enum": [
          "Healthy",
          "Degraded",
          "Unhealthy"
        ]
      },
      "InspectionReport": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "type",
          "contentType",
          "encrypted",
          "classical",
          "signed",
          "hybrid",
          "recipients",
          "signers",
          "sizes"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "AuthEnvelopedData",
              "SignedData",
              "EnvelopedData"
            ],
            "description": "EnvelopedData for a classical message."
          },
          "contentType": {
            "type": "string",
            "pattern": "^[0-2](\\.[0-9]+)+$",
            "description": "The outer content type."
          },
          "encapsulatedContentType": {
            "type": "string",
            "pattern": "^[0-2](\\.[0-9]+)+$",
            "description": "The type of the content it holds (id-signedData for a signed then encrypted message)."
          },
          "encrypted": {
            "type": "boolean"
          },
          "classical": {
            "type": "boolean",
            "description": "true for a classical message (RSA or ECDH recipients), which decrypt refuses and fpqc reencrypt migrates."
          },
          "signed": {
            "type": "boolean"
          },
          "hybrid": {
            "type": "boolean",
            "description": "true for a SignedData that states both a post-quantum signer and a classical co-signer; nothing verified."
          },
          "contentEncryptionAlgorithm": {
            "type": "string",
            "pattern": "^[0-2](\\.[0-9]+)+$"
          },
          "recipients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RecipientReport"
            },
            "description": "For an encrypted message."
          },
          "signers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SignerReport"
            },
            "description": "For a SignedData. A signed then encrypted message lists none: its signers are encrypted."
          },
          "sizes": {
            "$ref": "#/components/schemas/MessageSizes"
          }
        }
      },
      "MessageSizes": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "message"
        ],
        "description": "In bytes, read from the encoding, never estimated. Structural elements (recipientInfos, authentication, certificates) include their tag and length octets; content is the value's own bytes.",
        "properties": {
          "message": {
            "type": "integer",
            "format": "int64"
          },
          "recipientInfos": {
            "type": "integer",
            "format": "int64",
            "description": "Encrypted message: the element holding every entry."
          },
          "content": {
            "type": "integer",
            "format": "int64",
            "description": "The encrypted content (the sum of its segments in BER), or the signed content, absent for a detached signature."
          },
          "authentication": {
            "type": "integer",
            "format": "int64",
            "description": "Encrypted message: the authentication tag, with the authenticated attributes when present."
          },
          "certificates": {
            "type": "integer",
            "format": "int64",
            "description": "SignedData: the certificates element, absent when the message carries none."
          }
        }
      },
      "RecipientReport": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "kind",
          "sizes"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "kem",
              "key-transport",
              "key-agreement",
              "kek",
              "password",
              "other"
            ]
          },
          "identifier": {
            "type": "string",
            "description": "The recipient identifier."
          },
          "kem": {
            "type": "string",
            "pattern": "^[0-2](\\.[0-9]+)+$",
            "description": "The KEM, or the key-encryption algorithm of a classical entry."
          },
          "kdf": {
            "type": "string",
            "pattern": "^[0-2](\\.[0-9]+)+$"
          },
          "keyWrap": {
            "type": "string",
            "pattern": "^[0-2](\\.[0-9]+)+$"
          },
          "sizes": {
            "$ref": "#/components/schemas/RecipientSizes"
          }
        }
      },
      "RecipientSizes": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "entry",
          "kemCiphertext",
          "encryptedKey"
        ],
        "properties": {
          "entry": {
            "type": "integer",
            "format": "int32",
            "description": "The whole RecipientInfo as encoded."
          },
          "kemCiphertext": {
            "type": "integer",
            "format": "int32"
          },
          "encryptedKey": {
            "type": "integer",
            "format": "int32",
            "description": "The wrapped content key."
          }
        }
      },
      "SignerReport": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "postQuantum",
          "sizes"
        ],
        "properties": {
          "signatureAlgorithm": {
            "type": "string",
            "pattern": "^[0-2](\\.[0-9]+)+$"
          },
          "digestAlgorithm": {
            "type": "string",
            "pattern": "^[0-2](\\.[0-9]+)+$"
          },
          "subject": {
            "type": "string",
            "description": "From the certificate the message carries, as the other certificate members."
          },
          "issuer": {
            "type": "string"
          },
          "serialNumber": {
            "type": "string"
          },
          "thumbprint": {
            "type": "string",
            "pattern": "^[0-9A-F]{64}$",
            "description": "SHA-256 of the certificate."
          },
          "signingTime": {
            "type": "string",
            "format": "date-time"
          },
          "postQuantum": {
            "type": "boolean",
            "description": "false for an ECDSA co-signer."
          },
          "sizes": {
            "$ref": "#/components/schemas/SignerSizes"
          }
        }
      },
      "SignerSizes": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "signature"
        ],
        "properties": {
          "signature": {
            "type": "integer",
            "format": "int32",
            "description": "The signature value."
          }
        }
      },
      "VersionReport": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "product",
          "version",
          "profile",
          "platform",
          "deployment",
          "license"
        ],
        "properties": {
          "product": {
            "type": "string",
            "const": "Feranor PQC Sidecar"
          },
          "version": {
            "type": "string",
            "description": "1.0.0+<commit> for a release."
          },
          "profile": {
            "type": "string",
            "examples": [
              "ANSSI",
              "Standard"
            ]
          },
          "platform": {
            "type": "string",
            "description": ".NET, the operating system and OpenSSL, as fpqc info prints them."
          },
          "deployment": {
            "type": "string",
            "enum": [
              "Default",
              "Regulated"
            ]
          },
          "license": {
            "$ref": "#/components/schemas/LicenseReport"
          }
        }
      },
      "LicenseReport": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "status",
          "expiresSoon"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "Valid",
              "Missing",
              "Invalid",
              "Expired",
              "NotVerifiable"
            ]
          },
          "message": {
            "type": "string"
          },
          "licenseId": {
            "type": "string"
          },
          "licensee": {
            "type": "string"
          },
          "edition": {
            "type": "string"
          },
          "expiresOn": {
            "type": "string",
            "format": "date"
          },
          "expiresSoon": {
            "type": "boolean"
          },
          "daysLeft": {
            "type": "integer",
            "format": "int32"
          }
        }
      },
      "OperationsReport": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "capacity",
          "operations"
        ],
        "properties": {
          "capacity": {
            "type": "integer",
            "format": "int32",
            "description": "How many operations are kept in memory (1000)."
          },
          "operations": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OperationRecord"
            }
          }
        }
      },
      "OperationRecord": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "at",
          "operation",
          "status",
          "aborted",
          "durationMs"
        ],
        "properties": {
          "at": {
            "type": "string",
            "format": "date-time",
            "description": "When the operation was answered."
          },
          "operation": {
            "type": "string",
            "enum": [
              "encrypt",
              "decrypt",
              "sign",
              "verify",
              "inspect"
            ]
          },
          "flags": {
            "type": "string",
            "description": "The query flags set to true (sign, requireSignature, detached), separated by commas."
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "description": "What the client got; 499 when the client went away."
          },
          "code": {
            "$ref": "#/components/schemas/ProblemCode"
          },
          "aborted": {
            "type": "boolean",
            "description": "true for an answer cut short after it had started."
          },
          "inputSize": {
            "type": "integer",
            "format": "int64",
            "description": "The size the request announced."
          },
          "durationMs": {
            "type": "number",
            "format": "double"
          },
          "signer": {
            "type": "string",
            "description": "As Feranor-Signer reported it."
          },
          "signatureAlgorithm": {
            "type": "string",
            "description": "As Feranor-Signature-Algorithm reported it."
          },
          "hybrid": {
            "type": "string",
            "enum": [
              "true",
              "false"
            ],
            "description": "As Feranor-Hybrid reported it."
          },
          "signed": {
            "type": "string",
            "enum": [
              "true",
              "false"
            ],
            "description": "As Feranor-Signed reported it."
          }
        }
      },
      "LogsReport": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "capacity",
          "logs"
        ],
        "properties": {
          "capacity": {
            "type": "integer",
            "format": "int32",
            "description": "How many entries are kept in memory (2000)."
          },
          "logs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LogRecord"
            }
          }
        }
      },
      "LogRecord": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "at",
          "level",
          "category"
        ],
        "properties": {
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "level": {
            "type": "string",
            "enum": [
              "Information",
              "Warning",
              "Error",
              "Critical"
            ]
          },
          "category": {
            "type": "string"
          },
          "eventId": {
            "type": "integer",
            "format": "int32"
          },
          "eventName": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "exceptionType": {
            "type": "string",
            "description": "An exception's type, never its message, which may hold a server path."
          }
        }
      }
    }
  }
}
