Developer documentation / REST API

JSON REST API

Build with your repositories.

Read repositories, inspect commits, track issues, and connect your tools to git.bvdl.io. This reference covers every JSON endpoint under /api/v1 and /api.

BASE URLhttps://git.bvdl.io/api/v1v1
Alpha API. Endpoints and response fields can change without a deprecation period, and data can be wiped by a reset. Keep polling gentle. About the alpha

Hosting integrations can use OAuth and OpenID Connect for sign-in, selected repository access and signed deployment webhooks.

Your first request

Public repository reads work without a token. Try listing the first page:

curl -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories?per_page=10"

Send JSON request bodies with Content-Type: application/json. Examples use $TOKEN for your personal access token.

Path conventions

{owner} is the owner namespace; {repository} is the repository name without .git. Replace placeholders with your values and URL-encode path and query values. {number} is a repository-local issue or pull request number; {release} is a numeric release ID; {sha} is a full commit ID.

Authentication

Create a personal access token in Settings → Tokens, then send it as a Bearer token. API authentication uses the token, rather than your browser session. No CSRF token is needed for these API routes.

curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     https://git.bvdl.io/api/v1/user
Authentication and scope requirements
AccessBehavior
Public readsMost v1 GET endpoints accept anonymous requests for public data. A supplied invalid token still returns 401.
Token required/user, /search, your stars and watches, and every write require a valid token. Private repository reads require permission.
read scopeRead access only. A token does not grant repository access its owner does not already have.
write scopeIncludes read access. Mutations also check the repository permissions listed for each endpoint.
Full accessTokens with no scopes (null or an empty list) permit read and write, subject to the holder’s permissions.

Missing required, invalid, expired, or revoked tokens return 401. Tokens belonging to suspended accounts are also rejected. HTTP Basic authentication is supported with the token as the password, for example curl -u "username:$TOKEN".

Response formats

Successful reads and updates return 200; creation returns 201. A single resource is wrapped in data. Examples throughout this page show the actual field names with illustrative values.

200 OK · application/json
{
    "data": {
        "name": "main",
        "ref": "refs/heads/main",
        "commit": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "protected": false
    }
}

Paginated collections return data as an array and meta as an object. IDs and counts are numbers, flags are booleans, and timestamps use ISO 8601. Nullable fields are returned as null; fields conditionally included by an endpoint are called out below.

Empty bodies and special envelopes. A 204 No Content response has no body—skip JSON parsing. Search returns named result groups, and the contribution graph returns weeks. Unversioned endpoints have different metadata and errors.

Pagination

List endpoints in both API versions accept an integer page from 1 to 100000 (default 1) and an integer per_page from 1 to 100 (default 30). Malformed, empty, or out-of-range pagination values return 422 with field errors.

Paginated response · /api/v1
{
    "data": [
        {
            "name": "main",
            "ref": "refs/heads/main",
            "commit": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
            "protected": false
        }
    ],
    "meta": {
        "total": 1,
        "page": 1,
        "per_page": 30,
        "last_page": 1
    }
}

Increment page until it reaches meta.last_page. Pages beyond the collection return an empty data array. No pagination links or cursors are returned. Search and contribution graphs do not use this envelope.

Errors & status codes

For v1 request errors, error is a message string. Use the HTTP status for control flow; message text may vary. Validation errors add a top-level fields map of field names to arrays of messages.

422 Unprocessable Content · application/json
{
    "error": "The title field is required.",
    "fields": {
        "title": [
            "The title field is required."
        ]
    }
}
HTTP status codes
StatusMeaning
200Success; JSON response body.
201Resource created, or commit status recorded; JSON response body.
204Success; no response body.
401A required token is missing, or the supplied credential is invalid.
403The token scope or account permissions do not allow this action.
404Resource not found, or a private repository is not readable by the caller.
405This path does not support the requested HTTP method.
409The pull request cannot be merged in its current state, or a branch changed.
422Request validation failed. Check the fields map.
429Rate limit exceeded. Wait before retrying.
5xxServer failure. Retry with backoff; a JSON error envelope is not guaranteed for unexpected or upstream failures.

A merge waiting for commit indexing returns 409 with code: indexing_pending. No merge was performed. Wait at least the Retry-After seconds before retrying the merge with the same reviewed sha; use bounded retries with backoff if it remains pending. The delay is guidance, not a guarantee that indexing will have finished.

409 Conflict · Retry-After: 5 · /api/v1
{
    "error": "Commit history is still being indexed. Nothing was merged; wait at least 5 seconds, then refresh the pull request or retry the merge.",
    "code": "indexing_pending",
    "retry_after": 5
}

Query filters must have the documented types and allowed values; arrays such as ?state[]=open return 422. Omitted or empty optional filters use their defaults. Unknown label names and milestone IDs simply match no issues.

A private repository you may not read returns 404, exactly like a nonexistent repository: {"error":"Not found."}.

Rate limits

