Contracts

push-content-api.md

API Contract: push_content

Feature: 047-namespace-aware-artifact-body-sync Date: 2026-03-09 Direction: Client (spec-kitty CLI) → Server (SaaS receiver) Status: Draft (C-003: sender development may proceed against this contract)

Endpoint

POST /api/dossier/push-content/

The route is owned by the SaaS receiver. The sender targets this canonical route only; no client-side route configuration or fallback aliases.

Authentication

Authorization: Bearer <access_token>

Uses existing AuthClient / CredentialStore (C-002). No new auth flow.

Request

Headers

HeaderValue
Content-Typeapplication/json
AuthorizationBearer <access_token>

Body

{
  "project_uuid": "550e8400-e29b-41d4-a716-446655440000",
  "feature_slug": "047-namespace-aware-artifact-body-sync",
  "target_branch": "2.x",
  "mission_key": "software-dev",
  "manifest_version": "1.0.0",
  "artifact_path": "spec.md",
  "content_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
  "hash_algorithm": "sha256",
  "content_body": "# Feature Specification: ...\n\nFull markdown content here..."
}

Field Definitions

FieldTypeRequiredConstraintsDescription
project_uuidstring (UUID4)yesValid UUID4Project identity
feature_slugstringyes\d{3}-[a-z0-9-]+Feature identifier
target_branchstringyesNon-emptyTarget branch name
mission_keystringyesNon-emptyMission type key
manifest_versionstringyesNon-emptyArtifact manifest data version
artifact_pathstringyesFeature-relative path, no ..Path matching indexer convention
content_hashstringyes64 hex charsSHA-256 of content_body
hash_algorithmstringyessha256Hash algorithm identifier
content_bodystringyes≤512 KiB UTF-8Renderable text content

Validation Rules

  • All namespace fields (project_uuid, feature_slug, target_branch, mission_key, manifest_version) must be non-empty.
  • content_hash must be the SHA-256 hex digest of content_body encoded as UTF-8. Server may verify.
  • artifact_path must not contain .. path traversal.
  • content_body must not exceed 524,288 bytes (512 KiB) when UTF-8 encoded.

Responses

201 Created — Stored

{
  "status": "stored",
  "artifact_path": "spec.md",
  "content_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}

200 OK — Already Exists

{
  "status": "already_exists",
  "artifact_path": "spec.md",
  "content_hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}

400 Bad Request — Validation Error

{
  "error": "validation_error",
  "detail": "content_hash does not match content_body"
}

Client action: Log as failed, remove from queue (not retryable).

401 Unauthorized — Auth Expired

{
  "error": "authentication_required"
}

Client action: Keep in queue, wait for auth refresh.

404 Not Found — Index Entry Not Found (retryable)

{
  "error": "index_entry_not_found",
  "detail": "No indexed artifact for feature_slug=047-namespace-aware-artifact-body-sync artifact_path=spec.md"
}

Client action: Treat as retryable (FR-008). The dossier index event may not be materialized yet on the server. Increment retry count and set next_attempt_at with backoff.

404 Not Found — Namespace Not Found (non-retryable)

{
  "error": "namespace_not_found",
  "detail": "No namespace for project_uuid=... feature_slug=047-namespace-aware-artifact-body-sync target_branch=2.x"
}

Client action: Treat as non-retryable. The namespace tuple is malformed or the project/feature has never been registered with SaaS. Log as failed, remove from queue. A malformed namespace will never self-heal on retry and must be surfaced as a local diagnostic.

429 Too Many Requests

{
  "error": "rate_limited",
  "retry_after": 30
}

Client action: Retryable. Use retry_after value if provided, otherwise use standard backoff.

5xx Server Error

Client action: Retryable with exponential backoff.

Client Behavior Summary

ResponseUploadOutcomeQueue ActionRetryable
201 storeduploadedRemove from queueN/A
200 already_existsalready_existsRemove from queueN/A
400failedRemove from queueNo
401N/AKeep in queueYes (after auth refresh)
404 index_entry_not_foundN/AKeep, increment retryYes
404 namespace_not_foundfailedRemove from queueNo
429N/AKeep, increment retryYes
5xxN/AKeep, increment retryYes
Connection errorN/AKeep, increment retryYes

Critical 404 dispatch rule: The client MUST inspect the response body error field to distinguish index_entry_not_found (retryable) from namespace_not_found (non-retryable). A bare 404 without a parseable error field is treated as retryable (conservative default).

Backoff Schedule

Per-task exponential backoff:

RetryDelay
11 second
22 seconds
34 seconds
48 seconds
516 seconds
632 seconds
764 seconds
8128 seconds
9+300 seconds (5 min cap)

Formula: min(2^retry_count, 300) seconds.

next_attempt_at = current time + delay.

Idempotency

The client always submits the upload request. It does not maintain a local cache of presumed remote content state (FR-010). The receiver is responsible for returning already_exists when the content hash for the given namespace + artifact_path already matches. The client classifies already_exists as a successful no-op.