movere Docs

API reference

Audience: anyone calling the Mooov gateway over HTTPS — platforms on Mooov Connect and merchants with a direct API key. For the consent flow, webhooks deep-dive, and credential ceremony see the Connect protocol; for direct merchant setup see the merchant quickstart.

API         https://api.mooov.money     (mk_test_* and mk_live_*; see /environments/)

Merchant keys select their environment: mk_test_* selects test and mk_live_* selects live. Connect platform keys (mk_platform_*) use ?environment=test or ?environment=live to select an environment-scoped merchant grant; omitting the query defaults to live. Invalid, empty, or repeated environment values return 400 INVALID_ENVIRONMENT. The platform key itself is not environment-partitioned.

https://sandbox.api.mooov.money is a temporary compatibility alias; the hostname does not select the request environment. See Environments.

MCP-capable agents and editors can query these endpoint and webhook schemas directly; see Connect your AI agent.

1. Conventions

Authentication

Authenticated payment and customer calls carry the HMAC headers below. The signing scheme is identical for merchant keys (mk_test_* / mk_live_*) and platform keys (mk_platform_*):

X-Mooov-Key-Id:    <your key id>
X-Mooov-Timestamp: <RFC3339 UTC>
X-Mooov-Signature: <lowercase hex HMAC-SHA256>
Idempotency-Key:   <your uuid>          (required on writes)
Mooov-Merchant:    merch_<id>           (platform keys only — on-behalf-of)

The signed string is exactly four lines (no trailing newline):

<METHOD>\n<PATH>\n<TIMESTAMP>\n<hex SHA-256 of the raw body, or of "" on GET>

Sign only the URL pathname, without the query string. Send the environment query on the request URL. Platform payment/customer calls require Mooov-Merchant; a body merchant_id does not replace that header. Public Connect and grant-management endpoints have their own requirements in the Connect protocol.

We tolerate ±5 minutes of clock skew. Drop-in signers in Python and TypeScript are in §2 of the Connect protocol.

Scopes

Each key (and each Connect grant) carries a scope list. The wildcard * satisfies any scope.

Scope Gates
payments:write Create / capture / refund / void payments, saved-method charges, subscription checkouts
payments:read Payment timeline
customers:read Customer lookups

Newly minted merchant keys default to ["payments:write", "payments:read"].

Idempotency

Every payment mutation below requires an Idempotency-Key header. Replaying the same key with the same body returns the original result; the same key with a different body returns 409. A request still in flight returns 409 request already processing — retry with backoff.

Amounts

All amounts are integer minor units (4200 = £42.00). Currency is an ISO 4217 code, e.g. "GBP".

Payment states

created → processing → authorized → captured → partially_refunded → refunded
                     ↘ failed      ↘ voided

2. Payment intents

POST /v1/payment_intents — create (and usually confirm) a payment

Field Type Required Notes
payment_id string Yes Your stable id, e.g. pay_<uuid>. Used everywhere downstream.
amount int Yes Minor units, > 0
currency string Yes ISO 4217
flow string No ""/"server" (default), "redirect", "embedded"
payment_method string For server flow pm_* or ctoken_*. Required unless using customer_ref + setup_future_usage: "off_session".
success_url string For redirect/embedded Absolute https:// (localhost http:// allowed in sandbox)
cancel_url string For redirect/embedded Same rules
description string No Shown on hosted checkout / statements
customer_email string No Pre-fills hosted checkout
customer_name string No
customer_mobile string Depends on enabled method Customer mobile number in the format confirmed for your enabled payment method. Hosted wallet checkout collects its required customer details.
payment_method_type string No Omit for configured routing. Enabled Mooov6 PKR wallet merchants can select "jazzcash_wallet" with flow: "redirect" or "embedded".
customer_ref string No Your durable customer handle (platform keys only; live mode only until customer mappings are environment-scoped)
setup_future_usage string No "on_session" or "off_session"; requires customer_ref
metadata object No String→string, max 50 keys; reserved mooov_* keys are dropped
merchant_id string No Must match the authenticated merchant; does not replace Mooov-Merchant

Payment methods and currencies must be enabled for your merchant and chosen environment. The PKR wallet selector is restricted to Mooov6; an incompatible currency returns 400, and a different entity returns 422. Additional methods require explicit activation during onboarding before use.

Response example for a Mooov3 redirect payment:

{
  "payment_id": "pay_…",
  "state": "processing",
  "provider": {
    "provider":            "stripe",
    "provider_ref":        "cs_…",
    "status":              "pending",
    "hosted_url":          "https://checkout.stripe.com/c/pay/cs_example"
  }
}

