{
  "openapi": "3.1.0",
  "info": {
    "title": "RSX Platform Service",
    "version": "v1",
    "description": "API key management: issue, scope, rotate, and revoke platform API keys."
  },
  "servers": [
    {
      "url": "https://api.rsx.group"
    }
  ],
  "components": {
    "schemas": {
      "ApiKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "example": "0198f2b1-6c2a-7c9e-9d3a-1e2f3a4b5c6d"
          },
          "prefix": {
            "type": "string",
            "description": "Greppable display prefix — never the secret.",
            "example": "rsx_live_a1b2c3d4"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "example": "ci-deploy"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^(\\*|[a-z][a-z0-9-]*(:([a-z][a-z0-9-]*|\\*))*)$",
              "example": "example:widgets:add"
            },
            "example": [
              "example:widgets:*"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "revoked"
            ],
            "example": "active"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "example": "2026-07-18T12:00:00Z"
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Set during rotation — the old key's grace-window end.",
            "example": null
          },
          "rateCapacity": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 100000,
            "description": "Token-bucket burst size for this key.",
            "example": 120
          },
          "rateRefill": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0.1,
            "maximum": 10000,
            "description": "Sustained requests/second refill rate for this key.",
            "example": 20
          },
          "accountId": {
            "type": [
              "string",
              "null"
            ],
            "format": "uuid",
            "description": "Owning RSX ID account for developer self-issued keys; null for admin-issued keys.",
            "example": null
          }
        },
        "required": [
          "id",
          "prefix",
          "name",
          "scopes",
          "status",
          "createdAt",
          "expiresAt",
          "rateCapacity",
          "rateRefill",
          "accountId"
        ]
      },
      "CreatedApiKey": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKey"
          },
          {
            "type": "object",
            "properties": {
              "secret": {
                "type": "string",
                "description": "Shown once, at creation. Store it now — it is not retrievable.",
                "example": "rsx_live_a1b2c3d4e5f6..."
              }
            },
            "required": [
              "secret"
            ]
          }
        ]
      },
      "CreateKey": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "example": "ci-deploy"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^(\\*|[a-z][a-z0-9-]*(:([a-z][a-z0-9-]*|\\*))*)$",
              "example": "example:widgets:add"
            },
            "minItems": 1,
            "example": [
              "example:widgets:*"
            ]
          },
          "rateCapacity": {
            "type": "integer",
            "minimum": 1,
            "maximum": 100000,
            "description": "Token-bucket burst size for this key.",
            "example": 120
          },
          "rateRefill": {
            "type": "number",
            "minimum": 0.1,
            "maximum": 10000,
            "description": "Sustained requests/second refill rate for this key.",
            "example": 20
          },
          "expiresInDays": {
            "type": "integer",
            "minimum": 1,
            "maximum": 3650,
            "description": "Omit for a key that never expires.",
            "example": 90
          }
        },
        "required": [
          "name",
          "scopes"
        ]
      },
      "UpdateKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^(\\*|[a-z][a-z0-9-]*(:([a-z][a-z0-9-]*|\\*))*)$",
              "example": "example:widgets:add"
            },
            "minItems": 1
          },
          "rateCapacity": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 100000,
            "description": "Token-bucket burst size for this key.",
            "example": 120
          },
          "rateRefill": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0.1,
            "maximum": 10000,
            "description": "Sustained requests/second refill rate for this key.",
            "example": 20
          }
        },
        "required": [
          "id"
        ]
      },
      "RemoveKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          }
        },
        "required": [
          "id"
        ]
      },
      "RotateKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "graceSeconds": {
            "type": "integer",
            "minimum": 0,
            "maximum": 2592000,
            "default": 86400,
            "description": "How long the old key keeps working alongside the new one (§6.2 rotation window).",
            "example": 86400
          }
        },
        "required": [
          "id"
        ]
      },
      "AuditRecord": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "actor": {
            "type": "string",
            "description": "Who performed the mutation: the signed-in admin's email for management calls, or the RSX ID account id for a developer self-service call.",
            "example": "ada@rsx.group"
          },
          "action": {
            "type": "string",
            "enum": [
              "add",
              "update",
              "remove",
              "rotate"
            ]
          },
          "targetKeyId": {
            "type": "string",
            "format": "uuid"
          },
          "targetKeyPrefix": {
            "type": [
              "string",
              "null"
            ],
            "example": "rsx_live_a1b2c3d4"
          },
          "targetKeyName": {
            "type": [
              "string",
              "null"
            ],
            "example": "ci-deploy"
          },
          "at": {
            "type": "string",
            "format": "date-time"
          },
          "detail": {
            "type": [
              "string",
              "null"
            ],
            "example": "replacement=0198... grace=86400s"
          }
        },
        "required": [
          "id",
          "actor",
          "action",
          "targetKeyId",
          "targetKeyPrefix",
          "targetKeyName",
          "at",
          "detail"
        ]
      },
      "VerifiedKey": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "prefix": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^(\\*|[a-z][a-z0-9-]*(:([a-z][a-z0-9-]*|\\*))*)$",
              "example": "example:widgets:add"
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "revoked"
            ]
          },
          "rateCapacity": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1,
            "maximum": 100000,
            "description": "Token-bucket burst size for this key.",
            "example": 120
          },
          "rateRefill": {
            "type": [
              "number",
              "null"
            ],
            "minimum": 0.1,
            "maximum": 10000,
            "description": "Sustained requests/second refill rate for this key.",
            "example": 20
          }
        },
        "required": [
          "id",
          "prefix",
          "name",
          "scopes",
          "status",
          "rateCapacity",
          "rateRefill"
        ]
      },
      "VerifyKey": {
        "type": "object",
        "properties": {
          "hash": {
            "type": "string",
            "pattern": "^[0-9a-f]{64}$"
          }
        },
        "required": [
          "hash"
        ]
      },
      "CreateDeveloperKey": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "example": "my-app"
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^(\\*|[a-z][a-z0-9-]*(:([a-z][a-z0-9-]*|\\*))*)$",
              "example": "example:widgets:add"
            },
            "minItems": 1,
            "example": [
              "example:widgets:list"
            ]
          }
        },
        "required": [
          "name",
          "scopes"
        ]
      }
    },
    "parameters": {}
  },
  "paths": {
    "/platform/v1/keys/add": {
      "post": {
        "operationId": "addKey",
        "summary": "Create an API key",
        "tags": [
          "keys"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateKey"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Key created — the secret in this response is shown exactly once",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CreatedApiKey"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/platform/v1/keys/list": {
      "get": {
        "operationId": "listKeys",
        "summary": "List API keys",
        "tags": [
          "keys"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": ""
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20,
              "example": 20
            },
            "required": false,
            "name": "limit",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "enum": [
                "active",
                "revoked"
              ]
            },
            "required": false,
            "name": "status",
            "in": "query"
          },
          {
            "schema": {
              "type": "string",
              "example": "first-party"
            },
            "required": false,
            "name": "accountId",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKey"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "cursor": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "cursor"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/platform/v1/keys/get": {
      "get": {
        "operationId": "getKey",
        "summary": "Fetch one API key",
        "tags": [
          "keys"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "required": true,
            "name": "id",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ApiKey"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/platform/v1/keys/update": {
      "post": {
        "operationId": "updateKey",
        "summary": "Update a key's name or scopes",
        "tags": [
          "keys"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateKey"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Key updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ApiKey"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/platform/v1/keys/remove": {
      "post": {
        "operationId": "removeKey",
        "summary": "Revoke an API key",
        "tags": [
          "keys"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RemoveKey"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Key revoked — keys are never deleted, only revoked, so the audit trail stays resolvable",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ApiKey"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/platform/v1/keys/rotate": {
      "post": {
        "operationId": "rotateKey",
        "summary": "Rotate an API key",
        "tags": [
          "keys"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RotateKey"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replacement key — the old key keeps working until the grace window ends (§6.2: two active keys during rotation)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CreatedApiKey"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/platform/v1/audit/list": {
      "get": {
        "operationId": "listAudit",
        "summary": "List the key audit trail",
        "tags": [
          "audit"
        ],
        "parameters": [
          {
            "schema": {
              "type": "string",
              "example": ""
            },
            "required": false,
            "name": "cursor",
            "in": "query"
          },
          {
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50,
              "example": 50
            },
            "required": false,
            "name": "limit",
            "in": "query"
          }
        ],
        "responses": {
          "200": {
            "description": "Audit records, newest first",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/AuditRecord"
                      }
                    },
                    "meta": {
                      "type": "object",
                      "properties": {
                        "cursor": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "cursor"
                      ]
                    }
                  },
                  "required": [
                    "data",
                    "meta"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/platform/v1/keys/verify": {
      "post": {
        "operationId": "verifyKey",
        "summary": "Verify a key hash (internal)",
        "tags": [
          "keys"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyKey"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Key record for the supplied hash (status included — a revoked key still resolves, so the gateway can cache the revocation)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/VerifiedKey"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/platform/v1/developer/keys/create": {
      "post": {
        "operationId": "createDeveloperKey",
        "summary": "Create a developer API key (public scopes only)",
        "tags": [
          "developer"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDeveloperKey"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Key created — the secret is shown exactly once",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CreatedApiKey"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/platform/v1/developer/keys/list": {
      "get": {
        "operationId": "listDeveloperKeys",
        "summary": "List your own API keys",
        "tags": [
          "developer"
        ],
        "responses": {
          "200": {
            "description": "Your keys",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ApiKey"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/platform/v1/developer/keys/revoke": {
      "post": {
        "operationId": "revokeDeveloperKey",
        "summary": "Revoke one of your own API keys",
        "tags": [
          "developer"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RemoveKey"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/ApiKey"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/platform/v1/developer/keys/rotate": {
      "post": {
        "operationId": "rotateDeveloperKey",
        "summary": "Rotate one of your own API keys",
        "tags": [
          "developer"
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RotateKey"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Replacement key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/CreatedApiKey"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          }
        }
      }
    }
  },
  "webhooks": {}
}
