There are several endpoints that have a mismatch between the advertised openapi spec for bitbucket DC and the implementation.
They are not major blockers and you can easily work around it by checking the error messages or output and adapting the calls.
I ran into most of them by generating client code based on the openapi spec (honestly i am somewhat surprised that there were not more mismatches). The generated clients and instrumentation from the openapi spec is where these inconsistencies bite most and they should be easy to fix in the openapi spec.
I understand that this is not high priority, do with it what you want. The differences were found and validated by creating integration tests against an atlas-run based bitbucket dc instance.
# Bitbucket Data Center REST API: OpenAPI Specification Discrepancies & Reproductions
**Target Environment:** Bitbucket Data Center 10.4.x / 10.2.x
**Reference Artifact:** Official Atlassian OpenAPI 3.0 Document (`swagger.v3.json` / `bitbucket-openapi.json`)
**Base URL:** `http://localhost:7990/rest`
**Purpose:** Upstream bug reporting and developer community documentation for REST API & OpenAPI specification inconsistencies.
---
## Executive Summary
When validating the published OpenAPI 3.0 specification against live Bitbucket Data Center instances, several categories of divergences emerge:
1. **OpenAPI 3.0 Structural Violations:** Non-unique `operationId`s and incorrect parameter definitions (`content` used in place of `schema`).
2. **Schema Type & Wire Mismatches:** Epoch millisecond integers returned where RFC 3339 `date-time` strings are declared; JSON arrays returned where single objects are declared.
3. **Property Naming & Semantics:** Required fields expecting different property keys than the spec documents (e.g. `message` vs `commitMessage`).
4. **Undeclared Mandatory Parameters:** Endpoints that return `400` or unhandled `500` errors when parameters documented as optional are omitted.
5. **Undocumented Status Codes & Endpoints:** Missing `204 No Content` declarations and unlisted server evaluation routes (e.g. `/ui/latest/.../code-owners`).
Below are step-by-step reproductions for each confirmed discrepancy.
---
## 1. `createdDate` Declared as `string (date-time)` but Returned as Millisecond Epoch Integer
### Endpoint & Method
* `POST /ssh/latest/keys`
* `GET /ssh/latest/keys`
* `POST /access-tokens/latest/users/{userSlug}`
### OpenAPI Declaration
In 11 schemas across the published specification (including `RestSshKey`, `RestRawAccessToken`, and `RestSshAccessKey`), `createdDate` is declared as:
```json
"createdDate": {
"type": "string",
"format": "date-time"
}
```
*(Note: In 16 other schemas within the same specification, `createdDate` is correctly declared as an integer).*
### Reproduction
```bash
curl -i -u admin:admin -X POST "http://localhost:7990/rest/ssh/latest/keys" \
-H "Content-Type: application/json" \
-d '{
"text": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOm1+kKxH/sE3W5xS9U9V4U9t9O/g8I7pQ2a1b3c4d5e test@example.com"
}'
```
### Actual Server Response (`HTTP 201 Created`)
```json
{
"id": 1,
"text": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOm1+kKxH/sE3W5xS9U9V4U9t9O/g8I7pQ2a1b3c4d5e test@example.com",
"createdDate": 1787787636512
}
```
### Spec Expected Response
```json
{
"createdDate": "2026-09-07T12:00:36.512Z"
}
```
### Client Impact
Strictly-typed decoders (e.g., Go `time.Time`, Java `OffsetDateTime`, Python `pydantic`) fail unmarshalling with errors such as `input is not a JSON string`.
---
## 2. `commitId` Path Parameter Declared with `content` Instead of `schema`
### Endpoint & Method
* `GET /branch-utils/latest/projects/{projectKey}/repos/{repositorySlug}/branches/info/{commitId}`
### OpenAPI Declaration
The `commitId` path parameter is declared under a `content` map rather than directly as a `schema`:
```json
"parameters": [
{
"name": "commitId",
"in": "path",
"required": true,
"content": {
"application/json": {
"schema": {
"type": "string"
}
}
}
}
]
```
### Reproduction
Per OpenAPI 3.0 specification rules, declaring `content.application/json` on a path parameter directs code generators to JSON-serialize the value. This results in the parameter segment arriving wrapped in literal double quotes:
```bash
# Generated client invocation:
curl -i -u admin:admin "http://localhost:7990/rest/branch-utils/latest/projects/PRJ/repos/repo/branches/info/%2277507cd%22"
```
### Actual Server Response (`HTTP 500 Internal Server Error`)
The server unquotes the URL segment to `"\"77507cd\""` and passes it directly to git, which errors out:
```text
'git branch --no-color --contains "\"77507cd\"" --list -- master' exited with code 129
error: malformed object name
```
### Correct Request (Plain String Parameter)
```bash
curl -i -u admin:admin "http://localhost:7990/rest/branch-utils/latest/projects/PRJ/repos/repo/branches/info/77507cd"
# Returns HTTP 200 with valid branch metadata
```
### Query Parameter Companion Bug
Similarly, the `user` query parameter on `POST /ssh/latest/keys` is declared as `content.application/json` referencing `RestSshKey`, when the server description and implementation expect a plain username string.
---
## 3. Apply Suggestion Requires `message` but Spec Declares Optional `commitMessage`
### Endpoint & Method
* `POST /api/latest/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/comments/{commentId}/apply-suggestion`
### OpenAPI Declaration
`RestApplySuggestionRequest` defines an optional property `commitMessage`:
```json
"properties": {
"commentVersion": { "type": "integer" },
"pullRequestVersion": { "type": "integer" },
"suggestionIndex": { "type": "integer" },
"commitMessage": { "type": "string" }
}
```
### Reproduction: Sending Spec-Declared Property
```bash
curl -i -u admin:admin -X POST "http://localhost:7990/rest/api/latest/projects/PRJ/repos/repo/pull-requests/1/comments/10/apply-suggestion" \
-H "Content-Type: application/json" \
-d '{
"commentVersion": 0,
"pullRequestVersion": 1,
"suggestionIndex": 0,
"commitMessage": "Apply code review suggestion"
}'
```
### Actual Server Response (`HTTP 400 Bad Request`)
```json
{
"errors": [
{
"context": null,
"message": "'message' cannot be null or empty",
"exceptionName": "com.atlassian.bitbucket.validation.ArgumentValidationException"
}
]
}
```
### Reproduction: Sending Actual Required Field `message`
```bash
curl -i -u admin:admin -X POST "http://localhost:7990/rest/api/latest/projects/PRJ/repos/repo/pull-requests/1/comments/10/apply-suggestion" \
-H "Content-Type: application/json" \
-d '{
"commentVersion": 0,
"pullRequestVersion": 1,
"suggestionIndex": 0,
"message": "Apply code review suggestion"
}'
# Returns HTTP 204 No Content
```
---
## 4. `POST /gpg/latest/keys` Declares Single Object Response but Returns an Array
### Endpoint & Method
* `POST /gpg/latest/keys`
### OpenAPI Declaration
The `200` response schema for operation `addKey` is declared as a single `RestGpgKey`:
```json
"responses": {
"200": {
"description": "the GPG key that was just created",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/RestGpgKey"
}
}
}
}
}
```
### Reproduction
```bash
curl -i -u admin:admin -X POST "http://localhost:7990/rest/gpg/latest/keys" \
-H "Content-Type: application/json" \
-d '{
"text": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n...\n-----END PGP PUBLIC KEY BLOCK-----"
}'
```
### Actual Server Response (`HTTP 200 OK`)
Even when submitting a block containing a single key, the server answers with a JSON array:
```json
[
{
"id": "55f7a1b2c3d4",
"fingerprint": "04f4a9b8...",
"emailAddress": "user@example.com",
"subKeys": []
}
]
```
### Client Impact
Standard generated models expecting an object payload fail with:
`cannot unmarshal array into object of type RestGpgKey`.
---
## 5. `Default Tasks` Requires Matchers Even Though Spec Marks Them Optional
### Endpoint & Method
* `POST /default-tasks/latest/projects/{projectKey}/tasks`
* `POST /default-tasks/latest/projects/{projectKey}/repos/{repositorySlug}/tasks`
### OpenAPI Declaration
`RestDefaultTaskRequest` declares only `description` as required; `sourceMatcher` and `targetMatcher` are marked optional:
```json
"required": ["description"],
"properties": {
"description": { "type": "string" },
"sourceMatcher": { "$ref": "#/components/schemas/RestRefMatcher" },
"targetMatcher": { "$ref": "#/components/schemas/RestRefMatcher" }
}
```
### Reproduction: Sending Only Declared Required Fields
```bash
curl -i -u admin:admin -X POST "http://localhost:7990/rest/default-tasks/latest/projects/PRJ/tasks" \
-H "Content-Type: application/json" \
-d '{
"description": "Ensure unit tests pass before merge"
}'
```
### Actual Server Response (`HTTP 400 Bad Request`)
```json
{
"errors": [
{
"context": null,
"message": "A sourceMatcher with ID and type is required when creating or updating a default task.",
"exceptionName": "com.atlassian.bitbucket.validation.ArgumentValidationException"
}
]
}
```
---
## 6. Default Reviewers Lookup Mandates Undeclared Repository Query Parameters
### Endpoint & Method
* `GET /default-reviewers/latest/projects/{projectKey}/repos/{repositorySlug}/reviewers`
### OpenAPI Declaration
Query parameters `sourceRepoId` and `targetRepoId` are declared as optional (`required: false` or omitted). Additionally, the `200` response schema is declared as `[RestPullRequestCondition]`.
### Reproduction: Invoking Without Parameters
```bash
curl -i -u admin:admin "http://localhost:7990/rest/default-reviewers/latest/projects/PRJ/repos/repo/reviewers"
```
### Actual Server Response (`HTTP 400 Bad Request`)
```json
{
"errors": [
{
"context": null,
"message": "The source repository with id '0' does not exist",
"exceptionName": "com.atlassian.bitbucket.repository.NoSuchRepositoryException"
}
]
}
```
### Response Payload Discrepancy
When both `sourceRepoId` and `targetRepoId` are passed, the server does **not** return conditions; it returns a flat array of `ApplicationUser` objects:
```json
[
{
"name": "reviewer_user",
"id": 101,
"displayName": "Reviewer User",
"emailAddress": "reviewer@example.com"
}
]
```
---
## 7. Webhook Connectivity Test Throws `500 Internal Server Error` on Missing `url`
### Endpoint & Method
* `POST /api/latest/projects/{projectKey}/repos/{repositorySlug}/webhooks/test`
* `POST /api/latest/projects/{projectKey}/webhooks/test`
### OpenAPI Declaration
The `url` query parameter is marked optional in OpenAPI documentation alongside `webhookId`.
### Reproduction: Invoking with `webhookId` Alone
```bash
curl -i -u admin:admin -X POST "http://localhost:7990/rest/api/latest/projects/PRJ/repos/repo/webhooks/test?webhookId=1" \
-H "Content-Type: application/json" \
-d '{}'
```
### Actual Server Response (`HTTP 500 Internal Server Error`)
Rather than returning a validation error (`400 Bad Request`), the backend crashes inside `UnhandledExceptionMapper` due to an unexpected null URL parameter:
```text
HTTP/1.1 500 Internal Server Error
```
### Successful Call (With Explicit `url`)
```bash
curl -i -u admin:admin -X POST "http://localhost:7990/rest/api/latest/projects/PRJ/repos/repo/webhooks/test?webhookId=1&url=https://example.com/webhook" \
-H "Content-Type: application/json" \
-d '{}'
# Returns HTTP 200 OK
```
---
## 8. Pull Request Rebase Returns Undocumented `204 No Content` on No-Op
### Endpoint & Method
* `POST /git/latest/projects/{projectKey}/repos/{repositorySlug}/pull-requests/{pullRequestId}/rebase`
### OpenAPI Declaration
Documents only `200 OK` with a `RestRefChange` body.
### Reproduction: Rebasing an Already Up-To-Date Pull Request
```bash
curl -i -u admin:admin -X POST "http://localhost:7990/rest/git/latest/projects/PRJ/repos/repo/pull-requests/1/rebase" \
-H "Content-Type: application/json" \
-d '{}'
```
### Actual Server Response
```text
HTTP/1.1 204 No Content
Content-Length: 0
```
### Client Impact
Clients expecting a `200` JSON response envelope fail with unexpected EOF / empty body errors.
---
## 9. Duplicate `operationId`s in the Official OpenAPI 3.0 Document
### OpenAPI Artifact
Official Bitbucket Data Center `swagger.v3.json`.
### Verification Command
```bash
curl -s https://dac-static.atlassian.com/server/bitbucket/10.4.swagger.v3.json | \
jq -r '.. | objects | .operationId // empty' | \
sort | uniq -c | sort -rn | head -10
```
### Observed Output
```text
6 get
3 get_2
2 getStatus
2 delete_6
2 delete_7
```
### Standard Violation
OpenAPI Specification Section 4.7.13 explicitly mandates:
> *"The id MUST be unique among all operations described in the API. The operationId value is case-sensitive."*
Duplicate operation IDs cause namespace collisions in every standard client generator (including `openapi-generator`, Swagger Codegen, and language-specific SDK tools).