OAuth et Se connecter avec Sporty

Paiement par agent

Permettez à un agent IA de trouver du temps de court libre dans un club et de le payer, via l'Agentic Commerce Protocol ou MCP.

Un agent IA peut réserver un court dans un club Sporty et le payer avec une carte que l'acheteur lui a confiée. Il existe deux points d'entrée, qui reposent sur le même processus de paiement :

  • ACP : l'API de paiement de l'Agentic Commerce Protocol, version 2026-04-17, à l'adresse https://api.sporty.plus/api/acp.
  • MCP : un serveur MCP à l'adresse https://api.sporty.plus/mcp (Streamable HTTP), avec des outils pour trouver des clubs et du temps de court libre, en plus des outils de paiement ACP.

Les deux sont décrits par https://api.sporty.plus/.well-known/acp.json.

Le paiement se fait avec un jeton de paiement partagé Stripe (spt_…). Le débit est effectué sur le compte Stripe du club lui-même ; le club est le vendeur officiel (merchant of record).

Un club ne vend via des agents qu'une fois le paiement par agent activé. Tout le reste de cette page répond 403 not_sellable pour un club qui ne l'a pas fait.

Qui peut acheter

Chaque requête porte l'un de nos jetons d'accès OAuth dans Authorization: Bearer. C'est la nature du jeton qui détermine qui est l'acheteur.

JetonScopeL'acheteur
Celui d'un membre, issu du flux du code d'autorisationme.purchases.writeLe membre. Il paie le prix de sa propre adhésion, et la réservation est la sienne.
Le jeton client_credentials d'une plateforme validéeclub.checkout.write, accordé par le clubUn invité, désigné par son e-mail. Il paie ce que paie un nouveau venu.

me.purchases.write est distinct de me.reservations.write : un membre qui laisse une application réserver pour lui ne l'a pas autorisée pour autant à dépenser son argent.

Le paiement en tant qu'invité est réservé aux applications examinées par Sporty, car il crée des comptes. Le club doit aussi accorder club.checkout.write à l'application, dans son CMS, sous Clients API. Lorsque l'invité paie, nous créons un compte Sporty pour son e-mail et lui envoyons un lien pour choisir un mot de passe.

Si l'e-mail appartient déjà à un compte Sporty, le paiement est refusé avec requires_sign_in. Faites suivre OAuth à la personne et achetez plutôt en tant que membre. Sinon, n'importe qui pourrait ajouter des réservations au compte de quelqu'un d'autre en tapant son adresse.

Trouver du temps de court

Les articles du paiement sont des créneaux de court. L'identifiant d'un créneau indique le court, le début en UTC et la durée :

slot_812_20261003T1700Z_90

Obtenez-les avec l'outil MCP search_availability, qui liste les débuts libres pour l'acheteur avec ce que chacun lui coûte :

{
  "club_id": 214,
  "currency": "usd",
  "timezone": "America/New_York",
  "slots": [
    {
      "item_id": "slot_812_20261003T1700Z_90",
      "court": "Court 2",
      "sports": ["pickleball"],
      "starts_at": "2026-10-03T13:00:00-04:00",
      "ends_at": "2026-10-03T14:30:00-04:00",
      "minutes": 90,
      "total": 6533,
      "amounts": { "price": 6000, "tax": 533, "fee": 0, "total": 6533 }
    }
  ]
}

Trouvez d'abord le club avec search_clubs (par nom, ville ou adresse, et éventuellement par sport).

Les montants sont toujours des entiers en unités mineures : 6533 correspond à 65,33 $.

Le paiement, via ACP

Les points de terminaison sont ceux d'ACP. Chaque requête envoie API-Version: 2026-04-17, et chaque POST envoie une Idempotency-Key.

POST /api/acp/checkout_sessionsOuvrir un paiement pour un créneau.
GET /api/acp/checkout_sessions/{id}Le lire.
POST /api/acp/checkout_sessions/{id}Changer le créneau ou l'acheteur.
POST /api/acp/checkout_sessions/{id}/completePayer et réserver.
POST /api/acp/checkout_sessions/{id}/cancelL'abandonner.

Ouvrir ou modifier un paiement ne réserve rien. Cela calcule le prix du créneau, et la session est ready_for_payment ou not_ready_for_payment, avec des messages qui expliquent pourquoi. Rien n'est bloqué tant que l'acheteur n'a pas payé : un paiement que personne ne finalise ne coûte donc aucun court au club. Une session expire 30 minutes après sa dernière utilisation.

curl -X POST https://api.sporty.plus/api/acp/checkout_sessions \
  -H "Authorization: Bearer $TOKEN" \
  -H 'API-Version: 2026-04-17' \
  -H "Idempotency-Key: $(uuidgen)" \
  -H 'Content-Type: application/json' \
  -d '{
    "line_items": [{ "id": "slot_812_20261003T1700Z_90" }],
    "currency": "usd",
    "capabilities": {},
    "buyer": { "email": "jamie@example.com", "first_name": "Jamie", "last_name": "Rivera" }
  }'

La réponse est une session de paiement ACP. Les éléments importants ici :

  • line_items[0] : le court, le club et l'horaire, ainsi que ses totals.
  • totals : items_base_amount, subtotal, tax, fee (la surcharge carte d'un club, lorsqu'il en applique une) et total. Un club américain ajoute la taxe de vente en sus. Un club dont les prix incluent la TVA affiche la TVA comprise dans le prix, en tant que tax avec « (included) ».
  • fulfillment_options : toujours une seule option digital. Rien n'est expédié ; la réservation apparaît dans l'application Sporty.
  • capabilities.payment.handlers : un seul gestionnaire, stripe_spt (dev.acp.tokenized.card). Son config.merchant_id est le compte Stripe du club, pour les informations vendeur du jeton.
  • links : nos conditions d'utilisation et notre politique de confidentialité. Montrez-les à l'acheteur.

