Skip to content

feat!: bearer authentication, typed API errors and safe retries - #4

Merged
secretpray merged 5 commits into
nextfrom
feat/next-transport
Nov 6, 2026
Merged

secretpray merged 5 commits into
nextfrom
feat/next-transport

Conversation

@secretpray

@secretpray secretpray commented Nov 3, 2026 •

Copy link
Copy Markdown
Collaborator

Depends on #3. Authentication, error handling, retries and logging of the transport layer.

Authentication

  • Authorization: Bearer <api_key>. A value that already starts with Bearer and surrounding whitespace are normalized
  • Each endpoint declares whether it needs a key:
    • required: AuthenticationError is raised before the request when no key is configured
    • optional: the key is sent when present (machine listings, which are fresher with a key)
    • none: the key is never sent
  • A 401 for a key that does not start with octa_ak_ adds a hint on where to create an API key

Base URL

  • The default base URL is https://api-dev.octa.space
  • Trailing slashes are stripped, and a path prefix in base_url is kept

Errors

Status Class
400, 422 ValidationError
401 AuthenticationError
403 PermissionError
404 NotFoundError
409 ConflictError (new)
429 RateLimitError
503 ServiceUnavailableError
other 5xx ServerError (BadGatewayError 502, GatewayTimeoutError 504)
  • The error message is the API's message, falling back to the body text, cut to 512 characters
  • ApiError#retry_after, #rate_limit, #body, #request_id
  • A 2xx response that is not JSON (for example from a proxy) raises ApiError, not a parsing exception

Rate limits and retries

  • RateLimit-Limit/Remaining/Reset → OctaSpace::RateLimit; the latest value is client.rate_limit
  • Retry-After is read as seconds (a fraction is rounded up) or as an HTTP date
  • Only GET requests and session stop are retried, on network errors and on 429/502/503/504
    • Backoff with jitter, capped at max_retry_interval (10 s)
    • Retry-After is honoured up to max_retry_after (60 s); a longer wait, such as the restart cooldown, is raised to the caller
  • Starting and restarting sessions are never retried
  • Failover across base_urls applies only to GET on network errors. A request that changes state is never sent to a second host; previously a POST could be repeated on the next URL.

Other

  • Empty 202/204 bodies are accepted
  • One reused connection per URL (previously a new connection per request when there were several URLs)
  • One log line per request ([OctaSpace] GET https://… -> 200 (12 ms)). Headers and bodies are never logged; previously Faraday's logger printed the Authorization header. on_request no longer receives headers.
  • faraday-retry dependency removed

Verification

  • bundle exec rake test on Ruby 3.3, 3.4 and 4.0: 0 failures. New tests cover authentication modes, the retry policy, Retry-After limits, GET-only failover, 202/204, invalid JSON, the key hint, and that the key never reaches logs or hooks
  • standardrb: clean

The SDK becomes a bare library on the way to the next API:

- remove the Rails playground (test/dummy, bin/playground,
  lib/octaspace/playground), MockTransport, the Rails Engine and the
  Appraisal/Rails matrix; the Railtie stays (no Rails dependency)
- require Ruby >= 3.3 (3.2 is EOL); CI tests 3.3, 3.4 and 4.0
- CI: actions pinned to commit SHAs, read-only token, concurrency,
  runs on main and next and on every PR, strict gem build,
  bundler-audit job
- Dependabot for bundler and actions, targeting next, 3-day cooldown
- faraday >= 2.14.3 (fixes published advisories); minitest 6,
  current webmock/standard/rake; railties and puma removed
- docs/GIT_WORKFLOW_NEXT.md: how to work with the next branch
Transport for the next API (api-dev.octa.space):

- Authorization: Bearer <api_key>; a pasted "Bearer " prefix and
  whitespace are stripped; 401 with a non-octa_ak_ key explains that
  legacy tokens are refused
- per-endpoint auth mode: :required raises AuthenticationError before
  any request when no key is configured, :optional sends the key when
  present, :none never sends it
- default base URL https://api-dev.octa.space (BASE_URL_DEV); the
  production hosts still serve the legacy API
- errors: 400 -> ValidationError, 409 -> ConflictError, 503 ->
  ServiceUnavailableError; message taken from {"message"}; ApiError
  exposes retry_after, rate_limit and body
- RateLimit-* headers parsed into OctaSpace::RateLimit, the latest
  available as client.rate_limit; Retry-After as seconds or HTTP date
- retries only for GET and idempotent requests, on network errors and
  429/502/503/504, honouring Retry-After up to max_retry_after (60 s);
  POST start/restart are never retried
- failover across base_urls only for GET; a request that changes state
  is never sent to a second host
- JSON parsed by the SDK: empty 202/204 bodies are fine, a non-JSON
  2xx body raises ApiError
- logging is one line per request; headers (API key) and bodies are
  never logged, and on_request no longer sees headers
- faraday-retry dependency removed
@secretpray secretpray changed the title feat!: bearer auth, dev stand default and next API error handling feat!: bearer authentication, typed API errors and safe retries Nov 6, 2026
@secretpray
secretpray changed the base branch from chore/next-infra to next November 6, 2026 10:29
@secretpray
secretpray merged commit 3e8b21f into next Nov 6, 2026
5 checks passed
@secretpray
secretpray deleted the feat/next-transport branch November 6, 2026 10:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant