---
title: "Register a pointer-only document."
method: POST
path: "/v1/documents"
tags: ["Documents"]
---

# Register a pointer-only document.

`POST /v1/documents`

Idempotent on (user_id, source_site, provider, external_id, provider_version). Returns 201 on first registration; 200 on a re-register that matches an active row. Registration accepts `storage_mode = 'pointer_only'`; managed_blob and inline_small_text return 400.

## Request body

- object — Register a document pointer. Document registration accepts pointer_only mode; managed_blob and inline_small_text return 400.
  - `account_id` string, nullable
  - `consent_policy` object
  - `content_hash` string, nullable
  - `display_name` string, nullable
  - `external_id` string, required — Required. external_id.
  - `external_uri` string, nullable
  - `extraction_status` 'pending' | 'not_required' | 'unsupported' — Initial extraction-layer state at register time. 'pending' = caller intends to extract; 'not_required' = pointer-only flow (default); 'unsupported' = caller knows the file type cannot be extracted. Service-owned values ('running', 'complete', 'failed') are rejected.
  - `metadata` object
  - `mime_type` string, nullable
  - `provider` string, required — Required. provider.
  - `provider_version` string, nullable
  - `retention_policy` object
  - `semantic_index_status` 'pending' | 'not_required' — Initial semantic-index-layer state at register time. 'pending' = caller intends to index; 'not_required' = no indexing planned. Service-owned transitions handle 'running', 'complete', 'failed', 'stale'.
  - `size_bytes` integer
  - `source_modified_at` string, date-time
  - `source_site` string, required — Required. source_site.
  - `storage_mode` 'pointer_only'
  - `user_id` string, required — Required. user_id.

## Response `200`

Idempotent re-registration; document already existed.

- object — Document registration result. `created: true` when a new row was inserted; `false` when an active row with the same (user, source, external_id, version) already existed.
  - `created` boolean, required
  - `document` object, required — Document registry record. snake_case wire format.
    - `content_hash` string, nullable, required
    - `created_at` string, required
    - `delete_semantics` 'delete' | 'unpin' | 'tombstone' | 'null', nullable, required — What AtomicMemory's DELETE call does at the provider boundary for this row's storage_provider. `'delete'` = adapter issues the provider's removal operation; `'unpin'` = removes AtomicMemory's pin but the provider's other peers may continue to serve; `'tombstone'` = AtomicMemory stops managing the bytes but the decentralized network may still serve. `null` for pointer-only rows or providers not registered for cleanup.
    - `display_name` string, nullable, required
    - `external_id` string, required
    - `external_uri` string, nullable, required
    - `extraction_status` 'not_required' | 'pending' | 'running' | 'complete' | 'unsupported' | 'failed', required
    - `id` string, required
    - `indexed_at` string, nullable, required
    - `indexed_content_hash` string, nullable, required
    - `last_error` object, nullable, required
      - `code` string, required
      - `layer` 'raw_storage' | 'extraction' | 'semantic_index', required
      - `message` string, required
      - `occurred_at` string, required
    - `metadata` object, required
    - `mime_type` string, nullable, required
    - `provider_version` string, nullable, required
    - `raw_source_id` string, required
    - `raw_storage_metadata` object, required — Public-facing raw_storage_metadata. STRICTLY allowlisted: codec emits only name+version (AES-GCM internals never reach the wire); filecoin emits public fields (ipfs_cid, piece_cid, copy_count, provider_ids, copy_statuses) — `ipfs_cid` is an optional CIDv1 IPFS / CAR-root identity hint populated by drivers that derive one alongside the PieceCID; the canonical storage URI stays `filecoin://piece/<piece_cid>` regardless. The internal structured copies[{provider_id,status}] shape is flattened at the formatter; upload_result and other internal sidecars are NEVER emitted. The schema is deny-by-default (`.strict()`) at every level — a formatter regression that lets unknown keys through fails response-shape validation.
      - `codec` object
        - `name` 'none' | 'aes_gcm', required
        - `version` number, required
      - `filecoin` object
        - `copy_count` integer
        - `copy_statuses` string[]
        - `ipfs_cid` string
        - `piece_cid` string
        - `provider_ids` string[]
    - `raw_storage_status` 'pointer_recorded' | 'blob_stored' | 'inline_text_stored' | 'raw_storage_failed' | 'blob_deleted' | 'blob_pending' | 'blob_available' | 'blob_archival_failed' | 'blob_tombstoned', required
    - `registration_status` 'registered' | 'registration_failed', required
    - `semantic_index_status` 'not_required' | 'pending' | 'running' | 'complete' | 'failed' | 'stale', required
    - `size_bytes` number, nullable, required
    - `source_modified_at` string, nullable, required
    - `storage_artifact_id` string, uuid, nullable, required
    - `storage_mode` 'pointer_only' | 'managed_blob' | 'inline_small_text', required
    - `storage_provider` string, nullable, required
    - `storage_uri` string, nullable, required
    - `updated_at` string, required
    - `user_id` string, required

## Other responses

- `201` — Document registered.
- `400` — Input validation error
- `500` — Internal server error
- `502` — Upstream AI provider returned an unrecoverable failure (auth, non-retryable 4xx).
- `503` — Upstream AI provider is rate-limited, quota-exhausted, or returned 5xx; consult `retryable`.

---

[API](https://skmtc.net/atomicstrata/apis/atomicmemory-http-api.md) · [All operations](https://skmtc.net/atomicstrata/apis/atomicmemory-http-api/llms.txt) · [OpenAPI document](https://skmtc-service-staging.skmtc.workers.dev/v1/apis/atomicstrata/atomicmemory-http-api/versions/d501daa39bb2/schema)
