| name | codeberg-integration |
| description | Use when interacting with Codeberg repositories - viewing pull requests, issues, comments,
reviews, or any Gitea/Forgejo-based forge API operations. Triggers on: Codeberg, Forgejo,
Gitea API, pull request comments, code review on Codeberg.
|
Codeberg Integration
Overview
Interact with Codeberg (Forgejo/Gitea) repositories via the REST API. Authenticate with a personal access token stored in CODEBERG_TOKEN environment variable.
AI Disclosure Rule
MANDATORY: Every write operation (comment, issue, review, label, etc.) MUST prefix the body text with:
[Generated by AI]
This is non-negotiable. Never post content to Codeberg without this prefix. This applies to:
- Issue bodies and titles
- PR comments and review comments
- Review submissions
- Issue/PR edits
- Release notes
Authentication Setup
- Go to Codeberg > Settings > Applications > Generate New Token
- Name the token descriptively (e.g.
claude-code)
- Select required permissions based on usage:
- Read-only:
read:issue, read:repository
- Write comments/issues:
write:issue
- Manage PRs:
write:issue, read:repository
- Manage labels/milestones:
write:issue
- Manage releases:
write:repository
- Store securely:
export CODEBERG_TOKEN="your-token-here"
Security rules:
- NEVER hardcode tokens in skills, scripts, or committed files
- NEVER log or echo the token value
- Use
read:* scopes unless write access is explicitly needed
- Rotate tokens periodically
API Base
https://codeberg.org/api/v1
For self-hosted Forgejo/Gitea instances, replace codeberg.org with instance domain.
Quick Reference
Token variable note: In some shell environments $CODEBERG_TOKEN may not expand inside curl. Use this pattern:
TOKEN=$(env | grep CODEBERG_TOKEN | cut -d= -f2)
curl -s -H "Authorization: token $TOKEN" "<url>"
Read Operations
| Operation | Endpoint |
|---|
| Get repo info | GET /repos/{owner}/{repo} |
| List PRs | GET /repos/{owner}/{repo}/pulls?state=open&limit=10 |
| Get single PR | GET /repos/{owner}/{repo}/pulls/{index} |
| PR diff | GET /repos/{owner}/{repo}/pulls/{index}.diff |
| PR comments | GET /repos/{owner}/{repo}/issues/{index}/comments |
| PR reviews | GET /repos/{owner}/{repo}/pulls/{index}/reviews |
| Review comments | GET /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments |
| PR files changed | GET /repos/{owner}/{repo}/pulls/{index}/files |
| List issues | GET /repos/{owner}/{repo}/issues?state=open&type=issues&limit=10 |
| Get issue | GET /repos/{owner}/{repo}/issues/{index} |
| Issue comments | GET /repos/{owner}/{repo}/issues/{index}/comments |
| Issue timeline | GET /repos/{owner}/{repo}/issues/{index}/timeline |
| List labels | GET /repos/{owner}/{repo}/labels |
| List milestones | GET /repos/{owner}/{repo}/milestones |
| Repo branches | GET /repos/{owner}/{repo}/branches |
| File contents | GET /repos/{owner}/{repo}/contents/{filepath}?ref={branch} |
| List releases | GET /repos/{owner}/{repo}/releases |
| List repo topics | GET /repos/{owner}/{repo}/topics |
| Search repos | GET /repos/search?q={query}&limit=10 |
| User repos | GET /users/{username}/repos |
| Org repos | GET /orgs/{org}/repos |
| Commit history | GET /repos/{owner}/{repo}/git/commits?sha={branch}&limit=10 |
| Compare branches | GET /repos/{owner}/{repo}/compare/{base}...{head} |
Write Operations
Remember: ALL body/title text MUST start with [Generated by AI]
| Operation | Endpoint | Body |
|---|
| Comment on issue/PR | POST /repos/{owner}/{repo}/issues/{index}/comments | {"body":"[Generated by AI] ..."} |
| Create issue | POST /repos/{owner}/{repo}/issues | {"title":"[Generated by AI] ...","body":"[Generated by AI] ..."} |
| Edit issue | PATCH /repos/{owner}/{repo}/issues/{index} | {"title":"...","body":"...","state":"open|closed"} |
| Edit comment | PATCH /repos/{owner}/{repo}/issues/comments/{id} | {"body":"[Generated by AI] ..."} |
| Delete comment | DELETE /repos/{owner}/{repo}/issues/comments/{id} | -- |
| Add labels to issue | POST /repos/{owner}/{repo}/issues/{index}/labels | {"labels":[label_id,...]} |
| Remove label | DELETE /repos/{owner}/{repo}/issues/{index}/labels/{id} | -- |
| Create label | POST /repos/{owner}/{repo}/labels | {"name":"...","color":"#hex","description":"..."} |
| Assign issue | POST /repos/{owner}/{repo}/issues/{index}/assignees | {"assignees":["username"]} |
| Submit PR review | POST /repos/{owner}/{repo}/pulls/{index}/reviews | {"body":"[Generated by AI] ...","event":"APPROVED|REQUEST_CHANGES|COMMENT"} |
| Create PR | POST /repos/{owner}/{repo}/pulls | {"title":"[Generated by AI] ...","body":"[Generated by AI] ...","head":"branch","base":"main"} |
| Update PR | PATCH /repos/{owner}/{repo}/pulls/{index} | {"title":"...","body":"..."} |
| Merge PR | POST /repos/{owner}/{repo}/pulls/{index}/merge | {"Do":"merge|rebase|squash","merge_message_field":"..."} |
| Create release | POST /repos/{owner}/{repo}/releases | {"tag_name":"v1.0","name":"[Generated by AI] ...","body":"[Generated by AI] ..."} |
| Create milestone | POST /repos/{owner}/{repo}/milestones | {"title":"[Generated by AI] ...","description":"..."} |
| Add topic | PUT /repos/{owner}/{repo}/topics/{topic} | -- |
| Star repo | PUT /user/starred/{owner}/{repo} | -- |
Pagination: Add ?page=1&limit=50 (max 50). Check x-total-count response header.
URL Parsing
Codeberg URLs follow this pattern:
https://codeberg.org/{owner}/{repo}/pulls/{index}#issuecomment-{comment_id}
https://codeberg.org/{owner}/{repo}/issues/{index}
To extract API parameters from a URL:
Important: #issuecomment-{id} URLs can refer to THREE different things:
- Regular issue/PR comments →
/issues/{index}/comments
- Review body comments →
/pulls/{index}/reviews (the review itself)
- Inline review comments →
/pulls/{index}/reviews/{id}/comments
To reliably find any #issuecomment-{id}, use the timeline endpoint which returns all event types:
GET /repos/{owner}/{repo}/issues/{index}/timeline
Then filter by matching id field. The timeline event's type field tells you what it is (comment, review, etc.).
Usage Patterns
Fetch all comments on a PR
curl -s -H "Authorization: token $CODEBERG_TOKEN" \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/issues/{index}/comments" \
| python3 -m json.tool
Note: PRs are a type of issue in Gitea/Forgejo, so use /issues/{index}/comments for conversation comments.
Find a specific comment by ID (from #issuecomment-{id} URL)
Use the timeline endpoint -- it returns all comment types (regular, review, inline):
TOKEN=$(env | grep CODEBERG_TOKEN | cut -d= -f2)
curl -s -H "Authorization: token $TOKEN" \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/issues/{index}/timeline" \
| python3 -c "
import sys, json
target_id = COMMENT_ID # replace with actual ID
for e in json.load(sys.stdin):
if e.get('id') == target_id:
print(f\"Type: {e['type']}\")
print(f\"By: {e['user']['login']}\")
print(f\"Date: {e['created_at']}\")
print(f\"Body:\n{e.get('body','')}\")
break
"
To get inline review comments associated with a review, follow up with:
curl -s -H "Authorization: token $TOKEN" \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{review_id}/comments"
Post a comment on a PR/issue
TOKEN=$(env | grep CODEBERG_TOKEN | cut -d= -f2)
curl -s -X POST -H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"body":"[Generated by AI] Your comment text here"}' \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/issues/{index}/comments"
Create an issue
TOKEN=$(env | grep CODEBERG_TOKEN | cut -d= -f2)
curl -s -X POST -H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "[Generated by AI] Issue title here",
"body": "[Generated by AI] Description of the issue...",
"labels": [123, 456]
}' \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/issues"
Close or reopen an issue/PR
TOKEN=$(env | grep CODEBERG_TOKEN | cut -d= -f2)
curl -s -X PATCH -H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"state":"closed"}' \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/issues/{index}"
Submit a PR review
TOKEN=$(env | grep CODEBERG_TOKEN | cut -d= -f2)
curl -s -X POST -H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"body": "[Generated by AI] Review summary here",
"event": "COMMENT"
}' \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews"
Valid event values: APPROVED, REQUEST_CHANGES, COMMENT
Reply to inline review comments (threaded replies)
Forgejo does not have a dedicated reply endpoint (/replies returns 405). The correct approach is to add comments directly into the target review using POST /reviews/{id}/comments. This places your reply inside that review's thread, attributed to your user, appearing alongside the original inline comments.
Step 1 — find the review ID and its inline comments:
TOKEN=$(env | grep CODEBERG_TOKEN | cut -d= -f2)
curl -s -H "Authorization: token $TOKEN" \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews" \
| python3 -c "import sys,json; [print(f'id={r[\"id\"]} by={r[\"user\"][\"login\"]} comments={r[\"comments_count\"]}') for r in json.load(sys.stdin)]"
curl -s -H "Authorization: token $TOKEN" \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{review_id}/comments" \
| python3 -c "import sys,json; [print(f'id={c[\"id\"]} path={c[\"path\"]} pos={c[\"position\"]}') for c in json.load(sys.stdin)]"
Step 2 — post a reply into the review thread:
TOKEN=$(env | grep CODEBERG_TOKEN | cut -d= -f2)
curl -s -X POST -H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"body": "[Generated by AI] Reply text here",
"path": "path/to/file.cs",
"new_position": 25
}' \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{review_id}/comments"
Use the path and new_position values from the original inline comment you are replying to. The reply appears inside that review thread attributed to the posting user.
To reply to all inline comments in one review in bulk:
TOKEN=$(env | grep CODEBERG_TOKEN | cut -d= -f2)
curl -s -H "Authorization: token $TOKEN" \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{review_id}/comments" \
| python3 -c "
import sys, json
for c in json.load(sys.stdin):
print(c['id'], c['path'], c['position'])
"
Important notes:
DELETE /repos/{owner}/{repo}/issues/comments/{id} silently does nothing for review comments (wrong type). Review comments cannot currently be deleted via the public API.
PATCH /repos/{owner}/{repo}/issues/comments/{id} does work for editing review comments.
- There is no
in_reply_to threading in Forgejo's API — adding to the same review is the correct reply mechanism.
Create a PR
TOKEN=$(env | grep CODEBERG_TOKEN | cut -d= -f2)
curl -s -X POST -H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "[Generated by AI] PR title",
"body": "[Generated by AI] Description of changes...",
"head": "feature-branch",
"base": "main"
}' \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/pulls"
Add labels to an issue/PR
First, list available labels to get IDs:
TOKEN=$(env | grep CODEBERG_TOKEN | cut -d= -f2)
curl -s -H "Authorization: token $TOKEN" \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/labels" \
| python3 -c "import sys,json; [print(f'{l[\"id\"]}: {l[\"name\"]}') for l in json.load(sys.stdin)]"
Then apply:
curl -s -X POST -H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"labels":[LABEL_ID]}' \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/issues/{index}/labels"
Merge a PR
TOKEN=$(env | grep CODEBERG_TOKEN | cut -d= -f2)
curl -s -X POST -H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
-d '{"Do":"merge","merge_message_field":"[Generated by AI] Merge PR #N: title"}' \
"https://codeberg.org/api/v1/repos/{owner}/{repo}/pulls/{index}/merge"
Valid Do values: merge, rebase, squash
Common Mistakes
- Assuming
#issuecomment-{id} is always an issue comment -- it can be a review event. Use the timeline endpoint to find any comment by ID reliably
- Using
/pulls/{index}/comments for conversation comments -- use /issues/{index}/comments instead
- Looking only at
/issues/{index}/comments for PR feedback -- inline code review comments live under /pulls/{index}/reviews/{id}/comments, not the issues endpoint
- Forgetting that PR index and issue index share the same namespace
- Not handling pagination for repos with many comments (default limit is 30)
- Using session cookies instead of API tokens (insecure, fragile, expires)
$CODEBERG_TOKEN may not expand in some shell contexts -- use TOKEN=$(env | grep CODEBERG_TOKEN | cut -d= -f2) as a workaround
- Posting content without
[Generated by AI] prefix -- every write operation body/title MUST include this. No exceptions
- Trying to delete review comments with
/issues/comments/{id} -- silently does nothing; review comments are a different type and cannot be deleted via the public API
- Trying to reply to an inline comment with a
/replies endpoint -- returns 405; Forgejo has no dedicated reply endpoint. Use POST /pulls/{index}/reviews/{review_id}/comments with the same path and new_position as the original comment instead
Response Fields (Comment Object)
Key fields in a comment response:
id - Comment ID (matches #issuecomment-{id} in URLs)
body - Markdown content
user.login - Author username
user.full_name - Author display name
created_at / updated_at - Timestamps
html_url - Direct link to comment on web UI
assets - Attached files