# Gitea Wiki V2 API Developer Guide
**For .NET Plugin Developers**
This guide explains how to integrate with the Gitea Wiki V2 API from your .NET application. The V2 API provides structured, AI-friendly endpoints designed for external tool integration.
---
## Table of Contents
1. [Authentication](#authentication)
2. [Base URL](#base-url)
3. [API Endpoints](#api-endpoints)
- [List Wiki Pages](#list-wiki-pages)
- [Get Wiki Page](#get-wiki-page)
- [Create Wiki Page](#create-wiki-page)
- [Update Wiki Page](#update-wiki-page)
- [Delete Wiki Page](#delete-wiki-page)
- [Search Wiki](#search-wiki)
- [Get Link Graph](#get-link-graph)
- [Get Wiki Statistics](#get-wiki-statistics)
- [Get Page Revisions](#get-page-revisions)
4. [Data Models](#data-models)
5. [C# Example Code](#c-example-code)
6. [Error Handling](#error-handling)
7. [Best Practices](#best-practices)
---
## Authentication
All write operations (create, update, delete) require authentication. Read operations on public repositories work without authentication.
### Token Authentication
Use a personal access token in the `Authorization` header:
```http
Authorization: token YOUR_ACCESS_TOKEN
```
### Creating a Token
1. Go to Gitea → Settings → Applications → Generate New Token
2. Select scopes: `write:repository` (for wiki access)
3. Copy the token and store securely
### C# HttpClient Setup
```csharp
var client = new HttpClient();
client.BaseAddress = new Uri("https://your-gitea-instance.com/api/v2/");
client.DefaultRequestHeaders.Add("Authorization", $"token {accessToken}");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
```
---
## Base URL
```
https://{gitea-instance}/api/v2/repos/{owner}/{repo}/wiki
```
Replace:
- `{gitea-instance}` - Your Gitea server domain
- `{owner}` - Repository owner (user or organization)
- `{repo}` - Repository name
---
## API Endpoints
### List Wiki Pages
Returns all wiki pages with metadata.
```http
GET /api/v2/repos/{owner}/{repo}/wiki/pages
```
**Query Parameters:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `page` | int | 1 | Page number (1-based) |
| `limit` | int | 30 | Items per page (max 100) |
**Response:** `WikiPageListV2`
```json
{
"pages": [
{
"name": "Home",
"title": "Home",
"path": "Home.md",
"url": "https://gitea.example.com/api/v2/repos/owner/repo/wiki/pages/Home",
"html_url": "https://gitea.example.com/owner/repo/wiki/Home",
"word_count": 250,
"last_commit": {
"sha": "abc123...",
"message": "Update Home page",
"date": "2026-01-09T10:30:00Z",
"author": {
"name": "John Doe",
"email": "john@example.com"
}
}
}
],
"total_count": 15,
"has_more": false
}
```
---
### Get Wiki Page
Returns a single page with full content, HTML rendering, and link information.
```http
GET /api/v2/repos/{owner}/{repo}/wiki/pages/{pageName}
```
**Response:** `WikiPageV2`
```json
{
"name": "Getting-Started",
"title": "Getting Started",
"path": "Getting-Started.md",
"url": "https://gitea.example.com/api/v2/repos/owner/repo/wiki/pages/Getting-Started",
"html_url": "https://gitea.example.com/owner/repo/wiki/Getting-Started",
"content": "# Getting Started\n\nWelcome to the project...",
"content_html": "
Getting Started
\nWelcome to the project...
",
"word_count": 450,
"links_out": ["Installation", "Configuration", "FAQ"],
"links_in": ["Home", "README"],
"sidebar": "## Navigation\n- [[Home]]\n- [[Getting-Started]]",
"footer": "Copyright 2026",
"history_url": "https://gitea.example.com/api/v2/repos/owner/repo/wiki/pages/Getting-Started/revisions",
"last_commit": {
"sha": "def456...",
"message": "Add quick start section",
"date": "2026-01-08T14:22:00Z",
"author": {
"name": "Jane Smith",
"email": "jane@example.com"
},
"committer": {
"name": "Jane Smith",
"email": "jane@example.com"
}
}
}
```
**Key Fields for AI Integration:**
| Field | Description |
|-------|-------------|
| `content` | Raw markdown content (use for AI processing) |
| `content_html` | Pre-rendered HTML (use for display) |
| `links_out` | Pages this page links to |
| `links_in` | Pages that link to this page |
| `word_count` | Word count for content analysis |
---
### Create Wiki Page
Creates a new wiki page.
```http
POST /api/v2/repos/{owner}/{repo}/wiki/pages
```
**Request Body:** `CreateWikiPageV2Option`
```json
{
"name": "New-Feature",
"title": "New Feature Documentation",
"content": "# New Feature\n\nThis page documents the new feature...",
"message": "Add documentation for new feature"
}
```
| Field | Required | Description |
|-------|----------|-------------|
| `name` | Yes | Page name (used in URL, spaces become dashes) |
| `title` | No | Display title (defaults to name) |
| `content` | Yes | Markdown content |
| `message` | No | Commit message (auto-generated if empty) |
**Response:** Redirects to the created page (HTTP 302)
---
### Update Wiki Page
Updates an existing wiki page. Can also rename the page.
```http
PUT /api/v2/repos/{owner}/{repo}/wiki/pages/{pageName}
```
**Request Body:** `UpdateWikiPageV2Option`
```json
{
"title": "Updated Title",
"content": "# Updated Content\n\nNew content here...",
"message": "Update page content",
"rename_to": "New-Page-Name"
}
```
| Field | Required | Description |
|-------|----------|-------------|
| `title` | No | New display title |
| `content` | No | New markdown content |
| `message` | No | Commit message |
| `rename_to` | No | New page name (renames the page) |
**Response:** Redirects to the updated page (HTTP 302)
---
### Delete Wiki Page
Deletes a wiki page.
```http
DELETE /api/v2/repos/{owner}/{repo}/wiki/pages/{pageName}
```
**Response:** `WikiDeleteResponseV2`
```json
{
"success": true
}
```
---
### Search Wiki
Full-text search across all wiki pages.
```http
GET /api/v2/repos/{owner}/{repo}/wiki/search?q={query}
```
**Query Parameters:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `q` | string | required | Search query |
| `limit` | int | 20 | Max results (max 100) |
| `offset` | int | 0 | Skip N results |
**Response:** `WikiSearchResponseV2`
```json
{
"query": "installation",
"results": [
{
"name": "Getting-Started",
"title": "Getting Started",
"snippet": "...Follow these steps for **installation**. First, download the package...",
"score": 8.5,
"word_count": 450,
"last_updated": "2026-01-08T14:22:00Z"
},
{
"name": "Installation",
"title": "Installation Guide",
"snippet": "# **Installation** Guide\n\nThis page covers all **installation** methods...",
"score": 12.3,
"word_count": 890,
"last_updated": "2026-01-05T09:15:00Z"
}
],
"total_count": 2
}
```
**Score Calculation:**
- Title match: +10 points
- Page name match: +8 points
- Content matches: +0.5 per occurrence
- Normalized by word count
---
### Get Link Graph
Returns the wiki's link relationship graph (for visualization or analysis).
```http
GET /api/v2/repos/{owner}/{repo}/wiki/graph
```
**Response:** `WikiGraphV2`
```json
{
"nodes": [
{ "name": "Home", "title": "Home", "word_count": 250 },
{ "name": "Getting-Started", "title": "Getting Started", "word_count": 450 },
{ "name": "Installation", "title": "Installation Guide", "word_count": 890 },
{ "name": "Configuration", "title": "Configuration", "word_count": 320 }
],
"edges": [
{ "source": "Home", "target": "Getting-Started" },
{ "source": "Home", "target": "Installation" },
{ "source": "Getting-Started", "target": "Installation" },
{ "source": "Getting-Started", "target": "Configuration" },
{ "source": "Installation", "target": "Configuration" }
]
}
```
**Use Cases:**
- Build a visual wiki map
- Find navigation paths between pages
- Identify central/hub pages
- Detect isolated page clusters
---
### Get Wiki Statistics
Returns comprehensive wiki statistics and health metrics.
```http
GET /api/v2/repos/{owner}/{repo}/wiki/stats
```
**Response:** `WikiStatsV2`
```json
{
"total_pages": 15,
"total_words": 12500,
"total_commits": 87,
"last_updated": "2026-01-09T10:30:00Z",
"contributors": 5,
"health": {
"orphaned_pages": [
{ "name": "Old-Feature", "word_count": 120 }
],
"dead_links": [
{ "page": "Getting-Started", "broken_link": "Deprecated-Page" }
],
"outdated_pages": [
{ "name": "Legacy-Setup", "last_edit": "2025-03-15T00:00:00Z", "days_old": 300 }
],
"short_pages": [
{ "name": "TODO", "word_count": 25 }
]
},
"top_linked": [
{ "name": "Home", "incoming_links": 12 },
{ "name": "Installation", "incoming_links": 8 },
{ "name": "Configuration", "incoming_links": 6 }
]
}
```
**Health Metrics:**
| Metric | Description |
|--------|-------------|
| `orphaned_pages` | Pages with no incoming links (except Home) |
| `dead_links` | Links pointing to non-existent pages |
| `outdated_pages` | Pages not edited in 180+ days |
| `short_pages` | Pages with < 100 words |
---
### Get Page Revisions
Returns the revision history for a specific page.
```http
GET /api/v2/repos/{owner}/{repo}/wiki/pages/{pageName}/revisions
```
**Query Parameters:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `page` | int | 1 | Page number |
**Response:** `WikiRevisionsV2`
```json
{
"page_name": "Getting-Started",
"revisions": [
{
"sha": "abc123...",
"message": "Add troubleshooting section",
"date": "2026-01-09T10:30:00Z",
"author": {
"name": "John Doe",
"email": "john@example.com"
}
},
{
"sha": "def456...",
"message": "Fix typo in installation steps",
"date": "2026-01-08T14:22:00Z",
"author": {
"name": "Jane Smith",
"email": "jane@example.com"
}
}
],
"total_count": 15
}
```
---
## Data Models
### C# Model Definitions
```csharp
// Wiki Page (full details)
public class WikiPageV2
{
public string Name { get; set; }
public string Title { get; set; }
public string Path { get; set; }
public string Url { get; set; }
public string HtmlUrl { get; set; }
public string Content { get; set; }
public string ContentHtml { get; set; }
public int WordCount { get; set; }
public List LinksOut { get; set; }
public List LinksIn { get; set; }
public string Sidebar { get; set; }
public string Footer { get; set; }
public string HistoryUrl { get; set; }
public WikiCommitV2 LastCommit { get; set; }
}
// Wiki Commit
public class WikiCommitV2
{
public string Sha { get; set; }
public string Message { get; set; }
public DateTime Date { get; set; }
public WikiAuthorV2 Author { get; set; }
public WikiAuthorV2 Committer { get; set; }
}
// Wiki Author
public class WikiAuthorV2
{
public string Username { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string AvatarUrl { get; set; }
}
// Page List Response
public class WikiPageListV2
{
public List Pages { get; set; }
public long TotalCount { get; set; }
public bool HasMore { get; set; }
}
// Search Result
public class WikiSearchResultV2
{
public string Name { get; set; }
public string Title { get; set; }
public string Snippet { get; set; }
public float Score { get; set; }
public int WordCount { get; set; }
public DateTime LastUpdated { get; set; }
}
// Search Response
public class WikiSearchResponseV2
{
public string Query { get; set; }
public List Results { get; set; }
public long TotalCount { get; set; }
}
// Graph Node
public class WikiGraphNodeV2
{
public string Name { get; set; }
public string Title { get; set; }
public int WordCount { get; set; }
}
// Graph Edge
public class WikiGraphEdgeV2
{
public string Source { get; set; }
public string Target { get; set; }
}
// Graph Response
public class WikiGraphV2
{
public List Nodes { get; set; }
public List Edges { get; set; }
}
// Health Metrics
public class WikiHealthV2
{
public List OrphanedPages { get; set; }
public List DeadLinks { get; set; }
public List OutdatedPages { get; set; }
public List ShortPages { get; set; }
}
public class WikiOrphanedPageV2
{
public string Name { get; set; }
public int WordCount { get; set; }
}
public class WikiDeadLinkV2
{
public string Page { get; set; }
public string BrokenLink { get; set; }
}
public class WikiOutdatedPageV2
{
public string Name { get; set; }
public DateTime LastEdit { get; set; }
public int DaysOld { get; set; }
}
public class WikiShortPageV2
{
public string Name { get; set; }
public int WordCount { get; set; }
}
// Statistics Response
public class WikiStatsV2
{
public long TotalPages { get; set; }
public long TotalWords { get; set; }
public long TotalCommits { get; set; }
public DateTime LastUpdated { get; set; }
public long Contributors { get; set; }
public WikiHealthV2 Health { get; set; }
public List TopLinked { get; set; }
}
public class WikiTopLinkedPageV2
{
public string Name { get; set; }
public int IncomingLinks { get; set; }
}
// Create/Update Options
public class CreateWikiPageV2Option
{
public string Name { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public string Message { get; set; }
}
public class UpdateWikiPageV2Option
{
public string Title { get; set; }
public string Content { get; set; }
public string Message { get; set; }
public string RenameTo { get; set; }
}
```
---
## C# Example Code
### Complete Wiki Client
```csharp
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
public class GiteaWikiClient : IDisposable
{
private readonly HttpClient _client;
private readonly string _owner;
private readonly string _repo;
private readonly JsonSerializerOptions _jsonOptions;
public GiteaWikiClient(string baseUrl, string owner, string repo, string accessToken)
{
_owner = owner;
_repo = repo;
_client = new HttpClient
{
BaseAddress = new Uri(baseUrl.TrimEnd('/') + "/")
};
_client.DefaultRequestHeaders.Add("Authorization", $"token {accessToken}");
_jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
}
private string WikiPath => $"api/v2/repos/{_owner}/{_repo}/wiki";
// List all pages
public async Task ListPagesAsync(int page = 1, int limit = 30)
{
var response = await _client.GetAsync($"{WikiPath}/pages?page={page}&limit={limit}");
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync(_jsonOptions);
}
// Get a single page
public async Task GetPageAsync(string pageName)
{
var response = await _client.GetAsync($"{WikiPath}/pages/{Uri.EscapeDataString(pageName)}");
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync(_jsonOptions);
}
// Create a new page
public async Task CreatePageAsync(string name, string content, string title = null, string message = null)
{
var options = new CreateWikiPageV2Option
{
Name = name,
Title = title ?? name,
Content = content,
Message = message
};
var response = await _client.PostAsJsonAsync($"{WikiPath}/pages", options, _jsonOptions);
response.EnsureSuccessStatusCode();
// Follow redirect to get the created page
if (response.Headers.Location != null)
{
return await GetPageAsync(name);
}
return null;
}
// Update a page
public async Task UpdatePageAsync(string pageName, string content = null, string title = null, string renameTo = null, string message = null)
{
var options = new UpdateWikiPageV2Option
{
Title = title,
Content = content,
Message = message,
RenameTo = renameTo
};
var response = await _client.PutAsJsonAsync($"{WikiPath}/pages/{Uri.EscapeDataString(pageName)}", options, _jsonOptions);
response.EnsureSuccessStatusCode();
var finalName = renameTo ?? pageName;
return await GetPageAsync(finalName);
}
// Delete a page
public async Task DeletePageAsync(string pageName)
{
var response = await _client.DeleteAsync($"{WikiPath}/pages/{Uri.EscapeDataString(pageName)}");
response.EnsureSuccessStatusCode();
return true;
}
// Search wiki
public async Task SearchAsync(string query, int limit = 20, int offset = 0)
{
var url = $"{WikiPath}/search?q={Uri.EscapeDataString(query)}&limit={limit}&offset={offset}";
var response = await _client.GetAsync(url);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync(_jsonOptions);
}
// Get link graph
public async Task GetGraphAsync()
{
var response = await _client.GetAsync($"{WikiPath}/graph");
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync(_jsonOptions);
}
// Get statistics
public async Task GetStatsAsync()
{
var response = await _client.GetAsync($"{WikiPath}/stats");
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync(_jsonOptions);
}
// Get page revisions
public async Task GetRevisionsAsync(string pageName, int page = 1)
{
var url = $"{WikiPath}/pages/{Uri.EscapeDataString(pageName)}/revisions?page={page}";
var response = await _client.GetAsync(url);
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync(_jsonOptions);
}
public void Dispose()
{
_client?.Dispose();
}
}
```
### Usage Examples
```csharp
// Initialize client
using var wiki = new GiteaWikiClient(
baseUrl: "https://gitea.example.com",
owner: "myorg",
repo: "myproject",
accessToken: "your_token_here"
);
// List all pages
var pageList = await wiki.ListPagesAsync();
Console.WriteLine($"Found {pageList.TotalCount} wiki pages");
foreach (var page in pageList.Pages)
{
Console.WriteLine($"- {page.Title} ({page.WordCount} words)");
}
// Get a specific page
var page = await wiki.GetPageAsync("Getting-Started");
Console.WriteLine($"Content: {page.Content}");
Console.WriteLine($"Links to: {string.Join(", ", page.LinksOut)}");
Console.WriteLine($"Linked from: {string.Join(", ", page.LinksIn)}");
// Create a new page
var newPage = await wiki.CreatePageAsync(
name: "API-Reference",
content: "# API Reference\n\nThis page documents the API...",
title: "API Reference",
message: "Add API documentation"
);
// Update a page
var updated = await wiki.UpdatePageAsync(
pageName: "API-Reference",
content: "# API Reference\n\nUpdated content...",
message: "Update API docs"
);
// Rename a page
var renamed = await wiki.UpdatePageAsync(
pageName: "API-Reference",
renameTo: "API-Documentation",
message: "Rename API page"
);
// Search
var results = await wiki.SearchAsync("installation");
foreach (var result in results.Results)
{
Console.WriteLine($"Found: {result.Title} (score: {result.Score})");
Console.WriteLine($" {result.Snippet}");
}
// Get wiki health
var stats = await wiki.GetStatsAsync();
Console.WriteLine($"Wiki has {stats.TotalPages} pages with {stats.TotalWords} total words");
if (stats.Health.DeadLinks.Any())
{
Console.WriteLine("Broken links found:");
foreach (var deadLink in stats.Health.DeadLinks)
{
Console.WriteLine($" {deadLink.Page} -> {deadLink.BrokenLink}");
}
}
// Delete a page
await wiki.DeletePageAsync("Old-Page");
```
### AI Function Calling Integration
If you're using the wiki with AI function calling (e.g., OpenAI, Azure OpenAI):
```csharp
// Define functions for AI
var functions = new[]
{
new
{
name = "search_wiki",
description = "Search the project wiki for information",
parameters = new
{
type = "object",
properties = new
{
query = new { type = "string", description = "Search query" }
},
required = new[] { "query" }
}
},
new
{
name = "get_wiki_page",
description = "Get the full content of a wiki page",
parameters = new
{
type = "object",
properties = new
{
page_name = new { type = "string", description = "Name of the wiki page" }
},
required = new[] { "page_name" }
}
},
new
{
name = "create_wiki_page",
description = "Create a new wiki page",
parameters = new
{
type = "object",
properties = new
{
name = new { type = "string", description = "Page name" },
content = new { type = "string", description = "Markdown content" },
message = new { type = "string", description = "Commit message" }
},
required = new[] { "name", "content" }
}
},
new
{
name = "get_wiki_stats",
description = "Get wiki statistics including health metrics",
parameters = new { type = "object", properties = new { } }
}
};
// Handle function calls
async Task HandleFunctionCall(string functionName, JsonElement arguments)
{
switch (functionName)
{
case "search_wiki":
var query = arguments.GetProperty("query").GetString();
var results = await wiki.SearchAsync(query);
return JsonSerializer.Serialize(results);
case "get_wiki_page":
var pageName = arguments.GetProperty("page_name").GetString();
var page = await wiki.GetPageAsync(pageName);
return JsonSerializer.Serialize(page);
case "create_wiki_page":
var name = arguments.GetProperty("name").GetString();
var content = arguments.GetProperty("content").GetString();
var message = arguments.TryGetProperty("message", out var msg) ? msg.GetString() : null;
var newPage = await wiki.CreatePageAsync(name, content, message: message);
return JsonSerializer.Serialize(newPage);
case "get_wiki_stats":
var stats = await wiki.GetStatsAsync();
return JsonSerializer.Serialize(stats);
default:
throw new ArgumentException($"Unknown function: {functionName}");
}
}
```
---
## Error Handling
### Error Response Format
All errors return a consistent JSON structure:
```json
{
"error": {
"code": "WIKI_PAGE_NOT_FOUND",
"message": "Wiki page not found",
"details": {}
}
}
```
### Error Codes
| Code | HTTP Status | Description |
|------|-------------|-------------|
| `WIKI_PAGE_NOT_FOUND` | 404 | The requested page doesn't exist |
| `WIKI_PAGE_ALREADY_EXISTS` | 409 | A page with that name already exists |
| `WIKI_RESERVED_NAME` | 400 | The page name is reserved (e.g., `_Sidebar`) |
| `WIKI_DISABLED` | 403 | Wiki is disabled for this repository |
| `AUTH_TOKEN_MISSING` | 401 | No authentication token provided |
| `REPO_NOT_FOUND` | 404 | Repository doesn't exist |
| `REPO_ARCHIVED` | 403 | Repository is archived (read-only) |
| `ACCESS_DENIED` | 403 | Insufficient permissions |
### C# Error Handling
```csharp
public class GiteaApiException : Exception
{
public string ErrorCode { get; }
public int StatusCode { get; }
public GiteaApiException(string code, string message, int statusCode)
: base(message)
{
ErrorCode = code;
StatusCode = statusCode;
}
}
// In your client methods:
public async Task GetPageAsync(string pageName)
{
var response = await _client.GetAsync($"{WikiPath}/pages/{Uri.EscapeDataString(pageName)}");
if (!response.IsSuccessStatusCode)
{
var error = await response.Content.ReadFromJsonAsync(_jsonOptions);
throw new GiteaApiException(
error?.Error?.Code ?? "UNKNOWN",
error?.Error?.Message ?? "Unknown error",
(int)response.StatusCode
);
}
return await response.Content.ReadFromJsonAsync(_jsonOptions);
}
// Usage:
try
{
var page = await wiki.GetPageAsync("NonExistent");
}
catch (GiteaApiException ex) when (ex.ErrorCode == "WIKI_PAGE_NOT_FOUND")
{
Console.WriteLine("Page doesn't exist, creating it...");
await wiki.CreatePageAsync("NonExistent", "# New Page");
}
```
---
## Best Practices
### 1. Page Naming
- Use URL-friendly names: `Getting-Started`, not `Getting Started`
- Avoid special characters: `/`, `\`, `?`, `#`, `%`
- Keep names concise but descriptive
- The API automatically converts spaces to dashes
### 2. Content Format
- Use standard Markdown
- Wiki links: `[[Page-Name]]` or `[[Page-Name|Display Text]]`
- Relative links: `[text](./Other-Page)` or `[text](Other-Page)`
- The API extracts both formats for the `links_out` field
### 3. Commit Messages
- Always provide meaningful commit messages
- If omitted, auto-generated: "Add {page}" or "Update {page}"
- Good for audit trail and history
### 4. Rate Limiting
- Be mindful of API rate limits
- Cache responses when appropriate
- Use pagination for large wikis
### 5. Indexing
- The wiki is automatically indexed for search
- First access may trigger background indexing
- Stats and graph endpoints trigger re-indexing if needed
### 6. Error Recovery
```csharp
// Retry logic for transient failures
public async Task WithRetryAsync(Func> action, int maxRetries = 3)
{
for (int i = 0; i < maxRetries; i++)
{
try
{
return await action();
}
catch (HttpRequestException) when (i < maxRetries - 1)
{
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i)));
}
}
throw new Exception("Max retries exceeded");
}
// Usage:
var page = await WithRetryAsync(() => wiki.GetPageAsync("Home"));
```
---
## Support
For issues with the Wiki V2 API:
- Check the [Gitea documentation](https://docs.gitea.com)
- Report bugs at the Gitea repository
- For .NET client issues, check your authentication and base URL
---
*Document Version: 1.0 | API Version: v2 | Last Updated: January 2026*