Handle API-disabled 404 Reason header across SDKs#238
Handle API-disabled 404 Reason header across SDKs#238robzolkos wants to merge 4 commits intobasecamp:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the Go/TypeScript/Swift/Kotlin/Python/Ruby SDKs to detect Basecamp’s upcoming “public API disabled” account-level behavior (404 Not Found with Reason: API Disabled) and surface it as a distinct SDK error code (api_disabled) with exit code 10, while preserving existing Reason: Account Inactive handling.
Changes:
- Map
404+Reason: API Disabledto anapi_disablederror/code (exit code10) across SDKs (with Swift/Kotlin preserving exhaustive-match compatibility). - Preserve/extend request-id propagation where supported, and improve Swift header lookup robustness (case-insensitive).
- Update docs and add tests in each SDK to cover the new behavior.
Tip
If you aren't ready for review, convert to a draft PR.
Click "Convert to draft" or run gh pr ready --undo.
Click "Ready for review" or run gh pr ready to reengage.
Reviewed changes
Copilot reviewed 31 out of 31 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| typescript/tests/errors.test.ts | Adds tests for api_disabled exit code mapping and Reason-based 404 behavior. |
| typescript/src/errors.ts | Introduces api_disabled code/exit code and maps 404 + Reason to explicit errors. |
| typescript/README.md | Documents the new api_disabled error code and exit code. |
| swift/Tests/BasecampTests/ErrorTests.swift | Adds assertions and new tests for API-disabled and case-insensitive header handling. |
| swift/Sources/Basecamp/BasecampError.swift | Adds stable code, isAPIDisabled, exit code 10 mapping, and case-insensitive header lookup for Reason / Retry-After. |
| swift/README.md | Documents Swift compatibility approach: .notFound with code == "api_disabled". |
| ruby/test/basecamp/http_test.rb | Adds HTTP-layer tests for Reason: API Disabled and Reason: Account Inactive. |
| ruby/test/basecamp/errors_test.rb | Adds error model tests for ApiDisabledError, exit code 10, and error_from_response mapping. |
| ruby/lib/basecamp/http.rb | Maps 404 + Reason to ApiDisabledError / account-inactive NotFoundError in HTTP error handling. |
| ruby/lib/basecamp/exit_code.rb | Adds API_DISABLED = 10. |
| ruby/lib/basecamp/error_code.rb | Adds API_DISABLED = "api_disabled". |
| ruby/lib/basecamp/error.rb | Maps api_disabled to exit code 10. |
| ruby/lib/basecamp/client.rb | Passes response headers into Basecamp.error_from_response in a defensive fallback path. |
| ruby/lib/basecamp/api_disabled_error.rb | Introduces ApiDisabledError with message/hint/http status defaults. |
| ruby/lib/basecamp.rb | Extends error_from_response to accept headers and map Reason for 404s. |
| python/tests/test_http.py | Adds HTTP client tests mapping Reason: API Disabled/Account Inactive. |
| python/tests/test_errors.py | Adds unit tests for ApiDisabledError, exit code 10, and error_from_response mapping. |
| python/src/basecamp/errors.py | Adds ApiDisabledError, error/exit codes, and Reason-based 404 mapping. |
| python/src/basecamp/init.py | Exports ApiDisabledError. |
| python/README.md | Documents ApiDisabledError and api_disabled code. |
| kotlin/sdk/src/commonTest/kotlin/com/basecamp/sdk/ErrorTest.kt | Adds tests for 404 reason mapping and isApiDisabled behavior. |
| kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/services/BaseService.kt | Passes Reason header through to exception mapping. |
| kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/Download.kt | Passes Reason header through for download error mapping. |
| kotlin/sdk/src/commonMain/kotlin/com/basecamp/sdk/BasecampException.kt | Adds CODE_API_DISABLED, exit code 10, isApiDisabled, and 404 reason mapping. |
| kotlin/README.md | Documents Kotlin compatibility approach: NotFound with code == CODE_API_DISABLED. |
| go/pkg/basecamp/helpers_test.go | Adds tests for Reason-based mapping and request-id preservation. |
| go/pkg/basecamp/helpers.go | Adds checkReasonHeader and applies it to 404 handling in checkResponse. |
| go/pkg/basecamp/errors_test.go | Adds tests for ErrAPIDisabled, ErrAccountInactive, and exit code mapping. |
| go/pkg/basecamp/errors.go | Adds CodeAPIDisabled, exit code 10, and constructors for API-disabled / account-inactive errors. |
| go/pkg/basecamp/client.go | Applies Reason-based mapping to 404 handling in the manual HTTP client path. |
| go/README.md | Documents the new api_disabled error code and exit code. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
1 issue found across 31 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="ruby/lib/basecamp.rb">
<violation number="1" location="ruby/lib/basecamp.rb:92">
P3: `Basecamp.error_from_response` now receives the raw headers but still discards `X-Request-Id`, so errors raised from `download_url` never expose the request ID even though `Basecamp::Error` supports one. Parse the request ID from the headers (handling both casings) and assign it to the error before returning, matching the behavior of `Http#handle_error`.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
|
Fixed — preserved X-Request-Id on Go/Ruby reason-based 404 errors and tightened Swift |
|
Fixed issue identified by cubic — |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 31 out of 31 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…active The Basecamp API returns a Reason header on 404 responses to distinguish account-level conditions from regular not-found errors: - "Reason: API Disabled" — an administrator has turned off API access - "Reason: Account Inactive" — expired trial or suspended account Previously both cases returned a generic "resource not found" error, which was confusing for developers and end users. Now both checkResponse (used by service methods via the generated client) and the raw client's 404 handler inspect the Reason header and return specific, actionable errors: - ErrAPIDisabled() with CodeAPIDisabled and a hint to contact an admin - ErrAccountInactive() with CodeNotFound and a hint about the account state Added CodeAPIDisabled error code and ExitAPIDisabled (exit code 10) so CLI tools can programmatically detect this condition.
2541944 to
0779ef2
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 33 out of 33 changed files in this pull request and generated no new comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Summary
This PR updates the SDKs to recognize the upcoming BC3 behavior from bc3#10136, where public API access can be disabled at the account level and blocked requests return
404 Not FoundwithReason: API Disabled.Without this change, SDK consumers would see a generic not-found error, which obscures the real account-level condition and makes remediation unclear. This PR surfaces that response as a distinct SDK error/code with a helpful hint, while preserving existing source compatibility in Swift and Kotlin for consumers using exhaustive error matching.
What this does
Reason: API Disabledon404responses across the SDKsapi_disablederror/code and exit code10Account Inactivehandling for404responses withReason: Account InactiveCompatibility notes
code == "api_disabled"ReasonandRetry-After, which makes this handling more robust across transports and header representations.User-visible behavior
When an account administrator disables public API access, SDK consumers now get an actionable error instead of a generic not-found response, with guidance that API access can be re-enabled in Adminland under Manage API access.
Summary by cubic
Surfaces 404s with Reason: API Disabled as a distinct api_disabled error with exit code 10 across all SDKs, with a clear Adminland hint. Also recognizes Account Inactive on 404s, and preserves Kotlin binary compatibility.
New Features
Migration
code == "api_disabled"and exit code is 10. Kotlin keeps legacy NotFound constructors.Written for commit 0779ef2. Summary will update on new commits.