The v1 API allows 60 requests per minute per authenticated token owner, or per IP for anonymous requests. Multiple tokens for one account share the same budget. Unversioned API routes (including contribution graphs) have a separate budget of 120 per minute per token owner, or 60 per minute per anonymous IP.

For v1 responses, inspect X-RateLimit-Limit and X-RateLimit-Remaining. On 429, wait the seconds specified by Retry-After. The v1 rate-limit body also supplies retry_after:

429 Too Many Requests · /api/v1
{
    "error": "Too many requests. Please slow down and try again later.",
    "retry_after": 60
}

REST endpoints / v1

Users

Look up the account behind a token or a public user profile. Profiles never include the account email address.

Get the authenticated user

GET/api/v1/user

Returns the account identified by your token.

Token required.

Response 200 · User

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/user"

Get a user

GET/api/v1/users/{username}

Look up a username (case-insensitive). Unknown usernames return 404.

Public read.

Response 200 · User

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/users/octocat"
Response example application/json
{
    "data": {
        "id": 1,
        "username": "octocat",
        "name": "Octocat",
        "bio": null,
        "avatar_url": null,
        "created_at": "2026-09-01T12:00:00+00:00"
    }
}

User fields: id (integer), username and name (strings), bio and avatar_url (nullable strings), created_at (nullable timestamp). avatar_url is an application-served upload URL or null; external avatar URLs are never returned. Nested users use the same fields. Deleted issue, pull request, and release authors are represented by null.

REST endpoints / v1

Repositories

Read repository metadata and manage repositories under your own account. These endpoints do not upload files or Git objects; use Git to push your code.

List repositories

GET/api/v1/repositories

Without a token, lists public repositories. With a token, lists repositories you own or directly collaborate on. Paginated.

Public read; a token changes the collection returned.

Query parameters
ParameterDescription
sortquery · stringupdated (default, sorts by push time), stars, or created. Unknown values return 422.
directionquery · stringasc or desc (default). Repositories never pushed sort last when sorting by updated.

Response 200 · Repository list

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories?sort=updated&per_page=30"

Create a repository

POST/api/v1/repositories

Creates a repository owned by the token holder. Organization ownership is not supported here.

Write-capable token required.

JSON body fields
ParameterDescription
namestring · requiredUp to 100 characters; trimmed and lowercased. Start with a letter or digit, then letters, digits, dots, underscores or hyphens. Must be unique for the owner, not reserved, and not end in .git.
descriptionstring | nullOptional description, up to 1,000 characters.
privatebooleanWhether the repository is private. Defaults to false on creation.
default_branchstring | nullOptional, up to 255 characters. A short name such as main becomes refs/heads/main. Defaults to null.

Response 201 · Repository

Request example cURL
curl -X POST -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     -H "Content-Type: application/json" \
     -d '{"name":"hello-world","description":"My first repository","private":false}' \
     "https://git.bvdl.io/api/v1/repositories"

Get a repository

GET/api/v1/repositories/{owner}/{repository}

Returns metadata for a single repository.

Public read; a token is needed for private repositories.

Response 200 · Repository

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world"

Update a repository

PATCH/api/v1/repositories/{owner}/{repository}

Only supplied fields change. A blank description or default_branch clears that field.

Requires a write-capable token and repository admin permission.

JSON body fields
ParameterDescription
descriptionstring | nullOptional description, up to 1,000 characters.
privatebooleanWhether the repository is private. Defaults to false on creation.
default_branchstring | nullThe full ref of an existing branch, such as refs/heads/main; null clears the selection.

Response 200 · Repository

Request example cURL
curl -X PATCH -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     -H "Content-Type: application/json" \
     -d '{"description":"Updated project description"}' \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world"

Delete a repository

DELETE/api/v1/repositories/{owner}/{repository}

Permanently deletes the repository and queues its stored objects for removal.

Requires a write-capable token and repository admin permission.

Response 204 · No response body

Request example cURL
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world"
Response example application/json
{
    "data": {
        "id": 1,
        "name": "hello-world",
        "full_name": "octocat/hello-world",
        "clone_url": "https://git.bvdl.io/octocat/hello-world.git",
        "owner": {
            "id": 1,
            "username": "octocat",
            "name": "Octocat",
            "bio": null,
            "avatar_url": null,
            "created_at": "2026-09-01T12:00:00+00:00"
        },
        "private": false,
        "visibility": "public",
        "description": "My first repository",
        "default_branch": "refs/heads/main",
        "fork": false,
        "forked_from_id": null,
        "stars_count": 0,
        "forks_count": 0,
        "primary_language": null,
        "topics": [],
        "pushed_at": "2026-09-01T12:00:00+00:00",
        "created_at": "2026-09-01T12:00:00+00:00",
        "updated_at": "2026-09-01T12:00:00+00:00"
    }
}

Counts and IDs are integers; private and fork are booleans; topics is a string array. description, default_branch, primary_language, forked_from_id and pushed_at can be null. clone_url is an absolute HTTPS/HTTP URL on this instance. topics is included on reads and updates but omitted on creation.

