{
  "ok": true,
  "name": "Spotka API",
  "base_url": "https://spotka.co/api/v1",
  "how_to_call": "Send your key and a `do` parameter. Any of these work: POST form fields, POST JSON, or a GET query string. The key can go in the `key` parameter or an X-API-Key header.",
  "get_a_key": "Spotka app → Settings → API access → Create key.",
  "example": "curl -X POST https://spotka.co/api/v1 -d 'key=spk_XXXXXXXXXXXX&do=ideas'",
  "verbs": [
    {
      "do": "ideas",
      "what": "Ideas picked for you.",
      "params": {
        "limit": "optional, 1-50, default 20"
      }
    },
    {
      "do": "events",
      "what": "Events other members are hosting that suit you. Filters narrow YOUR ranked suggestions — this is not a search of all of Spotka, which is why the reply also reports deck_size. When nothing comes back, `empty.reason` says which of still_ranking / no_inventory / filters_too_narrow / end_of_list it is, so you know whether to wait, widen, or stop.",
      "params": {
        "limit": "optional, 1-50, default 20",
        "query": "optional, matches title, description, pitch, host and place",
        "location": "optional, matches the place",
        "starts_after": "optional ISO date — events with no time are excluded",
        "starts_before": "optional ISO date",
        "availability": "optional, open (hide full events) or any",
        "cursor": "optional, the next_cursor from the previous reply"
      }
    },
    {
      "do": "my_events",
      "what": "Events you host, events you joined, and invitations waiting for you.",
      "params": {
        "limit": "optional, 1-50, default 20"
      }
    },
    {
      "do": "create_event",
      "what": "Add an event. Times and places are plain text — no date formatting needed.",
      "params": {
        "title": "required",
        "when": "optional plain text, e.g. 'next Friday at 7pm'",
        "where": "optional plain text",
        "about": "optional description",
        "max_guests": "optional, 2-20",
        "visibility": "optional, public or private",
        "publish": "optional, true (default) or false",
        "raw": "test keys only — skip the writing model and set the fields exactly as given",
        "starts_at": "test keys only, with raw — an ISO 8601 start time"
      }
    },
    {
      "do": "update_event",
      "what": "Change an event you host. A partial update: anything you leave out stays exactly as it is. Nobody is notified — guests see the change next time they open it.",
      "params": {
        "event": "required, the event id",
        "title": "optional",
        "when": "optional plain text, e.g. 'Saturday 8pm'",
        "where": "optional plain text",
        "about": "optional description",
        "max_guests": "optional, 2-20",
        "visibility": "optional, public or private",
        "expected_version": "optional — send the version you read and the call fails rather than overwrite a newer change"
      }
    },
    {
      "do": "invite",
      "what": "Invite people to an event. Give `event`, or give `title` instead to create the event and invite in one call.",
      "params": {
        "event": "event id, or omit and pass create_event params",
        "to": "one or more email addresses, comma separated"
      }
    },
    {
      "do": "join",
      "what": "Take a seat at somebody else's event. The reply's `via` says which path was used: 'deck' when the event was in that member's suggestions, or 'share_link' for a test key when it was not (with `deck_size` so you can see why).",
      "params": {
        "event": "event id",
        "note": "optional message to the host"
      }
    },
    {
      "do": "leave",
      "what": "Give up a seat you took.",
      "params": {
        "event": "event id"
      }
    },
    {
      "do": "respond",
      "what": "Answer something addressed to you: accept or decline an invitation, or (as host) approve a guest's proposed time.",
      "params": {
        "event": "event id",
        "decision": "accept / decline / interested — or approve / decline when answering as the host",
        "guest": "as host only: the guest's user id"
      }
    },
    {
      "do": "event",
      "what": "One event in full: status, guests, who is interested, pending proposals. This is what you check a result against.",
      "params": {
        "event": "event id"
      }
    },
    {
      "do": "archive",
      "what": "Retire an event you host.",
      "params": {
        "event": "event id"
      }
    },
    {
      "do": "delete",
      "what": "Delete an unpublished draft you host. A published event must be archived instead.",
      "params": {
        "event": "event id"
      }
    },
    {
      "do": "profile",
      "what": "This member's own profile, split into `public` (what other members see) and `private` (their own context — dietary needs, interests, what they are focused on). Use the private half to choose what to suggest; never quote it into an invitation or a message.",
      "params": {}
    },
    {
      "do": "update_profile",
      "what": "Change this member's own profile. Only the fields you name change; everything else is left alone. Anything not on the writable list is refused by name rather than ignored.",
      "params": {
        "any of": "display_name, first_name, last_name, bio, city, company, job_title, personal_url, instagram, telegram, linkedin, musical_preferences, community_goals, current_focus, interests, dietary_preferences, dietary_other, allergies, allergies_other, languages",
        "lists": "interests, dietary_preferences, allergies and languages take a list, or comma-separated text"
      }
    },
    {
      "do": "calendar",
      "what": "What this member has coming up — events they host and events they joined, soonest first, with exact times. Events with no agreed time are returned separately in `undated`, never mixed into the schedule.",
      "params": {
        "limit": "optional, 1-50, default 20",
        "from": "optional ISO date. Defaults to now — an agenda is about what is ahead.",
        "to": "optional ISO date"
      }
    },
    {
      "do": "activity_history",
      "what": "What this API has changed on this account, newest first: which verb, what it touched, and whether it worked. A record of what happened, NOT an undo log — reverse things with the opposite verb.",
      "params": {
        "limit": "optional, 1-50, default 20",
        "verb": "optional, narrow to one action such as create_event",
        "since": "optional ISO date"
      }
    },
    {
      "do": "revoke_invite",
      "what": "Take back an invitation you sent. The link stops accepting a reply. Email that already went out cannot be unsent.",
      "params": {
        "invite": "required, the invitation id"
      }
    },
    {
      "do": "inbox",
      "what": "What is waiting: conversations with unread messages, and invitations waiting for an answer. Reading it marks nothing read.",
      "params": {
        "limit": "optional, 1-50, default 20"
      }
    },
    {
      "do": "conversation",
      "what": "One conversation's messages, newest first. Does NOT mark anything read.",
      "params": {
        "conversation": "required, the id from do=inbox",
        "limit": "optional, 1-50, default 30",
        "before": "optional, the `at` of the oldest message you have, to page back"
      }
    },
    {
      "do": "mark_read",
      "what": "Mark a conversation read. Only do this when the member has actually seen it.",
      "params": {
        "conversation": "required"
      }
    },
    {
      "do": "send_message",
      "what": "Send a message as the member. Needs a key with messaging enabled — it is off by default because it writes to other people in their name. Only replies in a conversation they already have, or writes to Fern.",
      "params": {
        "body": "required, the message",
        "conversation": "the id from do=inbox — or",
        "to": "fern"
      }
    },
    {
      "do": "capabilities",
      "what": "What your key may do, before you try it: which verbs are available to this key, what each one changes in the world (writes_data, publishes_publicly, sends_email, notifies_members, confirmation_recommended), and how much of each budget is left. Reading it does not spend your write budget.",
      "params": {}
    }
  ],
  "testing": {
    "what": "A key marked as a test key can act as any test account, write without the model, and undo what it made. Ask an admin for one.",
    "as": "Add as=<name or email> to any call to run it as that test account. Test accounts only — a test key can never act as a real member.",
    "extra_verbs": {
      "actors": "List the test accounts this key may act as.",
      "seed": "Top a test account up to the number of published events the app requires before a member may join anyone else's. Reports deck_size so you know whether the deck path is available.",
      "reset": "Archive every event this key created."
    },
    "create_event_raw": "Add raw=true (and starts_at=<ISO time>) to write the fields directly instead of through the model — sub-second, and the copy is exactly what you asked for.",
    "invites": "A test key can only invite other test accounts, so a suite never sends real email.",
    "example": "do=create_event&as=host@example&raw=true&title=T1&starts_at=2026-09-10T17:00:00Z  ->  do=join&as=guest@example&event=<id>  ->  do=event&as=host@example&event=<id>"
  },
  "limits": {
    "requests": "60 per minute, 1000 per day",
    "writes": "20 per hour, 60 per day",
    "invites": "25 people per day"
  },
  "errors": "Every failure returns { ok: false, error, message, hint }.",
  "mcp": {
    "url": "https://spotka.co/api/v1/mcp",
    "what": "Every verb above, offered as MCP tools, for clients that speak MCP natively (Claude, Cursor, Windsurf).",
    "connect": "claude mcp add --transport http spotka https://spotka.co/api/v1/mcp --header \"X-API-Key: spk_XXXXXXXXXXXX\""
  }
}