Mooov6 payment responses omit provider identity, references, session IDs, and provider-native error diagnostics. They retain status, an available hosted_url, and a neutral error when present. Optional fields may be absent rather than null.

Example provider object excerpt containing a Mooov6 error:

{
  "error": {
    "category": "risk",
    "code": "PAYMENT_DECLINED",
    "message": "payment was declined",
    "retryable": false
  }
}

Error categories are auth, validation, provider, timeout, risk, and internal. Codes and messages depend on the route and entity; Mooov6 uses neutral codes. An optional outcome: "unknown" indicates uncertainty that requires reconciliation before a new payment attempt.

Both integration shapes are walked through end-to-end in §4 of the Connect protocol.

POST /v1/payment_intents/{payment_id}/capture

Captures a previously authorized payment. Empty body (the empty body is still hashed for the signature and idempotency).

Response: the same envelope as create, with state: "captured". Re-capturing an already-captured payment returns 200 with the current state (idempotent replay). A payment not in authorized returns 409 payment not authorized for capture. If the provider rejects the capture you still get a 200 — state stays authorized and provider.status / provider.error carry the failure. Some routes reject a separate capture with the typed provider_operation_unsupported error. An already-captured payment returns its current state before this check.

POST /v1/payment_intents/{payment_id}/refund

Field Type Required Notes
amount int No Minor units. Omit (or 0) = refund the full remaining amount.
reason string No Stored on the refund for audit

Where the payment route supports refunds, refund less than the captured amount and state becomes partially_refunded; keep refunding until remaining_amount is 0 and it becomes refunded.

{
  "payment_id":       "pay_…",
  "state":            "partially_refunded",
  "refunded_amount":  500,
  "refunded_total":   500,
  "remaining_amount": 1500,
  "provider":         { "status": "approved" }
}
Status When
409 Payment not refundable in its current state / nothing left to refund
422 refund amount exceeds captured amount
502 Provider rejected the refund, or the configured route does not support refunds

refunded_amount in this API response is the amount of this operation. refunded_total is the cumulative total after the operation; remaining_amount is the captured balance still available to refund. An omitted amount or 0 means the full remaining balance, so a custom partial-refund form must reject zero before calling the API.

For Connect integrations, call this endpoint from your backend using your platform HMAC credentials, the lodge's Mooov-Merchant, and an explicit ?environment=live or ?environment=test. The active grant and key must permit payments:write; refunds:write alone is not sufficient. Scope the local payment lookup to the authenticated lodge and enforce your own admin refund permission before signing the request.

Persist a fresh Idempotency-Key once per intentional refund operation. Retries reuse that key and identical body; another intentional partial refund gets a new key. A timeout or 5xx is not proof the refund failed: reconcile an uncertain outcome before starting a new operation. Do not issue another refund merely because the local ledger is stale.

Use the response's refunded_total and the cumulative totals from payment.partially_refunded / payment.refunded notifications to update your ledger monotonically. API and provider events can describe the same funds, so never add all received amounts together. Webhook data.refunded_amount is cumulative, unlike this response's refunded_amount. See refund snapshots for exact fields, compatibility and out-of-order delivery handling.

Verify full refunds, successive partials, duplicate requests and over-refund rejection against a Test grant before Live use. Test Connect payment events are not delivered to platform destinations; check the API response and signed payment timeline instead.

POST /v1/payment_intents/{payment_id}/void

Voids an authorized (not yet captured) payment.

Field Type Required
reason string No

Response: { "payment_id": "pay_example", "state": "voided", "provider": { "status": "approved" } }. A payment that isn't authorized returns 409 payment not voidable in current state. A route without void support can return the typed 422 provider_operation_unsupported or 502 void not supported by configured provider. Refund availability is also route-specific.

GET /v1/payment_intents/{payment_id}/timeline

Returns every provider attempt and published event for the payment, oldest first. Requires payments:read.

{
  "payment_id": "pay_…",
  "timeline": [
    { "kind": "attempt", "operation": "charge",
      "provider": "stripe", "status": "approved",
      "occurred_at": "2026-06-10T12:00:00Z" },
    { "kind": "event", "operation": "payment.captured",
      "status": "published",
      "occurred_at": "2026-06-10T12:00:01Z" }
  ]
}

Entry fields: kind (attempt | event), operation, provider, status, error_code, message, occurred_at. operation on event entries uses internal event names (see the webhook event catalog for the mapping to the names you receive on Connect webhooks). Mooov6 timelines omit provider identity and clear provider-native error codes and messages.


3. Saved payment methods

POST /v1/charges/saved — charge a saved method

