markaDocs

Authentication

Two credentials — an OAuth token for the hosted MCP server and an API key for everything else. The header forms, rate limits, and the one error envelope.

Marka has two programmatic credentials, and which one you want depends on whether there is a browser in the loop.

OAuth 2.1 access tokenAPI key
Works at/api/mcp/api/mcp and /api/v1/*
You get it byletting your AI client open a browsercreating one in Settings → API keys
Looks likean opaque bearer tokenmk_…
Lives for1 hour, refreshable for 30 daysuntil it expires or you revoke it
You revoke it inSettings → Connected appsSettings → API keys
Use it foran AI client you sign in toscripts, CI, anything headless

Prefer the OAuth token where it applies. Nothing is pasted anywhere, the token is short-lived, and disconnecting one client does not disturb any other. The whole flow is described on MCP overview — your client discovers it from the 401 the gateway answers with.

Everything below is about the API key, which is the credential /api/v1 speaks. Create a key under Settings → API keys; Getting started walks through it and your first call.

Sending the key

Two header forms work, and they are equivalent:

x-api-key: mk_YOUR_KEY_HERE
Authorization: Bearer mk_YOUR_KEY_HERE

x-api-key is the primary form — it is what the OpenAPI security scheme declares, what the SDK sends, and what the hosted MCP config snippet emits. The Authorization: Bearer spelling is accepted because it is what most HTTP clients and MCP configurations reach for. Both forms work at both doors: /api/v1/* and /api/mcp.

Two details worth knowing:

  • If both headers are present, x-api-key wins. They are never merged.
  • Only mk_-prefixed bearer tokens are treated as API keys. An Authorization header carrying anything else is left alone rather than fed to the key validator, so an unrelated token does not turn into a spurious "invalid key".

Browser sessions

/api/v1/* additionally accepts a signed-in browser cookie, so you can explore your own API from a logged-in tab without minting a key first. This is an interactive convenience and is deliberately not part of the contract a client programs against — treat the API key as the credential.

/api/mcp takes an OAuth access token or an API key, and not a cookie session. Its tools mutate listings, and it stays a credential-bearing machine endpoint rather than a second, cookie-authenticated door into those mutations. A request with no usable credential answers 401 with a WWW-Authenticate header naming the discovery document — that header is how a client that knows nothing but the URL finds its way to the browser sign-in.

Keys cannot manage keys

Key creation, listing and revocation are a signed-in-browser surface. A request authenticated by an API key is refused there outright, so a leaked key cannot mint successor keys that would outlive revoking the original.

The same rule covers OAuth: an API key cannot start an authorization, and Settings → Connected apps is a signed-in-browser surface. A leaked key can neither mint a successor key nor trade itself for an OAuth grant that would survive revoking it.

Key lifecycle

Prefixmk_ — fixed, and not settable per key
Nameup to 32 characters
Expiry30 days, 90 days, 1 year, or never. Default: 1 year
Visibilityshown once at creation; only the first 10 characters are stored for display
Revocationdelete the key from settings

A key that is missing, malformed, never issued, revoked, or expired all answer the same way: 401.

OAuth token lifecycle

Granted byyou, on the consent screen, in your browser
Access token1 hour
Refresh token30 days
Client registrationdynamic (RFC 7591) — clients register themselves
PKCErequired, S256 only
RevocationSettings → Connected apps → Disconnect

Disconnecting removes the grant and every token issued under it, so it takes effect on the next request rather than when the current access token happens to expire. A client whose token has been disconnected gets the same 401 and discovery challenge it got before it was ever connected, so it can simply ask again.

Rate limits

Each key carries its own request budget: 1000 requests, over a 60-second window.

That window is worth reading carefully, because it does not measure elapsed time — it measures idle time. The counter resets only after 60 consecutive seconds during which the key made no request at all; every allowed request pushes the reset out again. In practice the budget behaves like 1000 requests per burst of activity, and a client that keeps polling without ever going quiet will eventually spend it.

Exceeding it answers 429:

{ "error": "Rate limit exceeded for this API key.", "code": "rate_limited" }

The remedy is to stop for a minute, or to spread work across more than one key. There is no published retry-after header on this surface.

Limits are stamped at mint time

The budget and window are recorded on each key when it is created. Changing the platform defaults affects keys minted afterwards, not keys you already hold.

Errors

Every non-2xx response on /api/v1 uses one envelope:

{
  "error": "Human-readable message.",
  "code": "optional_refinement"
}

error is always present and is written for you — it never carries provider or database internals. code is optional and is not an enum: it carries either an API-level code or the originating domain error code (listing.authority_locked, subscription.tier_too_low, …), and new codes appear without a contract change. Branch on the status first; treat code as a refinement.

Statuses

StatusMeaning
400Validation failed — the body or query did not match the schema
401Missing, malformed, or revoked API key
402The key's plan does not carry the entitlement this endpoint requires
403Authenticated, but not permitted to act on this resource
404No such resource, or it is not visible to this key
429Rate limit exceeded for this API key

Anything outside that set (a 502 or 503 from a failed upstream) is an operational condition, not part of the contract.

Codes you will actually branch on

CodeStatusWhen
invalid_request400The request body or query failed schema validation, or the body was not JSON
entitlement_required402A key-authenticated write on a plan without api_write
forbidden403Authenticated, but the resource belongs to someone else — e.g. attaching another seller's shipping profile
email_unverified403Publishing requires a confirmed email address on the account the key belongs to
rate_limited429The key spent its request budget
invalid_response—Raised by the SDK, not the API: a 2xx whose body was not JSON at all

Where keys are not enough

Reading is available to any valid key. Key-authenticated writes — createListing, updateListing, publishListing — additionally require the api_write entitlement, which is a Pro-or-higher feature. See Plans & limits.

On this page