Upside Down Research, LLC · Est. MMXXIII A little different look at the world
Pre-release · subject to change
Documentation

Your First Request

This page walks through the full lifecycle of a document: create → read → search → update → delete. It explains the two concepts that trip people up most: ULID addressing and ETag-guarded updates.

Setup

You need an API key and curl. See API keys.

export TUPSHAR_HOST="https://api.tupshar.housecarl.cloud"
export TUPSHAR_KEY="tupk_…"

Step 1: Create a document

POST /v1/file with a JSON body. filename and contents are required; tags and properties are optional.

curl -sS -X POST "$TUPSHAR_HOST/v1/file" \
  -H "Authorization: Bearer $TUPSHAR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "notes/onboarding.md",
    "contents": "Welcome to the team. Read the handbook before your first standup.",
    "tags": ["onboarding", "hr"],
    "properties": {"team": "engineering"}
  }'

201 Created:

{
  "id": "01JZ8K3M9QF2YV7X4B6N0CWE5T",
  "filename": "notes/onboarding.md",
  "tags": ["onboarding", "hr"],
  "properties": {"team": "engineering"},
  "links": [],
  "version": 1,
  "etag": "\"sha256:9f2b…|1|1789…\"",
  "created_at": "2026-06-12T21:00:00Z",
  "updated_at": "2026-06-12T21:00:00Z",
  "contents": "Welcome to the team. Read the handbook before your first standup."
}

The id is how you address this document. The filename is searchable metadata — useful, but not the address. Copy the id and etag; you'll need both.


Step 2: Read the document

DOC_ID="01JZ8K3M9QF2YV7X4B6N0CWE5T"

curl -sS "$TUPSHAR_HOST/v1/file/$DOC_ID" \
  -H "Authorization: Bearer $TUPSHAR_KEY"

200 OK returns the same shape as the create response. To fetch metadata only (no contents):

curl -sS "$TUPSHAR_HOST/v1/file/$DOC_ID?include=metadata" \
  -H "Authorization: Bearer $TUPSHAR_KEY"

If you already have the ETag and want to avoid a full download when nothing has changed:

curl -sS "$TUPSHAR_HOST/v1/file/$DOC_ID" \
  -H "Authorization: Bearer $TUPSHAR_KEY" \
  -H 'If-None-Match: "sha256:9f2b…|1|1789…"'
# → 304 Not Modified if unchanged

GET /v1/query — always GET, always query-string params, never a POST body.

curl -sS -G "$TUPSHAR_HOST/v1/query" \
  -H "Authorization: Bearer $TUPSHAR_KEY" \
  --data-urlencode "token=handbook standup"

Terms are OR-matched and BM25-ranked. Up to 32 terms per query. No phrase, AND, or negation syntax in this preview.

{
  "mode": "token",
  "results": [
    {
      "id": "01JZ8K3M9QF2YV7X4B6N0CWE5T",
      "filename": "notes/onboarding.md",
      "tags": ["onboarding", "hr"],
      "updated_at": "2026-06-12T21:00:00Z",
      "score": 3.17
    }
  ],
  "limit": 25,
  "total_estimate": 1,
  "next_cursor": null
}

Search results return the id but not contents — use GET /v1/file/{id} to fetch the full document.

# All documents tagged "onboarding"
curl -sS -G "$TUPSHAR_HOST/v1/query" \
  -H "Authorization: Bearer $TUPSHAR_KEY" \
  --data-urlencode "tag=onboarding"

# Documents where team=engineering
curl -sS -G "$TUPSHAR_HOST/v1/query" \
  -H "Authorization: Bearer $TUPSHAR_KEY" \
  --data-urlencode "property=team=engineering"

Step 4: Update (with If-Match)

Updates use optimistic concurrency control. You must send the current etag in If-Match; the server rejects stale writes.

DOC_ETAG='"sha256:9f2b…|1|1789…"'   # from the create or read response

curl -sS -X PUT "$TUPSHAR_HOST/v1/file/$DOC_ID" \
  -H "Authorization: Bearer $TUPSHAR_KEY" \
  -H "Content-Type: application/json" \
  -H "If-Match: $DOC_ETAG" \
  -d '{
    "filename": "notes/onboarding.md",
    "contents": "Welcome to the team. Read the handbook and schedule your onboarding week."
  }'

200 OK — the response contains a new etag and an incremented version:

{
  "id": "01JZ8K3M9QF2YV7X4B6N0CWE5T",
  "version": 2,
  "etag": "\"sha256:c41a…|2|1789…\""
}

If you omit If-Match: 428 Precondition Required — the server refuses to update without a guard.

If your ETag is stale (someone else updated first): 412 Precondition Failed — re-read the document, get its fresh ETag, and retry.


Step 5: Delete

curl -sS -X DELETE "$TUPSHAR_HOST/v1/file/$DOC_ID" \
  -H "Authorization: Bearer $TUPSHAR_KEY"

204 No Content. Deleting an already-deleted document also returns 204 — the operation is idempotent. Pass ?cascade=true to also remove inbound links from other documents that pointed at this one.


Key concepts recap

ConceptDetail
ULIDServer-assigned ID (e.g. 01JZ8K3M9QF2YV7X4B6N0CWE5T). The document's address. Filename is searchable metadata, not the address.
ETagAn opaque version fingerprint returned on every write. Pass it in If-Match on the next update.
If-Match ruleAlways required for PUT. Missing → 428. Stale → 412.
SearchAlways GET /v1/query with query-string params. Results contain id and metadata, not contents.

What's next?

  • HTTP API — complete endpoint reference with all parameters