Platform-key only (422 otherwise). Charges a payment method previously saved against one of your customer_refs — the engine behind off-session billing like dues and renewals.

This endpoint is temporarily unavailable in test mode because customer and provider mappings are not yet environment-scoped. Test requests return HTTP 422 with the stable test_customer_mappings_unavailable error without looking up a live customer. Direct tokenized test payments remain available through POST /v1/payment_intents when customer_ref is omitted.

Field Type Required Notes
payment_id string Yes Your stable id for this charge
amount int Yes Minor units, > 0
currency string Yes
customer_ref string Yes The handle you set at save time
payment_method_id string Yes The saved pm_*
off_session bool No Default true
auto_capture bool No Default true
description string No
customer_email string No
customer_name string No
metadata object No String→string
merchant_id string No Must match Mooov-Merchant; header still required
{
  "payment_id":   "pay_…",
  "status":       "succeeded",
  "state":        "captured",
  "provider":     "stripe",
  "provider_ref": "pi_example"
}

status is one of succeeded, requires_action, failed. On requires_action (an off-session charge tripped 3DS), next_action carries what your front-end needs to finish the challenge:

{
  "next_action": {
  "type":                 "use_stripe_sdk",
  "client_secret":        "pi_…_secret_…",
  "connected_account_id": "acct_…",
  "publishable_key":      "pk_…"
  }
}

On failed, error is the same typed provider-error object as payment intents.

GET /v1/customers/by_ref?ref=<customer_ref>

Platform-key only. Requires customers:read. Resolves your durable customer_ref to the underlying PSP customer:

{
  "customer_ref":       "mbr_8f3a",
  "stripe_customer_id": "cus_…",
  "merchant_id":        "merch_…"
}

404 when no mapping exists for that ref on the addressed merchant. In test mode, the endpoint returns test_customer_mappings_unavailable without reading the shared mapping store, regardless of whether a live mapping exists.


4. Subscription checkout

POST /v1/subscription_checkouts

Opens an embedded subscription checkout for an enabled recurring-card route. Requires a Connect platform key, payments:write, Mooov-Merchant, and an Idempotency-Key. Direct merchant keys receive 422. Customer mappings are currently live-only: test requests return test_customer_mappings_unavailable. Confirm recurring checkout enablement during onboarding before integration.

Field Type Required Notes
subscription_id string Yes Your stable enrollment ID
customer_ref string Yes Platform-scoped customer handle
amount int Yes Positive recurring amount in minor units
currency string Yes Enabled currency
interval string Yes day, week, month, or year
interval_count int No Defaults to 1; non-negative
trial_period_days int No Non-negative
success_url, cancel_url string Yes Absolute HTTPS return URLs
description string No Checkout description
metadata object No String-to-string, as for payment intents
merchant_id string No Must match Mooov-Merchant; header still required

Successful creation returns HTTP 200:

{
  "subscription_id": "sub_example",
  "status": "pending",
  "hosted_url": "https://pay.mooov.money/c/sub_example",
  "checkout_session_id": "cs_example"
}

Send the customer to the returned hosted_url. Creation is not payment completion; use subscription/payment webhooks to track the outcome. Provider creation errors return 502 with status: "failed" and an error object.


5. Connect grant management

Documented in full in the Connect protocol:

Endpoint Purpose
POST /v1/public/connect/token Exchange a consent-flow auth code for a merchant_id (§3)
POST /v1/grants/{grant_id}/revoke Disconnect a tenant; idempotent; 204 on success (§5.1)

6. Errors

Error bodies are JSON. The shape depends on the failure boundary:

Cross-cutting statuses:

Status Meaning
400 Malformed body, missing Idempotency-Key, validation failure
401 Missing/invalid signature, stale timestamp, unknown key
403 Key or grant lacks the required scope; missing/revoked environment-specific grant (MERCHANT_GRANT_REQUIRED)
404 Resource doesn't exist — also returned when your principal can't see it
409 Idempotency conflict or invalid state transition
422 Typed business errors (merchant_not_charge_capable, refund exceeds captured, …)
5xx Mooov-side or provider-side failure; retain the same Idempotency-Key and identical body for any retry

A 5xx does not prove that no payment occurred, and an idempotent retry may replay the stored failure. Reconcile an uncertain payment through webhooks or the timeline before starting a new operation. Unsupported capabilities need configuration or integration changes, not repeated retries.

The integrator-facing error code table is in §7 of the Connect protocol.

7. Versioning

/v1/* is stable: additive changes only. Breaking changes get a new prefix (/v2/*) with a 12-month overlap and a public deprecation calendar. Webhook event shapes follow the same contract.