REST endpoints / v1

Branches

Read branch names, their current commit IDs, and whether a protection rule matches each branch.

List branches

GET/api/v1/repositories/{owner}/{repository}/branches

Paginated, ordered by branch name.

Public read; a token is needed for private repositories.

Response 200 · Branch list

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/branches"

Get a branch

GET/api/v1/repositories/{owner}/{repository}/branches/{branch}

Use the short branch name, such as main or feature/login. The final path segment accepts slashes. An unknown branch returns 404.

Public read; a token is needed for private repositories.

Response 200 · Branch

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/branches/main"
Response example application/json
{
    "data": {
        "name": "main",
        "ref": "refs/heads/main",
        "commit": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "protected": false
    }
}

name, ref and commit are strings; protected is a boolean. commit is the full object ID, not an embedded commit object.

REST endpoints / v1

Commits

Read indexed commit history, newest committed first. Indexing happens after a push, so new commits may take time to appear.

List commits

GET/api/v1/repositories/{owner}/{repository}/commits

Paginated. Without sha, lists all indexed commits. A filtered history walk is bounded to approximately 10,000 commits.

Public read; a token is needed for private repositories.

Query parameters
ParameterDescription
shaquery · stringOptional short branch name or full indexed commit ID, up to 255 characters. Only reachable commits are returned. Unknown values return 404.

Response 200 · Commit list

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/commits?sha=main"

Get a commit

GET/api/v1/repositories/{owner}/{repository}/commits/{sha}

Use a full indexed commit ID. Adds parent IDs and diff statistics against the first parent. Unknown IDs return 404.

Public read; a token is needed for private repositories.

Response 200 · Commit with parents and stats

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/commits/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
Response example application/json
{
    "data": {
        "sha": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "tree": "cccccccccccccccccccccccccccccccccccccccc",
        "message": "Initial commit",
        "author": {
            "name": "Octocat",
            "email": "octocat@example.com",
            "date": "2026-09-01T12:00:00+00:00"
        },
        "committer": {
            "name": "Octocat",
            "email": "octocat@example.com",
            "date": "2026-09-01T12:00:00+00:00"
        },
        "parents": [],
        "stats": {
            "additions": 4,
            "deletions": 0,
            "total": 4,
            "files_changed": 1
        }
    }
}

sha, tree and message are strings. Git author and committer objects contain name, email and a nullable date, distinct from user profile resources. parents (string array) and stats (integer counts) appear only on the detail endpoint. Stats fall back to zero if stored objects are unavailable.

REST endpoints / v1

Commit statuses

Report CI results by context. The path sha must contain exactly 40 lowercase hexadecimal characters; malformed values return 422.

List commit statuses

GET/api/v1/repositories/{owner}/{repository}/commits/{sha}/statuses

Returns the latest status for each context, ordered by context. A valid SHA with no recorded statuses returns an empty list.

Public read; a token is needed for private repositories.

Query parameters
ParameterDescription
pagequery · integer1–100,000; default 1. Invalid pagination values return 422.
per_pagequery · integer1–100; default 30. Out-of-range values return 422.

Response 200 · Status list

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/commits/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/statuses"

Set a commit status

POST/api/v1/repositories/{owner}/{repository}/commits/{sha}/statuses

Creates or replaces the status for this SHA and context. The commit must exist in the repository or be the current head of an open pull request from a fork; otherwise 404.

Requires a write-capable token and repository admin permission.

JSON body fields
ParameterDescription
contextstring · requiredUp to 100 characters. Start with a letter or digit; then letters, digits, dots, underscores, slashes or hyphens.
statestring · requiredpending, success, failure, or error.
descriptionstring | nullOptional summary, up to 255 characters.

Response 201 · Status (including replacements)

Request example cURL
curl -X POST -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     -H "Content-Type: application/json" \
     -d '{"context":"ci/tests","state":"success","description":"All tests passed"}' \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/commits/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/statuses"
Response example application/json
{
    "data": {
        "commit_id": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "context": "ci/tests",
        "state": "success",
        "description": "All tests passed",
        "updated_at": "2026-09-01T12:00:00+00:00"
    }
}

commit_id, context and state are strings. description is nullable; updated_at is a timestamp. Status history is replaced per context rather than appended.

REST endpoints / v1

Issues

Issues are addressed by their per-repository number, not a global database ID.

List issues

GET/api/v1/repositories/{owner}/{repository}/issues

Paginated, newest first. All supplied label names must match.

Public read; a token is needed for private repositories.

Query parameters
ParameterDescription
statequery · stringopen (default), closed, or all. Unknown values return 422.
labelsquery · stringComma-separated label names, such as bug,documentation. At most 20 names of up to 50 characters each, and 1,024 characters in total. Whitespace and empty entries are ignored.
milestonequery · integerOptional positive milestone ID (up to 9223372036854775807). Unknown IDs or IDs from another repository return an empty list.

