{"openapi":"3.1.0","info":{"title":"Made Card Partners API","description":"# Overview\n\nThe Made Card Partners API lets a mortgage, real estate, or moving company offer Made Card to its own customers.\n\n- **Send them to Made.** You open a Made-hosted application. The customer creates their Made login there. You never collect an SSN, income, or card number.\n- **Stay connected.** Once the customer has a Made login, you receive a customer `access_token` (`link_...`). It stays valid until Made revokes it, so later visits do not ask for a Made password.\n- **Open Made Card.** A launch URL opens Made Card already signed in as that customer.\n- **Read the card.** Linked customers, card balances, transactions, payments, and rewards are available read-only.\n\nCustomers stay Made Card customers. Partner credentials cannot create payments, change an account, or read identity documents.\n\n## How the pieces fit\n\n1. Your server authenticates with `POST /v1/partners/auth` and gets a partner JWT.\n2. Your server starts a link session with `POST /v1/partners/link/sessions`.\n3. Your web page opens the returned `link_url` with Made Link, the web SDK. The customer applies on Made.\n4. Made Link calls your `onSuccess` with a one-time `public_token`. Your page sends it to your server.\n5. Your server exchanges it with `POST /v1/partners/link/token` and stores the customer `access_token`.\n6. Later, your server calls `POST /v1/partners/sessions/launch` or the read-only `GET` endpoints.\n\n## See it working\n\nNorthstar Home Loans (demo), at https://madepartner.com, is a sample partner built on this API and Made Link. It runs in Test mode only, and anyone can open it, with no sign-in. It has two demo customers: one without Made Card, who applies, and one with a card, whose balance and transactions Northstar shows. Its developer page shows the code behind each step. Once Made approves your partner application, **Try the Northstar demo** in the partner portal at https://partners.madecard.com opens the same site with the visit labelled with your company.\n\n## Getting credentials\n\nApply at https://partners.madecard.com/apply. Made reviews every application and emails you the decision. Approval opens the partner portal in Test mode, where you build with Test keys: on **API keys**, rotate the secret to get your Test `client_secret`, which is shown once. Your Live keys come when Made grants Live access, which you request from **Go live** in Live mode, and the Live secret is shown once too, on the sign-in screen right after the emailed code. Test and Live secrets are separate. Keep both secrets on your server. Testing and go-live has the steps. Questions: partners@madecard.com.\n\n# Quickstart\n\nConnect one customer end to end in Test mode. Replace the placeholders with your Test keys, from **API keys** in the partner portal's Test mode.\n\nIn Test mode the customer is a test customer you make up. Test data lists what to use: an email at `sandbox.madecard.com` that starts with your company's name and ends with something random, whose codes Made's window shows on screen, a test phone number, and an SSN of their own. Use a new email each time you run through this guide.\n\n## 1. Get a partner JWT\n\n```bash\ncurl -X POST https://api-sandbox.madecard.com/v1/partners/auth \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"client_id\": \"pk_test_yourcompany\", \"client_secret\": \"sk_test_...\"}'\n```\n\nTest keys start with `pk_test_` and `sk_test_`. Keys issued before these prefixes (`pk_yourcompany`, `sk_...`) keep working.\n\n```json\n{\n  \"error\": null,\n  \"message\": \"Authentication successful\",\n  \"data\": {\n    \"access_token\": \"eyJhbGciOi...\",\n    \"token_type\": \"bearer\",\n    \"expires_in\": 3600,\n    \"partner_id\": \"7270dd85-8fdc-443e-8777-bdba887ef138\",\n    \"slug\": \"yourcompany\",\n    \"livemode\": false\n  }\n}\n```\n\nSend `data.access_token` as `Authorization: Bearer <token>` on every other call. Request a new one before `expires_in` runs out. `livemode` is `false` in Test mode and `true` in Live mode.\n\n## 2. Start a link session\n\n```bash\ncurl -X POST https://api-sandbox.madecard.com/v1/partners/link/sessions \\\n  -H \"Authorization: Bearer $PARTNER_JWT\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"kind\": \"apply\",\n    \"prefill\": {\n      \"first_name\": \"Ada\",\n      \"last_name\": \"Lovelace\",\n      \"email\": \"yourcompany-ada-7k2m@sandbox.madecard.com\",\n      \"address_line_1\": \"1 Main St\",\n      \"city\": \"Virginia Beach\",\n      \"state\": \"VA\",\n      \"zipcode\": \"23451\"\n    },\n    \"lock_fields\": [\"first_name\", \"last_name\", \"email\", \"address\"]\n  }'\n```\n\nThe response is HTTP 201 and contains `link_url`. It expires in 60 minutes.\n\n## 3. Open it in the browser\n\n```html\n<script src=\"https://staging-app.madecard.com/sdk/v1/made-link.js\"></script>\n<button id=\"apply\" disabled>Apply for Made Card</button>\n<script>\n  const button = document.getElementById(\"apply\");\n  let made;\n  fetch(\"/made/link-session\", { method: \"POST\" })\n    .then((r) => r.json())\n    .then(({ link_url }) => {\n      made = MadeLink.create({\n        linkUrl: link_url,\n        onSuccess: (publicToken) =>\n          fetch(\"/made/exchange\", {\n            method: \"POST\",\n            headers: { \"Content-Type\": \"application/json\" },\n            body: JSON.stringify({ public_token: publicToken }),\n          }),\n        onExit: (error) => console.log(\"Made Link ended:\", error ? error.code : \"closed by the customer\"),\n      });\n      button.disabled = false;\n    });\n  button.addEventListener(\"click\", () => made.open());\n</script>\n```\n\n`/made/link-session` and `/made/exchange` are routes on your own server. The browser never sees your partner JWT, `client_secret`, or the customer `access_token`. The session is created before the click so `open()` runs inside the click handler, where browsers allow new windows. The Web SDK page has the full example.\n\n## 4. Exchange the public token\n\n```bash\ncurl -X POST https://api-sandbox.madecard.com/v1/partners/link/token \\\n  -H \"Authorization: Bearer $PARTNER_JWT\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"public_token\": \"public_...\"}'\n```\n\nStore `data.access_token` (`link_...`) and `data.user_id` against your own customer record.\n\n## 5. Open Made Card on a later visit\n\n```bash\ncurl -X POST https://api-sandbox.madecard.com/v1/partners/sessions/launch \\\n  -H \"Authorization: Bearer $PARTNER_JWT\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"access_token\": \"link_...\", \"target_path\": \"/dashboard/home\"}'\n```\n\nThe response is HTTP 201. Open `data.launch_url` within 5 minutes, for example with `MadeLink.openLaunchUrl(launch_url)`. The customer lands in Made Card already signed in.\n\n# Environments\n\n| What | Test mode | Live mode |\n|---|---|---|\n| API base URL | `https://api-sandbox.madecard.com/v1` | `https://api.madecard.com/v1` |\n| Made Link script | `https://staging-app.madecard.com/sdk/v1/made-link.js` | `https://app.madecard.com/sdk/v1/made-link.js` |\n| Made's window | `https://staging-app.madecard.com` | `https://app.madecard.com` |\n| Partner portal | `https://partners.madecard.com`, in Test mode | `https://partners.madecard.com`, in Live mode |\n| Docs | `https://api-sandbox.madecard.com/partners/docs` | `https://api.madecard.com/partners/docs` |\n| OpenAPI | `https://api-sandbox.madecard.com/partners/openapi.json` | `https://api.madecard.com/partners/openapi.json` |\n\nTest and Live credentials are separate. A Test `client_id` does not work in Live mode, and a Live one does not work in Test mode. Their prefixes tell them apart:\n\n| Key | Test mode | Live mode |\n|---|---|---|\n| `client_id` | `pk_test_yourcompany` | `pk_live_yourcompany` |\n| `client_secret` | `sk_test_...` | `sk_live_...` |\n\nSend a key to the other mode's API and `POST /v1/partners/auth` answers `PRTN_0106`, naming the API base the key belongs to. Keys issued before these prefixes (`pk_yourcompany`, `sk_...`) keep working in their own mode; their `client_id` never changes, and the secret takes the prefix the next time it is rotated. You build in Test mode first; your Live keys arrive when Made grants your request for Live access, made from Go live in Live mode. Testing and go-live covers how Test mode differs and how to go live.\n\nIn Test mode, apply as test customers, never as real people: Test data lists the emails and phone numbers whose codes Made's window shows on screen, the SSNs, and the other details to use.\n\nAll requests and responses are JSON over HTTPS.\n\nThe original `partner-link.js` script (`Made.link`) still works at `/partner-link.js` on both hosts. New integrations should use Made Link.\n\n# Test data\n\nWhat to type in Made's application when you test your integration in Test mode, so a tester on your team can play a new customer from start to finish without a Made mailbox. Nothing in Test mode touches real money or real credit.\n\n## Test mode and Live mode\n\n| What | Test mode | Live mode |\n|---|---|---|\n| Partner portal | `https://partners.madecard.com`, switched to Test | `https://partners.madecard.com`, switched to Live |\n| API base URL | `https://api-sandbox.madecard.com/v1`, with your Test keys | `https://api.madecard.com/v1`, with your Live keys |\n| Made's window (Made Link, launch URLs, Made Card) | `https://staging-app.madecard.com` | `https://app.madecard.com` |\n| Customers | Test customers you make up, with the details on this page | Real people |\n| One-time codes | Shown in Made's window for test emails and phone numbers | Sent only by email or text |\n| Decisions | Simulated: you choose them on the Testing tab | Made's real review |\n\nTest mode is Made's test environment. Your Test keys, origins, webhooks and customers are separate from Live mode's, and a test customer exists only in Test mode. Environments lists every base URL.\n\n## A test customer\n\nGive every test customer their own email, phone number and SSN: Made allows one Made account per email and per phone number, and one card per SSN.\n\n| Field | What to use |\n|---|---|\n| Email | An address at `sandbox.madecard.com` that starts with your company's name and ends with a few random letters or digits, such as `yourcompany-qa-7k2m@sandbox.madecard.com`. A new one for each test customer (see Codes on screen) |\n| Phone | A US number whose last seven digits are 555-0100 to 555-0199, with any area code, such as `(757) 555-0142`. A new one for each test customer: other testers use this range too, so vary the area code |\n| Date of birth | `04/12/1985`, or any date that makes the customer 18 or older |\n| Annual income | `145000` |\n| SSN | Area 987, a middle pair from 01 to 49, and any last four, such as `987-23-4561`. A new one for each test customer (see Test SSNs) |\n| Name | Letters and spaces only, such as `Ada Lovelace` |\n| Address | A street address in one of the 50 states or DC, such as `1 Main St, Virginia Beach, VA 23451`. Not a P.O. Box |\n\nSend what you know about the test customer in the link session's `prefill`, as you will for a real customer (Apply and link lists the formats). Prefill the test email: the Testing tab's decision applies only to test customers whose email you prefilled.\n\n## Codes on screen\n\nMade's window asks for one-time codes: to confirm the customer's email while they apply, to confirm their phone number after they accept their card offer, to sign in, to reset a password, and for a second sign-in step. The phone number gets no code while they apply: Made's window confirms the email with a code, then asks for the phone number and goes straight on to the review. In Test mode, Made sends no email or text to a test address or test phone number: `sandbox.madecard.com` has no mailbox, and the 555-0100 to 555-0199 numbers are kept for fiction. Instead, Made's window at `https://staging-app.madecard.com` fills in the code for you a moment after Made sends it.\n\n- Made's window shows a code only in the browser that asked Made to send it, and only for a test email or a test phone number.\n- A test email is not a password. Test mode is shared with Made's own tests and other partners, and anyone who types a test email into Made's window, a tester at another partner included, gets its code and can sign in as that test customer. Starting the address with your company's name and ending it with something random keeps anyone else from landing on it. Keep nothing real in a test customer.\n- A code expires 10 minutes after Made sends it, as in Live mode. If it doesn't appear, check that the email ends in exactly `@sandbox.madecard.com` (not a subdomain such as `@qa.sandbox.madecard.com`), then ask for a new code.\n- Your own team's addresses, such as `name@yourcompany.com`, get no code in Test mode: Made's test environment sends email only to addresses Made has allowed, and shows codes only for test emails and phone numbers. Use a test address for every test customer.\n- Live mode never shows a code, and Made can't deliver one to a test address or test number. Never use them in Live mode: such a customer can't finish signing up.\n\n## Test SSNs\n\nMade's card processor opens one card per SSN. A test customer on an SSN that already has a Made Card is declined as an existing cardholder, or approved but unable to open their card (the processor refuses a second card on one SSN with `ERR05170`). So give every test customer an SSN of their own.\n\n- Made keeps `987-01-0001` to `987-49-9999` for tests: area 987, a middle pair from 01 to 49, and a last four from 0001 to 9999. No one has one of these numbers: Social Security never issues areas 900 to 999, and the IRS numbers that start with 9 (ITINs and ATINs) use only middle pairs 50 and up.\n- Test mode is shared with Made's own tests and other partners. Pick the middle pair and the last four at random rather than counting up from `987-01-0001`, so you don't land on an SSN someone else used.\n- Don't use `987-65-4320` to `987-65-4329`, the range kept for advertising: Made's demos used it, and it already has a card.\n- Never enter a real person's SSN in Test mode.\n\n## A declined test customer can't be reused\n\n- A test customer declined by the identity check is blocked: Made refuses their email and SSN from then on. They can't sign in to Made again, their launch URL shows Made's message, and a `login` session can't connect them.\n- A decline you force on the Testing tab is a credit decline and blocks no one. The email and phone number still belong to that test customer's Made account, though, so signing up again with either is refused.\n- Either way, start the next test with a new email, phone number, and SSN.\n\n## Choosing the decision: the Testing tab\n\nIn Test mode, the partner portal's Activity page has a Testing tab, where owners, admins and developers choose what Made decides for your test customers:\n\n| Choice | What Made decides |\n|---|---|\n| Approve | Approves every test customer, on a test credit file that passes. The identity check still runs first, and can still decline |\n| Decline | Declines every test customer, on a test credit file whose credit score is below Made's minimum |\n| Random | Decides as it does for any test applicant, on one of two test credit files picked at random. Both pass, so most test customers are approved; the identity check can still decline |\n\n- It applies to test customers who sign up in one of your `apply` link sessions with their email prefilled. Made reads it when they submit their application, so a change doesn't touch applications already submitted.\n- Made finds the session by the prefilled email: if another partner's session prefilled the same address after yours, that partner's choice decides. A test email of your own avoids that.\n- An approved test customer who accepts their offer gets a Made Card in Test mode: `account.opened` arrives, `account_id` is set, and card reads work.\n- The tab exists only in Test mode. Live mode has no such control: Made decides every application for real.\n\n## Test-mode webhooks\n\n- Test mode sends the same events as Live mode, for your test customers, to the endpoints you add in Test mode. Live endpoints never receive them.\n- Every Test-mode event has `\"livemode\": false`, and is signed with that Test endpoint's own secret.\n- Made's servers have no fixed IP addresses, in Test mode or Live mode: verify the signature, and don't allow Made in by source address.\n- A Test endpoint needs a public `https` URL, as a Live one does: Made doesn't deliver to `localhost` (see Webhooks).\n\n## Nothing real in Test mode\n\n- Identity and credit checks run in sandbox mode: Test mode never pulls a real credit report, and made-up applicant details go through.\n- A Made Card opened in Test mode exists only in the card processor's test environment. It can't pay anywhere, and no money moves.\n\n# Auth and tokens\n\nEvery endpoint except `POST /v1/partners/auth` needs a partner JWT in the `Authorization` header:\n\n```\nAuthorization: Bearer <partner_jwt>\n```\n\nGet one by exchanging your `client_id` and `client_secret` at `POST /v1/partners/auth`. Their prefix says which mode a key belongs to: `pk_test_` and `sk_test_` for Test mode, `pk_live_` and `sk_live_` for Live mode. Keys issued before these prefixes (`pk_yourcompany`, `sk_...`) keep working unchanged: a `client_id` never changes, and the secret takes its mode's prefix the next time it is rotated. It lasts about 60 minutes (`expires_in`, in seconds). Cache it on your server and request a new one shortly before it expires.\n\nThe answer also has `livemode`: `true` in Live mode, `false` in Test mode. Check it once at startup, so a server configured with Test keys never runs as Live, or the other way round. Webhook events carry the same field.\n\n- A partner JWT belongs to the API key it was issued with. When that key's secret is rotated (in the partner portal, or by Made) or the key is revoked, every JWT issued with it stops working at once with `PRTN_0003`. Authenticate again with the current secret.\n- `POST /v1/partners/auth` has limits: calls a minute from one IP and for one `client_id`, and too many wrong secrets for a `client_id` from one IP lock that IP out of it for 15 minutes. Over a limit it answers `PRTN_0064` with HTTP 429 and a `Retry-After` header in seconds. Caching the JWT for its lifetime keeps you far below them.\n- A key sent to the other mode's API (a `pk_test_` or `sk_test_` key on the Live API, or a `pk_live_` or `sk_live_` key on the Test API) gets `PRTN_0106` with HTTP 401 at once. The message and `data.api_base` name the API base that key belongs to. It never counts toward the lockout.\n\n## Tokens at a glance\n\n| Token | Who holds it | Lifetime | Used for |\n|---|---|---|---|\n| Partner JWT | Your server | About 60 minutes | `Authorization: Bearer` on every partner call |\n| `public_token` (`public_...`) | Your `onSuccess` callback, then your server | 10 minutes, single use | Exchanging for the customer `access_token` |\n| Customer `access_token` (`link_...`) | Your server | Until Made revokes it | Identifying one linked customer in request bodies |\n| Launch URL | The customer's browser | 5 minutes, single use | Opening Made Card signed in |\n\nThe customer `access_token` goes in the JSON body of `link/refresh` and `sessions/launch`. It is never an `Authorization` header, and it cannot call Made's customer APIs.\n\n## Keep secrets on your server\n\n- `client_secret`, the partner JWT, and every `link_...` token belong on your server only.\n- Only `link_url`, `public_token`, and `launch_url` pass through the browser, and each is short-lived.\n\n# Apply and link\n\n## Start a session\n\n`POST /v1/partners/link/sessions` returns HTTP 201 with a `link_url` that is valid for 60 minutes. It stops working once the customer is linked.\n\n- `kind: \"apply\"` is the normal path for a customer who is new to Made Card.\n- `kind: \"login\"` is for recovery only: you lost the stored `access_token`, Made revoked the link, or an existing Made customer is connecting to you for the first time.\n- `prefill` fills the application with what you already know: `first_name`, `last_name`, `email`, `phone`, `address_line_1`, `address_line_2`, `city`, `state`, `zipcode`. Every field is optional.\n- `lock_fields` makes prefilled values binding. Allowed values are `first_name`, `last_name`, `email`, and `address`. The address lock checks `address_line_1`, `city`, `state`, and `zipcode`. Any other name returns `PRTN_0031`.\n  - By default all four are locked: omit `lock_fields` or send `[]`, and Made locks `first_name`, `last_name`, `email`, and `address`. The session response's `lock_fields` shows what Made locked.\n  - A lock applies only to a field you prefill. Made doesn't check a locked field you leave out of `prefill`.\n  - To leave a prefilled field editable, list only the others. For example, `[\"first_name\", \"last_name\", \"email\"]` locks the name and email and lets the customer change the address.\n  - If the customer's Made profile does not match a locked value, the session does not complete, the Made window tells the customer, and your page gets `onExit` with `IDENTITY_MISMATCH`. Start a new link session to try again: the Made window won't try that one again. Check the `prefill` you send, or lock fewer fields.\n- `partner_agent_id` is an optional ID Made assigns to one of your loan officers or agents. Leave it `null` unless Made gave you one. An ID that is not one of yours returns `PRTN_0030`.\n\n### Prefill formats\n\nMade trims every value and treats an empty string as not sent. A value in the wrong format, or a field Made does not know (such as `zip` or `lockFields`), returns HTTP 422 and no session is created.\n\nAn `apply` session also checks each value against the Made application's own rules, because the customer cannot change a locked value to get past the application. A `login` session checks only the format, so it can still match an existing customer's profile.\n\n| Field | Format | Also for `apply` |\n|---|---|---|\n| `first_name`, `last_name` | Up to 100 characters | 2 to 50 characters, only letters and spaces. `O'Brien` and `Smith-Jones` are refused |\n| `address_line_1` | Up to 200 characters | 5 to 40 characters, and a street address: not a P.O. Box, PMB, or registered agent |\n| `address_line_2` | Up to 200 characters | Up to 40 characters, and not a P.O. Box, PMB, or registered agent |\n| `city` | Up to 100 characters | Only letters, spaces, hyphens, and apostrophes. `St. Louis` is refused; `St Louis` is fine |\n| `email` | An email address | |\n| `phone` | A US mobile number. Made removes spaces, dashes, dots, parentheses, and a leading `+1` or `1`, then requires 10 digits: `(757) 555-0123` is stored as `7575550123` | |\n| `state` | A two-letter US state or territory code, in any case: `va` is stored as `VA` | One of the 50 states or `DC` |\n| `zipcode` | Five digits. A ZIP+4 such as `23451-1234` is stored as `23451` | |\n\nIf a value such as a name with an apostrophe does not fit, leave that field out and the customer types it on the application.\n\nThe session response returns `prefill` as Made stored it.\n\n## Open it with Made Link\n\nLoad the Made Link script (see Web SDK), create a handler with the `link_url`, and call `open()` from the customer's click:\n\n```js\nconst made = MadeLink.create({\n  linkUrl: link_url, // from POST /v1/partners/link/sessions\n  onSuccess: (publicToken, metadata) => { /* send publicToken to your server */ },\n  onExit: (error, metadata) => { /* error is null, or { code: \"USER_CLOSED\" | ... } */ },\n});\nbutton.addEventListener(\"click\", () => made.open());\n```\n\n- `onSuccess` fires once with the `publicToken`. For `kind: \"apply\"` that happens as soon as the customer has a Made login; the Made window stays open while they finish the application.\n- For `kind: \"login\"`, the Made window asks the customer to sign in to Made Card the way they usually do: a one-time code, a password, or a passkey, and their second factor if they set one up. `onSuccess` fires once they are signed in, and the window then closes itself.\n- When the browser is already signed in to Made, the Made window doesn't connect that account on its own. It first shows a Made confirmation, \"Link your Made account (j•••@example.com) to your company?\", with Continue and Use a different account. A reconnect (`kind: \"login\"`) and a session opened from a launch URL always ask. It skips the question only for a sign-in made in this window, or, for `kind: \"apply\"`, an account whose email is the one you prefilled.\n- Made connects a customer only with a Made sign-in from the last 15 minutes that isn't a session opened from a launch URL. An older sign-in, or a launch session, is asked to sign in again in the Made window first; if the customer leaves instead, your page gets `onExit` with `REAUTH_REQUIRED` (Made Link 1.2.0).\n- `onExit` fires once if the flow ends without success, with a code such as `POPUP_BLOCKED`, `USER_CLOSED`, `SESSION_EXPIRED`, since Made Link 1.1.0 `ACCOUNT_UNAVAILABLE` for a customer Made won't connect, or since 1.2.0 `REAUTH_REQUIRED`. The Web SDK page lists every code and what to do.\n- After `USER_CLOSED`, `onExit(null)` or `SESSION_EXPIRED`, create a new link session for the customer's next try instead of opening the old `link_url` again. Made returns a session's `prefill` to the Made window only for 15 minutes after the window first opens it, so an old `link_url` opens without it.\n\n## Allowed origins\n\nMade returns the `public_token` only to a page on one of your allowed origins. The session response lists them in `allowed_origins`.\n\n- An origin is `https://` plus the host and an optional port, with no path: `https://www.yourcompany.com`. `http://` works only for `localhost`.\n- `localhost` origins (`http://` or `https://`, `127.0.0.1` included) work only in Test mode. Live mode refuses them, so develop locally in Test mode.\n- Test mode and Live mode each have their own list, of up to 20. Add every site that opens Made Link to the mode it uses: your local and test sites to Test mode, your live sites to Live mode.\n- Manage each list in the partner portal after you sign in, on **Allowed origins** in that mode. Test mode's list starts empty, so add your own origins there (`http://localhost:<port>` works). Live mode's starts with your application's website, and Made adds the production origins from your Live access request when it grants it.\n- The Made window completes a link only when a page on one of your allowed origins opened it. Opened from any other page, or with no opener at all (a plain link, a new tab, `rel=\"noreferrer\"`), it tells the customer and issues no `public_token`, and a page that opened it gets `onExit` with `ORIGIN_NOT_ALLOWED`.\n\n## Exchange the public token\n\n`POST /v1/partners/link/token` with `{ \"public_token\": \"public_...\" }` returns the customer `access_token` (`link_...`) and `user_id`. The `public_token` works once and expires 10 minutes after the customer finishes.\n\nStore the `access_token` against your customer. `expires_in` is `null` because the link does not expire on a timer. It stays valid until Made revokes it. Linking the same customer again replaces the previous token.\n\n## Rotate the token\n\n`POST /v1/partners/link/refresh` with `{ \"access_token\": \"link_...\" }` issues a new `link_...` for the same customer. The previous token stops working immediately. The customer is not involved. Use it when a stored token may have leaked, or on your own rotation schedule; the link does not expire otherwise. A token that is invalid or already revoked returns `PRTN_0010`: start a `kind: \"login\"` session instead.\n\n## Attribution\n\nWhen a customer completes a link session, Made records you as the partner that brought them if they created their Made login in your `apply` session, or if Made has no channel for them yet:\n\n- The customer's channel is set to your partner slug. That is also how `GET /v1/partners/customers` finds customers who applied through you without an active link.\n- If you sent `partner_agent_id` and the customer has no loan officer or agent yet, that agent is recorded on the customer.\n\nThe first attribution stays. A customer who had a Made login before your session, or who came through another partner or agent, is not moved to you, but you can still read them while your link is active.\n\n# Web SDK\n\nMade Link is a small script with no dependencies. It opens the Made-hosted application in a window over your page and tells your page how it ended. Your server still makes every API call; the script only handles the browser side.\n\n## Load the script\n\n| File | Test mode | Live mode |\n|---|---|---|\n| Latest 1.x | `https://staging-app.madecard.com/sdk/v1/made-link.js` | `https://app.madecard.com/sdk/v1/made-link.js` |\n| Pinned 1.2.0 | `https://staging-app.madecard.com/sdk/v1/made-link@1.2.0.js` | `https://app.madecard.com/sdk/v1/made-link@1.2.0.js` |\n| TypeScript types | `https://staging-app.madecard.com/sdk/v1/made-link.d.ts` | `https://app.madecard.com/sdk/v1/made-link.d.ts` |\n\nA pinned file never changes, so you can check it with an integrity hash:\n\n```html\n<script\n  src=\"https://app.madecard.com/sdk/v1/made-link@1.2.0.js\"\n  integrity=\"sha384-YUtON3zsoNkJvhG73apj2mu4SzmgoxZkkOPvu7m4Oo4sY995f2x4M3PZoyHwsymn\"\n  crossorigin=\"anonymous\"\n></script>\n```\n\nThe latest file follows the newest 1.x release and is cached for 5 minutes. The script defines `window.MadeLink`.\n\n| Release | Integrity | Changes |\n|---|---|---|\n| 1.2.0 | `sha384-YUtON3zsoNkJvhG73apj2mu4SzmgoxZkkOPvu7m4Oo4sY995f2x4M3PZoyHwsymn` | Accepts only Made's origins for `linkUrl` and launch URLs, and adds the `REAUTH_REQUIRED` exit code |\n| 1.1.0 | `sha384-zEEURKHoc1Yfm52ZOe3FnogNtOxw4VP5XWjakI7AFup7LSGKcKfFIYQ6vqmb+ZHj` | Adds the `ACCOUNT_UNAVAILABLE` exit code |\n| 1.0.0 | `sha384-fRjOnr9KxuJAAY+Ump/Gfx7Js/uSwQxb82AoHda8F5tR73rLzBqCFZTGET41xVMs` | First release |\n\nTest mode's host and Live mode's host serve the same bytes for each release, so the hash is the same on both. A page pinned to 1.0.0 or 1.1.0 keeps working, but it can't tell `REAUTH_REQUIRED` apart, and 1.0.0 not `ACCOUNT_UNAVAILABLE` either (see Errors). To move, change the file name and the hash together.\n\n## Full example\n\nCreate the link session before the customer clicks, so the click handler can open the Made window right away. Browsers block windows that open after a network wait. When a try ends without success, create a new session for the next click (`prepare()` below) instead of opening the old `link_url` again.\n\n```html\n<button id=\"apply\" disabled>Apply for Made Card</button>\n<p id=\"status\" role=\"status\"></p>\n\n<script\n  src=\"https://staging-app.madecard.com/sdk/v1/made-link@1.2.0.js\"\n  integrity=\"sha384-YUtON3zsoNkJvhG73apj2mu4SzmgoxZkkOPvu7m4Oo4sY995f2x4M3PZoyHwsymn\"\n  crossorigin=\"anonymous\"\n></script>\n<script>\n  const button = document.getElementById(\"apply\");\n  const status = document.getElementById(\"status\");\n  let made = null;\n\n  // /made/link-session and /made/exchange are routes on your server. The browser never sees\n  // your client_secret, your partner JWT, or the customer's link_ token.\n  // A new link session for the next click. The button stays off until it is ready.\n  async function prepare() {\n    button.disabled = true;\n    const { link_url } = await fetch(\"/made/link-session\", { method: \"POST\" }).then((r) => r.json());\n    if (made) made.destroy();\n    made = MadeLink.create({\n      linkUrl: link_url,\n      onSuccess: async (publicToken, metadata) => {\n        await fetch(\"/made/exchange\", {\n          method: \"POST\",\n          headers: { \"Content-Type\": \"application/json\" },\n          body: JSON.stringify({ public_token: publicToken }),\n        });\n        status.textContent = \"Your Made Card account is connected.\";\n      },\n      onExit: (error, metadata) => {\n        if (!error || error.code === \"USER_CLOSED\") {\n          // The customer left. Their next click opens a new session, not this one again.\n          status.textContent = \"You can apply any time.\";\n          prepare();\n        } else if (error.code === \"SESSION_EXPIRED\" || error.code === \"IDENTITY_MISMATCH\") prepare();\n        else if (error.code === \"REAUTH_REQUIRED\") status.textContent = \"Try again, and sign in to Made Card to connect.\";\n        else if (error.code === \"ACCOUNT_UNAVAILABLE\") {\n          // Made won't connect this customer. Don't retry, and don't start a new session.\n          status.textContent = \"Your Made account can't be connected here. Please contact Made support.\";\n          button.disabled = true;\n        } else status.textContent = \"Made Card could not open here. Please contact us.\";\n      },\n      onEvent: (eventName, metadata) => console.log(\"Made Link\", eventName, metadata),\n    });\n    button.disabled = false;\n  }\n\n  button.addEventListener(\"click\", () => made.open());\n  prepare();\n</script>\n```\n\nA `link_url` lasts 60 minutes. If your page can stay open longer, call `prepare()` again before then. Made also returns a session's `prefill` to the Made window only for 15 minutes after the window first opens it, so an old `link_url` opened again later shows the customer an application without your prefill: another reason to start each try with a new session.\n\n## API\n\n`MadeLink.create(options)` returns a handler. It throws a `TypeError` if `linkUrl` is not a `link_url` from `POST /v1/partners/link/sessions`.\n\n| Option | Called with | When |\n|---|---|---|\n| `linkUrl` | | Required. The `link_url` your server received. |\n| `onSuccess` | `(publicToken, metadata)` | Required. The customer is linked to you. Send `publicToken` to your server. |\n| `onExit` | `(error, metadata)` | The flow ended without success. `error` is `null` when the customer closed it from inside the Made window: treat it like `USER_CLOSED`, and create a new link session for their next try. |\n| `onEvent` | `(eventName, metadata)` | Every step, for analytics and logs. |\n\n`metadata` is `{ sessionId, kind }`, where `kind` is `\"apply\"` or `\"login\"`.\n\n| Handler method | Does |\n|---|---|\n| `open()` | Opens the Made window, or focuses it if it is already open. Call it inside a click handler. |\n| `destroy()` | Closes the Made window and stops listening. No callback runs after it. |\n\nFor `kind: \"apply\"`, `onSuccess` fires as soon as the customer has a Made login, and the Made window stays open while they finish the application. Check the application later with the customer endpoints. For `kind: \"login\"`, the Made window closes itself after `onSuccess`.\n\n`MadeLink.version` is the loaded version, for example `\"1.2.0\"`.\n\n## Errors\n\n`onExit` receives `{ code, message }`. Branch on `code`; `message` is for your logs.\n\n| Code | What happened | What to do |\n|---|---|---|\n| `POPUP_BLOCKED` | The browser refused to open the window | Call `open()` inside the click handler, with the session created beforehand |\n| `USER_CLOSED` | The customer closed the Made window before finishing | Let them try again with a new link session, as for `SESSION_EXPIRED`. Made returns the session's `prefill` only for 15 minutes after the Made window first opens it, so the old `link_url` would open without it. `onExit(null)`, the Made window's own Close button, is the same case |\n| `ORIGIN_NOT_ALLOWED` | Your page's origin is not one of your allowed origins | Add the origin in the partner portal |\n| `IDENTITY_MISMATCH` | The customer's existing Made profile does not match a field you locked | Create a new link session: the Made window stops trying this one. Check the details you send, or lock fewer fields |\n| `SESSION_EXPIRED` | The `link_url` expired, was already used, or was not found | Create a new link session |\n| `ACCOUNT_UNAVAILABLE` | Made won't connect this customer, for example because it no longer lets them sign in. Since 1.1.0 | Don't retry, and don't create a new session for them. Tell the customer to contact Made support |\n| `REAUTH_REQUIRED` | Made asked the customer to sign in again, and they left first: the Made sign-in in the window was more than 15 minutes old, or came from a launch URL. Since 1.2.0 | Let them retry. The same `link_url` works until it expires |\n\nThe Made window shows the customer a message for `ORIGIN_NOT_ALLOWED`, `IDENTITY_MISMATCH`, `SESSION_EXPIRED`, and `ACCOUNT_UNAVAILABLE`, and stays open until they close it.\n\nMade connects a customer only with a Made sign-in from the last 15 minutes that isn't a session opened from a launch URL (Core answers the Made window `PRTN_0063` otherwise). So a customer already signed in to Made in that browser from earlier, or launched into Made Card from any partner's site, is asked to sign in again in the Made window before they are connected. Nothing changes for your page: once they sign in, `onSuccess` fires as usual. Made Link 1.2.0 reports an `ERROR` event with `REAUTH_REQUIRED` and keeps listening, so the sign-in still ends in `onSuccess`. 1.0.0 and 1.1.0 ignore it, and a customer who closes the window at that point gives `USER_CLOSED`, or `onExit(null)` from the window's own Close button.\n\nMade Link 1.0.0 doesn't know `ACCOUNT_UNAVAILABLE`. A page pinned to 1.0.0 gets no `onSuccess` for that customer, then `onExit(null)` when they close the Made window from inside, or `USER_CLOSED` when they close it some other way. Load 1.1.0 to tell this case apart.\n\n## Events\n\n| Event | When |\n|---|---|\n| `OPEN` | The Made window opened |\n| `READY` | The Made window loaded the session and accepted your page |\n| `SUCCESS` | Right before `onSuccess` |\n| `EXIT` | Right before `onExit`. `metadata.errorCode` holds the code, if any |\n| `ERROR` | Made reported an error. `metadata.errorCode` holds the code. `LOAD_TIMEOUT` means Made has not answered in 60 seconds; the flow stays open |\n\n## How the Made window shows your company\n\nThe Made window shows your company at the top of every apply and login page: the display name and logo Made approved for you, or your partner name until Made has approved one. To change them, an owner or admin of your team sends a request from **Company profile** in the partner portal. The logo must be a PNG, JPEG or WebP image (never SVG), at most 512 KB, at least 128 pixels on each side, and from square to 4 times as wide as it is tall. Made reviews each request, and once it approves, every new link session and launch shows the new name and logo. Your code doesn't change.\n\n## Open Made Card for a linked customer\n\nYour server creates a launch URL with `POST /v1/partners/sessions/launch`. Open it with:\n\n```js\nconst opened = MadeLink.openLaunchUrl(launch_url); // new tab\nif (!opened) MadeLink.openLaunchUrl(launch_url, { target: \"_self\" }); // the browser blocked the tab\n```\n\n`openLaunchUrl` returns `false` if the browser blocked the new tab. The tab opens without a reference back to your page. It throws a `TypeError` for anything other than a Made launch URL.\n\n`openLaunchUrl` needs the `launch_url` already in hand, and browsers block a tab opened after a network wait. If your click handler asks your server for the launch URL, open the tab first and send it there afterwards, as Launch URLs shows.\n\n## Types\n\nDownload `made-link.d.ts` into your project. It declares `window.MadeLink` and exports the types, including `MadeLinkOptions`, `MadeLinkError`, and `MadeLinkErrorCode`.\n\n## Security\n\n- Made Link accepts messages only from the origin of `linkUrl`, only from the window it opened, and only for that session.\n- The Made window sends the `public_token` only to an allowed origin. `publicToken` works once and expires 10 minutes after the customer finishes.\n- Keep the customer's `link_...` token on your server. Nothing in the browser needs it.\n\n## Moving from partner-link.js\n\n`partner-link.js` and `Made.link` keep working, now built on Made Link. To move:\n\n| partner-link.js | Made Link |\n|---|---|\n| `<script src=\".../partner-link.js\">` | `<script src=\".../sdk/v1/made-link.js\">` |\n| `Made.link({ linkUrl, onSuccess, onExit })`, which opens at once | `MadeLink.create({ linkUrl, onSuccess, onExit, onEvent })`, then `handler.open()` |\n| `onSuccess({ public_token, session_id })` | `onSuccess(publicToken, { sessionId, kind })` |\n| `onExit({ reason: \"popup_blocked\" })` | `onExit({ code: \"POPUP_BLOCKED\" })` |\n| `onExit({ reason: \"closed\" })` | `onExit({ code: \"USER_CLOSED\" })`, or the other codes above |\n| `handle.close()` | `handler.destroy()` |\n| `origin` option | Not needed. Messages are checked against the origin of `linkUrl` |\n\n# Launch URLs\n\n`POST /v1/partners/sessions/launch` turns a stored customer `access_token` into a one-time `launch_url`.\n\n```json\n{\n  \"access_token\": \"link_...\",\n  \"target_path\": \"/dashboard/home\"\n}\n```\n\nAllowed `target_path` values:\n\n| Path | Opens |\n|---|---|\n| `/dashboard/home` | Made Card home (default) |\n| `/dashboard/payments` | Payments |\n| `/dashboard/rewards` | Rewards |\n| `/dashboard/accounts` | Account details |\n| `/dashboard/transactions` | Transactions |\n\nAny other path returns `PRTN_0011`.\n\nOpen `launch_url` in a new tab within 5 minutes. It works once. The customer is signed in as themselves, and payments and every other action happen in Made Card, not through your credentials.\n\nWhile it signs the customer in, the launch page shows your display name and logo as Made approved them in the partner portal's Company profile, or your partner name until Made has approved one.\n\nThe launch signs the customer in on that browser for one visit. The session renews while they use it, and after a break it can be renewed, but it ends 12 hours after the launch at the latest, however often it was renewed; after that the customer opens it again from your site or signs in to Made Card. It also ends at once when the customer disconnects your app, Made revokes the connection, or your partner account is disabled; a `launch_url` not yet opened stops working then too. It stays a launch session the whole time: it can't be turned into a full Made sign-in, set or change the customer's password, add a passkey or biometric login, change the email or phone on file, or connect the customer to a partner through Made Link. The customer does those by signing in to Made Card directly, or with Forgot password.\n\n`target_path` applies once the customer has a Made Card account. Before that, the launch URL opens their application instead: while it is in review, after a decline, and after an approval until they accept the offer. `account_id` in `GET /v1/partners/customers/{user_id}` tells you which case you are in.\n\nIf Made has stopped the customer from signing in, whatever the reason, the launch URL shows Made's message, signs no one in, and stays unused. Your page is not told.\n\nUse launch for every return visit. Do not start a `login` session for a customer you already have an `access_token` for.\n\n## Open it from a click\n\nA launch URL works once and lasts 5 minutes, so the usual place to create it is the click on your \"Open Made Card\" button. Browsers block a tab that opens after a network wait, so open the tab first, inside the click, and send it to the launch URL when your server answers:\n\n```js\nbutton.addEventListener(\"click\", () => {\n  // Inside the click, before any network call. Don't pass \"noopener\": window.open then returns null,\n  // and you can't send the tab anywhere.\n  const tab = window.open(\"\", \"_blank\");\n  if (!tab) return showLaunchLink(); // the browser refused the tab\n  tab.opener = null; // Made Card gets no reference back to your page\n\n  // /made/launch is a route on your server that calls POST /v1/partners/sessions/launch.\n  fetch(\"/made/launch\", { method: \"POST\" })\n    .then((response) => (response.ok ? response.json() : Promise.reject(response)))\n    .then(({ launch_url }) => (tab.closed ? showLaunchLink() : tab.location.replace(launch_url)))\n    .catch(() => {\n      tab.close();\n      showError();\n    });\n});\n```\n\n- If the browser refuses the tab, show a plain link, such as `<a href=\"/made/open\" target=\"_blank\">`, to a page or route of yours that creates a fresh launch URL and goes to it. A launch URL you already created may be used up or expired by the time the customer clicks.\n- `MadeLink.openLaunchUrl(launch_url)` from the Web SDK opens a launch URL you already have in a new tab, with no reference back to your page. Call it inside the click too; after a network wait the browser blocks it the same way.\n\n# Customer data\n\nAll customer endpoints are read-only `GET` calls with your partner JWT.\n\n## Who you can see\n\nYour customers are the Made customers who:\n\n- have an active link with you: they finished a link session you started, and you exchanged its `public_token`. The link lasts until Made revokes it, or\n- Made attributes to you because they applied through your partner channel or through one of your agents.\n\n`GET /v1/partners/customers` lists both, newest first, and `GET /v1/partners/customers/{user_id}` returns either one's summary, with their `application_status`. An attributed customer with no active link shows `linked_at: null`. Asking for anyone else returns `PRTN_0013`.\n\nCard data needs an active link. For an attributed customer with no active link, the balance, transactions, payments, and rewards endpoints return `PRTN_0025`, and you get no webhooks about them. To read their card, start a `kind: \"login\"` session: once they sign in and you exchange the `public_token`, they are linked.\n\n## Endpoints\n\n| Endpoint | Returns |\n|---|---|\n| `GET /v1/partners/customers?limit=20&offset=0` | `customers`, `total`, `limit`, `offset` |\n| `GET /v1/partners/customers/{user_id}` | One customer summary |\n| `GET /v1/partners/customers/{user_id}/balance` | Card balance, available credit, credit limit, minimum payment due, and payment due date |\n| `GET /v1/partners/customers/{user_id}/transactions?limit=20&offset=0` | Card transactions: pending first, then newest first |\n| `GET /v1/partners/customers/{user_id}/payments?limit=20&offset=0` | Upcoming scheduled payments that have not been submitted yet |\n| `GET /v1/partners/customers/{user_id}/rewards` | Points balance and earn breakdown since the last statement |\n\n`limit` is 1 to 100 (default 20). `offset` is 0 to 100000 (default 0); a larger value is a 422.\n\nTransactions leave out voided and zero-amount rows. Payments the customer already made show up in transactions, not in `/payments`.\n\nThe balance, transactions, payments, and rewards endpoints return `PRTN_0025` for a customer with no active link (see above), and `PRTN_0014` while a linked customer has no card account yet, for example while their application is still in review. A customer with an account but no transactions gets an empty `data` list.\n\n## Application status\n\nEvery customer summary has `application_status`, where the customer's Made Card application stands:\n\n| `application_status` | Meaning |\n|---|---|\n| `not_started` | No application yet, or the last one closed without a decline: withdrawn, its offer expired, or Made could not process it. The customer can apply |\n| `in_progress` | The customer started an application and has not submitted it. After `onSuccess` for an `apply` session, this is the status until the customer submits |\n| `in_review` | Submitted, and Made has not decided yet. This can include identity checks or a manual review |\n| `approved` | Made approved it. `account_id` stays `null` until the customer accepts the offer and Made opens the card account |\n| `declined` | The application ended without a card: Made declined it, or the customer turned the offer down |\n\nOnce the customer has a card account, `application_status` is `approved` and `account_status` describes the account itself, for example `ACTIVE`.\n\nMade records a web application only when the customer submits it, so a customer attributed to you who signed up for Made without an `apply` session, and has not submitted an application yet, shows `not_started`.\n\n## Amounts and IDs\n\nAmounts in the balance, transactions, and payments responses are JSON numbers in US dollars, such as `42.5`, never strings. A transaction `id` is an integer. `user_id`, `account_id`, `card_id`, and a payment `id` are UUID strings.\n\n## Balance\n\n`GET /v1/partners/customers/{user_id}/balance` reads the card from Made's card processor. Made keeps each customer's answer for up to 30 seconds, so values can be that far behind, for example right after a purchase; `as_of` says when Made read them. They are the values the customer sees on Made Card's Home:\n\n| Field | Meaning |\n|---|---|\n| `current_balance` | Current card balance in US dollars, the Balance on Home |\n| `available_credit` | Credit the customer can still spend, in US dollars, the Available Credit on Home |\n| `credit_limit` | The card's credit limit in US dollars |\n| `minimum_payment_due` | Minimum payment due in US dollars, the Minimum due on Home. It already counts payments made this cycle. `null` until the card processor has a minimum due, for example before the first statement |\n| `payment_due_date` | When the next payment is due (`YYYY-MM-DD`), the Payment due date on Home. When the card processor has no due date yet, for example before the first statement, or its due date has passed, Made works out the next one from the customer's billing day, as Home does. `null` only when Made has neither |\n| `as_of` | When Made read these values, in UTC |\n\n```json\n{\n  \"error\": null,\n  \"message\": \"Balance retrieved\",\n  \"data\": {\n    \"current_balance\": 1250.4,\n    \"available_credit\": 3749.6,\n    \"credit_limit\": 5000.0,\n    \"minimum_payment_due\": 35.0,\n    \"payment_due_date\": \"2026-10-20\",\n    \"as_of\": \"2026-09-23T22:41:52.120000Z\"\n  }\n}\n```\n\nA `current_balance` of `0.0` is a real value, for example on a new card with no purchases yet. Show a `null` `minimum_payment_due` as not available yet, not as $0.\n\nMade never fills in zeros for a balance it could not read. If the card processor fails, does not answer within about 10 seconds, or answers without a balance, the call returns HTTP 503 with `PRTN_0032`. Show the balance as temporarily unavailable, keep showing the customer's other data, and try again later with backoff.\n\nYou can call `/balance` 20 times a minute for each customer, so many customers can open their pages at the same time. Made also caps how often one partner makes it read the card processor, at 1,200 reads a minute across all its customers; answers from the 30-second window don't count toward that. Past either limit, the call returns HTTP 429 with `PRTN_0036` and a `Retry-After` header with the seconds until the minute resets. Read the balance when the customer opens a page that shows it, not on a timer for every customer.\n\nThere is no partner endpoint to create a payment, transfer money, or change a card.\n\nTo follow an application, subscribe to webhooks (see Webhooks) and read `application_status` in the customer summary when an event arrives. Testing and go-live explains polling as the fallback.\n\n# Webhooks\n\nWebhooks tell your server when something changed for one of your customers, so you don't have to poll. A webhook only says \"look again\": it carries ids and the application status, never a name, email, balance or transaction. Read what you need through the API, which stays the source of truth.\n\nWebhooks are live in Test mode and in Live mode: manage them in the partner portal at `https://partners.madecard.com`, or with the API (see Managing endpoints with the API). As a fallback, follow applications by polling as described in Testing and go-live.\n\n## Events\n\n| Type | When | `data` |\n|---|---|---|\n| `link.completed` | You exchanged a customer's `public_token` for their `link_` token | `user_id`, `link_session_id`, `kind` (`apply` or `login`) |\n| `link.revoked` | The customer's link ended: Made revoked it, the customer disconnected you in Made Card, or Made removed your partner account. The `link_` token no longer works | `user_id`, `reason` (`admin`, `customer` or `partner_removed`) |\n| `application.status_changed` | The customer's `application_status` changed, as `GET /v1/partners/customers/{user_id}` returns it | `user_id`, `status` (`not_started`, `in_progress`, `in_review`, `approved` or `declined`) |\n| `account.opened` | The customer's Made Card account exists: `account_id` is set, and card reads stop answering `PRTN_0014` | `user_id`, `account_status` (for example `ACTIVE`) |\n| `launch.redeemed` | The customer opened Made Card from one of your launch URLs | `user_id`, `launch_session_id` |\n| `ping` | Only when you send a test event, from the portal or the API | `message` |\n\nYou hear about a customer only while they have an active link with you. Customers attributed to you without a link are in your customer list, but send no webhooks (see Customer data). Each endpoint receives only the types it subscribes to.\n\n## When a link is revoked\n\n`link.revoked` means the customer's `link_` token no longer works, whatever the `reason`:\n\n| `reason` | Who ended it |\n|---|---|\n| `customer` | The customer disconnected you from Made Card's account settings |\n| `admin` | Made revoked the link |\n| `partner_removed` | Made removed your partner account (every customer's link ends at once) |\n\nWhen you get it:\n\n- Stop calling the API with that customer's `link_` token, and delete it from your store. Every call with it now answers `PRTN_0010`.\n- Stop creating launch URLs for them, and hide the card data you showed from Made.\n- Show them as not connected, with a \"Reconnect Made Card\" action. When the customer chooses it, start a `kind: \"login\"` link session (see Apply and link), and exchange the new `public_token` as the first time. Don't reconnect on your own: after `customer`, they chose to disconnect.\n\n## What Made sends\n\nMade sends an HTTPS `POST` to your endpoint with a JSON body:\n\n```json\n{\n  \"id\": \"evt_0123456789abcdef01234567\",\n  \"type\": \"application.status_changed\",\n  \"created_at\": \"2026-09-25T14:00:00.000Z\",\n  \"api_version\": \"2026-09\",\n  \"livemode\": true,\n  \"data\": { \"user_id\": \"8b4e3a52-6f0e-4a8e-9a3c-2f5d1c7b9e10\", \"status\": \"approved\" }\n}\n```\n\n`livemode` is `true` on every event from Live mode and `false` on every event from Test mode, test events included, so one receiver can tell them apart. Each mode signs with its own endpoints' secrets.\n\nThe body on the wire is compact JSON with no spaces. Headers:\n\n| Header | Value |\n|---|---|\n| `Content-Type` | `application/json` |\n| `User-Agent` | `Made-Webhooks/1` |\n| `Made-Webhook-Id` | The event `id` |\n| `Made-Signature` | `t=<unix seconds>,v1=<signature>`, with a second `v1=` during a secret rotation |\n\nAnswer with any 2xx within 10 seconds. Do the work after you answer: store the event, return `200` or `204`, then re-read the customer. Made doesn't follow redirects, so a `3xx` counts as a failure.\n\n## Verify the signature\n\nEvery request is signed with your endpoint's signing secret (`whsec_...`), shown once in the portal when you create the endpoint or rotate its secret. Keep it in your secret store, like your `client_secret`.\n\n- `v1` is the lowercase hex HMAC-SHA256 of the UTF-8 string `<t>.<raw body>`.\n- The HMAC key is the whole signing secret exactly as the portal shows it, `whsec_` prefix included, as UTF-8 bytes. Don't base64-decode it or strip the prefix.\n- The raw body is exactly the bytes Made sent. Verify before you parse the JSON: re-serializing it changes the bytes and breaks the signature.\n- Accept the request when any `v1` matches, and `t` is within 5 minutes of your clock. Compare in constant time.\n- Anything else is a failure, never an error: a missing header, more than one `t`, a `t` that isn't ASCII digits, or no `v1` of exactly 64 lowercase hex characters. Your verifier should return false for these, not throw.\n\nNode (Express):\n\n```js\nimport crypto from \"node:crypto\";\nimport express from \"express\";\n\nfunction verifyMadeSignature(rawBody, header, secret, toleranceSeconds = 300) {\n  if (!header) return false;\n  const parts = header.split(\",\").map((part) => {\n    const index = part.indexOf(\"=\");\n    return [part.slice(0, index), part.slice(index + 1)];\n  });\n  const timestamps = parts.filter(([key]) => key === \"t\").map(([, value]) => value);\n  if (timestamps.length !== 1 || !/^\\d+$/.test(timestamps[0])) return false;\n  const timestamp = timestamps[0];\n  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > toleranceSeconds) return false;\n  const expected = Buffer.from(\n    crypto.createHmac(\"sha256\", secret).update(`${timestamp}.`).update(rawBody).digest(\"hex\")\n  );\n  return parts.some(([key, value]) => {\n    const candidate = Buffer.from(value);\n    return key === \"v1\" && candidate.length === expected.length && crypto.timingSafeEqual(candidate, expected);\n  });\n}\n\nconst app = express();\n\n// express.raw keeps the body as the exact bytes Made sent.\napp.post(\"/made/webhooks\", express.raw({ type: \"application/json\" }), (req, res) => {\n  if (!verifyMadeSignature(req.body, req.get(\"Made-Signature\"), process.env.MADE_WEBHOOK_SECRET)) {\n    return res.status(400).send(\"invalid signature\");\n  }\n  const event = JSON.parse(req.body.toString(\"utf8\"));\n  // Skip event.id if you have seen it, store it, answer, then re-read the customer through the API.\n  res.sendStatus(204);\n});\n```\n\nPython:\n\n```python\nimport hashlib\nimport hmac\nimport re\nimport time\n\n\ndef verify_made_signature(raw_body: bytes, header: str, secret: str, tolerance_seconds: int = 300) -> bool:\n    try:\n        parts = [part.split(\"=\", 1) for part in header.split(\",\") if \"=\" in part]\n        timestamps = [value for key, value in parts if key == \"t\"]\n        # Exactly one t, in ASCII digits only (str.isdigit() also accepts digits from other scripts).\n        if len(timestamps) != 1 or not re.fullmatch(r\"[0-9]+\", timestamps[0]):\n            return False\n        timestamp = timestamps[0]\n        if abs(time.time() - int(timestamp)) > tolerance_seconds:\n            return False\n        # Only v1 values shaped like a signature: exactly 64 lowercase hex characters.\n        candidates = [value for key, value in parts if key == \"v1\" and re.fullmatch(r\"[0-9a-f]{64}\", value)]\n    except (AttributeError, TypeError, ValueError, OverflowError):\n        return False  # a missing or malformed header\n    expected = hmac.new(secret.encode(\"utf-8\"), f\"{timestamp}.\".encode(\"ascii\") + raw_body, hashlib.sha256).hexdigest()\n    return any(hmac.compare_digest(value.encode(\"ascii\"), expected.encode(\"ascii\")) for value in candidates)\n```\n\nIn Flask, pass `request.get_data()`; in Django, `request.body`; in FastAPI, `await request.body()`. Never the parsed JSON.\n\n### Test vector\n\nCheck your verifier against these values (turn the 5-minute check off for this test, since `t` is in the past):\n\n- Signing secret: `whsec_test_current`\n- `t`: `1790344800`\n- Raw body (exactly these bytes, no trailing newline):\n\n```text\n{\"id\":\"evt_0123456789abcdef01234567\",\"type\":\"ping\",\"created_at\":\"2026-09-25T14:00:00.000Z\",\"api_version\":\"2026-09\",\"livemode\":false,\"data\":{\"message\":\"Test event from Made\"}}\n```\n\n- `Made-Signature: t=1790344800,v1=8267249fef38c46e5197a9fb429457aa8b97264a95733415e7bc45cb275f8cff`\n- During a rotation, with previous secret `whsec_test_previous`: `Made-Signature: t=1790344800,v1=8267249fef38c46e5197a9fb429457aa8b97264a95733415e7bc45cb275f8cff,v1=ee7ae1fa74e7990693f493e3412f9e51fac78e0e948042fea8667c85c94044b1`\n\n## Rotating the secret\n\nRotate an endpoint's secret in the portal when it may have leaked, or on your own schedule. The new secret is shown once. For the next 24 hours every request carries two `v1` signatures, one with the new secret and one with the previous one, so deploy the new secret any time within that day with no downtime. After 24 hours only the new secret signs.\n\n## Retries and deduplication\n\n- Delivery is at least once, in no guaranteed order. Deduplicate on the event `id` (the same id is also in `Made-Webhook-Id`), and treat the API as the truth: after `application.status_changed`, read `GET /v1/partners/customers/{user_id}` rather than trusting the order events arrived in.\n- A request that times out, fails to connect, or gets anything but a 2xx is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 6 hours and 12 hours, then every 24 hours, for up to 3 days after the event.\n- An endpoint whose deliveries keep failing for those 3 days is marked `failing`. It is `active` again once every delivery still waiting for a retry has gone through; a success while others are still being retried leaves it `failing`. If it is still failing 3 days later, Made disables it (`disabled_reason: \"failing\"`). Turn it back on in the portal once it works, and replay what it missed from its delivery log.\n- Made emails your team both times, in Test mode and in Live mode alike: once when the endpoint becomes `failing` (at most once a day for one endpoint), and once if Made disables it. The email goes to your active owners; if you have none, to your active admins, and if you have none of those either, to your active developers. It never goes to viewers or to invited members. The email links to the endpoint's page in the portal. Disabling an endpoint yourself sends no email.\n\n## Your endpoint\n\n- A public `https` URL on port 443 or 8443, with no user name or password in it. Made refuses hosts that resolve to private, loopback, link-local or other internal addresses, when you save the endpoint and again on every delivery.\n- Made reads at most the first 1 KB of your answer and ignores it. Don't put anything in it you need Made to see.\n- Made's servers have no fixed IP addresses: check the signature, not the source address.\n\n## Managing endpoints in the portal\n\nOwners, admins and developers manage endpoints on the portal's Webhooks page; viewers can't see it. Test mode and Live mode each have their own endpoints and secrets, so switch modes in the portal to manage each: a Test mode endpoint never receives Live events. You can do all of it with the API too (next section).\n\n- Add up to 5 endpoints, each with the event types it wants. The signing secret is shown once, when you add the endpoint.\n- Change an endpoint's event types or description, disable it, or turn it back on. To change the URL, add a new endpoint and delete the old one.\n- Rotate the signing secret (the previous one keeps signing for 24 hours).\n- Send a test event: a `ping`, or a sample of any type with made-up ids and `\"test\": true` in `data`, to that endpoint only.\n- Read the delivery log (each delivery's status, response code, time and a short error, never your response body), and replay any delivery: the same event, with the same `id`, sent again.\n- Made keeps delivery history for 30 days, counted from when each event was created. After that the event and its deliveries are deleted, so replay what you need before then. Two things stay longer: an event with a delivery still being sent or retried, and the event behind each endpoint's last delivery. A deleted endpoint's history is removed 30 days after you delete it.\n\nThe Activity page's audit lists every endpoint change, test event and replay made in the portal, and who made it.\n\n### Paused by Made\n\nMade can pause an endpoint, for example while it looks into something with you. The portal shows it as Paused by Made, and the endpoint has `status: \"disabled\"` and `disabled_reason: \"admin\"`: it receives nothing, and test events and replays sent to it are marked failed without a request. Only Made can turn it back on: turning it on yourself answers `PRTN_0073`, so contact Made at partners@madecard.com. You can still change its event types or description, rotate its secret, or delete it. When Made turns it back on it is `active`, with its failure count started again.\n\n## Managing endpoints with the API\n\nTeams that automate can manage endpoints with the partner JWT from `POST /v1/partners/auth` instead of the portal. The routes are in the API reference under Webhooks, with the same bodies, answers, limits and errors as the portal:\n\n| Method | Path | What it does |\n|---|---|---|\n| `GET` | `/v1/partners/webhooks` | Your endpoints, the event types, and the endpoint limit |\n| `POST` | `/v1/partners/webhooks` | Add an endpoint: `{url, events, description?}`. Answers 201 with `signing_secret`, shown only here |\n| `PATCH` | `/v1/partners/webhooks/{endpoint_id}` | Change `events`, `description` or `status` (`active` or `disabled`) |\n| `DELETE` | `/v1/partners/webhooks/{endpoint_id}` | Delete it |\n| `POST` | `/v1/partners/webhooks/{endpoint_id}/rotate-secret` | A new `signing_secret`; the previous one keeps signing for 24 hours |\n| `POST` | `/v1/partners/webhooks/{endpoint_id}/test` | Queue a test event: `{event_type?}`, `ping` by default |\n| `GET` | `/v1/partners/webhooks/{endpoint_id}/deliveries` | The delivery log, newest first (`limit`, `offset`, `status`) |\n| `POST` | `/v1/partners/webhooks/{endpoint_id}/deliveries/{delivery_id}/replay` | Send a delivery's event again |\n\n```bash\ncurl -X POST \"$MADE_API/v1/partners/webhooks\"   -H \"Authorization: Bearer $PARTNER_JWT\"   -H \"Content-Type: application/json\"   -d '{\"url\": \"https://example.com/made/webhooks\", \"events\": [\"link.completed\", \"link.revoked\", \"application.status_changed\"]}'\n```\n\n- Store the `signing_secret` from the answer at once; it isn't shown again (rotate the secret if you lose it).\n- The portal and the API spend the same budgets (10 endpoint changes a minute and 100 a day, test events and replays as in Errors), so a script can't lock your team out of the portal for longer than those windows, and the other way round.\n- Every call needs an active partner account (`PRTN_0015` when Made disabled it). Where Made has turned webhooks off, these routes answer 404.\n- An endpoint added with the API has `created_by: null`. Made records each change with the API key that made it; the portal's Activity page lists only the changes made in the portal.\n\n# Errors\n\nEvery response uses the same envelope. On success `error` is `null` and `data` holds the result. `POST /v1/partners/link/sessions` and `POST /v1/partners/sessions/launch` answer HTTP 201; every other success is HTTP 200.\n\nOn failure `error` is a stable code and `message` is safe to log:\n\n```json\n{ \"error\": \"PRTN_0010\", \"message\": \"This customer connection is invalid or has been revoked\" }\n```\n\nBranch on `error`, not on `message`.\n\nRequest validation failures return HTTP 422. There `error` is a list with one entry per problem, `field` is the top-level request field, and each message names the exact value:\n\n```json\n{\n  \"error\": [{ \"field\": \"prefill\", \"message\": \"Value error, phone must be a 10-digit US number, for example 7575550123\" }],\n  \"message\": \"• prefill: Value error, phone must be a 10-digit US number, for example 7575550123\",\n  \"data\": null\n}\n```\n\nThese are every error code the partner API returns.\n\n## Auth errors\n\n| HTTP | Code | Meaning | What to do |\n|---|---|---|---|\n| 401 | `AUTH_0013` | No `Authorization: Bearer` header | Send the partner JWT |\n| 401 | `AUTH_0010` / `AUTH_0011` / `AUTH_0012` | Partner JWT expired or invalid | Call `POST /v1/partners/auth` again |\n| 401 | `PRTN_0001` | The `Authorization` header is not a Bearer token | Send `Authorization: Bearer <partner JWT>` |\n| 401 | `PRTN_0003` | The partner JWT could not be read, or the API key it was issued with has been rotated or revoked since | Call `POST /v1/partners/auth` again |\n| 401 | `PRTN_0002` | Wrong `client_id` or `client_secret` | Check your `client_id` and `client_secret` (a rotated secret replaces the old one at once), and use this mode's keys: Test keys work only in Test mode, and Live keys only in Live mode |\n| 401 | `PRTN_0106` | The `client_id` or `client_secret` belongs to the other mode: a `pk_test_` or `sk_test_` key on the Live API, or a `pk_live_` or `sk_live_` key on the Test API. The message and `data.api_base` name the API base the key belongs to, and `data.key_livemode` says whether it is a Live key. It never counts toward the lockout | Send the key to `data.api_base`, or use this mode's key here |\n| 429 | `PRTN_0064` | Too many `POST /v1/partners/auth` calls in the last minute from your IP or for your `client_id`, or too many wrong secrets for your `client_id` from your IP, which locks that IP out of it for 15 minutes | Wait the number of seconds in the `Retry-After` header. Cache the partner JWT for its lifetime instead of authenticating on every call |\n| 503 | `PRTN_0065` | Made could not check its authentication limits | Retry with backoff |\n| 403 | `PRTN_0004` | The token is not a partner JWT | Use the token from `/v1/partners/auth` |\n| 403 | `PRTN_0015` | Your partner account is disabled | Contact Made |\n| 404 | `PRTN_0005` | Partner not found | Contact Made |\n\n## Host errors\n\n| HTTP | Code | Meaning | What to do |\n|---|---|---|---|\n| 404 | `PRTN_0081` | The partner API host doesn't serve this path: it isn't a partner API route or a docs page, or the method is wrong | Check the method and path against the API reference |\n| 404 | `PRTN_0082` | The partner API moved to its own host, and this host no longer serves it. The message names the base to use | Change your API base URL to the one in the message |\n\n## Link and launch errors\n\n| HTTP | Code | Meaning | What to do |\n|---|---|---|---|\n| 400 | `PRTN_0009` | `public_token` invalid, already used, or expired | See Retries in Testing and go-live |\n| 403 | `PRTN_0037` | Made no longer lets this customer sign in, for example after failed identity checks, so it can't connect them | Don't retry, and don't start a new session for them. The customer can contact Made support |\n| 401 | `PRTN_0010` | Customer `access_token` invalid or revoked | Start a `kind: \"login\"` session to reconnect |\n| 400 | `PRTN_0011` | `target_path` is not allowed | Use one of the five launch paths |\n| 400 | `PRTN_0030` | `partner_agent_id` is not one of your loan officers or agents | Send an ID Made gave you, or `null` |\n| 400 | `PRTN_0031` | `lock_fields` has a name Made cannot lock | Use `first_name`, `last_name`, `email`, or `address` |\n\n## In the Made window\n\nSome problems happen while the customer is in the Made window. They never reach your server as HTTP errors. The window tells the customer, and Made Link calls your `onExit`:\n\n| Made reports | Your page gets | Cause |\n|---|---|---|\n| `PRTN_0027` | `ORIGIN_NOT_ALLOWED` | The page that opened Made Link is not one of your allowed origins, or Made can't tell which page opened the Made window (open it with Made Link). Add the origin in the partner portal |\n| `PRTN_0008` | `IDENTITY_MISMATCH` | The customer's Made profile does not match a field you locked. Start a new link session |\n| `PRTN_0006` / `PRTN_0007` | `SESSION_EXPIRED` | The link session expired, was already used, or was not found |\n| `PRTN_0037` | `ACCOUNT_UNAVAILABLE` with Made Link 1.1.0 | Made no longer lets this customer sign in, so it can't connect them. Don't retry |\n| `PRTN_0063` | `REAUTH_REQUIRED` with Made Link 1.2.0, if the customer leaves instead of signing in again | The Made sign-in in the window is more than 15 minutes old, or came from a launch URL. The Made window asks the customer to sign in again, and the same `link_url` then completes |\n\nMade connects a customer only with a Made sign-in from the last 15 minutes that isn't a launch session, so a Made session left open in the browser, or one opened from any partner's launch URL, never links a customer on its own. On Made Link 1.0.0 and 1.1.0, a customer who closes the window instead of signing in again gives `USER_CLOSED`, or `onExit(null)` from the window's own Close button.\n\nOn Made Link 1.0.0, `PRTN_0037` in the Made window gives no `onSuccess` and no error code: your page gets `onExit(null)` when the customer closes the window from inside, or `USER_CLOSED` when they close it some other way. Load 1.1.0 to get `ACCOUNT_UNAVAILABLE` (see Web SDK).\n\n## Customer errors\n\n| HTTP | Code | Meaning | What to do |\n|---|---|---|---|\n| 404 | `PRTN_0013` | This customer is not yours | Check the `user_id` |\n| 404 | `PRTN_0025` | The customer is attributed to you but has no active link with you, so Made doesn't share their card data (balance, transactions, payments, rewards) | Start a `kind: \"login\"` session to link them, then exchange the `public_token` |\n| 404 | `PRTN_0014` | The customer has no card account yet | Try again after they are approved |\n| 503 | `PRTN_0032` | The card processor failed or did not answer in time, so Made could not read the balance | Show the balance as temporarily unavailable, not $0, and retry with backoff |\n| 429 | `PRTN_0036` | More than 20 balance calls for one customer in the current minute, or more than 1,200 card processor reads across your customers | Wait the number of seconds in the `Retry-After` header, then retry. Other reads are not limited |\n\n## Webhook errors\n\nFrom the `/v1/partners/webhooks` routes (and the same codes in the partner portal's Webhooks page):\n\n| HTTP | Code | Meaning | What to do |\n|---|---|---|---|\n| 404 | `PRTN_0055` | No webhook endpoint with this id for you: unknown, deleted, or another partner's | List your endpoints with `GET /v1/partners/webhooks` |\n| 422 | `PRTN_0056` | The URL isn't allowed: not `https` on port 443 or 8443, a user name or password in it, or a host that doesn't resolve or resolves to a private, loopback, link-local or other internal address | Use a public `https` URL. Made doesn't say which check failed |\n| 409 | `PRTN_0057` | You already have 5 endpoints | Delete one you no longer use, then add the new one |\n| 404 | `PRTN_0058` | No delivery with this id for this endpoint (or it's older than the 30 days Made keeps) | List the endpoint's deliveries and replay one of them |\n| 422 | `PRTN_0059` | An event type Made doesn't send. Endpoints can't subscribe to `ping`, which is only for test events | Use the types `GET /v1/partners/webhooks` lists |\n| 429 | `PRTN_0060` | Too many endpoint changes (creates and updates, 10 a minute and 100 a day), test events (5 a minute per endpoint, 100 a day) or replays (10 a minute per endpoint, 500 a day). The portal and the API share these budgets | Wait the number of seconds in the `Retry-After` header |\n| 503 | `PRTN_0061` | Made couldn't count the call against its budget, so it refused it | Retry with backoff |\n| 503 | `PRTN_0062` | Made couldn't check the URL right now | Retry in a moment |\n| 409 | `PRTN_0073` | Made paused this endpoint (`disabled_reason: \"admin\"`), so only Made can turn it back on | Contact Made at partners@madecard.com. You can still change its event types or description, rotate its secret, or delete it |\n\n# Testing and go-live\n\n## Test mode and Live mode\n\nYou work in one partner portal, `https://partners.madecard.com`, with two modes and a switch between them:\n\n- **Test mode** runs against Made's test environment. Build and test your whole integration here, with Test keys. Nothing you do in Test mode reaches real customers or real cards.\n- **Live mode** is production: real customers and real cards, with Live keys.\n\nMade approves your application once, and you never apply again. Approval gives you the portal and Test mode. Live keys come later, when Made grants Live access (see Going live). Test and Live keys, origins, webhooks and customers are separate: a Test key never works in Live mode, and a Live key never works in Test mode. Environments lists each mode's base URLs.\n\n## Testing in Test mode\n\n- Apply as test customers you make up, never as real people. Test data lists what to type: an email of your own at `sandbox.madecard.com` and a phone number from 555-0100 to 555-0199, whose one-time codes Made's window shows on screen in Test mode, a test SSN of their own, a date of birth, and an income.\n- Test mode sends email only to addresses Made has allowed, so a code for your own team's address doesn't arrive, and Made's window shows codes only for test emails and phone numbers. Use a test email for every test customer, and follow decisions with webhooks or the API rather than email.\n- Identity and credit checks run in sandbox mode in Test mode, so made-up applicant details go through. Never enter a real person's SSN in Test mode.\n- Decisions in Test mode are simulated. Choose approve, decline, or random for your test customers on the portal's Testing tab (see Test data).\n- A declined test customer can't be used again: after a decline by the identity check, Made blocks their email and SSN, so they can't sign in to Made again, their launch URL shows Made's message, and a `login` session can't connect them. Try again with a new email, phone number, and SSN.\n- Add `http://localhost:<port>` to your Test-mode allowed origins to open Made Link from local development. That works in Test mode only: Live mode refuses `localhost` origins.\n- To see the whole flow on a working partner first, open Northstar Home Loans (demo) at https://madepartner.com, where one demo customer applies for Made Card and another already has a card. Anyone can open it, with no sign-in. **Try the Northstar demo** in the partner portal opens the same site with the visit labelled with your company.\n\n## Going live\n\n1. **Made approves your application.** You can sign in to the partner portal and build in Test mode. Live mode has no keys yet: they come with Live access.\n2. **Build and test in Test mode** until the whole flow works end to end. The Go-live checklist below lists what to check.\n3. **Request Live access.** In Live mode, open **Go live**, mark the hardening steps you've done, and choose **Request Live access**. Give your planned launch date, your expected volume, the production origins (the live sites that will open Made Link), and a note for Made. An owner or an admin can ask, and one request can be open at a time. The steps you mark are your own statement: Made reviews your Test-mode integration itself.\n4. **Made reviews it** and grants or declines it, with a note, and emails whoever asked. A declined request changes nothing: do what Made's note says, then ask again from Go live.\n5. **When Made grants Live access,** Made issues your Live client ID and secret, and adds your production origins to Live mode's allowed origins. The Live secret is shown once, on the sign-in screen right after the emailed code, to the first owner, admin or developer who signs in to the partner portal after the grant. Copy it to your server's secret store then. If you were signed in when Made granted it, sign out and sign in again with a new code to see it. If you missed it or lost it, rotate the secret on **API keys** in Live mode to get a new one, shown once; the old secret stops working at once. Your Test secret is separate and doesn't change.\n6. **Switch your server and pages to Live mode:** the Live API base URL with your Live keys, and Made Link from the Live host (see Environments).\n\n## Retries\n\n| Situation | What to do |\n|---|---|\n| `AUTH_0010`, `AUTH_0011`, `AUTH_0012`, or `PRTN_0003` | Get a new partner JWT and retry once |\n| `PRTN_0009` on `POST /v1/partners/link/token` | The `public_token` was used or is more than 10 minutes old. If you already stored the customer's `link_` token, use it. Otherwise start a `kind: \"login\"` session: the customer now has a Made login |\n| `PRTN_0010` | The customer's `link_` token was revoked (a `link.revoked` webhook tells you when). Start a `kind: \"login\"` session to reconnect them |\n| `SESSION_EXPIRED` in `onExit` | Create a new link session. Link sessions last 60 minutes and stop working once the customer is linked |\n| `IDENTITY_MISMATCH` in `onExit` | Create a new link session; the Made window won't try the old one again. Check the `prefill` you send, or lock fewer fields |\n| `PRTN_0037`, or `ACCOUNT_UNAVAILABLE` in `onExit` | Don't retry, and don't start a new session for that customer. Made won't connect them; they can contact Made support |\n| `PRTN_0036` from `/balance` | Wait the `Retry-After` seconds, then retry |\n| `PRTN_0032` from `/balance` | Show the balance as temporarily unavailable, not $0, and retry with backoff. There is no `Retry-After` |\n| HTTP 5xx or a network error | Retry with exponential backoff. Every read is safe to retry. Link sessions and launch URLs are cheap to create again |\n\nExchange each `public_token` once, and save the result before you answer the browser.\n\n## Rate limits\n\n| Endpoint | Limit | Over the limit |\n|---|---|---|\n| `GET /v1/partners/customers/{user_id}/balance` | 20 calls a minute for each customer. Across all your customers, up to 1,200 card processor reads a minute; answers Made kept from the last 30 seconds don't count | HTTP 429 with `PRTN_0036` and a `Retry-After` header in seconds |\n\nMade does not publish a limit for the other endpoints. Keep your traffic modest:\n\n- Reuse the partner JWT for its whole hour instead of calling `/v1/partners/auth` for every request.\n- Page through `GET /v1/partners/customers` with `limit` and `offset` instead of refetching everything.\n- Let webhooks tell you when a customer's status changes. Without them, check it when the customer visits, or at most every few minutes in the background.\n- Read a balance when a page shows it, not on a timer for every customer.\n- Treat HTTP 429 or 503 as temporary. On a 429, wait the `Retry-After` seconds; otherwise back off before retrying.\n\n## Checking application status\n\nFollow each application with webhooks, or polling as a fallback. Subscribe an endpoint to `application.status_changed` and `account.opened` (see Webhooks), and read the customer when an event arrives. Webhooks are on in Test mode and in Live mode. Whenever an event may have been missed, read `GET /v1/partners/customers/{user_id}` after `onSuccess`, when the customer visits, or at most every few minutes:\n\n- `application_status` says where the application stands: `not_started`, `in_progress`, `in_review`, `approved`, or `declined`. Customer data explains each value.\n- Right after `onSuccess` for an `apply` session, it is `in_progress`: the customer has a Made login and is still filling in the application in the Made window. It becomes `in_review` when they submit.\n- `account_id` is `null` until the customer accepts an approved offer and Made opens the card account. Then `account_id` and `account_status` (for example `ACTIVE`) appear.\n- Balance, transactions, payments, and rewards return `PRTN_0014` until then.\n\n## Go-live checklist\n\n- [ ] Request Live access from **Go live** in Live mode once your Test-mode integration works end to end. Your Live keys arrive when Made grants it, and they are separate from your Test keys.\n- [ ] Store the Live `client_secret` in your server's secret store as soon as the portal shows it, on the sign-in screen after Made's grant: it is shown once. If you lose it or it leaks, rotate it on **API keys** in Live mode; the old secret stops working at once.\n- [ ] Check Live mode's allowed origins. Made adds the production origins from your request when it grants Live access; change them on **Allowed origins** in Live mode.\n- [ ] Switch the API base URL to Live mode's, `https://api.madecard.com/v1` (see Environments), and load Made Link from `https://app.madecard.com`, pinned with its integrity hash: `made-link@1.2.0.js` (see Web SDK).\n- [ ] Handle every `onExit` code, `ACCOUNT_UNAVAILABLE` included, and every `PRTN_` code your calls can return.\n- [ ] Don't retry `PRTN_0037` or `ACCOUNT_UNAVAILABLE`. Tell the customer to contact Made support.\n- [ ] When you exchange a `public_token`, store the customer's `user_id` with the `link_` token. Every customer read needs it.\n- [ ] Store `link_` tokens encrypted, and never log them, the partner JWT, or the `client_secret`.\n- [ ] Follow each new application with webhooks (`application.status_changed`, `account.opened`), or polling as a fallback: read `application_status` from `GET /v1/partners/customers/{user_id}` when the customer visits, or at most every few minutes, until `account_id` is set.\n- [ ] If you use webhooks: verify `Made-Signature` on the raw body, deduplicate on the event `id`, answer 2xx within 10 seconds, and keep the signing secret in your secret store.\n- [ ] On `/balance`, show `PRTN_0032` as temporarily unavailable, never $0, and back off; on `PRTN_0036`, wait the `Retry-After` seconds.\n- [ ] Open launch URLs in a tab the click opened before your server call (see Launch URLs), with a link as the fallback.\n- [ ] Try the whole flow in Test mode with test customers (see Test data): apply, exchange, launch, reconnect with `kind: \"login\"`, a closed Made window, and both an approval and a decline from the Testing tab.\n- [ ] Questions: partners@madecard.com.\n\n# For AI agents\n\nThis page is written for coding agents building a Made Card partner integration. The machine-readable sources are:\n\n- `/partners/openapi.json`: the full partner API as OpenAPI 3.1\n- `/partners/llms.txt`: an index of these docs in llms.txt format\n- `/partners/llms-full.txt`: every guide in one markdown file\n- `/partners/docs/<page>.md`: any single guide as raw markdown\n\n## The integration in ten lines\n\n1. Server: `POST /v1/partners/auth` with `client_id` and `client_secret` (`pk_test_`/`sk_test_` in Test mode, `pk_live_`/`sk_live_` in Live mode; older keys without the prefix still work; `PRTN_0106` means the key belongs to the other mode's API base, named in `data.api_base`), cache `data.access_token` as the partner JWT, and send `Authorization: Bearer <partner JWT>` on every other call.\n2. Server: `POST /v1/partners/link/sessions` with `kind: \"apply\"` and the customer's `prefill`, return `data.link_url` to the page.\n3. Browser: load Made Link 1.2.0 (`/sdk/v1/made-link@1.2.0.js` with its integrity hash, see Web SDK), build `MadeLink.create({ linkUrl, onSuccess, onExit })` as soon as the page has `link_url`, and call `handler.open()` inside the click handler.\n4. Browser: in `onSuccess(publicToken)`, send `publicToken` to your server. Nothing else. In `onExit`, handle every code (see Rules).\n5. Server: `POST /v1/partners/link/token` with `public_token`, store `data.access_token` (`link_...`) and `data.user_id`.\n6. Server: follow the application with webhooks (`application.status_changed` and `account.opened`, see Webhooks), or poll `application_status` from `GET /v1/partners/customers/{user_id}` as a fallback, until `account_id` is set.\n7. Server: once `account_id` is set, read `/balance`, `/transactions`, `/payments`, and `/rewards` under `GET /v1/partners/customers/{user_id}`.\n8. Server, later visits: `POST /v1/partners/sessions/launch` with that `access_token` and a `target_path`, then open `data.launch_url` in a tab the click already opened (see Launch URLs).\n9. On `PRTN_0010`, start a `kind: \"login\"` session to reconnect that customer. To replace a stored `link_...` token, for example after a leak, call `POST /v1/partners/link/refresh`; the old token stops working at once.\n10. On any `AUTH_001x`, get a new partner JWT and retry once.\n\n## Rules\n\n- Never send `client_secret`, the partner JWT, or a `link_...` token to a browser or mobile app.\n- The customer `access_token` goes in the JSON body. It is never an `Authorization` header.\n- Use `kind: \"apply\"` for new customers. Use `kind: \"login\"` only to reconnect. Returning customers get a launch URL, not a new session.\n- Create the link session before the click and call `open()` synchronously in the click handler. Otherwise browsers block the window and `onExit` receives `POPUP_BLOCKED`.\n- The page that opens Made Link must be on one of the partner's allowed origins (see Apply and link). Otherwise `onExit` receives `ORIGIN_NOT_ALLOWED` and `onSuccess` never fires.\n- Handle every `onExit` code: `POPUP_BLOCKED`, `USER_CLOSED`, `ORIGIN_NOT_ALLOWED`, `IDENTITY_MISMATCH`, `SESSION_EXPIRED`, `ACCOUNT_UNAVAILABLE`, `REAUTH_REQUIRED`. `onExit(null)` means the customer left from inside the Made window.\n  - `SESSION_EXPIRED` and `IDENTITY_MISMATCH`: create a new link session; the Made window won't try the old one again. For `IDENTITY_MISMATCH`, check the `prefill` you send, or lock fewer fields.\n  - `USER_CLOSED` and `onExit(null)`: the customer left. Create a new link session for their next click, as for `SESSION_EXPIRED`; don't open the old `link_url` again. Made returns a session's `prefill` only for 15 minutes after the Made window first opens it.\n  - `ACCOUNT_UNAVAILABLE` (Made Link 1.1.0, from `PRTN_0037`): Made won't connect this customer. Tell them to contact Made support. Don't retry, and don't create a new session for them.\n  - `REAUTH_REQUIRED` (Made Link 1.2.0, from `PRTN_0063`): Made asked the customer to sign in again, and they left first. Let them retry with the same `link_url`.\n- Pin Made Link in Live mode: `https://app.madecard.com/sdk/v1/made-link@1.2.0.js` with `integrity=\"sha384-YUtON3zsoNkJvhG73apj2mu4SzmgoxZkkOPvu7m4Oo4sY995f2x4M3PZoyHwsymn\"` and `crossorigin=\"anonymous\"`.\n- To open a launch URL from a click that first calls your server, open a blank tab inside the click with `window.open(\"\", \"_blank\")`, without `noopener`, then set that tab's location to `launch_url`. If the browser refuses the tab, show a link instead. Launch URLs has the code.\n- For `kind: \"apply\"`, `onSuccess` fires once the customer has a Made login. The Made window stays open while they finish; check the application with `GET /v1/partners/customers/{user_id}`.\n- A launch URL opens `target_path` only once `account_id` is set. Before that it opens the customer's application.\n- A working reference integration, Northstar Home Loans (demo), runs in Test mode at https://madepartner.com. Anyone can open it, with no sign-in. Its developer page shows the code behind each step, but build from these docs and `/partners/openapi.json`: the site is a demo, not a specification.\n- `public_token` and `launch_url` are single use. Do not store or reuse them.\n- Branch on the `error` code, not the message.\n- On `PRTN_0032` from `/balance`, show the balance as temporarily unavailable, never as $0, keep showing the other reads, and retry later.\n- `/balance` allows 20 calls a minute for each customer, and values can be up to 30 seconds old (`as_of` says when). On `PRTN_0036`, wait the `Retry-After` seconds before the next balance call. Read balances when a page shows them, not on a timer.\n- Follow an application with webhooks, or with `application_status` from `GET /v1/partners/customers/{user_id}` as a fallback. Amounts are JSON numbers, and a transaction `id` is an integer.\n- A webhook only says \"look again\". Verify `Made-Signature` over the raw request body before parsing it (HMAC-SHA256 keyed with the whole `whsec_` secret, compared in constant time; Webhooks has a test vector), deduplicate on the event `id`, answer 2xx within 10 seconds, then re-read the customer through the API. Events can arrive more than once and in any order.\n- The webhook signing secret is a server secret, like `client_secret`: never send it to a browser or app, and never log it.\n- Nothing in this API moves money or changes an account. Do not look for a payment endpoint.\n\n## Prompt you can paste\n\n```text\nIntegrate my app with the Made Card Partners API.\nSpec: https://api-sandbox.madecard.com/partners/openapi.json\nGuide: https://api-sandbox.madecard.com/partners/llms-full.txt\nFollow the \"For AI agents\" rules exactly. Keep client_secret, the partner JWT,\nand every link_ token on the server. Build: a server route that creates a link\nsession, a page that loads Made Link 1.2.0 (pinned, with its integrity hash) and\nopens it from a button, a server route that exchanges the public_token and stores\nthe access_token and user_id, and a route that returns a launch URL.\nHandle every onExit code, including ACCOUNT_UNAVAILABLE.\nAdd a webhook route that verifies Made-Signature on the raw body, dedupes on\nthe event id, answers 204 fast, then re-reads the customer through the API.\nUse environment variables MADE_API_BASE, MADE_CLIENT_ID, MADE_CLIENT_SECRET,\nMADE_WEBHOOK_SECRET.\n```\n\n## Minimal Node server\n\n```js\nimport express from \"express\";\n\nconst API = process.env.MADE_API_BASE; // https://api-sandbox.madecard.com/v1\nconst app = express();\napp.use(express.json());\n\nlet jwt = null;\nlet jwtExpiresAt = 0;\n\nasync function partnerJwt() {\n  if (jwt && Date.now() < jwtExpiresAt - 60_000) return jwt;\n  const res = await fetch(`${API}/partners/auth`, {\n    method: \"POST\",\n    headers: { \"Content-Type\": \"application/json\" },\n    body: JSON.stringify({ client_id: process.env.MADE_CLIENT_ID, client_secret: process.env.MADE_CLIENT_SECRET }),\n  });\n  const { data } = await res.json();\n  jwt = data.access_token;\n  jwtExpiresAt = Date.now() + data.expires_in * 1000;\n  return jwt;\n}\n\nasync function made(path, body) {\n  const res = await fetch(`${API}${path}`, {\n    method: \"POST\",\n    headers: { Authorization: `Bearer ${await partnerJwt()}`, \"Content-Type\": \"application/json\" },\n    body: JSON.stringify(body),\n  });\n  const payload = await res.json();\n  if (!res.ok) throw Object.assign(new Error(payload.message), { code: payload.error, status: res.status });\n  return payload.data;\n}\n\napp.post(\"/made/link-session\", async (req, res) => {\n  const session = await made(\"/partners/link/sessions\", { kind: \"apply\", prefill: req.body.prefill ?? {} });\n  res.json({ link_url: session.link_url });\n});\n\napp.post(\"/made/exchange\", async (req, res) => {\n  const link = await made(\"/partners/link/token\", { public_token: req.body.public_token });\n  // Save link.access_token and link.user_id with your customer record here.\n  res.json({ connected: true });\n});\n\napp.post(\"/made/launch\", async (req, res) => {\n  const accessToken = \"link_...\"; // Load the stored token for the signed-in customer.\n  const launch = await made(\"/partners/sessions/launch\", { access_token: accessToken, target_path: \"/dashboard/home\" });\n  res.json({ launch_url: launch.launch_url });\n});\n\napp.listen(3000);\n```\n","contact":{"name":"Made Card Partner Support","email":"partners@madecard.com"},"version":"1.0.0"},"paths":{"/v1/partners/auth":{"post":{"tags":["Authentication"],"summary":"Authenticate partner","description":"Exchange client_id and client_secret for a partner JWT. Send that JWT as Authorization: Bearer on every other partner API. Each client IP can make 60 calls a minute, and each client_id 30 a minute from addresses that haven't authenticated as it in the last day. 10 wrong secrets for a client_id from one IP within 10 minutes lock that IP out of that client_id for 15 minutes. Over any of these it returns PRTN_0064 with HTTP 429 and a Retry-After header in seconds. A key whose prefix names the other mode (pk_test_ or sk_test_ on the Live API, pk_live_ or sk_live_ on the Test API) gets PRTN_0106 at once, naming the API base that key belongs to; it never counts toward the lockout.","operationId":"authenticate_partner_v1_partners_auth_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerAuthRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_PartnerAuthResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"`PRTN_0002` Invalid partner client credentials; `PRTN_0106` POST /v1/partners/auth got a client_id (pk_live_ or pk_test_) or client_secret (sk_live_ or sk_test_) whose prefix names the other mode: a Test key on the Live API, or a Live key on the Test API. Refused before any lookup, and never counted toward the wrong-secret lockout. The message names the mode the key belongs to and that mode's API base","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0002":{"summary":"Invalid partner client credentials","value":{"error":"PRTN_0002","message":"Invalid client credentials"}},"PRTN_0106":{"summary":"POST /v1/partners/auth got a client_id (pk_live_ or pk_test_) or client_secret (sk_live_ or sk_test_) whose prefix names the other mode: a Test key on the Live API, or a Live key on the Test API. Refused before any lookup, and never counted toward the wrong-secret lockout. The message names the mode the key belongs to and that mode's API base","value":{"error":"PRTN_0106","message":"This is a {key_mode} mode key. Use it at {api_base}, or use your {host_mode} mode key here"}}}}}},"403":{"description":"`PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}}}}}},"429":{"description":"`PRTN_0064` Too many POST /v1/partners/auth calls for one client_id or from one client IP in the current minute, or too many wrong secrets for one client_id from one IP, which locks that pair out for a while","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0064":{"summary":"Too many POST /v1/partners/auth calls for one client_id or from one client IP in the current minute, or too many wrong secrets for one client_id from one IP, which locks that pair out for a while","value":{"error":"PRTN_0064","message":"Too many authentication attempts. Wait the number of seconds in the Retry-After header, then try again"}}}}},"headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}}},"503":{"description":"`PRTN_0065` Made could not check or count failed partner authentications because Redis failed, so it refused the call instead of allowing unlimited guesses","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0065":{"summary":"Made could not check or count failed partner authentications because Redis failed, so it refused the call instead of allowing unlimited guesses","value":{"error":"PRTN_0065","message":"Authentication is temporarily unavailable. Try again in a moment"}}}}}}}}},"/v1/partners/link/sessions":{"post":{"tags":["Apply and login"],"summary":"Start apply or login","description":"Create a Made-hosted apply page, or a login page for recovery or for an existing Made customer who is not linked to you yet. Open link_url with Made Link, the web SDK, within 60 minutes. Return visits use POST /v1/partners/sessions/launch, not this endpoint.","operationId":"create_link_session_v1_partners_link_sessions_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLinkSessionRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_LinkSessionResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"400":{"description":"`PRTN_0031` lock_fields contains a name Made cannot lock; `PRTN_0030` partner_agent_id is not one of this partner's loan officers or agents","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0031":{"summary":"lock_fields contains a name Made cannot lock","value":{"error":"PRTN_0031","message":"lock_fields accepts only first_name, last_name, email, and address"}},"PRTN_0030":{"summary":"partner_agent_id is not one of this partner's loan officers or agents","value":{"error":"PRTN_0030","message":"partner_agent_id is not one of your loan officers or agents. Leave it null unless Made gave you the ID"}}}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/link/token":{"post":{"tags":["Apply and login"],"summary":"Exchange public token","description":"Turn the one-time public_token from the Made Link onSuccess callback into a per-customer access_token (link_...). Store it on your server. It stays valid until Made revokes it, and linking the same customer again replaces it. It is not a user JWT and cannot create payments.","operationId":"exchange_public_token_v1_partners_link_token_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ExchangePublicTokenRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_PartnerUserAccessTokenResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"400":{"description":"`PRTN_0009` Partner public token is invalid, used, or expired","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0009":{"summary":"Partner public token is invalid, used, or expired","value":{"error":"PRTN_0009","message":"This connection token is invalid or has expired"}}}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled; `PRTN_0037` The customer is blocked, disabled, or deleted, so Made does not let them sign in or connect to a partner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}},"PRTN_0037":{"summary":"The customer is blocked, disabled, or deleted, so Made does not let them sign in or connect to a partner","value":{"error":"PRTN_0037","message":"This Made account can't be connected. The customer can contact Made support for help"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/link/refresh":{"post":{"tags":["Apply and login"],"summary":"Refresh customer access token","description":"Optional rotation. Issues a new link_ token for the same customer. The previous token stops working immediately. No Made UI and no customer password.","operationId":"refresh_access_token_v1_partners_link_refresh_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshAccessTokenRequest"}}},"required":true},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_PartnerUserAccessTokenResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token; `PRTN_0010` Partner user access token is invalid or revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}},"PRTN_0010":{"summary":"Partner user access token is invalid or revoked","value":{"error":"PRTN_0010","message":"This customer connection is invalid or has been revoked"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/sessions/launch":{"post":{"tags":["Open Made Card"],"summary":"Create launch URL","description":"Create a single-use URL, valid for 5 minutes, that opens Made Card already signed in from the stored link. The customer is not asked for a Made password. Allowed target_path values: /dashboard/home, /dashboard/payments, /dashboard/rewards, /dashboard/accounts, /dashboard/transactions.","operationId":"create_launch_session_v1_partners_sessions_launch_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateLaunchSessionRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_LaunchSessionResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"400":{"description":"`PRTN_0011` Requested Made Card launch path is not allowed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0011":{"summary":"Requested Made Card launch path is not allowed","value":{"error":"PRTN_0011","message":"That Made Card page cannot be opened from a partner"}}}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token; `PRTN_0010` Partner user access token is invalid or revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}},"PRTN_0010":{"summary":"Partner user access token is invalid or revoked","value":{"error":"PRTN_0010","message":"This customer connection is invalid or has been revoked"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/customers":{"get":{"tags":["Customers"],"summary":"List customers","description":"Read-only list, newest first, of Made customers with an active link to you or attributed to you through your partner channel or agents.","operationId":"list_customers_v1_partners_customers_get","parameters":[{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"exclusiveMinimum":0,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_PartnerCustomerListResponse_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/customers/{user_id}":{"get":{"tags":["Customers"],"summary":"Get customer","description":"Read-only summary for one of your customers, including where their Made Card application stands (application_status) and their card account once it opens.","operationId":"get_customer_v1_partners_customers__user_id__get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_PartnerCustomerSummary_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive; `PRTN_0013` Requested customer is not linked to this partner","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}},"PRTN_0013":{"summary":"Requested customer is not linked to this partner","value":{"error":"PRTN_0013","message":"Customer not found"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/customers/{user_id}/balance":{"get":{"tags":["Customers"],"summary":"Get customer balance","description":"Read-only card balance for one of your customers, read from the card processor: current balance, available credit, credit limit, minimum payment due, and payment due date, the same values Made Card's Home shows. Values can be up to 30 seconds old; as_of is when Made read them. If the card processor fails or does not answer within about 10 seconds, it returns PRTN_0032 with HTTP 503 instead of zeros. Retry later. You can make 20 balance calls a minute for each customer; over that it returns PRTN_0036 with HTTP 429 and a Retry-After header in seconds.","operationId":"get_customer_balance_v1_partners_customers__user_id__balance_get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_PartnerBalance_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive; `PRTN_0013` Requested customer is not linked to this partner; `PRTN_0014` Linked customer has no card account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}},"PRTN_0013":{"summary":"Requested customer is not linked to this partner","value":{"error":"PRTN_0013","message":"Customer not found"}},"PRTN_0014":{"summary":"Linked customer has no card account","value":{"error":"PRTN_0014","message":"This customer does not have a card account yet"}}}}}},"429":{"description":"`PRTN_0036` Partner went over a balance limit for the current minute, for one customer or across all its customers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0036":{"summary":"Partner went over a balance limit for the current minute, for one customer or across all its customers","value":{"error":"PRTN_0036","message":"Too many balance requests in the last minute. Wait the number of seconds in the Retry-After header, then try again"}}}}},"headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}}},"503":{"description":"`PRTN_0032` The card processor failed, did not answer in time, or answered without a balance, so Made could not read the balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0032":{"summary":"The card processor failed, did not answer in time, or answered without a balance, so Made could not read the balance","value":{"error":"PRTN_0032","message":"Balance temporarily unavailable. Try again in a moment"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/customers/{user_id}/transactions":{"get":{"tags":["Customers"],"summary":"List customer transactions","description":"Read-only card transactions for one of your customers: pending first, then newest first. Voided and zero-amount rows are left out. Returns an empty list when there are none. Amounts are JSON numbers in US dollars, and each transaction id is an integer.","operationId":"get_customer_transactions_v1_partners_customers__user_id__transactions_get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"exclusiveMinimum":0,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Card transactions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_list_PartnerTransaction__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive; `PRTN_0013` Requested customer is not linked to this partner; `PRTN_0014` Linked customer has no card account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}},"PRTN_0013":{"summary":"Requested customer is not linked to this partner","value":{"error":"PRTN_0013","message":"Customer not found"}},"PRTN_0014":{"summary":"Linked customer has no card account","value":{"error":"PRTN_0014","message":"This customer does not have a card account yet"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/customers/{user_id}/payments":{"get":{"tags":["Customers"],"summary":"List scheduled payments","description":"Read-only upcoming scheduled payments for one of your customers that have not been submitted yet. Payments already made appear in transactions. There is no partner API to trigger a payment. Amounts are JSON numbers in US dollars.","operationId":"get_customer_payments_v1_partners_customers__user_id__payments_get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"exclusiveMinimum":0,"default":20,"title":"Limit"}},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":0,"default":0,"title":"Offset"}}],"responses":{"200":{"description":"Scheduled payments","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_list_PartnerScheduledPayment__"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive; `PRTN_0013` Requested customer is not linked to this partner; `PRTN_0014` Linked customer has no card account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}},"PRTN_0013":{"summary":"Requested customer is not linked to this partner","value":{"error":"PRTN_0013","message":"Customer not found"}},"PRTN_0014":{"summary":"Linked customer has no card account","value":{"error":"PRTN_0014","message":"This customer does not have a card account yet"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/customers/{user_id}/rewards":{"get":{"tags":["Customers"],"summary":"Get customer rewards","description":"Read-only points balance and earn breakdown since the last statement for one of your customers.","operationId":"get_customer_rewards_v1_partners_customers__user_id__rewards_get","parameters":[{"name":"user_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"User Id"}}],"responses":{"200":{"description":"Rewards summary","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_PartnerRewardsSummary_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive; `PRTN_0013` Requested customer is not linked to this partner; `PRTN_0014` Linked customer has no card account","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}},"PRTN_0013":{"summary":"Requested customer is not linked to this partner","value":{"error":"PRTN_0013","message":"Customer not found"}},"PRTN_0014":{"summary":"Linked customer has no card account","value":{"error":"PRTN_0014","message":"This customer does not have a card account yet"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/webhooks":{"get":{"tags":["Webhooks"],"summary":"List webhook endpoints","description":"Send the partner JWT from POST /v1/partners/auth. The same endpoints your team manages in the partner portal, with the same limits: changes made here and in the portal share one budget. PRTN_0055 for an endpoint that isn't yours, or is deleted. Where Made has turned webhooks off, every webhooks route answers 404 like an unknown path. Your endpoints that aren't deleted, oldest first, each with its last delivery; the event types an endpoint can subscribe to (ping is only for test events); and the endpoint limit. Never a signing secret: secret_last4 only.","operationId":"list_webhooks_v1_partners_webhooks_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_WebhookEndpointList_"}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}}}}}},"409":{"description":"`PRTN_0057` The partner already has the most webhook endpoints allowed (5); deleted endpoints don't count","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0057":{"summary":"The partner already has the most webhook endpoints allowed (5); deleted endpoints don't count","value":{"error":"PRTN_0057","message":"You can have up to 5 webhook endpoints. Delete one to add another"}}}}}},"422":{"description":"Request validation failed (error is then a list of problems), or: `PRTN_0056` The webhook URL is refused: not https, credentials in it, a port that isn't allowed, a host that doesn't resolve, or a host that resolves to a private, loopback, link-local, metadata or other non-public address; `PRTN_0059` An event type Made doesn't send. Endpoints subscribe to link.completed, link.revoked, application.status_changed, account.opened and launch.redeemed; ping is only for test events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0056":{"summary":"The webhook URL is refused: not https, credentials in it, a port that isn't allowed, a host that doesn't resolve, or a host that resolves to a private, loopback, link-local, metadata or other non-public address","value":{"error":"PRTN_0056","message":"Use a public https URL, with no user name or password in it, on port 443 or 8443. Private, loopback, link-local and metadata addresses aren't allowed"}},"PRTN_0059":{"summary":"An event type Made doesn't send. Endpoints subscribe to link.completed, link.revoked, application.status_changed, account.opened and launch.redeemed; ping is only for test events","value":{"error":"PRTN_0059","message":"Unknown event type"}}}}}},"429":{"description":"`PRTN_0060` Too many webhook endpoint changes (creates and updates), test events or replays for this endpoint or partner in the current window","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0060":{"summary":"Too many webhook endpoint changes (creates and updates), test events or replays for this endpoint or partner in the current window","value":{"error":"PRTN_0060","message":"Too many webhook changes, test events or replays. Try again later"}}}}},"headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}}},"503":{"description":"`PRTN_0062` Every webhook URL check this server runs at once is in use (slow DNS answers), so Made refused the new endpoint without checking its URL instead of waiting; `PRTN_0061` Made could not count a webhook endpoint change, test event or replay against its budget because Redis failed, so it refused the call instead of letting Made's servers call the partner's host without a limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0062":{"summary":"Every webhook URL check this server runs at once is in use (slow DNS answers), so Made refused the new endpoint without checking its URL instead of waiting","value":{"error":"PRTN_0062","message":"Made couldn't check the URL just now. Try again in a moment"}},"PRTN_0061":{"summary":"Made could not count a webhook endpoint change, test event or replay against its budget because Redis failed, so it refused the call instead of letting Made's servers call the partner's host without a limit","value":{"error":"PRTN_0061","message":"Webhooks are temporarily unavailable. Try again in a moment"}}}}}}},"security":[{"Bearer":[]}]},"post":{"tags":["Webhooks"],"summary":"Add a webhook endpoint","description":"Send the partner JWT from POST /v1/partners/auth. The same endpoints your team manages in the partner portal, with the same limits: changes made here and in the portal share one budget. PRTN_0055 for an endpoint that isn't yours, or is deleted. Where Made has turned webhooks off, every webhooks route answers 404 like an unknown path. url must be a public https URL on port 443 or 8443, with no user name or password, whose host resolves only to public addresses (PRTN_0056). events lists the types to receive (PRTN_0059 for any other, ping included). Up to 5 endpoints (PRTN_0057). Creates and updates share a budget of 10 a minute and 100 a day (PRTN_0060 with Retry-After; PRTN_0061 when Made can't count it). PRTN_0062 when Made can't check the URL right now. signing_secret (whsec_...) is returned only here: store it in your secret store.","operationId":"create_webhook_v1_partners_webhooks_post","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointCreateRequest"}}},"required":true},"responses":{"201":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_WebhookEndpointCreated_"}}}},"422":{"description":"Request validation failed (error is then a list of problems), or: `PRTN_0056` The webhook URL is refused: not https, credentials in it, a port that isn't allowed, a host that doesn't resolve, or a host that resolves to a private, loopback, link-local, metadata or other non-public address; `PRTN_0059` An event type Made doesn't send. Endpoints subscribe to link.completed, link.revoked, application.status_changed, account.opened and launch.redeemed; ping is only for test events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0056":{"summary":"The webhook URL is refused: not https, credentials in it, a port that isn't allowed, a host that doesn't resolve, or a host that resolves to a private, loopback, link-local, metadata or other non-public address","value":{"error":"PRTN_0056","message":"Use a public https URL, with no user name or password in it, on port 443 or 8443. Private, loopback, link-local and metadata addresses aren't allowed"}},"PRTN_0059":{"summary":"An event type Made doesn't send. Endpoints subscribe to link.completed, link.revoked, application.status_changed, account.opened and launch.redeemed; ping is only for test events","value":{"error":"PRTN_0059","message":"Unknown event type"}}}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}}}}}},"409":{"description":"`PRTN_0057` The partner already has the most webhook endpoints allowed (5); deleted endpoints don't count","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0057":{"summary":"The partner already has the most webhook endpoints allowed (5); deleted endpoints don't count","value":{"error":"PRTN_0057","message":"You can have up to 5 webhook endpoints. Delete one to add another"}}}}}},"429":{"description":"`PRTN_0060` Too many webhook endpoint changes (creates and updates), test events or replays for this endpoint or partner in the current window","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0060":{"summary":"Too many webhook endpoint changes (creates and updates), test events or replays for this endpoint or partner in the current window","value":{"error":"PRTN_0060","message":"Too many webhook changes, test events or replays. Try again later"}}}}},"headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}}},"503":{"description":"`PRTN_0062` Every webhook URL check this server runs at once is in use (slow DNS answers), so Made refused the new endpoint without checking its URL instead of waiting; `PRTN_0061` Made could not count a webhook endpoint change, test event or replay against its budget because Redis failed, so it refused the call instead of letting Made's servers call the partner's host without a limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0062":{"summary":"Every webhook URL check this server runs at once is in use (slow DNS answers), so Made refused the new endpoint without checking its URL instead of waiting","value":{"error":"PRTN_0062","message":"Made couldn't check the URL just now. Try again in a moment"}},"PRTN_0061":{"summary":"Made could not count a webhook endpoint change, test event or replay against its budget because Redis failed, so it refused the call instead of letting Made's servers call the partner's host without a limit","value":{"error":"PRTN_0061","message":"Webhooks are temporarily unavailable. Try again in a moment"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/webhooks/{endpoint_id}":{"patch":{"tags":["Webhooks"],"summary":"Change a webhook endpoint","description":"Send the partner JWT from POST /v1/partners/auth. The same endpoints your team manages in the partner portal, with the same limits: changes made here and in the portal share one budget. PRTN_0055 for an endpoint that isn't yours, or is deleted. Where Made has turned webhooks off, every webhooks route answers 404 like an unknown path. Only the fields you send change: events (PRTN_0059 for an unknown type), description (null clears it), and status: disabled stops deliveries (disabled_reason partner), active turns the endpoint back on and starts its failure count again. An endpoint Made paused (disabled_reason admin) can't be turned on (PRTN_0073): contact Made. The URL can't change: add a new endpoint instead. Counts against the change budget it shares with creates (PRTN_0060 with Retry-After; PRTN_0061 when Made can't count it).","operationId":"update_webhook_v1_partners_webhooks__endpoint_id__patch","parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Endpoint Id"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEndpointUpdateRequest"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_WebhookEndpointResult_"}}}},"422":{"description":"Request validation failed (error is then a list of problems), or: `PRTN_0059` An event type Made doesn't send. Endpoints subscribe to link.completed, link.revoked, application.status_changed, account.opened and launch.redeemed; ping is only for test events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0059":{"summary":"An event type Made doesn't send. Endpoints subscribe to link.completed, link.revoked, application.status_changed, account.opened and launch.redeemed; ping is only for test events","value":{"error":"PRTN_0059","message":"Unknown event type"}}}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive; `PRTN_0055` No webhook endpoint with this id for the partner: unknown, deleted, or another partner's","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}},"PRTN_0055":{"summary":"No webhook endpoint with this id for the partner: unknown, deleted, or another partner's","value":{"error":"PRTN_0055","message":"That webhook endpoint was not found"}}}}}},"409":{"description":"`PRTN_0073` The partner asked to turn on a webhook endpoint that Made paused (disabled_reason admin). Only Made can turn it back on","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0073":{"summary":"The partner asked to turn on a webhook endpoint that Made paused (disabled_reason admin). Only Made can turn it back on","value":{"error":"PRTN_0073","message":"Made paused this webhook endpoint, so only Made can turn it back on. Contact Made"}}}}}},"429":{"description":"`PRTN_0060` Too many webhook endpoint changes (creates and updates), test events or replays for this endpoint or partner in the current window","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0060":{"summary":"Too many webhook endpoint changes (creates and updates), test events or replays for this endpoint or partner in the current window","value":{"error":"PRTN_0060","message":"Too many webhook changes, test events or replays. Try again later"}}}}},"headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}}},"503":{"description":"`PRTN_0061` Made could not count a webhook endpoint change, test event or replay against its budget because Redis failed, so it refused the call instead of letting Made's servers call the partner's host without a limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0061":{"summary":"Made could not count a webhook endpoint change, test event or replay against its budget because Redis failed, so it refused the call instead of letting Made's servers call the partner's host without a limit","value":{"error":"PRTN_0061","message":"Webhooks are temporarily unavailable. Try again in a moment"}}}}}}},"security":[{"Bearer":[]}]},"delete":{"tags":["Webhooks"],"summary":"Delete a webhook endpoint","description":"Send the partner JWT from POST /v1/partners/auth. The same endpoints your team manages in the partner portal, with the same limits: changes made here and in the portal share one budget. PRTN_0055 for an endpoint that isn't yours, or is deleted. Where Made has turned webhooks off, every webhooks route answers 404 like an unknown path. The endpoint stops receiving events at once and no longer counts toward the limit. Deliveries still queued for it are not sent.","operationId":"delete_webhook_v1_partners_webhooks__endpoint_id__delete","parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Endpoint Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_WebhookEndpointDeleted_"}}}},"422":{"description":"Request validation failed (error is then a list of problems), or: `PRTN_0059` An event type Made doesn't send. Endpoints subscribe to link.completed, link.revoked, application.status_changed, account.opened and launch.redeemed; ping is only for test events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0059":{"summary":"An event type Made doesn't send. Endpoints subscribe to link.completed, link.revoked, application.status_changed, account.opened and launch.redeemed; ping is only for test events","value":{"error":"PRTN_0059","message":"Unknown event type"}}}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive; `PRTN_0055` No webhook endpoint with this id for the partner: unknown, deleted, or another partner's","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}},"PRTN_0055":{"summary":"No webhook endpoint with this id for the partner: unknown, deleted, or another partner's","value":{"error":"PRTN_0055","message":"That webhook endpoint was not found"}}}}}},"409":{"description":"`PRTN_0073` The partner asked to turn on a webhook endpoint that Made paused (disabled_reason admin). Only Made can turn it back on","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0073":{"summary":"The partner asked to turn on a webhook endpoint that Made paused (disabled_reason admin). Only Made can turn it back on","value":{"error":"PRTN_0073","message":"Made paused this webhook endpoint, so only Made can turn it back on. Contact Made"}}}}}},"429":{"description":"`PRTN_0060` Too many webhook endpoint changes (creates and updates), test events or replays for this endpoint or partner in the current window","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0060":{"summary":"Too many webhook endpoint changes (creates and updates), test events or replays for this endpoint or partner in the current window","value":{"error":"PRTN_0060","message":"Too many webhook changes, test events or replays. Try again later"}}}}},"headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}}},"503":{"description":"`PRTN_0061` Made could not count a webhook endpoint change, test event or replay against its budget because Redis failed, so it refused the call instead of letting Made's servers call the partner's host without a limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0061":{"summary":"Made could not count a webhook endpoint change, test event or replay against its budget because Redis failed, so it refused the call instead of letting Made's servers call the partner's host without a limit","value":{"error":"PRTN_0061","message":"Webhooks are temporarily unavailable. Try again in a moment"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/webhooks/{endpoint_id}/rotate-secret":{"post":{"tags":["Webhooks"],"summary":"Rotate a webhook endpoint's signing secret","description":"Send the partner JWT from POST /v1/partners/auth. The same endpoints your team manages in the partner portal, with the same limits: changes made here and in the portal share one budget. PRTN_0055 for an endpoint that isn't yours, or is deleted. Where Made has turned webhooks off, every webhooks route answers 404 like an unknown path. The new signing_secret is returned only here. For 24 hours (previous_secret_expires_at) every delivery carries a second v1 signature made with the previous secret, so you can deploy the new one with no downtime.","operationId":"rotate_webhook_secret_v1_partners_webhooks__endpoint_id__rotate_secret_post","parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Endpoint Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_WebhookSecretRotated_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive; `PRTN_0055` No webhook endpoint with this id for the partner: unknown, deleted, or another partner's","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}},"PRTN_0055":{"summary":"No webhook endpoint with this id for the partner: unknown, deleted, or another partner's","value":{"error":"PRTN_0055","message":"That webhook endpoint was not found"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/webhooks/{endpoint_id}/test":{"post":{"tags":["Webhooks"],"summary":"Send a test event to a webhook endpoint","description":"Send the partner JWT from POST /v1/partners/auth. The same endpoints your team manages in the partner portal, with the same limits: changes made here and in the portal share one budget. PRTN_0055 for an endpoint that isn't yours, or is deleted. Where Made has turned webhooks off, every webhooks route answers 404 like an unknown path. Queues a ping, or a sample of another type with made-up ids and test: true in data, to this endpoint only, whatever it subscribes to. The answer is the queued delivery; Made sends it within about half a minute. PRTN_0059 for an unknown type. PRTN_0060 with Retry-After past 5 a minute for one endpoint or 100 a day; PRTN_0061 when Made can't count it.","operationId":"send_webhook_test_v1_partners_webhooks__endpoint_id__test_post","parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Endpoint Id"}}],"requestBody":{"content":{"application/json":{"schema":{"anyOf":[{"$ref":"#/components/schemas/WebhookTestRequest"},{"type":"null"}],"title":"Body"}}}},"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_WebhookDeliveryResult_"}}}},"422":{"description":"Request validation failed (error is then a list of problems), or: `PRTN_0059` An event type Made doesn't send. Endpoints subscribe to link.completed, link.revoked, application.status_changed, account.opened and launch.redeemed; ping is only for test events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0059":{"summary":"An event type Made doesn't send. Endpoints subscribe to link.completed, link.revoked, application.status_changed, account.opened and launch.redeemed; ping is only for test events","value":{"error":"PRTN_0059","message":"Unknown event type"}}}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive; `PRTN_0055` No webhook endpoint with this id for the partner: unknown, deleted, or another partner's","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}},"PRTN_0055":{"summary":"No webhook endpoint with this id for the partner: unknown, deleted, or another partner's","value":{"error":"PRTN_0055","message":"That webhook endpoint was not found"}}}}}},"429":{"description":"`PRTN_0060` Too many webhook endpoint changes (creates and updates), test events or replays for this endpoint or partner in the current window","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0060":{"summary":"Too many webhook endpoint changes (creates and updates), test events or replays for this endpoint or partner in the current window","value":{"error":"PRTN_0060","message":"Too many webhook changes, test events or replays. Try again later"}}}}},"headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}}},"503":{"description":"`PRTN_0061` Made could not count a webhook endpoint change, test event or replay against its budget because Redis failed, so it refused the call instead of letting Made's servers call the partner's host without a limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0061":{"summary":"Made could not count a webhook endpoint change, test event or replay against its budget because Redis failed, so it refused the call instead of letting Made's servers call the partner's host without a limit","value":{"error":"PRTN_0061","message":"Webhooks are temporarily unavailable. Try again in a moment"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/webhooks/{endpoint_id}/deliveries":{"get":{"tags":["Webhooks"],"summary":"List a webhook endpoint's deliveries","description":"Send the partner JWT from POST /v1/partners/auth. The same endpoints your team manages in the partner portal, with the same limits: changes made here and in the portal share one budget. PRTN_0055 for an endpoint that isn't yours, or is deleted. Where Made has turned webhooks off, every webhooks route answers 404 like an unknown path. Newest first, with the response code, duration and a short error (never your response body). status keeps only pending, retrying, succeeded or failed (a final failure) deliveries. Made keeps 30 days of history.","operationId":"list_webhook_deliveries_v1_partners_webhooks__endpoint_id__deliveries_get","parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Endpoint Id"}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":100,"exclusiveMinimum":0,"description":"Deliveries per page.","default":25,"title":"Limit"},"description":"Deliveries per page."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":100000,"minimum":0,"description":"Deliveries to skip.","default":0,"title":"Offset"},"description":"Deliveries to skip."},{"name":"status","in":"query","required":false,"schema":{"anyOf":[{"enum":["pending","retrying","succeeded","failed"],"type":"string"},{"type":"null"}],"description":"Only deliveries with this status.","title":"Status"},"description":"Only deliveries with this status."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_WebhookDeliveryList_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive; `PRTN_0055` No webhook endpoint with this id for the partner: unknown, deleted, or another partner's","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}},"PRTN_0055":{"summary":"No webhook endpoint with this id for the partner: unknown, deleted, or another partner's","value":{"error":"PRTN_0055","message":"That webhook endpoint was not found"}}}}}}},"security":[{"Bearer":[]}]}},"/v1/partners/webhooks/{endpoint_id}/deliveries/{delivery_id}/replay":{"post":{"tags":["Webhooks"],"summary":"Send a delivery's event again","description":"Send the partner JWT from POST /v1/partners/auth. The same endpoints your team manages in the partner portal, with the same limits: changes made here and in the portal share one budget. PRTN_0055 for an endpoint that isn't yours, or is deleted. Where Made has turned webhooks off, every webhooks route answers 404 like an unknown path. Queues the same event (same id) to this endpoint as a new delivery. PRTN_0058 for a delivery that isn't this endpoint's. PRTN_0060 with Retry-After past 10 a minute for one endpoint or 500 a day; PRTN_0061 when Made can't count it.","operationId":"replay_webhook_delivery_v1_partners_webhooks__endpoint_id__deliveries__delivery_id__replay_post","parameters":[{"name":"endpoint_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Endpoint Id"}},{"name":"delivery_id","in":"path","required":true,"schema":{"type":"string","format":"uuid","title":"Delivery Id"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponse_WebhookDeliveryResult_"}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}},"401":{"description":"`AUTH_0013` Invalid authentication scheme; `AUTH_0011` Signature is invalid; `AUTH_0010` Signature has expired; `PRTN_0001` Invalid authentication scheme for partner APIs; `PRTN_0003` Invalid or expired partner token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"AUTH_0013":{"summary":"Invalid authentication scheme","value":{"error":"AUTH_0013","message":"Your session has expired, please login again."}},"AUTH_0011":{"summary":"Signature is invalid","value":{"error":"AUTH_0011","message":"Your session has expired, please login again."}},"AUTH_0010":{"summary":"Signature has expired","value":{"error":"AUTH_0010","message":"Your session has expired, please login again."}},"PRTN_0001":{"summary":"Invalid authentication scheme for partner APIs","value":{"error":"PRTN_0001","message":"Invalid authentication scheme"}},"PRTN_0003":{"summary":"Invalid or expired partner token","value":{"error":"PRTN_0003","message":"Invalid token or expired token"}}}}}},"403":{"description":"`PRTN_0004` Token is not a partner application token; `PRTN_0015` Partner tenant is disabled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0004":{"summary":"Token is not a partner application token","value":{"error":"PRTN_0004","message":"This endpoint requires a partner token"}},"PRTN_0015":{"summary":"Partner tenant is disabled","value":{"error":"PRTN_0015","message":"This partner is not active"}}}}}},"404":{"description":"`PRTN_0005` Partner tenant not found or inactive; `PRTN_0055` No webhook endpoint with this id for the partner: unknown, deleted, or another partner's; `PRTN_0058` No delivery with this id for this webhook endpoint","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0005":{"summary":"Partner tenant not found or inactive","value":{"error":"PRTN_0005","message":"Partner not found"}},"PRTN_0055":{"summary":"No webhook endpoint with this id for the partner: unknown, deleted, or another partner's","value":{"error":"PRTN_0055","message":"That webhook endpoint was not found"}},"PRTN_0058":{"summary":"No delivery with this id for this webhook endpoint","value":{"error":"PRTN_0058","message":"That webhook delivery was not found"}}}}}},"429":{"description":"`PRTN_0060` Too many webhook endpoint changes (creates and updates), test events or replays for this endpoint or partner in the current window","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0060":{"summary":"Too many webhook endpoint changes (creates and updates), test events or replays for this endpoint or partner in the current window","value":{"error":"PRTN_0060","message":"Too many webhook changes, test events or replays. Try again later"}}}}},"headers":{"Retry-After":{"description":"Seconds to wait before calling again.","schema":{"type":"integer","minimum":1}}}},"503":{"description":"`PRTN_0061` Made could not count a webhook endpoint change, test event or replay against its budget because Redis failed, so it refused the call instead of letting Made's servers call the partner's host without a limit","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PartnerError"},"examples":{"PRTN_0061":{"summary":"Made could not count a webhook endpoint change, test event or replay against its budget because Redis failed, so it refused the call instead of letting Made's servers call the partner's host without a limit","value":{"error":"PRTN_0061","message":"Webhooks are temporarily unavailable. Try again in a moment"}}}}}}},"security":[{"Bearer":[]}]}}},"components":{"schemas":{"BaseResponse_LaunchSessionResponse_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/LaunchSessionResponse"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[LaunchSessionResponse]"},"BaseResponse_LinkSessionResponse_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/LinkSessionResponse"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[LinkSessionResponse]"},"BaseResponse_PartnerAuthResponse_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/PartnerAuthResponse"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[PartnerAuthResponse]"},"BaseResponse_PartnerBalance_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/PartnerBalance"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[PartnerBalance]"},"BaseResponse_PartnerCustomerListResponse_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/PartnerCustomerListResponse"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[PartnerCustomerListResponse]"},"BaseResponse_PartnerCustomerSummary_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/PartnerCustomerSummary"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[PartnerCustomerSummary]"},"BaseResponse_PartnerRewardsSummary_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/PartnerRewardsSummary"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[PartnerRewardsSummary]"},"BaseResponse_PartnerUserAccessTokenResponse_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/PartnerUserAccessTokenResponse"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[PartnerUserAccessTokenResponse]"},"BaseResponse_WebhookDeliveryList_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/WebhookDeliveryList"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[WebhookDeliveryList]"},"BaseResponse_WebhookDeliveryResult_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/WebhookDeliveryResult"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[WebhookDeliveryResult]"},"BaseResponse_WebhookEndpointCreated_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/WebhookEndpointCreated"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[WebhookEndpointCreated]"},"BaseResponse_WebhookEndpointDeleted_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/WebhookEndpointDeleted"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[WebhookEndpointDeleted]"},"BaseResponse_WebhookEndpointList_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/WebhookEndpointList"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[WebhookEndpointList]"},"BaseResponse_WebhookEndpointResult_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/WebhookEndpointResult"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[WebhookEndpointResult]"},"BaseResponse_WebhookSecretRotated_":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"$ref":"#/components/schemas/WebhookSecretRotated"},{"type":"null"}]}},"type":"object","required":["message"],"title":"BaseResponse[WebhookSecretRotated]"},"BaseResponse_list_PartnerScheduledPayment__":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"items":{"$ref":"#/components/schemas/PartnerScheduledPayment"},"type":"array"},{"type":"null"}],"title":"Data"}},"type":"object","required":["message"],"title":"BaseResponse[list[PartnerScheduledPayment]]"},"BaseResponse_list_PartnerTransaction__":{"properties":{"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error"},"message":{"type":"string","title":"Message"},"data":{"anyOf":[{"items":{"$ref":"#/components/schemas/PartnerTransaction"},"type":"array"},{"type":"null"}],"title":"Data"}},"type":"object","required":["message"],"title":"BaseResponse[list[PartnerTransaction]]"},"CreateLaunchSessionRequest":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"The stored customer access token (`link_...`).","examples":["link_9Vd2..."]},"target_path":{"type":"string","enum":["/dashboard/home","/dashboard/payments","/dashboard/rewards","/dashboard/accounts","/dashboard/transactions"],"title":"Target Path","description":"Made Card page to open.","default":"/dashboard/home"}},"type":"object","required":["access_token"],"title":"CreateLaunchSessionRequest"},"CreateLinkSessionRequest":{"properties":{"kind":{"type":"string","enum":["apply","login"],"title":"Kind","description":"`apply` for a customer new to Made Card. `login` only to reconnect a customer whose link you lost or Made revoked, or an existing Made customer connecting to you for the first time.","default":"apply"},"prefill":{"$ref":"#/components/schemas/PartnerIdentityPrefill","description":"Customer details you already have. They fill the Made application. For `apply`, each value must also pass the application's own rules."},"lock_fields":{"items":{"type":"string","enum":["first_name","last_name","email","address"]},"type":"array","title":"Lock Fields","description":"Prefilled values the customer's Made profile must match. `address` checks address_line_1, city, state, and zipcode. A lock applies only to a field you prefill. Omit it or send `[]` to lock all four. Any other name fails with `PRTN_0031`."},"partner_agent_id":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Partner Agent Id","description":"Optional Made-assigned ID for one of your loan officers or agents. Leave null unless Made gave you one. An ID that is not yours fails with `PRTN_0030`."}},"additionalProperties":false,"type":"object","title":"CreateLinkSessionRequest"},"ExchangePublicTokenRequest":{"properties":{"public_token":{"type":"string","title":"Public Token","description":"One-time token from the Made Link `onSuccess` callback. Expires 10 minutes after Made links the customer.","examples":["public_3q2x..."]}},"type":"object","required":["public_token"],"title":"ExchangePublicTokenRequest"},"HTTPValidationError":{"properties":{"detail":{"items":{"$ref":"#/components/schemas/ValidationError"},"type":"array","title":"Detail"}},"type":"object","title":"HTTPValidationError"},"LaunchSessionResponse":{"properties":{"launch_url":{"type":"string","title":"Launch Url","description":"Single-use URL that opens Made Card signed in. Open it within `expires_in` seconds.","examples":["https://staging-app.madecard.com/partner/launch?code=launch_Qe7..."]},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds the launch URL stays valid, 300.","examples":[300]},"target_path":{"type":"string","title":"Target Path","description":"The Made Card page it opens.","examples":["/dashboard/home"]}},"type":"object","required":["launch_url","expires_in","target_path"],"title":"LaunchSessionResponse"},"LinkSessionResponse":{"properties":{"session_id":{"type":"string","format":"uuid","title":"Session Id","description":"Link session ID."},"kind":{"type":"string","title":"Kind","description":"`apply` or `login`.","examples":["apply"]},"link_url":{"type":"string","title":"Link Url","description":"Made-hosted page to open with Made Link, the web SDK. Valid for 60 minutes, and stops working once the customer is linked.","examples":["https://staging-app.madecard.com/partner/apply?session=5b0c9a1e-0d7e-4c6e-9d0a-2f4b8e6f1a33"]},"expires_at":{"type":"string","format":"date-time","title":"Expires At","description":"When `link_url` stops working."},"prefill":{"additionalProperties":true,"type":"object","title":"Prefill","description":"The prefill Made stored for this session."},"lock_fields":{"items":{"type":"string"},"type":"array","title":"Lock Fields","description":"The fields Made will enforce."},"partner_slug":{"type":"string","title":"Partner Slug","description":"Your partner slug."},"partner_name":{"type":"string","title":"Partner Name","description":"Your partner name, as shown to the customer."},"allowed_origins":{"items":{"type":"string"},"type":"array","title":"Allowed Origins","description":"Web origins allowed to open `link_url` with Made Link. Made returns the `public_token` only to a page on one of these. Manage them in the partner portal.","examples":[["https://www.yourcompany.com"]]}},"type":"object","required":["session_id","kind","link_url","expires_at","prefill","lock_fields","partner_slug","partner_name"],"title":"LinkSessionResponse"},"PartnerAuthRequest":{"properties":{"client_id":{"type":"string","title":"Client Id","description":"Your partner client ID from Made: pk_test_ in Test mode, pk_live_ in Live mode. Keys issued before these prefixes (pk_yourcompany) keep working.","examples":["pk_test_yourcompany"]},"client_secret":{"type":"string","title":"Client Secret","description":"Your partner client secret: sk_test_ in Test mode, sk_live_ in Live mode. Keep it on your server.","examples":["sk_test_your_secret"]}},"type":"object","required":["client_id","client_secret"],"title":"PartnerAuthRequest"},"PartnerAuthResponse":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"Partner JWT. Send it as `Authorization: Bearer` on every other call."},"token_type":{"type":"string","title":"Token Type","description":"Always `bearer`.","default":"bearer"},"expires_in":{"type":"integer","title":"Expires In","description":"Seconds until the partner JWT expires, about 3600.","examples":[3600]},"partner_id":{"type":"string","format":"uuid","title":"Partner Id","description":"Your partner ID at Made."},"slug":{"type":"string","title":"Slug","description":"Your partner slug at Made.","examples":["yourcompany"]},"livemode":{"type":"boolean","title":"Livemode","description":"True in Live mode, false in Test mode. Every webhook event carries it too.","examples":[false]}},"type":"object","required":["access_token","expires_in","partner_id","slug","livemode"],"title":"PartnerAuthResponse"},"PartnerBalance":{"properties":{"current_balance":{"type":"number","title":"Current Balance","description":"Current card balance in US dollars, the Balance on Made Card's Home.","examples":[1250.4]},"available_credit":{"type":"number","title":"Available Credit","description":"Credit the customer can still spend, in US dollars, the Available Credit on Made Card's Home.","examples":[3749.6]},"credit_limit":{"type":"number","title":"Credit Limit","description":"The card's credit limit in US dollars.","examples":[5000.0]},"minimum_payment_due":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Minimum Payment Due","description":"Minimum payment due in US dollars, the Minimum due on Made Card's Home. It already counts payments made this cycle. Null while the card processor has no minimum due yet, for example before the first statement. Show it as not available yet, not as $0.","examples":[35.0]},"payment_due_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Payment Due Date","description":"When the next payment is due, the Payment due date on Made Card's Home. When the card processor has no due date yet, for example before the first statement, or its due date has passed, Made works out the next one from the customer's billing day, as Home does. Null only when Made has neither.","examples":["2026-10-20"]},"as_of":{"type":"string","format":"date-time","title":"As Of","description":"When Made read these values from the card processor, in UTC. Made keeps each customer's answer for up to 30 seconds, so as_of can be that far in the past."}},"type":"object","required":["current_balance","available_credit","credit_limit","as_of"],"title":"PartnerBalance","description":"A customer's card balance, read from the card processor at most 30 seconds ago. Values match Made Card's Home."},"PartnerCustomerListResponse":{"properties":{"customers":{"items":{"$ref":"#/components/schemas/PartnerCustomerSummary"},"type":"array","title":"Customers","description":"Customers on this page, newest first."},"total":{"type":"integer","title":"Total","description":"Total customers you can see."},"limit":{"type":"integer","title":"Limit","description":"Page size used."},"offset":{"type":"integer","title":"Offset","description":"Offset used."}},"type":"object","required":["customers","total","limit","offset"],"title":"PartnerCustomerListResponse"},"PartnerCustomerSummary":{"properties":{"user_id":{"type":"string","format":"uuid","title":"User Id","description":"Made customer ID."},"email":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Email","description":"Customer email."},"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name","description":"Customer first name."},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name","description":"Customer last name."},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Customer mobile number."},"account_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Account Id","description":"Card account ID. Null until the customer has an account."},"account_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Account Status","description":"Card account status, for example `ACTIVE`."},"application_status":{"type":"string","enum":["not_started","in_progress","in_review","approved","declined"],"title":"Application Status","description":"Where the customer's Made Card application stands. `not_started`: no application, or the last one closed without a decline (withdrawn, its offer expired, or Made could not process it), so the customer can apply again. `in_progress`: started but not submitted, which includes a customer who connected to Made in an `apply` session and is still filling in the application. `in_review`: submitted, and Made has not decided yet. `approved`: Made approved it; `account_id` appears once the customer accepts the offer. `declined`: the application ended without a card, because Made declined it or the customer turned the offer down.","examples":["in_review"]},"linked_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Linked At","description":"When the current link with you was created. Null if Made attributes the customer to you without an active link."}},"type":"object","required":["user_id","application_status"],"title":"PartnerCustomerSummary"},"PartnerError":{"title":"PartnerError","type":"object","required":["error","message"],"properties":{"error":{"type":"string","description":"Stable error code. Branch on this.","examples":["PRTN_0010"]},"message":{"type":"string","description":"Human-readable message, safe to log."}}},"PartnerIdentityPrefill":{"properties":{"first_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"First Name","description":"Customer first name, up to 100 characters. For `apply`, 2 to 50 letters or spaces.","examples":["Ada"]},"last_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Last Name","description":"Customer last name, up to 100 characters. For `apply`, 2 to 50 letters or spaces.","examples":["Lovelace"]},"email":{"anyOf":[{"type":"string","format":"email"},{"type":"null"}],"title":"Email","description":"Customer email.","examples":["ada@example.com"]},"phone":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Phone","description":"Customer US mobile number. Made removes spaces, dashes, dots, parentheses, and a leading +1 or 1, then requires 10 digits.","examples":["7575550123"]},"address_line_1":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address Line 1","description":"Street address, up to 200 characters. For `apply`, 5 to 40 characters, and not a P.O. Box or registered agent.","examples":["1 Main St"]},"address_line_2":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Address Line 2","description":"Apartment, suite, or unit, up to 200 characters. For `apply`, up to 40 characters, and not a P.O. Box or registered agent.","examples":["Apt 4"]},"city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"City","description":"City, up to 100 characters. For `apply`, only letters, spaces, hyphens, and apostrophes.","examples":["Virginia Beach"]},"state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"State","description":"Two-letter US state or territory code, in any case. For `apply`, one of the 50 states or DC.","examples":["VA"]},"zipcode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zipcode","description":"Five-digit ZIP code. A ZIP+4 such as 23451-1234 is shortened to five digits.","examples":["23451"]}},"additionalProperties":false,"type":"object","title":"PartnerIdentityPrefill","description":"Every field is optional. Made trims each value, and an empty string counts as not sent."},"PartnerPointsBalance":{"properties":{"earned_points":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Earned Points","description":"Points earned."},"pending_points":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Pending Points","description":"Points from transactions that have not settled."},"redeemable_points":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Redeemable Points","description":"Points the customer can redeem now."},"mortgage_unlocked_points":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Mortgage Unlocked Points","description":"Mortgage Match points unlocked."},"mortgage_earnable_points":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Mortgage Earnable Points","description":"Mortgage Match points still available to earn."}},"type":"object","title":"PartnerPointsBalance","description":"The partner copy of reward_schemas.AccountPointsBalanceResponse; field names must stay in sync."},"PartnerPointsBucket":{"properties":{"label":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Label","description":"Bucket name.","examples":["3x Essentials"]},"points":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Points","description":"Points earned in this bucket."}},"type":"object","title":"PartnerPointsBucket","description":"The partner copy of reward_schemas.AccountPointsMultiplierBreakdownResponse; field names must stay in sync."},"PartnerRewardsSummary":{"properties":{"account_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Account Id","description":"Card account ID."},"year_month":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Year Month","description":"Statement month as YYYYMM.","examples":[202609]},"start_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Start Date","description":"Start of the period, the last statement date."},"end_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"End Date","description":"End of the period. Null for the current period."},"balance":{"anyOf":[{"$ref":"#/components/schemas/PartnerPointsBalance"},{"type":"null"}],"description":"Points balance."},"multiplier_breakdown":{"anyOf":[{"additionalProperties":{"$ref":"#/components/schemas/PartnerPointsBucket"},"type":"object"},{"type":"null"}],"title":"Multiplier Breakdown","description":"Points earned by multiplier, keyed `1`, `2`, and `3`."}},"type":"object","title":"PartnerRewardsSummary","description":"The partner copy of reward_schemas.AccountPointsSummaryResponse; field names must stay in sync."},"PartnerScheduledPayment":{"properties":{"id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Id","description":"Payment ID."},"account_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Account Id","description":"Card account ID."},"effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Effective Date","description":"Date the payment is scheduled for."},"amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amount","description":"Payment amount in dollars."},"principal":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Principal","description":"Portion applied to principal."},"interest":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Interest","description":"Portion applied to interest."},"fees":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Fees","description":"Portion applied to fees."},"status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Status","description":"Always `SCHEDULED` here.","examples":["SCHEDULED"]},"repayment_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Repayment Type","description":"`ONE_TIME` or `RECURRING`.","examples":["RECURRING"]},"repayment_strategy":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Repayment Strategy","description":"How the amount is chosen, for example `MINIMUM_PAYMENT`."},"is_business_day":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Business Day","description":"Whether `effective_date` is a business day."},"actual_effective_date":{"anyOf":[{"type":"string","format":"date"},{"type":"null"}],"title":"Actual Effective Date","description":"Date the payment will actually process."},"bank_account_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Bank Account Id","description":"Bank account the payment pulls from."},"is_paused":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Paused","description":"Whether the customer paused it."},"is_scheduled_on_due_date":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Scheduled On Due Date","description":"Whether it is set to run on the due date."},"is_payment_done_by_check":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Payment Done By Check","description":"Whether it is a check payment."}},"type":"object","title":"PartnerScheduledPayment","description":"The partner copy of payment_schemas.AccountRepaymentResponse; field names must stay in sync.\n\nIts amounts are floats, so they go out as JSON numbers instead of pydantic's Decimal strings."},"PartnerTransaction":{"properties":{"id":{"type":"integer","title":"Id","description":"Transaction ID, an integer.","examples":[48213]},"card_id":{"anyOf":[{"type":"string","format":"uuid"},{"type":"null"}],"title":"Card Id","description":"Card the transaction was made on."},"transaction_type":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transaction Type","description":"`DEBIT`, `CREDIT`, `FEE`, and similar."},"amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Amount","description":"Amount in dollars, including related fees."},"requested_amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Requested Amount","description":"Amount requested at authorization."},"additional_fees_amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Additional Fees Amount","description":"Related fees rolled into `amount`."},"original_amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Original Amount","description":"Amount before related fees."},"original_requested_amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Original Requested Amount","description":"Requested amount before related fees."},"currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Currency","description":"Currency code.","examples":["USD"]},"merchant_name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Merchant Name","description":"Merchant name."},"merchant_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Merchant Category","description":"Merchant category."},"merchant_sub_category":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Merchant Sub Category","description":"Merchant sub-category."},"merchant_category_code":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Merchant Category Code","description":"Four-digit MCC."},"merchant_logo":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Merchant Logo","description":"Category logo URL."},"transaction_date":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Transaction Date","description":"Display date.","examples":["Sep 21, 2026"]},"latest_status":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Latest Status","description":"Latest status, for example `PENDING` or `COMPLETED`."},"is_in_dispute":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is In Dispute","description":"Whether the customer disputed it."},"merchant_city":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Merchant City","description":"Merchant city."},"merchant_state":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Merchant State","description":"Merchant state."},"merchant_zipcode":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Merchant Zipcode","description":"Merchant ZIP code."},"on_statement_as":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"On Statement As","description":"How it appears on the statement."},"local_transaction_amount":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Local Transaction Amount","description":"Amount in the local currency."},"local_currency":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Local Currency","description":"Local currency code."},"is_void":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Void","description":"Always false here; voided rows are omitted."},"merchant_name_sanitized":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Merchant Name Sanitized","description":"Cleaned merchant name."},"merchant_icon_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Merchant Icon Url","description":"Merchant icon URL."},"location_address_latitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Location Address Latitude","description":"Merchant latitude."},"location_address_longitude":{"anyOf":[{"type":"number"},{"type":"null"}],"title":"Location Address Longitude","description":"Merchant longitude."},"multiplier":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Multiplier","description":"Points multiplier applied, 1 to 3."},"hsr_eligibility":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Hsr Eligibility","description":"Made home-services redemption state."},"zero_percent_apr_eligibility":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Zero Percent Apr Eligibility","description":"Made 0% APR offer state."},"is_eligible_for_price_match":{"anyOf":[{"type":"boolean"},{"type":"null"}],"title":"Is Eligible For Price Match","description":"Whether price match can be claimed."},"price_match_merchant_claim_url":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Price Match Merchant Claim Url","description":"Price match claim URL."},"merchant_price_match_coverage_days":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Merchant Price Match Coverage Days","description":"Price match window in days."}},"type":"object","required":["id"],"title":"PartnerTransaction","description":"A card transaction as returned to partners. Mirrors the Made card transaction list.\n\nThe route declares this model, so amounts go out as JSON numbers instead of pydantic's Decimal strings."},"PartnerUserAccessTokenResponse":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"Customer access token (`link_...`). Store it on your server. It stays valid until Made revokes it.","examples":["link_9Vd2..."]},"token_type":{"type":"string","title":"Token Type","description":"Always `bearer`.","default":"bearer"},"expires_in":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Expires In","description":"Always null. The link does not expire on a timer; it stays valid until Made revokes it."},"user_id":{"type":"string","format":"uuid","title":"User Id","description":"The Made customer ID for this link."}},"type":"object","required":["access_token","user_id"],"title":"PartnerUserAccessTokenResponse"},"RefreshAccessTokenRequest":{"properties":{"access_token":{"type":"string","title":"Access Token","description":"The current customer access token (`link_...`). It stops working once the new one is issued.","examples":["link_9Vd2..."]}},"type":"object","required":["access_token"],"title":"RefreshAccessTokenRequest"},"ValidationError":{"properties":{"loc":{"items":{"anyOf":[{"type":"string"},{"type":"integer"}]},"type":"array","title":"Location"},"msg":{"type":"string","title":"Message"},"type":{"type":"string","title":"Error Type"}},"type":"object","required":["loc","msg","type"],"title":"ValidationError"},"WebhookDelivery":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The delivery's id: one event going to one endpoint. A replay is a new delivery."},"event_id":{"type":"string","title":"Event Id","description":"The event's evt_ id, the one you deduplicate on (also Made-Webhook-Id)."},"event_type":{"type":"string","title":"Event Type","description":"The event's type."},"attempt":{"type":"integer","title":"Attempt","description":"Attempts made so far (0 while the first is pending)."},"status":{"type":"string","enum":["pending","retrying","succeeded","failed"],"title":"Status","description":"pending, retrying, succeeded, or failed (a final failure)."},"response_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Response Code","description":"The HTTP status of the last attempt, or null when your endpoint didn't answer."},"duration_ms":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Duration Ms","description":"How long the last attempt took, in milliseconds."},"error":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Error","description":"A short description. Never the response body."},"next_attempt_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Next Attempt At","description":"When Made tries next, while pending or retrying."},"last_attempt_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Last Attempt At","description":"When the last attempt ran."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When the delivery was queued."},"delivered_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Delivered At","description":"When a 2xx answered it."}},"type":"object","required":["id","event_id","event_type","attempt","status","created_at"],"title":"WebhookDelivery"},"WebhookDeliveryList":{"properties":{"deliveries":{"items":{"$ref":"#/components/schemas/WebhookDelivery"},"type":"array","title":"Deliveries","description":"The endpoint's deliveries, newest first."},"has_more":{"type":"boolean","title":"Has More","description":"Whether more deliveries follow this page."}},"type":"object","required":["deliveries","has_more"],"title":"WebhookDeliveryList"},"WebhookDeliveryResult":{"properties":{"delivery":{"$ref":"#/components/schemas/WebhookDelivery","description":"The queued delivery. Made sends it within about half a minute."}},"type":"object","required":["delivery"],"title":"WebhookDeliveryResult"},"WebhookEndpoint":{"properties":{"id":{"type":"string","format":"uuid","title":"Id","description":"The endpoint's id."},"url":{"type":"string","title":"Url","description":"Where Made sends events. It can't change: add a new endpoint instead."},"description":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Description","description":"Your label for the endpoint, or null."},"events":{"items":{"type":"string"},"type":"array","title":"Events","description":"The event types it subscribes to."},"status":{"type":"string","enum":["active","disabled","failing"],"title":"Status","description":"active; failing (deliveries kept failing for 3 days, but it still receives events); or disabled (it receives nothing; disabled_reason says why)."},"created_at":{"type":"string","format":"date-time","title":"Created At","description":"When it was created."},"updated_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Updated At","description":"When it last changed, or null."},"created_by":{"anyOf":[{"$ref":"#/components/schemas/WebhookPerson"},{"type":"null"}],"description":"The portal member who created it, or null (created through the partner API, or by a member no longer known)."},"secret_last4":{"type":"string","title":"Secret Last4","description":"The signing secret's last 4 characters. Never the secret."},"previous_secret_expires_at":{"anyOf":[{"type":"string","format":"date-time"},{"type":"null"}],"title":"Previous Secret Expires At","description":"While a rotation's previous secret still signs deliveries (next to the new one), when it stops."},"last_delivery":{"anyOf":[{"$ref":"#/components/schemas/WebhookLastDelivery"},{"type":"null"}],"description":"The most recently attempted delivery, or null before the first attempt."},"failure_count":{"type":"integer","title":"Failure Count","description":"Consecutive failed delivery attempts since the last success."},"disabled_reason":{"anyOf":[{"type":"string","enum":["partner","failing","admin"]},{"type":"null"}],"title":"Disabled Reason","description":"Why a disabled endpoint is disabled: partner (you disabled it), failing (Made disabled it after days of failed deliveries; turn it back on once it works), or admin (Made paused it: only Made can turn it back on). Null otherwise."}},"type":"object","required":["id","url","events","status","created_at","secret_last4","failure_count"],"title":"WebhookEndpoint"},"WebhookEndpointCreateRequest":{"properties":{"url":{"type":"string","maxLength":4096,"title":"Url","description":"A public https URL on port 443 or 8443, with no user name, password or fragment, of at most 2,048 characters, whose host resolves only to public addresses."},"events":{"items":{"type":"string"},"type":"array","maxItems":20,"minItems":1,"title":"Events","description":"The event types to receive; at least one, not ping."},"description":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Description","description":"Your label for the endpoint (blank is null)."}},"type":"object","required":["url","events"],"title":"WebhookEndpointCreateRequest"},"WebhookEndpointCreated":{"properties":{"endpoint":{"$ref":"#/components/schemas/WebhookEndpoint","description":"The new endpoint."},"signing_secret":{"type":"string","title":"Signing Secret","description":"whsec_…, shown only here. Store it in your secret store."}},"type":"object","required":["endpoint","signing_secret"],"title":"WebhookEndpointCreated"},"WebhookEndpointDeleted":{"properties":{"deleted":{"type":"boolean","title":"Deleted","description":"Always true.","default":true}},"type":"object","title":"WebhookEndpointDeleted"},"WebhookEndpointList":{"properties":{"endpoints":{"items":{"$ref":"#/components/schemas/WebhookEndpoint"},"type":"array","title":"Endpoints","description":"The endpoints that aren't deleted, oldest first."},"event_types":{"items":{"$ref":"#/components/schemas/WebhookEventType"},"type":"array","title":"Event Types","description":"Every event type, in order; ping is only for test events and can't be subscribed to."},"limits":{"$ref":"#/components/schemas/WebhookLimits","description":"The endpoint limit."}},"type":"object","required":["endpoints","event_types","limits"],"title":"WebhookEndpointList"},"WebhookEndpointResult":{"properties":{"endpoint":{"$ref":"#/components/schemas/WebhookEndpoint","description":"The endpoint as it is now."}},"type":"object","required":["endpoint"],"title":"WebhookEndpointResult"},"WebhookEndpointUpdateRequest":{"properties":{"events":{"anyOf":[{"items":{"type":"string"},"type":"array","maxItems":20,"minItems":1},{"type":"null"}],"title":"Events","description":"The event types to receive, replacing the current list."},"description":{"anyOf":[{"type":"string","maxLength":256},{"type":"null"}],"title":"Description","description":"A new label; null or blank clears it."},"status":{"anyOf":[{"type":"string","enum":["active","disabled"]},{"type":"null"}],"title":"Status","description":"disabled stops deliveries (disabled_reason partner); active turns the endpoint back on and starts its failure count again. An endpoint Made paused (disabled_reason admin) can't be turned on (PRTN_0073)."}},"type":"object","title":"WebhookEndpointUpdateRequest","description":"Only the fields sent change. description null or blank clears it."},"WebhookEventType":{"properties":{"type":{"type":"string","title":"Type","description":"The event type, like application.status_changed."},"description":{"type":"string","title":"Description","description":"When Made sends it."}},"type":"object","required":["type","description"],"title":"WebhookEventType"},"WebhookLastDelivery":{"properties":{"status":{"type":"string","enum":["pending","retrying","succeeded","failed"],"title":"Status","description":"pending, retrying, succeeded, or failed (a final failure)."},"response_code":{"anyOf":[{"type":"integer"},{"type":"null"}],"title":"Response Code","description":"The HTTP status your endpoint answered, or null when it didn't answer."},"at":{"type":"string","format":"date-time","title":"At","description":"When Made last attempted it."}},"type":"object","required":["status","at"],"title":"WebhookLastDelivery"},"WebhookLimits":{"properties":{"max_endpoints":{"type":"integer","title":"Max Endpoints","description":"How many endpoints that aren't deleted a partner can have.","default":5}},"type":"object","title":"WebhookLimits"},"WebhookPerson":{"properties":{"name":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Name","description":"The portal member's name, when they gave one."},"email":{"type":"string","title":"Email","description":"The portal member's email."}},"type":"object","required":["email"],"title":"WebhookPerson","description":"The member who created an endpoint."},"WebhookSecretRotated":{"properties":{"endpoint":{"$ref":"#/components/schemas/WebhookEndpoint","description":"The endpoint, with the new secret's last 4 characters."},"signing_secret":{"type":"string","title":"Signing Secret","description":"The new whsec_… secret, shown only here."},"previous_secret_expires_at":{"type":"string","format":"date-time","title":"Previous Secret Expires At","description":"Until then, deliveries carry a second v1 signature made with the previous secret."}},"type":"object","required":["endpoint","signing_secret","previous_secret_expires_at"],"title":"WebhookSecretRotated"},"WebhookTestRequest":{"properties":{"event_type":{"anyOf":[{"type":"string","maxLength":64},{"type":"null"}],"title":"Event Type","description":"Defaults to ping. Any other type sends a sample with made-up ids and test: true in data."}},"type":"object","title":"WebhookTestRequest"}},"securitySchemes":{"Bearer":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Partner JWT from POST /v1/partners/auth. Not the customer link_ token."}}},"servers":[{"url":"https://api.madecard.com","description":"Live mode"},{"url":"https://api-sandbox.madecard.com","description":"Test mode"}]}