OpenMercantil Billing API

Session-bound Stripe checkout, invoices and portal contracts. External actions are bounded, idempotent and never exposed through the public MCP.

Operations 8

POST /api/v1/donation Create a one-time donation Checkout session #
POST /api/v1/credits/checkout Create an idempotent credit-pack Checkout session #
POST /api/v1/checkout Create a subscription Checkout session #
GET /api/v1/billing/invoices List the authenticated user's invoices and subscription #
GET /api/v1/billing/portal Redirect to the authenticated user's Stripe portal #
POST /api/v1/billing/portal Create a Stripe portal session as JSON #
POST /api/v1/portal Create a Stripe Customer Portal session through the legacy alias #
POST /api/v1/stripe-webhook Receive a signed Stripe event #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/openmercantil-billing-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no email required.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

openmercantil-billing-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Openmercantil Billing API
  contact:
    name: OpenMercantil
    url: https://openmercantil.es/soporte
    email: social@openmercantil.es
  termsOfService: https://openmercantil.es/terminos-de-uso
  x-refined-note:
  - x-account-segment-contract differs across the merged source definitions and was not carried
  - x-company-identity-contract differs across the merged source definitions and was not carried
  - x-contract-status differs across the merged source definitions and was not carried
  - x-corrections differs across the merged source definitions and was not carried
  - x-dcat-catalog differs across the merged source definitions and was not carried
  - x-language differs across the merged source definitions and was not carried
  - x-methodology differs across the merged source definitions and was not carried
  - x-publisher differs across the merged source definitions and was not carried
  - x-rate-limit differs across the merged source definitions and was not carried
  - x-sources differs across the merged source definitions and was not carried
  - x-spatial differs across the merged source definitions and was not carried
  - x-temporal differs across the merged source definitions and was not carried
  version: '1.0'
  description: 'Operations tagged Billing across 2 of this provider''s published API definitions: openmercantil-openapi-1.9.3.json, openmercantil-openapi.yml. Each path carries the servers of the definition it was published in.'
servers:
- url: https://openmercantil.es
  description: Production
tags:
- name: Billing
  description: Session-bound Stripe checkout, invoices and portal contracts. External actions are bounded, idempotent and never exposed through the public MCP.