Response 200 · Issue list

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/issues?state=all&labels=documentation"

Create an issue

POST/api/v1/repositories/{owner}/{repository}/issues

Opens an issue and assigns its number.

Requires a write-capable token and repository push permission.

JSON body fields
ParameterDescription
titlestring · requiredUp to 255 characters.
bodystring | nullOptional Markdown text, up to 65,535 characters.
label_idsinteger[] | nullLabel IDs belonging to this repository.
milestone_idinteger | nullA milestone ID belonging to this repository; null clears it on update.

Response 201 · Issue

Request example cURL
curl -X POST -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     -H "Content-Type: application/json" \
     -d '{"title":"Improve the README","body":"Add installation instructions."}' \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/issues"

Get an issue

GET/api/v1/repositories/{owner}/{repository}/issues/{number}

Returns an issue, its labels, milestone and comment count. Unknown numbers return 404.

Public read; a token is needed for private repositories.

Response 200 · Issue

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/issues/1"

Update an issue

PATCH/api/v1/repositories/{owner}/{repository}/issues/{number}

All fields are optional. Omitted fields are preserved. label_ids replaces the label set; [] or null clears it. Changing state updates closed_at.

Write-capable token and issue authorship or repository issue-management permission required.

JSON body fields
ParameterDescription
titlestringIf supplied, nonempty and at most 255 characters.
bodystring | nullOptional Markdown text, up to 65,535 characters.
statestringopen or closed.
label_idsinteger[] | nullLabel IDs belonging to this repository.
milestone_idinteger | nullA milestone ID belonging to this repository; null clears it on update.

Response 200 · Issue

Request example cURL
curl -X PATCH -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     -H "Content-Type: application/json" \
     -d '{"state":"closed"}' \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/issues/1"
Response example application/json
{
    "data": {
        "number": 1,
        "title": "Improve the README",
        "body": "Add installation instructions.",
        "state": "open",
        "author": {
            "id": 1,
            "username": "octocat",
            "name": "Octocat",
            "bio": null,
            "avatar_url": null,
            "created_at": "2026-09-01T12:00:00+00:00"
        },
        "labels": [
            {
                "id": 2,
                "name": "documentation",
                "color": "0969da"
            }
        ],
        "milestone": {
            "id": 1,
            "title": "First release",
            "state": "open",
            "due_date": null
        },
        "comments": 0,
        "closed_at": null,
        "created_at": "2026-09-01T12:00:00+00:00",
        "updated_at": "2026-09-01T12:00:00+00:00"
    }
}

number and comments are integers; comments is a count, not comment bodies. body, author, milestone and closed_at can be null. Labels contain id, name and color. Milestones contain id, title, state and nullable due_date. Timestamps use ISO 8601.

REST endpoints / v1

Pull requests

List, inspect, open and merge pull requests. Closing, reviewing and editing pull requests are available through the web interface.

List pull requests

GET/api/v1/repositories/{owner}/{repository}/pulls

Paginated, newest first.

Public read; a token is needed for private repositories.

Query parameters
ParameterDescription
statequery · stringopen (default), closed, merged, or all. Unknown values return 422.

Response 200 · Pull request list

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/pulls?state=all"

Create a pull request

POST/api/v1/repositories/{owner}/{repository}/pulls

base and head must be different, existing short branch names in this same repository. Invalid branches return 422. Cross-repository creation is not supported.

Requires a write-capable token and repository push permission.

JSON body fields
ParameterDescription
titlestring · requiredUp to 255 characters.
bodystring | nullOptional Markdown text, up to 65,535 characters.
basestring · requiredTarget branch, up to 255 characters, for example main.
headstring · requiredSource branch, up to 255 characters, for example docs.

Response 201 · Pull request

Request example cURL
curl -X POST -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     -H "Content-Type: application/json" \
     -d '{"title":"Update documentation","base":"main","head":"docs"}' \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/pulls"

Get a pull request

GET/api/v1/repositories/{owner}/{repository}/pulls/{number}

Use the per-repository pull request number. Unknown numbers return 404. Send Accept: application/vnd.github.diff to retrieve a raw Git unified diff instead of the JSON detail.

Public read; a token is needed for private repositories.

Query parameters
ParameterDescription
include_diff_statsquery · boolean (0 or 1)Defaults to 0: JSON detail omits diff_stats and reads no file contents or commit history, making it suitable for status polling. Set to 1 to compute commits, changed_files, additions, deletions and available. Existing clients using diff_stats must add include_diff_stats=1. Diff content is loaded separately through /diff or the raw diff Accept header.

Response 200 · Pull request or raw diff

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/pulls/1"

Get a pull request diff

GET/api/v1/repositories/{owner}/{repository}/pulls/{number}/diff

