{
  "schema_version": "1.0",
  "name": "Trusteddit PKI Services",
  "description": "Per-device certificate enrollment API for mobile apps that sign media with C2PA Content Credentials on-device. Each device generates a hardware-bound ECDSA P-256 key, proves integrity via platform attestation (App Attest / Play Integrity), enrolls with its Cognito identity to receive a short-lived (24h) X.509 signing certificate, then performs all C2PA signing locally — no media ever leaves the device.",
  "url": "https://client-testing.trusteddit.com",
  "provider": {
    "name": "Sanmarcsoft LLC",
    "url": "https://www.sanmarcsoft.com"
  },
  "documentation": {
    "url": "https://client-testing.trusteddit.com/docs",
    "description": "Developer documentation for enrollment-based C2PA signing"
  },
  "api": {
    "type": "rest",
    "base_url": "https://enroll.trusteddit.com",
    "base_url_testing": "https://enroll-testing.trusteddit.com",
    "authentication": [
      {
        "type": "bearer",
        "description": "Cognito ID token. Provide in Authorization: Bearer <token> on every enrollment request. User pool us-east-1_knEL7cqS3, audience (app client) 5vlgjrab90897c45ls9jkf9s2p. Use the ID token, not the access token. The token's sub claim becomes the user segment of the issued certificate SAN."
      },
      {
        "type": "custom",
        "deprecated": true,
        "description": "DEPRECATED — Legacy Cloudflare Access service token (CF-Access-Client-Id / CF-Access-Client-Secret) against POST /v1/enroll. No longer accepted for device enrollment. Use the Cognito Bearer + attestation flow instead.",
        "headers": ["CF-Access-Client-Id", "CF-Access-Client-Secret"]
      }
    ],
    "endpoints": [
      {
        "path": "/api/v1/enroll/nonce",
        "method": "GET",
        "description": "Returns a single-use challenge nonce (valid 120s) for device key attestation. The attestation challenge is SHA-256(nonce || CSR-SPKI-DER). Requires the Cognito Bearer token.",
        "authentication": "required",
        "response": {
          "content_type": "application/json",
          "schema": {
            "nonce": "string (base64url single-use challenge)",
            "expiresAt": "string (ISO 8601, ~120s out)",
            "challengeAlgorithm": "string (\"SHA-256\")"
          }
        },
        "errors": [
          { "status": 401, "code": "NO_TOKEN", "description": "Missing or invalid Cognito Bearer (ID) token" }
        ]
      },
      {
        "path": "/api/v1/enroll/device",
        "method": "POST",
        "description": "Submit a PKCS#10 CSR plus a device attestation to receive a signed 24h X.509 certificate for C2PA signing. The CSR contains only the public key (CN must equal deviceId); the private key stays in the device Secure Enclave / hardware keystore. Returns leaf certificate + intermediate CA + root CA. Production enforces attestation (fails closed); the testing lane is attestation-optional.",
        "authentication": "required",
        "request": {
          "content_type": "application/json",
          "parameters": [
            {
              "name": "csr",
              "type": "string",
              "required": true,
              "description": "PEM-encoded PKCS#10 CSR, ECDSA P-256. The CSR Common Name MUST equal deviceId."
            },
            {
              "name": "deviceId",
              "type": "string",
              "required": true,
              "description": "Stable device identifier matching /^[A-Za-z0-9_-]{8,64}$/. Becomes the certificate Subject CN."
            },
            {
              "name": "attestation",
              "type": "object",
              "required": true,
              "description": "Device attestation. Fields: platform ('ios'|'android'), token (App Attest assertion or Play Integrity token), nonce (from GET /api/v1/enroll/nonce), attestedSpkiDerBase64 (optional, base64 DER of the CSR SubjectPublicKeyInfo the attestation covers). Challenge = SHA-256(nonce || CSR-SPKI-DER). Required in production; optional on the testing lane."
            }
          ]
        },
        "response": {
          "content_type": "application/json",
          "schema": {
            "certificate": "string (PEM-encoded X.509 leaf certificate, 24h lifetime)",
            "intermediateCA": "string (PEM-encoded Trusteddit-Phenom-Issuer-CA certificate)",
            "rootCA": "string (PEM-encoded root CA; isolated testing root on the testing lane)",
            "expiresAt": "string (ISO 8601, 24h from issuance)",
            "serialNumber": "string",
            "san": "string (https://thephenom.app/c2pa/user/<sub>/device/<deviceId>)"
          }
        },
        "errors": [
          { "status": 400, "code": "CSR_NOT_P256", "description": "CSR key is not ECDSA P-256" },
          { "status": 400, "code": "CSR_BAD_SIGNATURE", "description": "CSR self-signature is invalid" },
          { "status": 400, "code": "CSR_CN_MISMATCH", "description": "CSR Common Name does not equal the supplied deviceId" },
          { "status": 400, "code": "BAD_DEVICE_ID", "description": "deviceId fails the /^[A-Za-z0-9_-]{8,64}$/ regex" },
          { "status": 401, "code": "NO_TOKEN", "description": "Missing or invalid Cognito Bearer (ID) token" },
          { "status": 403, "code": "ATTESTATION_MISSING", "description": "Attestation missing or failed; production fails closed" },
          { "status": 429, "code": "DEVICE_RATE_LIMITED", "description": "Exceeded 4 enrollments per day per device" }
        ]
      },
      {
        "path": "/v1/health",
        "method": "GET",
        "description": "Gateway health check. Returns service status, version, and timestamp. No authentication required.",
        "authentication": "none",
        "response": {
          "content_type": "application/json",
          "schema": {
            "status": "string",
            "service": "string",
            "version": "string",
            "timestamp": "string (ISO 8601)"
          }
        }
      },
      {
        "path": "/v1/cert-info",
        "method": "GET",
        "description": "Public signing certificate information. Returns issuer, subject, algorithm, key size, validity dates, and fingerprint. No authentication required.",
        "authentication": "none",
        "response": {
          "content_type": "application/json",
          "schema": {
            "issuer": "string",
            "subject": "string",
            "algorithm": "string",
            "keySize": "number",
            "validFrom": "string (ISO 8601)",
            "validTo": "string (ISO 8601)",
            "fingerprint": "string"
          }
        }
      }
    ],
    "rate_limits": [
      { "resource": "enrollment", "limit": 4, "period": "day", "per": "device", "description": "4 certificate enrollments per device per 24h. Exceeding returns 429 DEVICE_RATE_LIMITED." },
      { "resource": "local_signing", "limit": "unlimited", "description": "No limit on local signing with an enrolled certificate" },
      { "resource": "tsa_timestamps", "limit": "unlimited", "description": "RFC 3161 timestamp requests are unauthenticated and unlimited" }
    ]
  },
  "architecture": {
    "model": "per-device-enrollment-client-signing",
    "description": "Mobile apps generate a hardware-bound ECDSA P-256 keypair on-device, fetch a nonce, build a CSR (CN = deviceId), produce a platform attestation over SHA-256(nonce || CSR-SPKI-DER), enroll with their Cognito identity to receive a 24h signing certificate, then perform all C2PA signing locally. Private keys never leave the device. The enrollment API only sees the public key (via CSR). Production enforces attestation (App Attest); the testing lane is attestation-optional and chains to an isolated testing root.",
    "tsa_url": "https://tsa.trusteddit.com",
    "tsa_url_testing": "https://tsa-testing.trusteddit.com",
    "tsa_description": "RFC 3161 Timestamp Authority. Accepts timestamp queries (application/timestamp-query). No auth required. Only receives hash digests, never media."
  },
  "capabilities": [
    "per_device_certificate_enrollment",
    "client_side_c2pa_signing",
    "rfc3161_timestamping",
    "certificate_info",
    "ecdsa_p256",
    "short_lived_certificates",
    "cognito_bearer_auth",
    "ios_app_attest",
    "android_play_integrity_coming_soon",
    "hardware_attestation"
  ],
  "supported_media_types": [
    "image/jpeg",
    "image/png",
    "image/webp",
    "image/heic",
    "video/mp4",
    "video/quicktime",
    "audio/mpeg",
    "audio/mp4"
  ],
  "links": {
    "documentation": "https://client-testing.trusteddit.com/docs",
    "dashboard": "https://client-testing.trusteddit.com",
    "verification": "https://verifieddit.com",
    "c2pa_specification": "https://c2pa.org/specifications/specifications/2.1/specs/C2PA_Specification.html",
    "github": "https://github.com/Sanmarcsoft/trusteddit-pki-services"
  }
}