paths:
  /api/v1/donation:
    post:
      operationId: postDonationCheckout
      tags:
      - Billing
      summary: Create a one-time donation Checkout session
      description: Anonymous or authenticated. Body is capped at 16 KiB; Stripe work is fail-closed, budgeted by trusted client IP and idempotent within a 15-minute retry window. A supplied idempotency key is hashed and namespaced before Stripe.
      x-plane: billing
      security: []
      parameters:
      - name: Idempotency-Key
        in: header
        required: false
        schema:
          type: string
          pattern: ^[A-Za-z0-9._:\-]{8,200}$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
              - amount_cents
              properties:
                amount_cents:
                  type: integer
                  minimum: 100
                  maximum: 100000
                message:
                  type: string
                  maxLength: 500
                idempotency_key:
                  type: string
                  pattern: ^[A-Za-z0-9._:\-]{8,200}$
      responses:
        '200':
          description: Checkout URL and Stripe session id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StripeSessionResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '413':
          description: Request body too large
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          description: Action budget or Stripe unavailable
    servers:
    - url: https://openmercantil.es
      description: Production
  /api/v1/credits/checkout:
    post:
      operationId: postCreditsCheckout
      tags:
      - Billing
      summary: Create an idempotent credit-pack Checkout session
      description: Requires login and CSRF. The body is capped at 16 KiB and every external attempt consumes a strict per-user budget.
      x-plane: billing
      security:
      - cookieAuth: []
      parameters:
      - $ref: '#/components/parameters/CsrfHeader'
      - name: Idempotency-Key
        in: header
        required: false
        schema:
          type: string
          pattern: ^[A-Za-z0-9._:\-]{8,200}$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
              - pack
              properties:
                pack:
                  type: string
                  enum:
                  - pack_250
                  - pack_600
                  - pack_1500
                  - pack_4000
                csrf:
                  type: string
                idempotency_key:
                  type: string
                  pattern: ^[A-Za-z0-9._:\-]{8,200}$
      responses:
        '200':
          description: Checkout URL and Stripe session id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StripeSessionResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Invalid CSRF token
        '413':
          description: Request body too large
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          description: Action budget or Stripe unavailable
    servers:
    - url: https://openmercantil.es
      description: Production
  /api/v1/checkout:
    post:
      operationId: postSubscriptionCheckout
      tags:
      - Billing
      summary: Create a subscription Checkout session
      description: 'Requires a Secure, SameSite browser session and X-CSRF-Token. Trial policy is server-owned: clients cannot send trial_days or no_default_trial. Idempotency is scoped to user, plan and billing period before Stripe.'
      x-plane: billing
      security:
      - cookieAuth: []
      parameters:
      - $ref: '#/components/parameters/CsrfHeader'
      - name: Idempotency-Key
        in: header
        required: false
        schema:
          type: string
          pattern: ^[A-Za-z0-9._:\-]{8,200}$
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
              - plan
              properties:
                plan:
                  type: string
                  enum:
                  - profesional
                  - max
                  - pro
                  - business
                  - agency
                billing:
                  type: string
                  enum:
                  - monthly
                  - yearly
                  - anual
                  default: monthly
                coupon:
                  type: string
                  maxLength: 80
                csrf:
                  type: string
                  description: Body fallback accepted by the runtime; prefer X-CSRF-Token.
                idempotency_key:
                  type: string
                  pattern: ^[A-Za-z0-9._:\-]{8,200}$
      responses:
        '200':
          description: Checkout URL and Stripe session id
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StripeSessionResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '413':
          description: Request body too large
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          description: Action budget or Stripe unavailable
        '403':
          description: Invalid CSRF token
    servers:
    - url: https://openmercantil.es
      description: Production
  /api/v1/billing/invoices:
    get:
      operationId: getBillingInvoices
      tags:
      - Billing
      summary: List the authenticated user's invoices and subscription
      x-plane: billing
      security:
      - cookieAuth: []
      responses:
        '200':
          description: Local billing projection
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/BillingInvoicesResponse'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
    servers:
    - url: https://openmercantil.es
      description: Production
  /api/v1/billing/portal:
    get:
      operationId: getBillingPortal
      tags:
      - Billing
      summary: Redirect to the authenticated user's Stripe portal
      x-plane: billing
      security:
      - cookieAuth: []
      responses:
        '302':
          description: Redirect to Stripe Customer Portal
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          description: Action budget or Stripe unavailable
    post:
      operationId: postBillingPortal
      tags:
      - Billing
      summary: Create a Stripe portal session as JSON
      x-plane: billing
      security:
      - cookieAuth: []
      parameters:
      - $ref: '#/components/parameters/CsrfHeader'
      responses:
        '200':
          description: Stripe Customer Portal URL
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StripeSessionResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Invalid CSRF token
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          description: Action budget or Stripe unavailable
    servers:
    - url: https://openmercantil.es
      description: Production
  /api/v1/portal:
    post:
      operationId: postLegacyBillingPortal
      tags:
      - Billing
      summary: Create a Stripe Customer Portal session through the legacy alias
      description: Deprecated alias of `POST /api/v1/billing/portal`. Requires an authenticated browser session and a valid CSRF token supplied either in `X-CSRF-Token` or the form field `csrf`. New clients must use the canonical billing route.
      deprecated: true
      x-replaced-by: /api/v1/billing/portal
      x-plane: billing
      x-rate-limit: 'Per-user action budget: 5 attempts per 15 minutes and 30 per day'
      x-mcp-excluded: true
      security:
      - cookieAuth: []
      parameters:
      - name: X-CSRF-Token
        in: header
        required: false
        description: Required unless the same token is sent in the form field `csrf`.
        schema:
          type: string
          minLength: 32
          maxLength: 256
      requestBody:
        required: false
        description: Legacy form fallback for CSRF. Prefer the header and an empty body.
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              additionalProperties: false
              properties:
                csrf:
                  type: string
                  minLength: 32
                  maxLength: 256
      responses:
        '200':
          description: Stripe Customer Portal URL
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/StripeSessionResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: Invalid or missing CSRF token
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '429':
          $ref: '#/components/responses/TooManyRequests'
        '503':
          description: Action-budget storage or Stripe Customer Portal is unavailable
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
    servers:
    - url: https://openmercantil.es
      description: Production
  /api/v1/stripe-webhook:
    post:
      operationId: receiveStripeWebhook
      tags:
      - Billing
      summary: Receive a signed Stripe event
      description: Provider callback, not an end-user API. The exact raw JSON body is capped at 512 KiB and verified against `Stripe-Signature` before durable inbox claim or side effects. Provider `event.id` supplies idempotency; terminal duplicates return 200, active leases return 503 for retry, and failures return 500 so Stripe retries. No webhook secret, signature, raw payload or internal exception is returned. This route is excluded from every public MCP surface.
      x-plane: integration-provider-callback
      x-provider: stripe
      x-max-body-bytes: 524288
      x-idempotency-key: Stripe event.id
      x-log-policy: Redact Stripe-Signature and the raw request body
      x-mcp-excluded: true
      security: []
      parameters:
      - name: Stripe-Signature
        in: header
        required: true
        description: Stripe-generated signature verified over the exact raw body. It is sensitive request metadata and must never be logged or echoed.
        schema:
          type: string
          minLength: 1
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/StripeWebhookEventRequest'
      responses:
        '200':
          description: Event accepted, deliberately ignored, or already terminal. All response bodies are non-secret acknowledgements.
          content:
            text/plain:
              schema:
                type: string
                enum:
                - ok
                - duplicate (terminal idempotent skip)
                - ignored (other source)
                - credits ok
                - donation ok
                - subscription missing
                - historical subscription ignored
                - clawback rejected
                - clawback
                - ignored
                - ignored invoice
                - ignored payment failure
        '400':
          description: Signature missing/invalid, or the raw request stream cannot be read
          content:
            text/plain:
              schema:
                type: string
                const: Bad signature
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
              example:
                error: request_body_unreadable
        '413':
          description: Raw callback body exceeds 512 KiB
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RequestBodyTooLargeError'
              example:
                error: request_body_too_large
                max_bytes: 524288
        '500':
          description: Inbox persistence or event processing failed; Stripe must retry
          content:
            text/plain:
              schema:
                type: string
                enum:
                - webhook inbox unavailable
                - error
        '503':
          description: Another worker owns an active lease or a signed OpenMercantil checkout cannot yet be resolved to its authoritative account owner; Stripe must retry
          headers:
            Retry-After:
              description: Retry delay in seconds
              schema:
                type: integer
                const: 5
          content:
            text/plain:
              schema:
                type: string
                enum:
                - event already processing
                - credits owner unavailable
                - subscription owner unavailable
    servers:
    - url: https://openmercantil.es
      description: Production
