K
KiflyDeveloper Docs
Sign up →
Agent Integration

Kifly REST API

The HTTP surface behind every Kifly integration. The MCP server is a client of these same endpoints — anything an MCP tool does, you can do here.

You do not need an API key to start. Discovery, carts and the payment-link handoff are open to any agent that identifies itself (below).


Base URL

https://kifly.ai

Identify yourself — the UCP-Agent header

An agent with no Kifly credential is admitted by declaring the URL of the UCP profile your agent publishes about itself. This is the UCP spec's own mechanism (ucp.dev 2026-08-25, where it is meta.ucp-agent.profile and maps to this header), not a Kifly invention.

Two wire forms are accepted. The RFC 8941 structured-field dictionary is the spec's form and what we recommend:

UCP-Agent: profile="https://your-agent.example/.well-known/ucp"

A bare absolute URL also works, and will keep working:

UCP-Agent: https://your-agent.example/.well-known/ucp

The URL must be absolute and https: — http: is rejected rather than upgraded.

Copy-paste a first call:

curl -sS 'https://kifly.ai/api/agent/search?q=hat&limit=3' \
  -H 'UCP-Agent: profile="https://your-agent.example/.well-known/ucp"'
  • Required. It is attribution, not authentication or authorization.
  • Rate limit: open callers are limited per profile. An API key raises it.
  • Use a domain you control. your-agent.example above is a placeholder; reserved names and bare IP addresses are served with an advisory and will later be refused. /.well-known/ucp publishes the date.

Optional: say which of your users a call is for

A platform serving many people (one profile, many users) can tell Kifly which user and conversation a call belongs to. It never changes what you are allowed to do.

Kifly-Buyer: ref="u_5d9c1f0a7b3e", session="s_2026_10_01_0915"

On MCP the same fields go in meta["ai.kifly.buyer"] = { "ref": "…", "session": "…" } beside ucp-agent.

  • Send an opaque id, never a person. Use HMAC-SHA256(your secret, your user id), the same advice OpenAI gives for safety_identifier and Anthropic for metadata.user_id. 8 to 64 characters of A-Z a-z 0-9 . _ : -.
  • Anything that looks like an email or phone number is dropped, the call is served, and an advisory tells you why.
  • session is your conversation or task id.

Optional: sign your requests to be recognised as yourself

Sign each request with a key your profile publishes and Kifly recognises you as yourself. Signing is optional: a signature that does not verify is served like an unsigned call, with an advisory saying why.

  • Standard: RFC 9421 HTTP Message Signatures, the UCP signature profile. Publish your public key(s) in your profile's keys[] (a JWK Set). ES256 (P-256), ES384 and Ed25519 are accepted.
  • Cover: @method, @authority, @path; @query when there is a query; content-digest and content-type when there is a body; and ucp-agent, idempotency-key, signature-agent when you send them. Include created and expires (at most 24 h apart) and a nonce.
  • Web Bot Auth works too. A tag="web-bot-auth" signature whose Signature-Agent is on the same host as your profile is verified against that directory; keyid must be the key's RFC 7638 thumbprint.
  • What it earns: recognition as yourself, a higher rate allowance, and buyer refs marked as attested when the signature covers kifly-buyer.
  • Feedback: responses carry Kifly-Agent-Trust and IETF RateLimit-Policy / RateLimit headers.

Advisories — what to fix, without a refusal

When something about your request should change but does not stop it, Kifly answers normally and tells you, the same way on every surface. An advisory never changes the status code.

  • REST: a Kifly-Advisory response header (reserved_profile_host;refused_from="2026-11-01") and a kifly:advisories array in a JSON body.
  • MCP: result._meta["ai.kifly/advisories"] on the tool result.

Each entry is { code, message, documentation, refused_from }. Read message, fix it, and the advisory stops.

Optional: link a buyer's Kifly account (UCP identity linking)

A platform can link one of its users to their Kifly account (OAuth 2.0 with PKCE; discovery at /.well-known/oauth-authorization-server, iss on every authorization response, the UCP scope dev.ucp.shopping.order:read accepted). Then call with that buyer's token and your UCP-Agent header: the call is the verified buyer's, and it is credited to your platform. Nothing requires linking; it upgrades what a call can do, it never gates the open surface.

Or authenticate with a key

Authorization: Bearer kfa_live_xxxxxxxxxxxx

Send either Authorization or UCP-Agent, never both. If an Authorization header is present we honour it and never fall back to the open path — so a revoked or mistyped key stays a clean 401 instead of silently downgrading you to open access with less data and no warning.

See MCP Integration → Authentication for which credential fits what you are building.

Which capabilities actually need one. Buyer identity (/api/agent/buyer, /api/agent/buyer/otp, /api/agent/buyer/verify, /api/agent/buyer/me, /api/agent/buyer/orders), order history (/api/agent/order-status) and settlement. Everything else on this page is open.

