Seller setup

Live Last updated

From an empty tenant to a live public offer — sign up, invite your team, mint an API key for your agent, publish a configuration version, and register a webhook endpoint.

Live. Every step on this page works today. The curl examples call this deployment's API at https://app.skipthesell.com with the OpenAPI document's path templates — replace {placeholders} with your resource ids. The same operations are in the checkout UI; the API is the product.

A seller is a tenant: an isolated account holding its catalog, offers, rules, deals and settings. This page walks the self-serve path from nothing to a listed, purchasable offer — the same path our own onboarding runs through. The vocabulary lives in core concepts.

Sign up#

Sign-up is one request, open to anyone. It creates the tenant and its first member — you, the owner — pending sign-in:

# operationId: create_tenant
curl -X POST https://app.skipthesell.com/api/v1/tenants \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: <generate a fresh value, e.g. a UUID>' \
  -d '{"name": "Acme", "owner_email": "[email protected]"}'

The Idempotency-Key is yours to generate — one fresh value per logical operation, a retry replays the stored response; this endpoint is open to anyone, so a guessable shared value can collide with another caller's. (Authenticated endpoints scope the key to your credential, where collisions are yours alone.)

The response carries the new tenant and your member. A single-use magic-link email goes to the owner address — one message carrying both the sign-in link and its sign-in code (the typed alternative, for links eaten by mail scanners or signing in on another device; either one signs you in once and cancels the other). Sign-in exchanges either half for a session:

# operationId: request_magic_link
curl -X POST https://app.skipthesell.com/api/v1/magic-links \
  -H 'Content-Type: application/json' \
  -d '{"email": "[email protected]"}'

# operationId: create_session
curl -c cookies.txt -X POST https://app.skipthesell.com/api/v1/sessions \
  -H 'Content-Type: application/json' \
  -d '{"token": "<token from the email>"}'

# operationId: exchange_sign_in_code — the same session from the typed code
# (case-insensitive; separators like the email's ABCDE-FG234 grouping are ignored)
curl -c cookies.txt -X POST https://app.skipthesell.com/api/v1/sign-in-codes \
  -H 'Content-Type: application/json' \
  -d '{"email": "[email protected]", "code": "ABCDE-FG234"}'

create_session sets the __Host-session cookie — save it with -c cookies.txt and send it back with -b cookies.txt on every call that needs the human's session. Check who you are:

# operationId: get_me
curl -b cookies.txt https://app.skipthesell.com/api/v1/me

To sign out (or sign another device out), revoke the session by its sess_… id, which GET /api/v1/sessions lists:

# operationId: delete_session
curl -b cookies.txt -X DELETE https://app.skipthesell.com/api/v1/sessions/{session_id}

Invite your team#

Members are humans with a role from the versioned permission schema (linked from meta.discovery as permission_schema). Schema v1's roles: owner, configurator, deal-manager, viewer. An invitation magic link is emailed; the member exists immediately:

# operationId: create_member
curl -b cookies.txt -X POST https://app.skipthesell.com/api/v1/members \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: invite-001' \
  -d '{"email": "[email protected]", "role": "configurator"}'

An API key for your agent#

Your seller agent — the software you run to configure offers and read your deals — authenticates with a scoped API key, as Authorization: Bearer <key>:

# operationId: create_api_key
curl -b cookies.txt -X POST https://app.skipthesell.com/api/v1/api-keys \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: key-001' \
  -d '{"name": "config agent", "scopes": ["draft:write", "draft:read", "draft:publish", "simulation:write", "configuration-version:read"]}'

The response carries the raw secret exactly once — store it now; it is never served again, and only its hash is stored. Scopes are the explicit list, frozen at creation and enforced ever after: pick them from the permission schema document, and give an agent the least set that does its job. Keys are listed (GET /api/v1/api-keys), revoked (POST /api/v1/api-keys/{api_key_id}/revoke) and optionally expiring (expires_at at creation). Every key event lands in the tenant's audit feed.

With that key, everything a human can do in the UI, your agent can do through the API — the agent quickstart shows the full seller-agent path.

Your seller handle and tenant settings#

Tenant settings are per-tenant operational policy outside the versioned configuration: the seller handle (your public identifier, addressed by your public offer listing), the deal-token TTL, the escalation email recipients and the start-flow rate limits:

# operationId: get_tenant_settings
curl -b cookies.txt https://app.skipthesell.com/api/v1/tenant-settings

Changes are a PATCH with the ETag from your read (If-Match), and never create a configuration version. The handle is renamable only while your tenant has no deals — once the first deal exists, your public URLs are permanent:

# operationId: update_tenant_settings
curl -b cookies.txt -X PATCH https://app.skipthesell.com/api/v1/tenant-settings \
  -H 'Content-Type: application/json' \
  -H 'If-Match: "<ETag from the read>"' \
  -d '{"public_handle": "acme"}'

Publish a configuration#

Your catalog, offers and rules form a configuration document. Changes happen in a draft, are validated and simulated, and publish as an immutable configuration version — the thing every new deal pins. (The model is in core concepts; the pricing language is in the pricing rules primer.)

Create a draft, then put a configuration document in it. The document's exact shape is the published configuration v1 schema (linked from meta.discovery as configuration_schema); this minimal one offers one service in USD with a monthly flat fee and a per-seat price:

