{
  "openapi": "3.1.0",
  "info": {
    "title": "DonoLink Developer API",
    "version": "1.0.0",
    "description": "Personal read-only activity API. 120 requests per account per 60 seconds, 300 per IP. Store keys server-side or in personal native secure storage. No OAuth, browser CORS, writes or guaranteed delivery/retention. Historical imported donations are included with the import source as platform."
  },
  "servers": [
    {
      "url": "https://donolink.nl/api/v1"
    }
  ],
  "security": [
    {
      "ApiKey": []
    }
  ],
  "paths": {
    "/me": {
      "get": {
        "operationId": "getMe",
        "summary": "Read the key owner and expiry",
        "responses": {
          "200": {
            "description": "Success. Private, no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data"
                  ],
                  "properties": {
                    "data": {
                      "type": "object",
                      "required": [
                        "id",
                        "username",
                        "display_name",
                        "scopes",
                        "key"
                      ],
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "username": {
                          "type": "string"
                        },
                        "display_name": {
                          "type": "string"
                        },
                        "scopes": {
                          "type": "array",
                          "items": {
                            "const": "activity:read"
                          }
                        },
                        "key": {
                          "type": "object",
                          "properties": {
                            "prefix": {
                              "type": "string"
                            },
                            "expires_at": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/activity": {
      "get": {
        "operationId": "listActivity",
        "summary": "Read stored personal activity",
        "description": "Current paid donations including imports and stored stream events. Pagination uses recorded_at, not occurred_at. Empty pages retain the previous cursor. Deduplicate by event ID. Updates and deletes are not events.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "Insertion time, then internal ID and source. Use asc for polling.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc"
              ],
              "default": "desc"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "One event type.",
            "schema": {
              "type": "string",
              "enum": [
                "donation",
                "follow",
                "subscription",
                "giftsub",
                "cheer",
                "raid"
              ]
            }
          },
          {
            "name": "platform",
            "in": "query",
            "required": false,
            "description": "One platform.",
            "schema": {
              "type": "string",
              "enum": [
                "donolink",
                "twitch",
                "kick",
                "youtube",
                "tiktok",
                "tipeeestream",
                "streamlabs",
                "streamelements",
                "kofi",
                "csv"
              ]
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Inclusive recorded_at boundary. UTC ISO 8601 with Z.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "Exclusive recorded_at boundary. UTC ISO 8601 with Z.",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Signed opaque cursor, bound to account, order and filters. Keep these fixed.",
            "schema": {
              "type": "string",
              "maxLength": 2048
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success. Private, no-store.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "data",
                    "pagination"
                  ],
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Activity"
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "required": [
                        "has_more",
                        "next_cursor"
                      ],
                      "properties": {
                        "has_more": {
                          "type": "boolean"
                        },
                        "next_cursor": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "429": {
            "$ref": "#/components/responses/Error"
          },
          "503": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKey": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "dl_live_<64 lowercase hex characters>",
        "description": "Personal key with activity:read. Never place keys in URLs."
      }
    },
    "schemas": {
      "Activity": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "id",
          "type",
          "platform",
          "occurred_at",
          "recorded_at",
          "actor",
          "message",
          "value",
          "unit",
          "currency",
          "refunded_amount_cents"
        ],
        "properties": {
          "id": {
            "type": "string",
            "pattern": "^(don|evt)_[a-z0-9]{15}$"
          },
          "type": {
            "type": "string",
            "enum": [
              "donation",
              "follow",
              "subscription",
              "giftsub",
              "cheer",
              "raid"
            ]
          },
          "platform": {
            "type": "string",
            "enum": [
              "donolink",
              "twitch",
              "kick",
              "youtube",
              "tiktok",
              "tipeeestream",
              "streamlabs",
              "streamelements",
              "kofi",
              "csv"
            ]
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time"
          },
          "recorded_at": {
            "type": "string",
            "format": "date-time"
          },
          "actor": {
            "type": "object",
            "required": [
              "name"
            ],
            "properties": {
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "message": {
            "type": [
              "string",
              "null"
            ]
          },
          "value": {
            "type": "integer",
            "minimum": 0
          },
          "unit": {
            "type": "string",
            "enum": [
              "cents",
              "none",
              "months",
              "subscriptions",
              "bits",
              "diamonds",
              "viewers"
            ]
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "refunded_amount_cents": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 0
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "API error. Retry 429 and 503 after 60 seconds.",
        "headers": {
          "X-Request-Id": {
            "schema": {
              "type": "string"
            }
          },
          "Retry-After": {
            "description": "Present for 429 and 503",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "error"
              ],
              "properties": {
                "error": {
                  "type": "object",
                  "required": [
                    "code",
                    "message",
                    "request_id"
                  ],
                  "properties": {
                    "code": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "request_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
