> For the complete documentation index, see [llms.txt](https://docs.loomgate.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.loomgate.io/api-reference/client.md).

# Client API

Các route trình duyệt mà loomgate.js gọi bằng publishable key.

{% hint style="info" %}
Các route này do [`loomgate.js`](/integrations/browser.md) gọi thay bạn. Hãy dùng SDK: nó thu dữ liệu thiết bị bắt buộc (`device`), xử lý bước 3-D Secure, release khi bị từ chối và tải lại form khi số tiền đổi. Trang này chỉ để tham khảo.
{% endhint %}

Xác thực bằng publishable key (`Authorization: Bearer pk_live_…`) cùng `client_secret` của payment trong body. Publishable key phải thuộc cùng tài khoản với payment. Giới hạn 60 request/phút cho mỗi địa chỉ IP, tính riêng từng route.

## Load a payment intent for the card form

> The publishable key must belong to the merchant of the payment intent. \`device\` (what the browser SDK collected) is required; each load is recorded for the payment, at most 100.

```json
{"openapi":"3.0.0","info":{"title":"Loomgate API","version":"v1"},"tags":[{"name":"Client","description":"Browser routes used by loomgate.js (publishable key and client secret). Use the SDK rather than calling them yourself."}],"servers":[{"url":"https://api.loomgate.io"}],"security":[{"publishable_key":[]}],"components":{"securitySchemes":{"publishable_key":{"scheme":"bearer","type":"http","description":"Publishable key `pk_live_…` (safe in browsers)."}},"schemas":{"ClientBootstrapRequest":{"type":"object","properties":{"client_secret":{"type":"string","description":"The `client_secret` returned when the payment intent was created."},"device":{"type":"object","additionalProperties":true,"description":"What loomgate.js collected about the browser (time zone, languages, screen, hardware, client hints, canvas / WebGL / audio hashes, fonts). Sent by the SDK; unknown keys are dropped."},"sdk":{"type":"object","additionalProperties":true,"description":"The SDK that loaded the form: {name, version, distribution: hosted | npm, framework: react | null}."},"page_url":{"type":"string","nullable":true,"description":"The page URL, as the browser shows it."}},"required":["client_secret","device"]},"ClientBootstrapResponse":{"type":"object","properties":{"payment_intent":{"$ref":"#/components/schemas/ClientBootstrapPaymentIntent"},"elements":{"$ref":"#/components/schemas/ClientElements"}},"required":["payment_intent","elements"]},"ClientBootstrapPaymentIntent":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["pending","processing","succeeded","canceled"]},"currency":{"type":"string","enum":["usd","eur"]},"amount_total":{"type":"integer","description":"The amount the card form must charge."}},"required":["id","status","currency","amount_total"]},"ClientElements":{"type":"object","properties":{"account_id":{"type":"string","description":"Account id the card form mounts against."},"payment_methods":{"type":"array","items":{"type":"string"}}},"required":["account_id","payment_methods"]},"ErrorResponse":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/Error"}},"required":["error"]},"Error":{"type":"object","properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","card_error","rate_limit_error","api_error"]},"code":{"type":"string"},"message":{"type":"string"},"param":{"type":"string","description":"The request field at fault."},"decline_code":{"type":"string","description":"Card declines only, when the reason is known."}},"required":["type","code","message"]}}},"paths":{"/partner/v1/client/payment_intents/bootstrap":{"post":{"description":"The publishable key must belong to the merchant of the payment intent. `device` (what the browser SDK collected) is required; each load is recorded for the payment, at most 100.","operationId":"clientBootstrapPaymentIntent","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientBootstrapRequest"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientBootstrapResponse"}}}},"400":{"description":"invalid_request_error: `validation_error` or a more specific code; `param` names the field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"authentication_error: missing, invalid or revoked key / client secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"invalid_request_error: no such object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"rate_limit_error: `rate_limited`, too many requests; retry after the `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"api_error: `provider_error`, the payment could not be processed; retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"summary":"Load a payment intent for the card form","tags":["Client"]}}}}
```

## Confirm a payment intent from the browser

> Confirms the amount the card form was loaded with. If the amount changed since (or the form was never loaded): 409 \`payment\_intent\_updated\`, load the form again and confirm. A payment still missing details the seller must provide: 400 \`payment\_details\_missing\`. When \`next\_action\` is not null, finish with \`handleNextAction(next\_action.client\_secret)\`. Payment attempts are limited: each confirm sent counts, per payment intent (\`confirmation\_attempts\`; once they are used: 400 \`payment\_intent\_not\_pending\`, restart checkout with a new payment intent) and per account in 10-minute windows (429 \`rate\_limited\`, retry after \`Retry-After\` seconds).

```json
{"openapi":"3.0.0","info":{"title":"Loomgate API","version":"v1"},"tags":[{"name":"Client","description":"Browser routes used by loomgate.js (publishable key and client secret). Use the SDK rather than calling them yourself."}],"servers":[{"url":"https://api.loomgate.io"}],"security":[{"publishable_key":[]}],"components":{"securitySchemes":{"publishable_key":{"scheme":"bearer","type":"http","description":"Publishable key `pk_live_…` (safe in browsers)."}},"schemas":{"ClientConfirmRequest":{"type":"object","properties":{"confirmation_token":{"type":"string","description":"Confirmation token created by the browser SDK card form."},"billing_details":{"$ref":"#/components/schemas/BillingDetailsInput"},"return_url":{"type":"string","nullable":true,"description":"Absolute http(s) URL for redirect-based methods."},"client_secret":{"type":"string","description":"The `client_secret` returned when the payment intent was created."},"device":{"type":"object","additionalProperties":true,"description":"What loomgate.js collected about the browser (time zone, languages, screen, hardware, client hints, canvas / WebGL / audio hashes, fonts). Sent by the SDK; unknown keys are dropped."}},"required":["confirmation_token","billing_details","client_secret","device"]},"BillingDetailsInput":{"type":"object","properties":{"email":{"type":"string"},"name":{"type":"string"},"address":{"$ref":"#/components/schemas/BillingAddressInput"}},"required":["email","name","address"]},"BillingAddressInput":{"type":"object","properties":{"line1":{"type":"string"},"line2":{"type":"string","nullable":true},"city":{"type":"string","nullable":true},"state":{"type":"string","nullable":true},"postal_code":{"type":"string"},"country":{"type":"string","description":"ISO 3166-1 alpha-2."}},"required":["line1","postal_code","country"]},"ConfirmResponse":{"type":"object","properties":{"payment_intent":{"$ref":"#/components/schemas/PaymentIntentSummary"},"next_action":{"nullable":true,"type":"object","allOf":[{"$ref":"#/components/schemas/NextAction"}]}},"required":["payment_intent","next_action"]},"PaymentIntentSummary":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["pending","processing","succeeded","canceled"]}},"required":["id","status"]},"NextAction":{"type":"object","properties":{"type":{"type":"string","enum":["handle_next_action"]},"client_secret":{"type":"string","description":"Pass to handleNextAction() in the browser SDK."}},"required":["type","client_secret"]},"ErrorResponse":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/Error"}},"required":["error"]},"Error":{"type":"object","properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","card_error","rate_limit_error","api_error"]},"code":{"type":"string"},"message":{"type":"string"},"param":{"type":"string","description":"The request field at fault."},"decline_code":{"type":"string","description":"Card declines only, when the reason is known."}},"required":["type","code","message"]}}},"paths":{"/partner/v1/client/payment_intents/confirm":{"post":{"description":"Confirms the amount the card form was loaded with. If the amount changed since (or the form was never loaded): 409 `payment_intent_updated`, load the form again and confirm. A payment still missing details the seller must provide: 400 `payment_details_missing`. When `next_action` is not null, finish with `handleNextAction(next_action.client_secret)`. Payment attempts are limited: each confirm sent counts, per payment intent (`confirmation_attempts`; once they are used: 400 `payment_intent_not_pending`, restart checkout with a new payment intent) and per account in 10-minute windows (429 `rate_limited`, retry after `Retry-After` seconds).","operationId":"clientConfirmPaymentIntent","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientConfirmRequest"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmResponse"}}}},"400":{"description":"invalid_request_error: `validation_error` or a more specific code; `param` names the field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"authentication_error: missing, invalid or revoked key / client secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"card_error: the payment method was declined (`decline_code` when known) or the payment failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"invalid_request_error: no such object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"invalid_request_error: `idempotency_key_reused`, or `payment_intent_updated` on confirm (the amount changed since the card form was loaded: load the form again, then confirm).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"rate_limit_error: `rate_limited`, too many requests; retry after the `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"api_error: `provider_error`, the payment could not be processed; retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"summary":"Confirm a payment intent from the browser","tags":["Client"]}}}}
```

## Release a payment attempt after a failed confirmation

> Lets the buyer try again with another payment method.

```json
{"openapi":"3.0.0","info":{"title":"Loomgate API","version":"v1"},"tags":[{"name":"Client","description":"Browser routes used by loomgate.js (publishable key and client secret). Use the SDK rather than calling them yourself."}],"servers":[{"url":"https://api.loomgate.io"}],"security":[{"publishable_key":[]}],"components":{"securitySchemes":{"publishable_key":{"scheme":"bearer","type":"http","description":"Publishable key `pk_live_…` (safe in browsers)."}},"schemas":{"ClientReleaseRequest":{"type":"object","properties":{"client_secret":{"type":"string","description":"The `client_secret` returned when the payment intent was created."}},"required":["client_secret"]},"ReleaseResponse":{"type":"object","properties":{"payment_intent":{"$ref":"#/components/schemas/PaymentIntentSummary"}},"required":["payment_intent"]},"PaymentIntentSummary":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["pending","processing","succeeded","canceled"]}},"required":["id","status"]},"ErrorResponse":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/Error"}},"required":["error"]},"Error":{"type":"object","properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","card_error","rate_limit_error","api_error"]},"code":{"type":"string"},"message":{"type":"string"},"param":{"type":"string","description":"The request field at fault."},"decline_code":{"type":"string","description":"Card declines only, when the reason is known."}},"required":["type","code","message"]}}},"paths":{"/partner/v1/client/payment_intents/release":{"post":{"description":"Lets the buyer try again with another payment method.","operationId":"clientReleasePaymentIntent","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientReleaseRequest"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ReleaseResponse"}}}},"400":{"description":"invalid_request_error: `validation_error` or a more specific code; `param` names the field.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"authentication_error: missing, invalid or revoked key / client secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"402":{"description":"card_error: the payment method was declined (`decline_code` when known) or the payment failed.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"invalid_request_error: no such object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"rate_limit_error: `rate_limited`, too many requests; retry after the `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"api_error: `provider_error`, the payment could not be processed; retry later.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"summary":"Release a payment attempt after a failed confirmation","tags":["Client"]}}}}
```

## Download the Apple Pay domain verification file

> Serve it unchanged at \`https\://\<your-domain>/.well-known/apple-developer-merchantid-domain-association\` (no extension, a plain 200 over HTTPS: no redirect, no authentication), add the TXT record shown on the merchant dashboard, then verify the domain there. Apple Pay and Google Pay are offered on the domain once it is activated.

```json
{"openapi":"3.0.0","info":{"title":"Loomgate API","version":"v1"},"tags":[{"name":"Client","description":"Browser routes used by loomgate.js (publishable key and client secret). Use the SDK rather than calling them yourself."}],"servers":[{"url":"https://api.loomgate.io"}],"security":[{"publishable_key":[]}],"components":{"securitySchemes":{"publishable_key":{"scheme":"bearer","type":"http","description":"Publishable key `pk_live_…` (safe in browsers)."}},"schemas":{"ErrorResponse":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/Error"}},"required":["error"]},"Error":{"type":"object","properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","card_error","rate_limit_error","api_error"]},"code":{"type":"string"},"message":{"type":"string"},"param":{"type":"string","description":"The request field at fault."},"decline_code":{"type":"string","description":"Card declines only, when the reason is known."}},"required":["type","code","message"]}}},"paths":{"/partner/v1/client/apple-pay/domain-association":{"get":{"description":"Serve it unchanged at `https://<your-domain>/.well-known/apple-developer-merchantid-domain-association` (no extension, a plain 200 over HTTPS: no redirect, no authentication), add the TXT record shown on the merchant dashboard, then verify the domain there. Apple Pay and Google Pay are offered on the domain once it is activated.","operationId":"downloadApplePayDomainAssociation","parameters":[],"responses":{"200":{"description":"The file, byte for byte.","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"authentication_error: missing, invalid or revoked key / client secret.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"invalid_request_error: no such object.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"rate_limit_error: `rate_limited`, too many requests; retry after the `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}},"summary":"Download the Apple Pay domain verification file","tags":["Client"]}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.loomgate.io/api-reference/client.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