Pour payer, envoyez le jeton :

{
  "payment_data": {
    "handler_id": "stripe_spt",
    "instrument": { "type": "card", "credential": { "type": "spt", "token": "spt_123" } }
  }
}

Une finalisation réussie répond status: "completed" avec un order, dont la permalink_url est la page des réservations du membre sur le site du club.

Quand le paiement n'aboutit pas

Ces cas reviennent sous forme de messages sur la session, et non d'erreurs HTTP, car l'acheteur peut y remédier :

codeCe qui s'est passéSession
payment_declinedLa carte a été refusée. Rien n'a été réservé.ready_for_payment : essayez un autre jeton.
requires_3dsLa carte exige 3-D Secure, que le paiement par agent ne prend pas encore en charge.ready_for_payment
out_of_stockQuelqu'un a pris le créneau depuis qu'il a été proposé.not_ready_for_payment : choisissez un autre créneau.
price_change (avertissement)Le prix a changé depuis le devis, par exemple parce que la première adhésion d'un invité est tarifée différemment. Rien n'a été débité.ready_for_payment au nouveau total : faites-le confirmer par l'acheteur.
missingL'e-mail ou le nom d'un invité manque.not_ready_for_payment
requires_sign_inL'e-mail d'un invité correspond déjà à un compte Sporty.not_ready_for_payment

Les erreurs HTTP utilisent le corps d'erreur d'ACP, {type, code, message, param}, et concernent les requêtes que l'agent a mal formulées : un article inconnu (400 invalid_item_id), un club qui ne vend pas via des agents ou n'a pas autorisé cette application (403), une session qui appartient à quelqu'un d'autre (404), l'annulation d'une session finalisée (405).

Idempotence

Un POST répété avec la même Idempotency-Key reçoit la première réponse, avec Idempotent-Replayed: true, et rien ne se produit deux fois. Cela inclut le débit de la carte. La même clé avec un corps différent répond 422 idempotency_conflict. Tant que la première requête est en cours, une répétition répond 409 idempotency_in_flight. Les clés sont conservées pendant 24 heures, par application et par membre.

Le paiement, via MCP

Le serveur MCP s'authentifie avec les mêmes jetons OAuth. Un client qui n'en a pas reçoit 401 avec WWW-Authenticate: Bearer resource_metadata="…/.well-known/oauth-protected-resource/mcp", et y trouve notre serveur d'autorisation. Le client peut s'enregistrer lui-même et demander me.purchases.write au membre.

Outil
search_clubsLes clubs qui vendent du temps de court via des agents.
search_availabilityLes débuts libres dans un club, au prix applicable à l'acheteur.
create_checkout_sessionCréation ACP.
get_checkout_sessionLecture ACP.
update_checkout_sessionMise à jour ACP.
complete_checkout_sessionFinalisation ACP.
cancel_checkout_sessionAnnulation ACP.

Les outils de paiement suivent la liaison MCP d'ACP : les arguments sont {meta, id, payload}, où meta contient api_version (obligatoire) et idempotency_key, et payload est le corps REST. Le structuredContent du résultat est la même session de paiement que celle renvoyée par l'API REST.

Un refus revient sous forme d'erreur d'outil (isError: true) dont le texte est l'objet d'erreur ACP, et non sous forme d'erreur JSON-RPC.

Être informé de la commande ensuite

Indiquez où envoyer les événements de vos commandes :

curl -X PUT https://api.sporty.plus/api/acp/webhook \
  -H "Authorization: Bearer $TOKEN" -H 'API-Version: 2026-04-17' \
  -H 'Content-Type: application/json' \
  -d '{ "url": "https://agent.example/agentic_commerce/webhooks/order_events", "secret": "…" }'

Fournissez votre propre secret d'au moins 24 caractères, ou omettez-le et un secret sera créé et affiché une seule fois. GET affiche le webhook et sa dernière livraison ; DELETE le supprime. L'URL doit être en https sur une adresse publique.

Nous envoyons les événements de commande ACP, chacun avec la commande complète :

  • order_create lorsqu'un paiement est effectué ;
  • order_update lorsque la réservation est annulée et remboursée. Le status de la commande devient canceled, et ses adjustments détaillent le remboursement.
POST /agentic_commerce/webhooks/order_events
Merchant-Signature: t=1790000000,v1=5f2c…
Content-Type: application/json

{"type":"order_create","data":{"type":"order","id":"ord_48213","checkout_session_id":"cs_…","status":"confirmed", …}}

v1 est le HMAC-SHA256 en hexadécimal de {t}.{body} avec votre secret. Refusez un t datant de plus de cinq minutes. La livraison est garantie au moins une fois : une livraison échouée est retentée avec un délai qui double à chaque fois, jusqu'à dix fois.

Annulations et remboursements

Une réservation achetée via un agent est remboursée sur la carte qui a servi à la payer lorsqu'elle est annulée dans le délai d'annulation du club. L'acheteur peut annuler dans l'application Sporty, ou via DELETE /api/partner/v1/me/reservations/{id} si l'agent détient me.reservations.write. Le remboursement inclut l'éventuelle surcharge carte.

Pas encore pris en charge

  • Tout ce qui n'est pas du temps de court : adhésions, événements et articles de la boutique.
  • Les vérifications 3-D Secure.
  • Le partage du prix entre joueurs. L'acheteur réserve le court et le paie en totalité ; des partenaires peuvent être ajoutés dans l'application.
  • Plus d'un créneau par paiement.
  • Un flux de produits. Le temps de court change à la minute, les agents le trouvent donc avec search_availability.