Compares the merge base to the head. Open requests use current branch tips (saved tips if a branch was deleted); closed and merged requests use saved snapshots. Public forks are supported; a head that can no longer be disclosed returns 404. Defaults to structured JSON; send Accept: application/vnd.github.diff for raw Git unified diff text. Unknown PRs return 404, invalid numeric numbers return 422, unsupported media types return 406. Raw requests return 409 when objects or the merge base are unavailable and 422 when a complete diff exceeds limits; errors always use the JSON error envelope.

Public read; a token is needed for private repositories.

Query parameters
ParameterDescription
Acceptheader · optionalapplication/json (default) or application/vnd.github.diff. Also accepts application/vnd.github.v3.diff, application/x-diff and text/plain. Quality weights are honored. Raw responses use the selected media type and Vary: Accept.

Response 200 · Diff or raw diff

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/pulls/1/diff"

Merge a pull request

PUT/api/v1/repositories/{owner}/{repository}/pulls/{number}/merge

Uses the same merge behavior and branch protections as the web interface: fast-forward when possible, otherwise a merge commit. Supports existing pull requests from forks. Returns the merged pull request with state, merged_at, merged_by and merge_commit populated. The merge_commit is the resulting base tip (the head for a fast-forward). Indexing and webhook delivery complete asynchronously. No body is required. Returns 409 for closed requests, conflicts, missing branches, nothing to merge, unmet reviews or status checks, quota limits, or changed branches. If current branch history is not fully indexed, returns 409 with code indexing_pending, retry_after in seconds and a Retry-After header. This response leaves the pull request and branches unchanged. Wait at least that long before retrying; use bounded retries with backoff if indexing remains pending. Keep the reviewed sha on retries so a newer push requires review. Storage write failures return 503. Repeating this request after a successful merge returns 200 with the saved result without merging or emitting events again, even if branches have moved or been deleted. If sha is supplied it must match the saved merged head; a mismatch still returns 409. GET pull request details also includes merge_commit; it is null for historical merges made before this field was recorded.

Requires a write-capable token and repository push permission.

JSON body fields
ParameterDescription
shastring · optionalExpected current head commit: exactly 40 lowercase hexadecimal characters. A mismatch returns 409 without merging. Omit to merge the current head; malformed or null values return 422.

Response 200 · Merged pull request

Request example cURL
curl -X PUT -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     -H "Content-Type: application/json" \
     -d '{"sha":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}' \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/pulls/1/merge"
Response example application/json
{
    "data": {
        "number": 2,
        "title": "Update documentation",
        "body": null,
        "state": "open",
        "base_ref": "main",
        "head_ref": "docs",
        "base_commit": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
        "head_commit": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
        "merge_commit": null,
        "author": {
            "id": 1,
            "username": "octocat",
            "name": "Octocat",
            "bio": null,
            "avatar_url": null,
            "created_at": "2026-09-01T12:00:00+00:00"
        },
        "merged_at": null,
        "merged_by": null,
        "created_at": "2026-09-01T12:00:00+00:00",
        "updated_at": "2026-09-01T12:00:00+00:00"
    }
}

number is an integer. state is open, closed or merged. base_ref and head_ref are short branch names; base_commit and head_commit are commit IDs. body, author, merged_at and merged_by can be null. merged_by uses the User resource. The /diff JSON data contains available, truncated, changed_files, additions, deletions and files. Each file has filename, status (added/modified/deleted), additions, deletions, binary, available, too_large, base_mode, head_mode and patch (a complete per-file Git diff, or null for binary, oversized or missing blobs). Totals cover only returned, computed files; available describes the comparison baseline, so also check each file. At most 500 files are returned; truncated flags omitted files. Blobs over 1 MiB are not loaded; text rendering is bounded to 512 KiB per side, 5,000 combined lines and 1,000,000 comparison cells. Request-wide read budgets can also return 422. JSON replaces invalid UTF-8 with replacement characters; raw text preserves bytes. Renames appear as deletion plus addition. Raw binary changes use Git binary-difference notices, not binary patches. File modes, submodules and missing final newlines are preserved. Responses are private and not cacheable. Patch/email format is not supported.

REST endpoints / v1

Releases

Read release metadata. Creating releases and uploading assets are currently available through the web interface only. Drafts are visible only to callers who may manage releases; others receive 404 for a draft.

List releases

GET/api/v1/repositories/{owner}/{repository}/releases

Paginated, newest published first. Includes asset counts; omits the assets array.

Public read; a token is needed for private repositories.

Response 200 · Release list

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/releases"

Get a release

GET/api/v1/repositories/{owner}/{repository}/releases/{release}

Use the numeric release ID, not a tag name. Includes assets. The release must belong to the repository in the URL.

Public read; a token is needed for private repositories.