How to get a token — read this before you retry. Run the OAuth 2.1 flow advertised at /.well-known/oauth-authorization-server. It supports Dynamic Client Registration, so your agent registers itself at /oauth/register and the only human step is one consent screen; buyer:write is in scopes_supported, so register_buyer and verify_buyer work once you hold the grant. A person can alternatively mint a long-lived key from the Kifly dashboard.

There is no credential obtainable with no human consent at any point, and the buyer-identity endpoints cannot bootstrap one. Get the grant instead. If that blocks a job you need to do, say so through POST /api/agent/feedback — it needs no credential.


Open endpoints — no credential required

MethodPathWhat it does
GET/api/agent/searchSearch or browse the network catalog. q, limit, cursor, category, seller_handle, sort_by, price and stock filters. Returns JSON-LD.
GET/api/agent/sellersList sellers on the network.
GET/api/agent/sellerOne seller's public profile, delivery coverage and catalog size. Name it by handle, domain (any URL on the store's site) or name. Call it first when the user names a store; a miss returns SELLER_NOT_FOUND with details.did_you_mean.
GET/api/agent/policiesA seller's published store policies.
POST/api/agent/cartCreate a cart.
GET DELETE/api/agent/cart/{id}Read or cancel a cart.
POST/api/agent/cart/{id}/itemsAdd a line item.
PATCH DELETE/api/agent/cart/{id}/items/{itemId}Change quantity, or remove.
POST/api/agent/cart/{id}/shipping-addressSet the shipping address.
POST/api/agent/quick-buyCreate a cart, add one item and set the address in a single call.
POST/api/agent/payment-linkA Stripe payment link a human confirms.
POST/api/agent/feedbackTell us what broke. No credential needed.
GET/api/agent/platform/limitsLive cart limits.
GET/api/agent/docs/{slug}These pages as JSON. overview, rest, mcp, errors, limits. A bad slug returns 404 listing every valid one.

Everything else — buyer identity, order history, autonomous settlement — requires an issued key and returns 401/403 naming what it needed.

POST /api/agent/cart/{id}/checkout is not on that list. It settles, so it needs checkout:write, which is deliberately outside what an open caller can hold — no tool call and no tokenless request completes a purchase. The open path to a sale is /api/agent/payment-link: a Stripe link a human confirms. See Payment Rails.

GET /api/agent/products is permanently retired and returns 410. Use /api/agent/search.


Mutating requests need an Idempotency-Key

Every POST, PATCH and DELETE on the agent surface requires one:

Idempotency-Key: 16-to-64-characters-unique-per-request

A retry with the same key and the same body returns the original response rather than acting twice. A missing key is 400 IDEMPOTENCY_KEY_REQUIRED. Keys are namespaced per caller, so your keys can never collide with another agent's.


Errors

Every failure is JSON with a stable machine-readable code:

{ "error": "human readable prose", "code": "STABLE_SCREAMING_SNAKE" }

Branch on code, never on error — the prose gets reworded, the code does not. Upstream faults return 503 with retryable: true and a Retry-After header; retry those rather than treating them as permanent.

→ Full list on the Error Reference page.


Why there are two surfaces

The MCP connector never completes a purchase on its own. Its checkout always returns a payment link a human confirms.

Autonomous settlement — x402, where your agent pays USDC on Base and re-requests — lives on this REST surface only, at POST /api/agent/cart/{id}/checkout with an X-Payment header. Each settlement is a fresh per-transaction signature from the buyer's own wallet.

So: MCP if a human is in the loop, REST if your agent settles for itself. They are two trust models, not two ways to do one thing.


Machine-readable contracts

Prefer reading a schema to reading this page. Both are generated from the running server, so neither can drift from it:

WhatWhere
UCP capability manifestGET /.well-known/ucp
MCP method contract (OpenRPC)GET /api/mcp/openrpc.json
Per-seller manifestGET /store/{handle}/.well-known/ucp

Something here confused you?

Then we have a bug, and we would rather hear it than not:

curl -sS -X POST 'https://kifly.ai/api/agent/feedback' \
  -H 'UCP-Agent: profile="https://your-agent.example/.well-known/ucp"' \
  -H 'Idempotency-Key: my-unique-key-0000000000' \
  -H 'content-type: application/json' \
  -d '{
        "category": "tool_description",
        "severity": "friction",
        "message": "What was unclear, and what you expected instead.",
        "context": { "endpoint": "/api/agent/search" }
      }'

category is one of discoverability, response_shape, tool_description, latency, error_handling, missing_capability, other. severity is blocker, friction or nice_to_have. It returns 202 immediately — no credential, and a human reads every report.

REST API: Kifly Docs