gitea/docs/wiki-v2-api-developer-guide.md
logikonline b816ee4eec feat: add Phases 3-5 enhancements (org profiles, pages, wiki v2 API)
Phase 3: Organization Public Profile Page
- Pinned repositories with groups
- Public members display with roles
- API endpoints for pinned repos and groups

Phase 4: Gitea Pages Foundation
- Landing page templates (simple, docs, product, portfolio)
- Custom domain support with verification
- YAML configuration parser (.gitea/landing.yaml)
- Repository settings UI for pages

Phase 5: Enhanced Wiki System with V2 API
- Full CRUD operations via v2 API
- Full-text search with WikiIndex table
- Link graph visualization
- Wiki health metrics (orphaned, dead links, outdated)
- Designed for external AI plugin integration
- Developer guide for .NET integration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 15:14:27 -05:00

27 KiB

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
  2. Base URL
  3. API Endpoints
  4. Data Models
  5. C# Example Code
  6. Error Handling
  7. 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:

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

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.

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

{
  "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.

GET /api/v2/repos/{owner}/{repo}/wiki/pages/{pageName}

Response: WikiPageV2

{
  "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": "<h1>Getting Started</h1>\n<p>Welcome to the project...</p>",
  "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.

POST /api/v2/repos/{owner}/{repo}/wiki/pages

Request Body: CreateWikiPageV2Option

{
  "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.

PUT /api/v2/repos/{owner}/{repo}/wiki/pages/{pageName}

Request Body: UpdateWikiPageV2Option

{
  "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.

DELETE /api/v2/repos/{owner}/{repo}/wiki/pages/{pageName}

Response: WikiDeleteResponseV2

{
  "success": true
}

Search Wiki

Full-text search across all wiki pages.

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

{
  "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

Returns the wiki's link relationship graph (for visualization or analysis).

GET /api/v2/repos/{owner}/{repo}/wiki/graph

Response: WikiGraphV2

{
  "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.

GET /api/v2/repos/{owner}/{repo}/wiki/stats

Response: WikiStatsV2

{
  "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.

GET /api/v2/repos/{owner}/{repo}/wiki/pages/{pageName}/revisions

Query Parameters:

Parameter Type Default Description
page int 1 Page number

Response: WikiRevisionsV2

{
  "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

// 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<string> LinksOut { get; set; }
    public List<string> 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<WikiPageV2> 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<WikiSearchResultV2> 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<WikiGraphNodeV2> Nodes { get; set; }
    public List<WikiGraphEdgeV2> Edges { get; set; }
}

// Health Metrics
public class WikiHealthV2
{
    public List<WikiOrphanedPageV2> OrphanedPages { get; set; }
    public List<WikiDeadLinkV2> DeadLinks { get; set; }
    public List<WikiOutdatedPageV2> OutdatedPages { get; set; }
    public List<WikiShortPageV2> 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<WikiTopLinkedPageV2> 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

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<WikiPageListV2> 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<WikiPageListV2>(_jsonOptions);
    }

    // Get a single page
    public async Task<WikiPageV2> GetPageAsync(string pageName)
    {
        var response = await _client.GetAsync($"{WikiPath}/pages/{Uri.EscapeDataString(pageName)}");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<WikiPageV2>(_jsonOptions);
    }

    // Create a new page
    public async Task<WikiPageV2> 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<WikiPageV2> 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<bool> DeletePageAsync(string pageName)
    {
        var response = await _client.DeleteAsync($"{WikiPath}/pages/{Uri.EscapeDataString(pageName)}");
        response.EnsureSuccessStatusCode();
        return true;
    }

    // Search wiki
    public async Task<WikiSearchResponseV2> 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<WikiSearchResponseV2>(_jsonOptions);
    }

    // Get link graph
    public async Task<WikiGraphV2> GetGraphAsync()
    {
        var response = await _client.GetAsync($"{WikiPath}/graph");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<WikiGraphV2>(_jsonOptions);
    }

    // Get statistics
    public async Task<WikiStatsV2> GetStatsAsync()
    {
        var response = await _client.GetAsync($"{WikiPath}/stats");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadFromJsonAsync<WikiStatsV2>(_jsonOptions);
    }

    // Get page revisions
    public async Task<WikiRevisionsV2> 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<WikiRevisionsV2>(_jsonOptions);
    }

    public void Dispose()
    {
        _client?.Dispose();
    }
}

Usage Examples

// 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):

// 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<string> 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:

{
  "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

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<WikiPageV2> GetPageAsync(string pageName)
{
    var response = await _client.GetAsync($"{WikiPath}/pages/{Uri.EscapeDataString(pageName)}");

    if (!response.IsSuccessStatusCode)
    {
        var error = await response.Content.ReadFromJsonAsync<ErrorResponse>(_jsonOptions);
        throw new GiteaApiException(
            error?.Error?.Code ?? "UNKNOWN",
            error?.Error?.Message ?? "Unknown error",
            (int)response.StatusCode
        );
    }

    return await response.Content.ReadFromJsonAsync<WikiPageV2>(_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

// Retry logic for transient failures
public async Task<T> WithRetryAsync<T>(Func<Task<T>> 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
  • 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