Response 200 · Release with assets

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/releases/1"
Response example application/json
{
    "data": {
        "id": 1,
        "tag_name": "v1.0.0",
        "name": "First release",
        "body": null,
        "draft": false,
        "prerelease": false,
        "author": {
            "id": 1,
            "username": "octocat",
            "name": "Octocat",
            "bio": null,
            "avatar_url": null,
            "created_at": "2026-09-01T12:00:00+00:00"
        },
        "assets_count": 1,
        "assets": [
            {
                "id": 1,
                "filename": "source.zip",
                "size": 1024,
                "content_type": "application/zip",
                "download_count": 0,
                "download_url": "https://git.bvdl.io/octocat/hello-world/releases/1/assets/1/download",
                "uploaded_at": "2026-09-01T12:00:00+00:00",
                "created_at": "2026-09-01T12:00:00+00:00"
            }
        ],
        "published_at": "2026-09-01T12:00:00+00:00",
        "created_at": "2026-09-01T12:00:00+00:00",
        "updated_at": "2026-09-01T12:00:00+00:00"
    }
}

IDs and counts are integers; draft and prerelease are booleans. body, author and published_at can be null. Asset size is bytes. download_url is an absolute web URL (or null when unavailable); private downloads require web authorization and do not use these API token routes.

REST endpoints / v1

Stars & watches

Stars bookmark a repository; watches subscribe to activity. All PUT and DELETE operations here are idempotent and return 204 with an empty body, even when already in the requested state. They need read access to the repository and a write-capable token.

List your starred repositories

GET/api/v1/user/starred

Paginated, most recently starred first.

Token required.

Response 200 · Repository list

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/user/starred"

Star a repository

PUT/api/v1/user/starred/{owner}/{repository}

No request body.

Write-capable token and repository read access required.

Response 204 · No response body

Request example cURL
curl -X PUT -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/user/starred/octocat/hello-world"

Unstar a repository

DELETE/api/v1/user/starred/{owner}/{repository}

No request body.

Write-capable token and repository read access required.

Response 204 · No response body

Request example cURL
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/user/starred/octocat/hello-world"

List stargazers

GET/api/v1/repositories/{owner}/{repository}/stargazers

Paginated users, most recent star first.

Public read; a token is needed for private repositories.

Response 200 · User list

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/stargazers"

List your watched repositories

GET/api/v1/user/watched

Paginated, most recently watched first. Muted watches (ignore) are excluded.

Token required.

Response 200 · Repository list

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/user/watched"

Watch a repository

PUT/api/v1/user/watched/{owner}/{repository}

No request body. A new watch uses level all; an existing watch keeps its level. This endpoint does not change notification levels.

Write-capable token and repository read access required.

Response 204 · No response body

Request example cURL
curl -X PUT -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/user/watched/octocat/hello-world"

Unwatch a repository

DELETE/api/v1/user/watched/{owner}/{repository}

No request body.

Write-capable token and repository read access required.

Response 204 · No response body

Request example cURL
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/user/watched/octocat/hello-world"

List responses use the Repository and User shapes above with the standard pagination envelope. Mutation responses have no JSON body to parse.

REST endpoints / v1

OAuth and OpenID Connect

Register clients under Settings → Applications. Use authorization codes with S256 PKCE, an exact HTTPS callback, state, and an OIDC nonce. Consent selects repositories explicitly. Access tokens last one hour; refresh tokens rotate and last 30 days, with an absolute 90-day connection lifetime.

Discover the identity provider

GET/.well-known/openid-configuration

Discover issuer, protocol URLs and supported capabilities. Validate ID tokens using the advertised RS256 signing keys, issuer, client audience and original nonce.

Public.

Response 200 · Discovery metadata

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/.well-known/openid-configuration"

Get signing keys

GET/oauth/jwks

Public RSA keys for verifying ID tokens.

Public.

Response 200 · JSON Web Key Set

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/oauth/jwks"

Request authorization

GET/oauth/authorize

Required query parameters: response_type=code, client_id, redirect_uri, scope, state, code_challenge, code_challenge_method=S256. Include nonce for OIDC. The user logs in and selects repositories. Consent is always explicit.

Browser session; login and MFA when needed.

Response 200 · Consent page or redirect

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/oauth/authorize"

Exchange or refresh credentials

POST/oauth/token

Send grant_type=authorization_code, code, redirect_uri and code_verifier. To refresh, send grant_type=refresh_token and refresh_token; do not send scope. Store the new token pair atomically.

Client secret via Basic or client_secret_post. Public clients send client_id without a secret.

Response 200 · access_token, refresh_token, expires_in, scope; id_token with openid

Request example cURL
curl -X POST -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/oauth/token"

Read OIDC identity

GET/oauth/userinfo

Returns sub and approved profile/email claims. Match sub against the verified ID token.

Bearer access token with openid scope.

Response 200 · OIDC claims

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/oauth/userinfo"

Read OIDC identity

POST/oauth/userinfo

Same response as GET; no request body needed.

Bearer access token with openid scope.

Response 200 · OIDC claims

Request example cURL
curl -X POST -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/oauth/userinfo"

Disconnect an application

POST/oauth/revoke

Send token containing a current access token or refresh token. Revokes the whole connection and disables its hooks. Unknown tokens return 200.

Client authentication. Public clients send client_id.

Response 200 · Empty response