{
  "version": 1,
  "profile_version": 1,
  "currencies": ["USD"],
  "entity": {
    "legal_name": "Acme, Inc.",
    "address": "1 Main Street, Springfield, NY 10001, USA",
    "signatory_title": "Authorized Signatory"
  },
  "prices": {
    "base": {"kind": "flat_recurring", "amounts": {"USD": 120000}},
    "seat": {"kind": "per_unit", "amounts": {"USD": 5000}}
  },
  "offers": {
    "svc": {
      "name": "Service",
      "listed": true,
      "terms": {"term_months": {"allowed": [12, 24]}},
      "variables": {
        "seats": {"type": "int", "min": 1, "max": 500, "default": 10}
      },
      "rules": [
        {"id": "base", "phase": "lines", "effects": [{"line": {"name": "Base fee", "kind": "flat_monthly", "amount": "price(\"base\")"}}]},
        {"id": "seats", "phase": "lines", "effects": [{"line": {"name": "Seats", "kind": "per_seat_monthly", "qty": "seats", "amount": "price(\"seat\")"}}]}
      ],
      "done_condition": ["signed"]
    }
  }
}

Both amounts are per month: a flat_recurring or per_unit price carries a monthly amount — base is $1,200/mo, seat is $50 per seat per month — and the line kinds (flat_monthly, per_seat_monthly) carry that period; the buyer's billing frequency later scales one monthly amount to one invoice. The entity block names the legal party every published version's contracts bind and its countersignature records — legal_name, address and signatory_title are required for publishing, so even a minimal document carries it.

# operationId: create_draft
curl -X POST https://app.skipthesell.com/api/v1/drafts \
  -H 'Authorization: Bearer <api key>' \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: draft-001' \
  -d '{"name": "Launch offer"}'

# operationId: update_draft_document
curl -X PUT https://app.skipthesell.com/api/v1/drafts/{draft_id}/document \
  -H 'Authorization: Bearer <api key>' \
  -H 'Content-Type: application/json' \
  -H 'If-Match: "<ETag from the create response>"' \
  -d @configuration.json

The whole-document PUT is also the import path: what a published version exports can be put back verbatim, so you can keep your configuration in git. A draft may hold a document that does not validate yet — validate_draft is the answer to "would this publish?", running the full publish-time check (the entity block included) and reporting every diagnostic at once:

# operationId: validate_draft
curl -X POST https://app.skipthesell.com/api/v1/drafts/{draft_id}/validate \
  -H 'Authorization: Bearer <api key>'

Before publishing, run a simulation: a hypothetical deal with an explicit evaluation context (currency, term, the buyer's choices), priced deterministically through the same engine production quotes use — with no side effects:

# operationId: simulate_draft
curl -X POST https://app.skipthesell.com/api/v1/drafts/{draft_id}/simulations \
  -H 'Authorization: Bearer <api key>' \
  -H 'Content-Type: application/json' \
  -d '{"offer": "svc", "context": {"currency": "USD", "term_months": 12, "event": {"kind": "start", "at": "2026-11-07"}, "variables": {"seats": 25}}}'

Publishing compiles the draft into a new immutable version — publish re-validates, so what you validated is what ships, and a concurrent edit answers 412 instead:

# operationId: publish_draft
curl -X POST https://app.skipthesell.com/api/v1/drafts/{draft_id}/publish \
  -H 'Authorization: Bearer <api key>' \
  -H 'If-Match: "<ETag you validated against>"' \
  -H 'Idempotency-Key: publish-001'

The response is the new cfgver_… version, identified by the content hash of its canonical document. Every deal started from now pins this version. And because the offer is marked listed, it is now public — no credential needed:

# operationId: list_public_offers
curl https://app.skipthesell.com/api/v1/sellers/{handle}/offers

From here, every deal a buyer starts lands in the seller console's deal pipeline — the same deals your agent reads through the API, where the UI is just one client:

The seller console's deal pipeline: every deal in the tenant, newest first, with the lifecycle's state badges.

Webhooks#

Register the endpoint your systems receive events on. The signing secret is generated server-side and shown exactly once, in this response:

# operationId: create_webhook_endpoint
curl -b cookies.txt -X POST https://app.skipthesell.com/api/v1/webhook-endpoints \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: hook-001' \
  -d '{"url": "https://hooks.acme.example/skip", "event_types": ["*"]}'

Verify your receiver against the webhooks guide's signing scheme, then send yourself a test event and read the delivery log — every attempt recorded, replayable:

# operationId: test_webhook_endpoint
curl -b cookies.txt -X POST https://app.skipthesell.com/api/v1/webhook-endpoints/{webhook_endpoint_id}/test \
  -H 'Idempotency-Key: test-001'

# operationId: list_webhook_deliveries
curl -b cookies.txt https://app.skipthesell.com/api/v1/webhook-endpoints/{webhook_endpoint_id}/deliveries

The audit feed#

Everything above — and everything your buyers do — is recorded in the tenant's audit feed: append-only, system-written, newest first. Read it with a principal holding the audit-event:read scope — your owner session, or an API key minted with the scope:

# operationId: list_audit_events
curl -b cookies.txt 'https://app.skipthesell.com/api/v1/audit-events?limit=50'

Where next#

  • Pricing rules primer — how to price with rules, beyond flat fees.
  • Agent quickstart — your agent's path from API key to a published offer, and a buyer agent's path through a deal.
  • Webhooks — the signing scheme, the events, retries and replay.
  • Self-hosting — the same product, your own binary.