components:
  responses:
    BadRequest:
      description: Invalid request
      headers:
        Cache-Control:
          $ref: '#/components/headers/NoStoreCacheControl'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    TooManyRequests:
      description: Rate limit exceeded
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    Unauthorized:
      description: Authentication required (no active session)
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
    TooManyRequests_2:
      description: Rate limit exceeded
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
        X-RateLimit-Limit:
          description: Maximum requests per window (60 per minute by IP on the public anonymous tier).
          schema:
            type: integer
        X-RateLimit-Remaining:
          description: Remaining requests in the current window.
          schema:
            type: integer
        X-RateLimit-Reset:
          description: Unix timestamp when the rate-limit counter resets.
          schema:
            type: integer
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse_2'
    BadRequest_2:
      description: Invalid request
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse_2'
  schemas:
    BillingInvoice:
      type: object
      description: Authenticated user's invoice metadata; URLs point to Stripe-hosted documents.
      properties:
        id:
          type: integer
        stripe_invoice_id:
          type: string
        invoice_number:
          type:
          - string
          - 'null'
        amount_paid_cents:
          type:
          - integer
          - 'null'
        amount_due_cents:
          type:
          - integer
          - 'null'
        subtotal_cents:
          type:
          - integer
          - 'null'
        tax_cents:
          type:
          - integer
          - 'null'
        tax_rate_pct:
          type:
          - number
          - 'null'
        currency:
          type:
          - string
          - 'null'
        status:
          type:
          - string
          - 'null'
        invoice_pdf:
          type:
          - string
          - 'null'
          format: uri
        hosted_invoice_url:
          type:
          - string
          - 'null'
          format: uri
        period_start:
          type:
          - string
          - 'null'
        period_end:
          type:
          - string
          - 'null'
        paid_at:
          type:
          - string
          - 'null'
        created_at:
          type: string
      additionalProperties:
        $ref: '#/components/schemas/JsonValue'
    ErrorResponse:
      type: object
      description: Closed compatibility envelope for public/account errors. Route-specific schemas narrow these fields further where required.
      required:
      - error
      properties:
        error:
          type: string
          minLength: 1
        message:
          type: string
        detail:
          type: string
        code:
          type: string
        status:
          type:
          - integer
          - string
        projection:
          type: string
        reason:
          type: string
        source_catalog_version:
          type: string
        allowed_parameters:
          type: array
          uniqueItems: true
          items:
            type: string
        slug:
          type: string
        key:
          type: string
        maximum:
          type: integer
          minimum: 1
        parameter:
          type: string
        fields:
          type: array
          items:
            type: string
        max_bytes:
          type: integer
          minimum: 1
        allowed:
          type: array
          items:
            $ref: '#/components/schemas/JsonValue'
        valid:
          type: array
          items:
            $ref: '#/components/schemas/JsonValue'
        date:
          type: string
        login_url:
          type: string
        plan:
          type: string
        limited_by:
          type: string
          enum:
          - minute
          - day
        daily_limit:
          type: integer
          minimum: 1
        reset_at:
          type: integer
          minimum: 1
        reset_at_human:
          type: string
          format: date-time
        retry_after_s:
          type: integer
          minimum: 1
        retry_after:
          type: integer
          minimum: 1
        upgrade:
          type: string
          format: uri
        upgrade_url:
          type: string
        action:
          type: string
        limit:
          type: integer
          minimum: 0
        remaining:
          type: integer
          minimum: 0
        needed:
          type: integer
          minimum: 0
        shortfall:
          type: integer
          minimum: 0
        ok:
          type: boolean
        _alias_of:
          type: string
      additionalProperties: false
    JsonValue:
      description: A JSON value used only inside explicitly documented extension maps.
      oneOf:
      - type:
        - string
        - number
        - boolean
        - 'null'
      - type: array
        items:
          $ref: '#/components/schemas/JsonValue'
      - type: object
        additionalProperties:
          $ref: '#/components/schemas/JsonValue'
    BillingInvoicesResponse:
      type: object
      required:
      - ok
      - subscription
      - invoices
      properties:
        ok:
          type: boolean
        subscription:
          anyOf:
          - $ref: '#/components/schemas/BillingSubscription'
          - type: 'null'
        invoices:
          type: array
          items:
            $ref: '#/components/schemas/BillingInvoice'
          maxItems: 50
      additionalProperties: false
    StripeWebhookEventRequest:
      type: object
      description: Stripe Event envelope. The exact signed raw bytes, not a reserialized object, are used for verification. Provider extension fields remain typed JSON and are not reflected to callers.
      required:
      - id
      - type
      - data
      properties:
        id:
          type: string
          minLength: 1
          description: Provider event id and durable idempotency key.
        object:
          type: string
          const: event
        type:
          type: string
          minLength: 1
        created:
          type: integer
          minimum: 0
        livemode:
          type: boolean
        api_version:
          type:
          - string
          - 'null'
        data:
          type: object
          required:
          - object
          properties:
            object:
              type: object
              additionalProperties:
                $ref: '#/components/schemas/JsonValue'
            previous_attributes:
              type: object
              additionalProperties:
                $ref: '#/components/schemas/JsonValue'
          additionalProperties:
            $ref: '#/components/schemas/JsonValue'
      additionalProperties:
        $ref: '#/components/schemas/JsonValue'
    RequestBodyTooLargeError:
      type: object
      required:
      - error
      - max_bytes
      properties:
        error:
          type: string
          const: request_body_too_large
        max_bytes:
          type: integer
          minimum: 1
      additionalProperties: false
    StripeSessionResponse:
      type: object
      required:
      - url
      - session_id
      properties:
        url:
          type: string
          format: uri
        session_id:
          type: string
      additionalProperties: false
    BillingSubscription:
      type: object
      description: Authenticated user's current subscription metadata.
      properties:
        id:
          type: integer
        stripe_subscription_id:
          type: string
        stripe_customer_id:
          type:
          - string
          - 'null'
        plan:
          type: string
        billing:
          type:
          - string
          - 'null'
        amount_cents:
          type:
          - integer
          - 'null'
        currency:
          type:
          - string
          - 'null'
        status:
          type: string
        current_period_start:
          type:
          - string
          - 'null'
        current_period_end:
          type:
          - string
          - 'null'
        cancel_at_period_end:
          type:
          - integer
          - boolean
        canceled_at:
          type:
          - string
          - 'null'
        created_at:
          type: string
        updated_at:
          type: string
      additionalProperties:
        $ref: '#/components/schemas/JsonValue'
    ErrorResponse_2:
      type: object
      properties:
        error:
          type: string
        message:
          type: string
      additionalProperties: true
  parameters:
    CsrfHeader:
      name: X-CSRF-Token
      in: header
      required: true
      description: Fresh token returned by GET /api/v1/user/me for this authenticated session.
      schema:
        type: string
        minLength: 32
        maxLength: 256
  headers:
    NoStoreCacheControl:
      description: Error responses must not be stored.
      schema:
        type: string
        const: no-store
  securitySchemes:
    cookieAuth:
      type: apiKey
      in: cookie
      name: ob_sess
      description: Browser session cookie set after login at /mi-cuenta/login. Mutations also require X-CSRF-Token header (obtain via GET /api/v1/user/me).
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key
      description: Optional opaque omk_* API credential for public GETs. Anonymous access remains valid; a credential with the operation's x-api-credential-scope (or public:read) selects its account quota. Never place credentials in query strings.
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: opaque omk_* credential
      description: 'Optional Authorization: Bearer transport for the same opaque omk_* API credential accepted by X-API-Key. It is not a JWT or OAuth access token.'
    sessionCookie:
      type: apiKey
      in: cookie
      name: session
      description: Session cookie issued after web sign-in, required only for billing endpoints.
externalDocs:
  description: Documentación narrativa con ejemplos en curl/Python/JavaScript
  url: https://openmercantil.es/api/documentacion
x-refined-from:
- openmercantil-openapi-1.9.3.json
- openmercantil-openapi.yml