Request example cURL
curl -X POST -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/oauth/revoke"

Load consent repository choices

GET/api/oauth/consent/repositories

Internal browser picker. Page is a positive integer; results have id and full_name with has_more. This is not an integration-token endpoint.

Authenticated browser session.

Response 200 · data, has_more

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/oauth/consent/repositories"

Scopes: openid, profile, email, repositories:read, webhooks:manage. OAuth supports /api/v1/user (profile), selected repository listing/details/branches and Git HTTPS clone/fetch (repositories:read), and connection-owned push webhooks (webhooks:manage plus repositories:read and current administrator rights). Other API actions and pushes are refused. Use a Git credential helper with username oauth2 and the access token as password. Reauthorize to change selected repositories or scopes. Suspension, security changes, permission loss and revocation are enforced on subsequent access and queued webhook delivery. The hosting backend must validate state and OIDC tokens, verify X-Webhook-Signature over the raw payload, deduplicate X-Webhook-Delivery and serialize deployment promotion.

REST endpoints / v1

Deployment webhooks

Manage signed push hooks. OAuth clients can manage only hooks belonging to their exact connection; manually created hooks and other connections are isolated.

List webhooks

GET/api/v1/repositories/{owner}/{repository}/webhooks

Paginated results, with page and per_page (1–100). Secrets are never returned.

Current repository administrator and write-capable PAT or selected-repository OAuth webhooks:manage.

Response 200 · data and meta

Request example cURL
curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/webhooks"

Create a push webhook

POST/api/v1/repositories/{owner}/{repository}/webhooks

HTTPS public destination with existing SSRF protections. A repository can have at most 100 hooks created through this endpoint.

Current administrator plus token permission as above.

JSON body fields
ParameterDescription
urlstring · requiredPublic HTTPS URL, at most 2,048 characters.
secretstring · required32–255 characters; generate a random secret and keep it on the receiving backend.
eventsstring[] · requiredExactly ["push"].
activebooleanDefaults to true.

Response 201 · Webhook

Request example cURL
curl -X POST -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/webhooks"

Update a webhook

PATCH/api/v1/repositories/{owner}/{repository}/webhooks/{webhook}

Accepts any subset of url, secret, events and active with the same validation as creation.

Current administrator plus token permission as above.

Response 200 · Webhook

Request example cURL
curl -X PATCH -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/webhooks/{webhook}"

Delete a webhook

DELETE/api/v1/repositories/{owner}/{repository}/webhooks/{webhook}

The ID must belong to the repository and, for OAuth, the current connection.

Current administrator plus token permission as above.

Response 204 · Empty response

Request example cURL
curl -X DELETE -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     "https://git.bvdl.io/api/v1/repositories/octocat/hello-world/webhooks/{webhook}"
Response example application/json
{
    "data": {
        "id": 1,
        "url": "https://hosting.example/webhooks/git",
        "events": [
            "push"
        ],
        "active": true,
        "has_secret": true
    }
}

Push payloads include repository.id, ref, branch, before, after and pusher. Match the configured full branch ref and deploy the after commit. Verify X-Webhook-Signature (sha256= plus HMAC-SHA256 of the raw body) in constant time, and deduplicate X-Webhook-Delivery across retries. Branch deletion is a separate event and is not included in push-only subscriptions.

Additional endpoints / api

Organizations

Organization reads are available on the unversioned API only. Both require a token and use the unversioned response format.

List your organizations

GET/api/orgs

Lists organizations you belong to, sorted by name. Accepts page and per_page. Returns 200 with a paginated organization list; each organization includes your role (owner or member).

Get an organization

GET/api/orgs/{org}

{org} is the organization slug. Returns 200 with member and visible repository counts, or 404 for an unknown slug. Members see the total repository count; nonmembers see only the public repository count.

curl -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     https://git.bvdl.io/api/orgs/bvdl
200 OK · Organization detail
{
    "data": {
        "id": 1,
        "name": "BVDL",
        "slug": "bvdl",
        "description": null,
        "avatar_url": null,
        "created_at": "2026-09-01T12:00:00+00:00",
        "member_count": 2,
        "repo_count": 3
    }
}

IDs and counts are integers; name and slug are strings; description and avatar_url are nullable strings. avatar_url is currently null; organizations use generated initials. List items add role instead of member_count and repo_count.

Additional endpoints / api

Contribution graph

GET/api/contributions/graph

Returns daily indexed commit counts. A username or public repository can be queried anonymously. With neither selector, a token is required and the graph belongs to the token holder. Private repositories require read permission; unknown or unreadable targets return 404.

Query parameters
ParameterDescription
usernamestring · optionalUsername, up to 255 characters, case-insensitive. Counts commits matched by the user’s author email across repositories.
repositorystring · optionalowner/repository, up to 512 characters. Takes precedence over username when both are supplied.
weeksinteger · optional1–53, default 52. Invalid values return 422.
curl -H "Accept: application/json" \
     "https://git.bvdl.io/api/contributions/graph?username=octocat&weeks=1"
200 OK · One-week example
{
    "weeks": [
        [
            {
                "date": "2026-08-30",
                "count": 0
            },
            {
                "date": "2026-08-31",
                "count": 2
            },
            {
                "date": "2026-09-01",
                "count": 1
            },
            {
                "date": "2026-09-02",
                "count": 0
            },
            {
                "date": "2026-09-03",
                "count": 0
            },
            {
                "date": "2026-09-04",
                "count": 0
            },
            {
                "date": "2026-09-05",
                "count": 0
            }
        ]
    ]
}

weeks contains arrays of seven days, Sunday first, oldest week first through the current week. date is YYYY-MM-DD; count is an integer (zero on quiet or future days). There is no data or meta wrapper. Validation and missing-token errors use the unversioned nested shape; an invalid supplied token is rejected by authentication as {"error":"Unauthenticated."}.

Additional endpoints / api

Unversioned API

The original endpoints remain available at /api. Every endpoint in the table below requires a token, including public repository reads. Use the documented paths and shapes for your selected API version; switching only the prefix is not sufficient.

All list endpoints accept page and per_page with the same defaults and caps as v1, but omit meta.last_page. Stop when page × per_page ≥ total. Single resources still use a data wrapper.

Unversioned list envelope · empty collection
{
    "data": [],
    "meta": {
        "total": 0,
        "page": 1,
        "per_page": 30
    }
}
Unversioned validation error · 422
{
    "error": {
        "message": "The title field is required.",
        "code": "validation_failed",
        "fields": {
            "title": [
                "The title field is required."
            ]
        }
    }
}

Other errors use error.message and error.code, with codes such as unauthenticated, forbidden, not_found, method_not_allowed or http_error. Rate-limit errors use the standard unversioned HTTP-error envelope, with no v1 retry_after body field.

Unversioned endpoints (organization and contribution routes are documented above)
Endpoint / responseParameters and permissions
GET/api/user
200 · Identity
The token holder: id, username and name only.
GET/api/repositories
200 · Repository list
Owned or directly collaborated repositories, most recently pushed first (never pushed last). No sort or direction filter.
POST/api/repositories
201 · Repository
Write-capable token. name and visibility (public or private) are required; description is optional and nullable. The name and description constraints match v1. No default_branch input.
GET/api/repositories/{owner}/{repository}
200 · Repository
Requires repository read access.
PUT/api/repositories/{owner}/{repository}
200 · Repository
Admin permission and a write-capable token. Optional description, visibility (public or private), and default_branch (existing full ref or null). Only supplied fields change.
DELETE/api/repositories/{owner}/{repository}
204 · Empty body
Admin permission and a write-capable token. Permanently deletes the repository.
GET/api/repositories/{owner}/{repository}/branches
200 · Branch list
Read access. Same branch fields as v1, excluding protected; ordered by name.
GET/api/repositories/{owner}/{repository}/commits
200 · Commit list
Read access. Same commit list fields as v1, newest committed first. No sha filter or detail endpoint.
GET/api/repositories/{owner}/{repository}/issues
200 · Issue list
Read access. Same state, labels and milestone filters as v1; newest first.
POST/api/repositories/{owner}/{repository}/issues
201 · Issue
Push permission and a write-capable token. Same title, body, label_ids and milestone_id fields and constraints as v1.
GET/api/repositories/{owner}/{repository}/issues/{number}
200 · Issue
Read access. Lookup by per-repository number.
GET/api/repositories/{owner}/{repository}/pulls
200 · Pull request list
Read access. Same state filter as v1; newest first.
POST/api/repositories/{owner}/{repository}/pulls
201 · Pull request
Push permission and a write-capable token. Same title, body, base and head fields and branch constraints as v1.
GET/api/repositories/{owner}/{repository}/pulls/{number}
200 · Pull request
Read access. Lookup by per-repository number.
PUT/api/repositories/{owner}/{repository}/pulls/{number}/merge
200 · Merged pull request
Push permission and a write-capable token. Same optional sha field, merge behavior, branch protections and 409/503 outcomes as v1. Pending indexing returns 409 with error.code indexing_pending, top-level retry_after in seconds and a Retry-After header. Wait before retrying with the same reviewed sha. Returns state, merged_at and merged_by in the unversioned pull request shape.

Resource differences

Unversioned repository objects contain id, name, full_name, owner, visibility, private, default_branch, description, fork, pushed_at and created_at. Owner objects contain only id, username and name.

Issue and pull request fields match v1 except that nested users include only id, username and name, and issue milestones omit due_date. There is no unversioned issue update route. These routes do not expose v1 release, status, search, star or watch operations.

Create a repository · unversioned request
curl -X POST -H "Authorization: Bearer $TOKEN" \
     -H "Accept: application/json" \
     -H "Content-Type: application/json" \
     -d '{"name":"hello-world","visibility":"public"}' \
     https://git.bvdl.io/api/repositories
↑ Back to top