Skip to main content

Check pending

Check if the server wants Hoard to sync.

curl example

Response:

Response fields

reason values

Clients do not need to distinguish manual from scheduled — the server is the single decision-maker. The original trigger is recorded server-side on sync_cycle_logs.trigger_source for analytics.

Agent heartbeat

Signal that Hoard is alive and check for updates.
Request body (optional):
Response:

Response fields

Upload inventory

Upload a gzipped inventory CSV from TCGplayer.
The request body is the gzip-compressed CSV data. The server queues it for processing, matches cards to the catalog, and calculates price updates. Response (HTTP 202):
Error responses:

Upload inventory (V4 presigned-URL flow)

Newer agent builds upload the CSV directly to Cloudflare R2 using a short-lived presigned PUT URL, then notify Hoard with the resulting R2 key. Hoard’s bandwidth and Ruby heap never see the bytes. The flow is three calls:
  1. POST /api/v1/sync/upload-request — Hoard returns a 10-minute presigned PUT URL.
  2. PUT <upload_url> — agent uploads the raw CSV bytes directly to R2.
  3. POST /api/v1/sync with Content-Type: application/json and body {key, product_line} — Hoard validates the key, enqueues the worker, and returns 202.
Both steps 1 and 3 require users.use_r2_sync_pipeline = true; otherwise they return 403 r2_pipeline_disabled and the agent should fall back to the raw-CSV POST documented above.

Step 1: Mint a presigned URL

Request body (optional):
Response (HTTP 200):
Error responses:

Step 2: PUT the CSV to R2

The PUT goes to Cloudflare, not Hoard — no Authorization header required (the presigned URL is the auth). Send the raw CSV bytes as the PUT body.

Step 3: Notify Hoard with the key

Request body:
Response (HTTP 202):
Error responses:

curl example (V4 round trip)

Get price updates

Download a CSV of price changes to import back to TCGplayer.
Returns a CSV string (text/csv), not JSON. Empty string if no updates are available. If a price rollback is pending, the rollback CSV is served instead.

Conditional GET (ETag)

When no rollback or Quick Add work is pending, the response includes a weak ETag fingerprint of the underpriced-cards set. Clients that send If-None-Match: <last-etag> will receive 304 Not Modified if nothing has changed since the last pull — avoiding the CSV serialization roundtrip on idle polls. Rollback and Quick Add responses always return 200 because they carry side-effectful state transitions that must run on every poll.

curl example

Request manual sync

Mark the account as having an explicit user-requested sync. The next poll on /api/v1/sync/pending returns action: "sync".

curl example

Response:

Cooldown response (402)

When the per-account credits_metering_enabled flag is off (the default), this endpoint always returns 200 — behavior is unchanged from before the credits-metering feature shipped. When the flag is on AND the user is still within the post-sync cooldown window, the endpoint returns 402:
The user can either wait the remaining seconds or call POST /api/v1/sync/skip_wait (below) to spend a credit and skip the wait.

Skip wait

Spend 1 credit to bypass the manual-sync cooldown. Wraps the Credits::SkipWaitCharge service which uses a FOR UPDATE row lock plus a reservation pattern so concurrent calls with the same idempotency key cannot double-charge.
This endpoint is independent of credits_metering_enabled — calling it IS the user’s explicit opt-in. A flag-OFF user with a balance can call it just fine; the gate is the balance, not the flag.

Headers

curl example

Response (200, charged):

Success statuses (HTTP 200)

Error responses

Cancel sync

Release the sync lock if something goes wrong mid-sync.
Response:
Returns 409 with code: "no_sync_in_progress" if no sync is running. The server auto-clears stale locks after 5 minutes, so this is a courtesy call.

Log sync cycle

Report the results of a completed sync cycle. Hoard sends this at the end of every cycle, whether it succeeded or failed.
Request body:

Request fields

Response (HTTP 201): Empty body.

Upload debug screenshot

Send a Base64-encoded screenshot to the server when a sync step fails.
The screenshot must be Base64-encoded PNG and must not exceed 2 MB decoded. Include sync_cycle_id so the server can attach the screenshot to the correct cycle even if the final sync log has not arrived yet. Without sync_cycle_id, at least one existing sync log entry must exist. Request body:
Response (HTTP 201): Empty body. If sync_cycle_id is present and the final sync log has not arrived yet, the server creates a placeholder failed log row and updates that row when POST /api/v1/sync/log arrives with the same identifier. Error responses: