Skip to content

Examples

These examples use two environment variables, the same ones the Homestead agent skill reads:

Terminal window
export HOMESTEAD_URL=http://localhost:8080
Terminal window
export HOMESTEAD_TOKEN=hst_YOUR_TOKEN

The token is an agent token with the Helper preset, which covers everything here. Create one with homestead token create --name curl (see Connect an agent). A member’s hsd_ device token works for the first four examples; lease, ingest and complete accept agent tokens only. IDs below are placeholders; use the ones your server returns.

No authentication needed.

Terminal window
curl -sS "$HOMESTEAD_URL/api/v1/health"
{"status":"ok","version":"dev"}

POST /resolve turns free text into candidates. It never creates anything. Needs people:write. Set lookupProfiles to false to skip fetching public names and avatars.

Terminal window
curl -sS "$HOMESTEAD_URL/api/v1/resolve" \
-H "Authorization: Bearer $HOMESTEAD_TOKEN" \
-H "Content-Type: application/json" \
-d '{"input":"https://bsky.app/profile/alice.bsky.social\nhello world","lookupProfiles":false}'

Reply (abridged):

{
"candidates": [
{
"key": "c1",
"input": "https://bsky.app/profile/alice.bsky.social",
"platform": "bluesky",
"handle": "alice.bsky.social",
"profileUrl": "https://bsky.app/profile/alice.bsky.social",
"confidence": "high",
"existing": null,
"suggestedPersonId": null,
"warnings": []
}
],
"unrecognized": ["hello world"]
}

A bare handle comes back with "confidence": "low" and alternatives. Pass hintPlatform to choose for it. warnings holds i18n keys such as addWarningNeedsAgent.

POST /quick-add creates people and accounts in one atomic step. It requires an Idempotency-Key. Here, Alice is created with her Bluesky account, and the second entry attaches an Instagram account to the person the first entry created (sameAsEntry: 0).

Terminal window
curl -sS "$HOMESTEAD_URL/api/v1/quick-add" \
-H "Authorization: Bearer $HOMESTEAD_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: add-alice-1" \
-d '{"entries":[
{"platform":"bluesky","handle":"alice.bsky.social",
"person":{"new":{"displayName":"Alice","kind":"friend","closeness":2}},
"circleIds":["cir_01j9ebw22bbvzap1dk44j2y9eh"]},
{"platform":"instagram","handle":"alice.cooks",
"person":{"sameAsEntry":0}}
]}'

The reply has people (full person objects with their accounts), createdPeople, createdAccounts and skipped. Sending the same request with the same key again returns the same reply. The same key with a different body returns 409 idempotency_conflict.

GET /feed needs feed:read. Leave out circleId for “All”.

Terminal window
curl -sS "$HOMESTEAD_URL/api/v1/feed?limit=20" -H "Authorization: Bearer $HOMESTEAD_TOKEN"

The reply has entries, nextCursor, seed and generatedAt. Each entry has a type:

typeHolds
momentmoment: one post or story
hearth_pickpick: a visit suggestion with person, account, reason and copyVariant
caught_upunseenRemaining: 0: everything unseen has been shown
sectionsection: "earlier": seen moments from the last 3 days follow
recent_visitsPeople recently visited with the companion (first page only)

Pass nextCursor back as cursor for the next page until it’s null. Agents never mark moments seen.

This is what an agent does every few hours. It needs tasks:work and ingest:write.

  1. Lease due tasks for the platforms your browser is logged in to. A lease lasts 10 minutes.

    Terminal window
    curl -sS -X POST "$HOMESTEAD_URL/api/v1/agent/tasks/lease" \
    -H "Authorization: Bearer $HOMESTEAD_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"platforms":["instagram","douyin"],"limit":5}'

    Each task has an id, kind (fetch or lookup), the account (with profileUrl), since, knownItemIds to skip, wantKinds, maxItems and instructions naming the playbook, such as instagram.fetch.v1. If tasks is empty, stop and come back after retryAfterSeconds.

  2. Open the profile in the agent’s browser and follow the playbook. Collect up to maxItems moments newer than since, skipping knownItemIds.

  3. Upload media the browser already loaded. You get back a mediaId. (Or pass the media url directly if its host is on the platform’s media host list.)

    Terminal window
    curl -sS "$HOMESTEAD_URL/api/v1/media/uploads" \
    -H "Authorization: Bearer $HOMESTEAD_TOKEN" \
    -F file=@/tmp/story-1.jpg -F kind=image
  4. Ingest the moments. Use task id + ":" + page number as the idempotency key.

    Terminal window
    curl -sS "$HOMESTEAD_URL/api/v1/ingest" \
    -H "Authorization: Bearer $HOMESTEAD_TOKEN" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: tsk_01j9a1b2c3d4e5f6g7h8j9k02t:1" \
    -d '{"account":{"id":"acc_01j9te0skfktnnmw6zvew46azb"},
    "taskId":"tsk_01j9a1b2c3d4e5f6g7h8j9k02t",
    "items":[{
    "platformItemId":"3456789012345678901",
    "kind":"story",
    "url":"https://www.instagram.com/stories/lin.cooks/3456789012345678901/",
    "publishedAt":"2026-10-17T07:12:00Z",
    "media":[{"mediaId":"med_01j9a1b2c3d4e5f6g7h8j9k01w"}]
    }]}'

    The reply has a batchId, the accepted and duplicates counts, momentIds, and rejected items with an index and a code. Valid items are stored even if some are rejected.

  5. Complete the task with the batch IDs. Other statuses are login_required, blocked, not_found, rate_limited (with retryAfterSeconds) and error.

    Terminal window
    curl -sS -X POST "$HOMESTEAD_URL/api/v1/agent/tasks/tsk_01j9a1b2c3d4e5f6g7h8j9k02t/complete" \
    -H "Authorization: Bearer $HOMESTEAD_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"status":"ok","ingestBatchIds":["ing_01j9a1b2c3d4e5f6g7h8j9k02g"]}'

    A successful completion returns 204.

Wait at least 20 seconds before the next profile on the same platform, and stop after 15 minutes or when no tasks are left.

  • kind must be one the platform produces, and url must be on the platform’s own hosts.
  • publishedAt can’t be more than 10 minutes in the future. Stories older than 48 hours are rejected as too_old.
  • text is stripped of HTML and control characters, up to 5000 characters.
  • Media URLs must match the platform’s media hosts, or the item is rejected as media_host_not_allowed. The server downloads them itself, up to 50 MB in 30 seconds.
  • A repeated platformItemId for the same account counts as a duplicate, and its text and media are updated if they changed.