Error Model¶
All errors follow RFC 7807
application/problem+json:
{
"type": "https://sfmapi/errors/conflict",
"title": "Conflict",
"status": 409,
"detail": "Project 'foo' already exists",
"instance": "/v1/projects"
}
Status Code Mapping¶
HTTP |
Server error class |
Caller handling |
|---|---|---|
403 |
|
Treat as an auth or tenancy failure. |
404 |
|
Resource does not exist in the current tenant scope. |
409 |
|
Retry only after changing the request or idempotency key. |
413 |
|
Reduce upload size or increase the storage quota. |
422 |
|
Fix request fields using the structured |
429 |
|
Back off or reduce concurrent work. |
503 |
Backend unavailable error |
Retry when the selected backend/provider is available. |
507 |
|
Free capacity or move to a larger storage backend. |
Server-Side Hierarchy¶
Stable exception classes for sfmapi extensions.
- exception sfmapi.errors.BadRequestError(detail='', **extras)[source]
Bases:
SfmApiError400 — request couldn’t be parsed or violates a non-schema invariant.
Use
ValidationError(422) when the request shape is well-formed but a field value is wrong; use this when the request couldn’t even be interpreted (e.g., malformed Content-Range, conflicting query params).- Parameters:
detail (str)
extras (Any)
- Return type:
None
- status_code: int = 400
- error_type: str = 'bad_request'
- title: str = 'Bad request'
- exception sfmapi.errors.CapabilityUnavailableError(*, capability, reason='')[source]
Bases:
SfmApiErrorThe requested SfM feature isn’t supported by the current backend.
Returns
501 Not Implementedrather than 5xx because the request itself is well-formed and the server is healthy — it just doesn’t expose the requested capability. Thecapabilityextra carries the canonical feature name (seeapp.core.capabilities) so clients can correlate withGET /v1/capabilities.- Parameters:
capability (str)
reason (str)
- Return type:
None
- status_code: int = 501
- error_type: str = 'capability_unavailable'
- title: str = 'Capability not available in this deployment'
- exception sfmapi.errors.ConflictError(detail='', **extras)[source]
Bases:
SfmApiError409 — the request conflicts with the current resource state.
Use for optimistic-concurrency / state-machine violations (e.g., finalizing an upload that’s already finalized, deleting a project with active jobs). Use
ValidationError(422) when the request itself is wrong; use this when the request would be valid against a different server state.- Parameters:
detail (str)
extras (Any)
- Return type:
None
- status_code: int = 409
- error_type: str = 'conflict'
- title: str = 'Conflict'
- exception sfmapi.errors.NotFoundError(detail='', **extras)[source]
Bases:
SfmApiError404 — the addressed resource doesn’t exist for this tenant.
Routes MUST raise this rather than returning
Noneso the failure shows up asapplication/problem+json. Tenant scoping is implicit: a row that exists under a different tenant looks like 404 to the caller (seeL2— multi-tenant boundary).- Parameters:
detail (str)
extras (Any)
- Return type:
None
- status_code: int = 404
- error_type: str = 'not_found'
- title: str = 'Resource not found'
- exception sfmapi.errors.PycolmapUnavailableError(reason='')[source]
Bases:
CapabilityUnavailableErrorBackwards-compat: the colmap_mod backend can’t load pycolmap.
Subclass of
CapabilityUnavailableErrorso callers that catch the parent see this too. Status code stays 501 — the failure is the same shape (a capability the deployment doesn’t expose), even though the reason is backend-specific.- Parameters:
reason (str)
- Return type:
None
- error_type: str = 'pycolmap_unavailable'
- title: str = 'pycolmap not available in this deployment'
- exception sfmapi.errors.QuotaExceededError(detail='', **extras)[source]
Bases:
SfmApiError429 — caller hit a per-tenant or per-request rate / size cap.
Currently fires on oneshot request-body caps (
oneshot_max_request_bytes); Phase 5 plumbs in fair-share scheduling + per-tenant job quotas through this same error. SDK ergonomics shims interpret 429 as retryable whenretry_afteris set.- Parameters:
detail (str)
extras (Any)
- Return type:
None
- status_code: int = 429
- error_type: str = 'quota_exceeded'
- title: str = 'Quota exceeded'
- exception sfmapi.errors.SfmApiError(detail='', **extras)[source]
Bases:
ExceptionBase class for every domain error sfmapi raises.
Subclasses MUST set
status_code(HTTP) anderror_type(the slug used to buildtype: https://sfmapi.github.io/errors/<slug>in the RFC 7807 envelope). The FastAPI exception handler inapp/main.py::sfmapi_error_handlerturns these intoapplication/problem+jsonresponses; never raise rawHTTPExceptionfrom services — keep the wire shape consistent.Free-form
extrasare merged into the envelope as top-level keys (capabilityon 501,retry_afteron 429); see the typed surface inProblemResponse.- Parameters:
detail (str)
extras (Any)
- Return type:
None
- status_code: int = 500
- error_type: str = 'internal'
- title: str = 'Internal error'
- as_problem(instance=None)[source]
- Parameters:
instance (str | None)
- Return type:
dict[str, Any]
- exception sfmapi.errors.StorageError(detail='', **extras)[source]
Bases:
SfmApiError507 Insufficient Storage — the backing store rejected a write.
Used for blob-store write failures, snapshot rename collisions, workspace-out-of-space conditions. Distinct from a generic 500: the request itself was valid, the storage layer couldn’t hold it.
- Parameters:
detail (str)
extras (Any)
- Return type:
None
- status_code: int = 507
- error_type: str = 'storage'
- title: str = 'Storage error'
- exception sfmapi.errors.TenantViolationError(detail='', **extras)[source]
Bases:
SfmApiError403 — tenant scoping was bypassed or auth resolution failed.
Raised by
app.core.tenancy.current_tenant()when the Authorization header is missing / unrecognized, and by repository helpers that detect a tenant_id mismatch. SeeL2indecisions.md(multi-tenant from day 1,defaultuntil auth lands). Once real auth ships, 401 (missing) and 403 (rejected) will split — for now both surface here.- Parameters:
detail (str)
extras (Any)
- Return type:
None
- status_code: int = 403
- error_type: str = 'tenant_violation'
- title: str = 'Tenant boundary violation'
- exception sfmapi.errors.ValidationError(detail='', **extras)[source]
Bases:
SfmApiError422 — semantic validation failed after parsing succeeded.
Distinct from FastAPI’s
fastapi.RequestValidationError(Pydantic schema-shape failures): this fires from inside services when a value is well-typed but rejected on a domain rule (cross-field consistency, foreign-key lookup, plausibility). Both paths emit the same RFC 7807problem+jsonshape — seeL19for the structurederrors[]invariant.- Parameters:
detail (str)
extras (Any)
- Return type:
None
- status_code: int = 422
- error_type: str = 'validation'
- title: str = 'Validation error'
Client packages map this RFC 7807 envelope into their own exception
types. Keep SDK-specific exception imports in the sfmapi-sdk
repository so this server reference stays focused on the wire contract.