toolfactory API
One typed v4 contract — projected into CRUD · client · UI · cost · docs.
Credit-metered cloud tools (subtitle transcription, and more).
Rendered natively from the v4 document via a semantic IR — the requests-shape, ⛁ cost, signature collisions, effective (composed) shape, and the View-as access projection shown as-is, not flattened to a 3.1 view.
System
Liveness check.
curl -X GET "https://app.toolfactory.saastemly.com/api/health"
const res = await fetch("https://app.toolfactory.saastemly.com/api/health", {
method: "GET"
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/health")
data = res.json()| field | type | notes |
|---|---|---|
| ok* | boolean | |
| service* | string |
{
"ok": false,
"service": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Check if the API is working
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/ok" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/ok", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/ok", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes |
|---|---|---|
| ok* | boolean | Indicates if the API is working |
{
"ok": false
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Displays an error page
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/error" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/error", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/error", headers={"authorization": "Bearer <token>"})
data = res.json()"string"
| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Session
The current Better Auth session, or null when signed out.
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/get-session"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/get-session", {
method: "GET"
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/get-session")
data = res.json()| field | type | notes | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| user* | object |
|
{
"user": {
"id": "string",
"email": "string",
"name": "string"
}
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Sign out the current user
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/sign-out" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/sign-out", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/sign-out", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={})
data = res.json()| field | type | notes |
|---|---|---|
| success | boolean |
{
"success": false
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Update the current session
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/update-session" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/update-session", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/update-session", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={})
data = res.json()| field | type | notes |
|---|---|---|
| session | Session |
{
"session": {
"id": "string",
"expiresAt": "2026-01-01T00:00:00Z",
"token": "string",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z",
"ipAddress": "string",
"userAgent": "string",
"userId": "string"
}
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}List all active sessions for the user
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/list-sessions" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/list-sessions", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/list-sessions", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes |
|---|---|---|
| id* | string | readOnly |
| expiresAt* | string<date-time> | |
| token* | string | |
| createdAt* | string<date-time> | |
| updatedAt* | string<date-time> | |
| ipAddress | string | |
| userAgent | string | |
| userId* | string |
[
{
"id": "string",
"expiresAt": "2026-01-01T00:00:00Z",
"token": "string",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z",
"ipAddress": "string",
"userAgent": "string",
"userId": "string"
}
]| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Revoke a single session
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/revoke-session" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"token":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/revoke-session", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"token": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/revoke-session", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"token": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| token* | string | The token to revoke max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| status* | boolean | Indicates if the session was revoked successfully |
{
"status": false
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Revoke all sessions for the user
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/revoke-sessions" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/revoke-sessions", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/revoke-sessions", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={})
data = res.json()| field | type | notes |
|---|---|---|
| status* | boolean | Indicates if all sessions were revoked successfully |
{
"status": false
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Revoke all other sessions for the user except the current one
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/revoke-other-sessions" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/revoke-other-sessions", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/revoke-other-sessions", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={})
data = res.json()| field | type | notes |
|---|---|---|
| status* | boolean | Indicates if all other sessions were revoked successfully |
{
"status": false
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Credits
The signed-in user's credit balance.
curl -X GET "https://app.toolfactory.saastemly.com/api/credits" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/credits", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/credits", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes |
|---|---|---|
| balance* | integer | ≥ -9007199254740991≤ 9007199254740991 |
{
"balance": 0
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}The signed-in user's recent credit ledger (grants, subscription credits, and usage debits), newest first.
curl -X GET "https://app.toolfactory.saastemly.com/api/credits/transactions" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/credits/transactions", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/credits/transactions", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| transactions* | array | array of
|
{
"transactions": [
{
"id": "string",
"delta": 0,
"reason": "string",
"createdAt": 0,
"amountCents": 0
}
]
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Activity
The signed-in user's Activity, queryable Splunk-style: a bucketed event timeseries + matching ledger events. q filters by type=grant|usage, op (=/!=/~contains), and credits (numeric); range picks the window (1d/7d/30d).
curl -X GET "https://app.toolfactory.saastemly.com/api/logs?q=&range=" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/logs?q=&range=", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/logs?q=&range=", headers={"authorization": "Bearer <token>"})
data = res.json()| name | type | notes | value |
|---|---|---|---|
| q | string max len 200pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | max len 200pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | |
| range | enum "1d" "7d" "30d" |
| field | type | notes | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| series* | array | array of
| |||||||||||||||||||||
| events* | array | array of
| |||||||||||||||||||||
| range* | string | "1d" "7d" "30d" | |||||||||||||||||||||
| error | string |
{
"series": [
{
"t": 0,
"count": 0,
"credits": 0
}
],
"events": [
{
"id": "string",
"at": 0,
"type": "grant",
"op": "string",
"credits": 0,
"keyName": "string"
}
],
"range": "1d",
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Billing
Available credit packs (server-authoritative pricing).
curl -X GET "https://app.toolfactory.saastemly.com/api/billing/packs"
const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/packs", {
method: "GET"
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/billing/packs")
data = res.json()| field | type | notes | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| packs* | array | array of
|
{
"packs": [
{
"id": "string",
"credits": 0,
"priceCents": 0,
"label": "string"
}
]
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Available subscription plans (server-authoritative pricing) — recurring monthly credits at the subsidized rate.
curl -X GET "https://app.toolfactory.saastemly.com/api/billing/plans"
const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/plans", {
method: "GET"
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/billing/plans")
data = res.json()| field | type | notes | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| plans* | array | array of
|
{
"plans": [
{
"id": "string",
"name": "string",
"credits": 0,
"priceCents": 0,
"label": "string"
}
]
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Start a Stripe Checkout for a CUSTOM credit top-up (amount in cents, $1–$1000); returns the hosted checkout URL.
curl -X POST "https://app.toolfactory.saastemly.com/api/billing/checkout" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"amountCents":0}'const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/checkout", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"amountCents": 0
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/billing/checkout", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"amountCents": 0
})
data = res.json()| field | type | notes |
|---|---|---|
| amountCents* | integer | ≥ 100≤ 100000 |
| field | type | notes |
|---|---|---|
| url* | string |
{
"url": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Stripe publishable key for the on-site Payment Element (non-secret, public).
curl -X GET "https://app.toolfactory.saastemly.com/api/billing/payment-config"
const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/payment-config", {
method: "GET"
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/billing/payment-config")
data = res.json()| field | type | notes |
|---|---|---|
| publishableKey* | string |
{
"publishableKey": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Create a PaymentIntent for an ON-SITE credit top-up (Payment Element + in-page 3DS). Pay X → get Y: returns the client secret + the breakdown (the charge, any sales tax on top, the total, and the credits you'll receive — already net of the processing fee). Credit lands on the webhook.
curl -X POST "https://app.toolfactory.saastemly.com/api/billing/payment-intent" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"amountCents":0,"useDefaultCard":false}'const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/payment-intent", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"amountCents": 0,
"useDefaultCard": false
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/billing/payment-intent", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"amountCents": 0,
"useDefaultCard": False
})
data = res.json()| field | type | notes |
|---|---|---|
| amountCents* | integer | ≥ 100≤ 100000 |
| useDefaultCard | boolean |
| field | type | notes | |||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| clientSecret* | string | ||||||||||||||||
| breakdown* | object |
|
{
"clientSecret": "string",
"breakdown": {
"subtotalCents": 0,
"taxCents": 0,
"totalCents": 0,
"credits": 0
}
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}The signed-in user's CURRENT billing problem (a failed subscription renewal or a declined auto-top-up) needing action, or null when healthy. Drives the persistent alert shown in the billing tab, the sidebar, and the header so the user can fix it (update card / cancel subscription / turn off auto-top-up) from anywhere.
curl -X GET "https://app.toolfactory.saastemly.com/api/billing/payment-health" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/payment-health", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/billing/payment-health", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes |
|---|---|---|
| alert* | anyOf |
{
"alert": {
"kind": "subscription_past_due",
"message": "string"
}
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Quote an on-site top-up of amountCents WITHOUT charging: returns the credits you'll receive (already net of the processing fee), any sales tax added on top, and the total due — the SAME breakdown the PaymentIntent will use, so a one-click Pay matches exactly. Drives the live preview in the Add-credits dialog.
curl -X GET "https://app.toolfactory.saastemly.com/api/billing/purchase-quote?amountCents=" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/purchase-quote?amountCents=", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/billing/purchase-quote?amountCents=", headers={"authorization": "Bearer <token>"})
data = res.json()| name | type | notes | value |
|---|---|---|---|
| amountCents* | integer ≥ 100≤ 100000 | ≥ 100≤ 100000 |
| field | type | notes |
|---|---|---|
| subtotalCents* | integer | ≥ -9007199254740991≤ 9007199254740991 |
| taxCents* | integer | ≥ -9007199254740991≤ 9007199254740991 |
| totalCents* | integer | ≥ -9007199254740991≤ 9007199254740991 |
| credits* | integer | ≥ -9007199254740991≤ 9007199254740991 |
{
"subtotalCents": 0,
"taxCents": 0,
"totalCents": 0,
"credits": 0
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Create a $0 SetupIntent to vault a card on-site ('Add card'). Returns the client secret the Payment Element confirms.
curl -X POST "https://app.toolfactory.saastemly.com/api/billing/setup-intent" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/setup-intent", {
method: "POST",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/billing/setup-intent", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes |
|---|---|---|
| clientSecret* | string |
{
"clientSecret": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Subscribe to a plan ONE-CLICK on the saved default card (recurring credits at the subscriber rate). Returns the first invoice's client secret for in-page confirmation; credits land on invoice.paid.
curl -X POST "https://app.toolfactory.saastemly.com/api/billing/subscribe" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"planId":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/subscribe", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"planId": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/billing/subscribe", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"planId": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| planId* | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| clientSecret* | string |
{
"clientSecret": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}The signed-in user's CURRENT subscription (plan id · status · period end · pending cancel · the cycle's paid ceiling — the highest plan price already charged this cycle, below/at which a plan change defers to next renewal), or null when none.
curl -X GET "https://app.toolfactory.saastemly.com/api/billing/subscription" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/subscription", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/billing/subscription", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes |
|---|---|---|
| subscription* | anyOf |
{
"subscription": {
"planId": "string",
"status": "string",
"currentPeriodEnd": 0,
"cancelAtPeriodEnd": false,
"paidCeilingCents": 0
}
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Schedule the subscription to cancel at the period end (cancel:true) or resume it (cancel:false). Access continues until the period ends.
curl -X POST "https://app.toolfactory.saastemly.com/api/billing/subscription" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"cancel":false}'const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/subscription", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"cancel": false
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/billing/subscription", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"cancel": False
})
data = res.json()| field | type | notes |
|---|---|---|
| cancel* | boolean |
| field | type | notes |
|---|---|---|
| ok* | boolean |
{
"ok": false
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Switch the active subscription to another plan, decided against the cycle's PAID CEILING. ABOVE the ceiling = an immediate prorated upgrade (only the difference ABOVE the ceiling is charged to the saved card now; returns a clientSecret only if that charge needs 3DS) that grants the matching prorated credits. AT OR BELOW the ceiling = a deferred change that takes effect at the next renewal (currentPeriodEnd) with no charge — so changing back to a plan you already paid for this cycle never re-charges.
curl -X POST "https://app.toolfactory.saastemly.com/api/billing/subscription-plan" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"planId":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/subscription-plan", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"planId": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/billing/subscription-plan", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"planId": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| planId* | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| kind* | string | "upgrade" "downgrade" |
| clientSecret* | anyOf | |
| currentPeriodEnd* | integer | ≥ -9007199254740991≤ 9007199254740991 |
{
"kind": "upgrade",
"clientSecret": "string",
"currentPeriodEnd": 0
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Estimate a credit refund (buyback) WITHOUT executing: pass credits to quote a specific amount (omit to quote the whole balance, capped at it). Returns the credits being quoted, the net cash you'd receive (a flat rate below the cheapest you can buy, minus Stripe fees — always less than you paid), and whether enough refundable payment remains to process it.
curl -X GET "https://app.toolfactory.saastemly.com/api/billing/refund-quote?credits=" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/refund-quote?credits=", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/billing/refund-quote?credits=", headers={"authorization": "Bearer <token>"})
data = res.json()| name | type | notes | value |
|---|---|---|---|
| credits | integer ≤ 9007199254740991> 0 | ≤ 9007199254740991> 0 |
| field | type | notes |
|---|---|---|
| credits* | integer | ≥ -9007199254740991≤ 9007199254740991 |
| netCents* | integer | ≥ -9007199254740991≤ 9007199254740991 |
| eligible* | boolean |
{
"credits": 0,
"netCents": 0,
"eligible": false
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Refund (buy back) credits for cash to your original card(s): pass credits to refund a specific amount (omit to refund the whole balance, capped at it) at a flat rate below the cheapest acquisition price, minus Stripe fees — so you receive less than you paid. Stripe caps each refund at what was charged. Does not cancel a subscription.
curl -X POST "https://app.toolfactory.saastemly.com/api/billing/refund" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"credits":0,"requestId":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/refund", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"credits": 0,
"requestId": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/billing/refund", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"credits": 0,
"requestId": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| credits | integer | ≤ 9007199254740991> 0 |
| requestId | string | min len 8max len 128pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| refundedCents* | integer | ≥ -9007199254740991≤ 9007199254740991 |
| creditsRefunded* | integer | ≥ -9007199254740991≤ 9007199254740991 |
{
"refundedCents": 0,
"creditsRefunded": 0
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Open the Stripe billing portal to manage/cancel a subscription. Returns the portal URL.
curl -X POST "https://app.toolfactory.saastemly.com/api/billing/portal" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/portal", {
method: "POST",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/billing/portal", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes |
|---|---|---|
| url* | string |
{
"url": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}List the signed-in user's saved cards (each with its billing address); empty until they have a Stripe customer.
curl -X GET "https://app.toolfactory.saastemly.com/api/billing/methods" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/methods", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/billing/methods", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| methods* | array | array of
|
{
"methods": [
{
"id": "string",
"brand": "string",
"last4": "string",
"expMonth": 0,
"expYear": 0,
"name": "string",
"line1": "string",
"line2": "string",
"city": "string",
"region": "string",
"postalCode": "string",
"country": "string",
"isDefault": false
}
]
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}The signed-in user's auto top-up config (enabled · threshold credits · amount).
curl -X GET "https://app.toolfactory.saastemly.com/api/billing/auto-topup" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/auto-topup", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/billing/auto-topup", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes |
|---|---|---|
| enabled* | boolean | |
| thresholdCredits* | integer | ≥ -9007199254740991≤ 9007199254740991 |
| amountCents* | integer | ≥ -9007199254740991≤ 9007199254740991 |
{
"enabled": false,
"thresholdCredits": 0,
"amountCents": 0
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Enable/disable + configure auto top-up. Enabling requires a saved default card.
curl -X POST "https://app.toolfactory.saastemly.com/api/billing/auto-topup" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"enabled":false,"thresholdCredits":0,"amountCents":0}'const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/auto-topup", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"enabled": false,
"thresholdCredits": 0,
"amountCents": 0
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/billing/auto-topup", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"enabled": False,
"thresholdCredits": 0,
"amountCents": 0
})
data = res.json()| field | type | notes |
|---|---|---|
| enabled* | boolean | |
| thresholdCredits* | integer | ≥ 1≤ 100000 |
| amountCents* | integer | ≥ 100≤ 100000 |
| field | type | notes |
|---|---|---|
| ok* | boolean |
{
"ok": false
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Set one of the user's saved cards as the default for invoices.
curl -X POST "https://app.toolfactory.saastemly.com/api/billing/methods/default" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"id":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/methods/default", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"id": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/billing/methods/default", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"id": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| id* | string | min len 1max len 255pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| ok* | boolean |
{
"ok": false
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Remove (detach) one of the user's saved cards.
curl -X POST "https://app.toolfactory.saastemly.com/api/billing/methods/delete" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"id":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/billing/methods/delete", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"id": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/billing/methods/delete", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"id": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| id* | string | min len 1max len 255pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| ok* | boolean |
{
"ok": false
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}API keys
API keys with per-key usage + their place in the delegation tree — name, masked prefix, granted scopes, last used, own + POOLED-subtree credits used vs the key's PAID limit, its % share of the free rate-limit allowance, expiry, revoked state, parent key, and depth. A signed-in user sees all their keys; a caller holding keys:provision sees only the subtree it minted. (Caps are edited via /api/auth/api-key/*; children are minted via provisionKey and revoked via revokeKey.)
curl -X GET "https://app.toolfactory.saastemly.com/api/keys" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/keys", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/keys", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| keys* | array | array of
|
{
"keys": [
{
"id": "string",
"name": "string",
"start": "string",
"scopes": [
"string"
],
"enabled": false,
"expiresAt": 0,
"lastUsed": 0,
"creditsUsed": 0,
"subtreeCreditsUsed": 0,
"creditLimit": 0,
"rateLimitSharePct": 0,
"parentKeyId": "string",
"depth": 0
}
]
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Mint a CHILD API key. The caller (a signed-in user, or a key/MCP connection holding keys:provision) may request a name, a SUBSET of scopes, and per-key credit/rate caps; the server CLAMPS each to the caller's own effective grant — a child can never out-scope or out-spend an ancestor (caps are min/∩ up the chain, and the child's spend pools into every ancestor's cap). Returns the plaintext key ONCE. Revoking the parent cascade-revokes the child.
curl -X POST "https://app.toolfactory.saastemly.com/api/keys/provision" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"name":"string","scopes":[],"creditLimit":0,"rateLimitSharePct":0,"expiresInDays":0,"parentKeyId":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/keys/provision", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"name": "string",
"scopes": [],
"creditLimit": 0,
"rateLimitSharePct": 0,
"expiresInDays": 0,
"parentKeyId": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/keys/provision", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"name": "string",
"scopes": [],
"creditLimit": 0,
"rateLimitSharePct": 0,
"expiresInDays": 0,
"parentKeyId": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| name* | string | min len 1max len 120pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| scopes* | array | = [] max items 20 array of string max len 64pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| creditLimit | anyOf | |
| rateLimitSharePct | anyOf | |
| expiresInDays | anyOf | |
| parentKeyId | anyOf |
| field | type | notes |
|---|---|---|
| key* | string | |
| keyId* | string | |
| scopes* | array | array of string |
| creditLimit* | anyOf | |
| rateLimitSharePct* | anyOf |
{
"key": "string",
"keyId": "string",
"scopes": [
"string"
],
"creditLimit": 0,
"rateLimitSharePct": 0
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Cascade-revoke an API key — soft-disables the key AND every child key it provisioned, transitively. A signed-in user may revoke any of their own keys; a caller holding keys:provision may revoke only a key inside its own subtree. Returns how many keys were disabled.
curl -X POST "https://app.toolfactory.saastemly.com/api/keys/revoke" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"keyId":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/keys/revoke", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"keyId": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/keys/revoke", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"keyId": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| keyId* | string | min len 1max len 255pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| ok* | boolean | |
| revoked* | integer | ≥ -9007199254740991≤ 9007199254740991 |
{
"ok": false,
"revoked": 0
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Create a new API key for a user
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/api-key/create" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"configId":"string","name":"string","expiresIn":0,"prefix":"string","remaining":0,"metadata":{},"refillAmount":0,"refillInterval":0,"rateLimitTimeWindow":0,"rateLimitMax":0,"rateLimitEnabled":false,"permissions":{},"userId":"string","organizationId":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/api-key/create", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"configId": "string",
"name": "string",
"expiresIn": 0,
"prefix": "string",
"remaining": 0,
"metadata": {},
"refillAmount": 0,
"refillInterval": 0,
"rateLimitTimeWindow": 0,
"rateLimitMax": 0,
"rateLimitEnabled": false,
"permissions": {},
"userId": "string",
"organizationId": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/api-key/create", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"configId": "string",
"name": "string",
"expiresIn": 0,
"prefix": "string",
"remaining": 0,
"metadata": {},
"refillAmount": 0,
"refillInterval": 0,
"rateLimitTimeWindow": 0,
"rateLimitMax": 0,
"rateLimitEnabled": False,
"permissions": {},
"userId": "string",
"organizationId": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| configId | string | The configuration ID to use for the API key. If not provided, the default configuration will be used. max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| name | string | Name of the Api Key max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| expiresIn | number | null | Expiration time of the Api Key in seconds ≥ -1000000000000≤ 1000000000000 |
| prefix | string | Prefix of the Api Key max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| remaining | number | null | Remaining number of requests. Server side only ≥ -1000000000000≤ 1000000000000 |
| metadata | any | |
| refillAmount | number | Amount to refill the remaining count of the Api Key. server-only. Eg: 100 ≥ -1000000000000≤ 1000000000000 |
| refillInterval | number | Interval to refill the Api Key in milliseconds. server-only. Eg: 1000 ≥ -1000000000000≤ 1000000000000 |
| rateLimitTimeWindow | number | The duration in milliseconds where each request is counted. Once the `maxRequests` is reached, the request will be rejected until the `timeWindow` has passed, at which point the `timeWindow` will be reset. server-only. Eg: 1000 ≥ -1000000000000≤ 1000000000000 |
| rateLimitMax | number | Maximum amount of requests allowed within a window. Once the `maxRequests` is reached, the request will be rejected until the `timeWindow` has passed, at which point the `timeWindow` will be reset. server-only. Eg: 100 ≥ -1000000000000≤ 1000000000000 |
| rateLimitEnabled | boolean | Whether the key has rate limiting enabled. server-only. Eg: true |
| permissions | object | Permissions of the Api Key. |
| userId | string | User Id of the user that the Api Key belongs to. server-only. Eg: "user-id" max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| organizationId | string | Organization Id of the organization that the Api Key belongs to. Eg: 'org-id' max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| id* | string | Unique identifier of the API key |
| createdAt* | string<date-time> | Creation timestamp |
| updatedAt* | string<date-time> | Last update timestamp |
| name | string | null | Name of the API key |
| prefix | string | null | Prefix of the API key |
| start | string | null | Starting characters of the key (if configured) |
| key* | string | The full API key (only returned on creation) |
| enabled* | boolean | Whether the key is enabled |
| expiresAt | string | null | Expiration timestamp |
| referenceId* | string | ID of the reference owning the key |
| lastRefillAt | string | null | Last refill timestamp |
| lastRequest | string | null | Last request timestamp |
| metadata | object | null | Metadata associated with the key |
| rateLimitMax | number | null | Maximum requests in time window |
| rateLimitTimeWindow | number | null | Rate limit time window in milliseconds |
| remaining | number | null | Remaining requests |
| refillAmount | number | null | Amount to refill |
| refillInterval | number | null | Refill interval in milliseconds |
| rateLimitEnabled* | boolean | Whether rate limiting is enabled |
| requestCount* | number | Current request count in window |
| permissions | object | null | Permissions associated with the key |
{
"id": "string",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z",
"name": "string",
"prefix": "string",
"start": "string",
"key": "string",
"enabled": false,
"expiresAt": "2026-01-01T00:00:00Z",
"referenceId": "string",
"lastRefillAt": "2026-01-01T00:00:00Z",
"lastRequest": "2026-01-01T00:00:00Z",
"metadata": {},
"rateLimitMax": 0,
"rateLimitTimeWindow": 0,
"remaining": 0,
"refillAmount": 0,
"refillInterval": 0,
"rateLimitEnabled": false,
"requestCount": 0,
"permissions": {}
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Retrieve an existing API key by ID
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/api-key/get?configId=&id=" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/api-key/get?configId=&id=", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/api-key/get?configId=&id=", headers={"authorization": "Bearer <token>"})
data = res.json()| name | type | notes | value |
|---|---|---|---|
| configId | string max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | The configuration ID to use for the API key lookup. If not provided, the default configuration will be used. max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | |
| id | string max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | The id of the Api Key max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| id* | string | ID |
| name | string | null | The name of the key |
| start | string | null | Shows the first few characters of the API key, including the prefix. This allows you to show those few characters in the UI to make it easier for users to identify the API key. |
| prefix | string | null | The API Key prefix. Stored as plain text. |
| userId* | string | The owner of the user id |
| refillInterval | number | null | The interval in milliseconds between refills of the `remaining` count. Example: 3600000 // refill every hour (3600000ms = 1h) |
| refillAmount | number | null | The amount to refill |
| lastRefillAt | string | null | The last refill date |
| enabled* | boolean | Sets if key is enabled or disabled = true |
| rateLimitEnabled* | boolean | Whether the key has rate limiting enabled |
| rateLimitTimeWindow | number | null | The duration in milliseconds |
| rateLimitMax | number | null | Maximum amount of requests allowed within a window |
| requestCount* | number | The number of requests made within the rate limit time window |
| remaining | number | null | Remaining requests (every time api key is used this should updated and should be updated on refill as well) |
| lastRequest | string | null | When last request occurred |
| expiresAt | string | null | Expiry date of a key |
| createdAt* | string<date-time> | created at |
| updatedAt* | string<date-time> | updated at |
| metadata | object | null | Extra metadata about the apiKey |
| permissions | string | null | Permissions for the api key (stored as JSON string) |
{
"id": "string",
"name": "string",
"start": "string",
"prefix": "string",
"userId": "string",
"refillInterval": 0,
"refillAmount": 0,
"lastRefillAt": "2026-01-01T00:00:00Z",
"enabled": true,
"rateLimitEnabled": false,
"rateLimitTimeWindow": 0,
"rateLimitMax": 0,
"requestCount": 0,
"remaining": 0,
"lastRequest": "2026-01-01T00:00:00Z",
"expiresAt": "2026-01-01T00:00:00Z",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z",
"metadata": {},
"permissions": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Update an existing API key by ID
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/api-key/update" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"configId":"string","keyId":"string","userId":"string","name":"string","enabled":false,"remaining":0,"refillAmount":0,"refillInterval":0,"metadata":{},"expiresIn":0,"rateLimitEnabled":false,"rateLimitTimeWindow":0,"rateLimitMax":0,"permissions":{}}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/api-key/update", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"configId": "string",
"keyId": "string",
"userId": "string",
"name": "string",
"enabled": false,
"remaining": 0,
"refillAmount": 0,
"refillInterval": 0,
"metadata": {},
"expiresIn": 0,
"rateLimitEnabled": false,
"rateLimitTimeWindow": 0,
"rateLimitMax": 0,
"permissions": {}
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/api-key/update", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"configId": "string",
"keyId": "string",
"userId": "string",
"name": "string",
"enabled": False,
"remaining": 0,
"refillAmount": 0,
"refillInterval": 0,
"metadata": {},
"expiresIn": 0,
"rateLimitEnabled": False,
"rateLimitTimeWindow": 0,
"rateLimitMax": 0,
"permissions": {}
})
data = res.json()| field | type | notes |
|---|---|---|
| configId | string | The configuration ID to use for the API key lookup. If not provided, the default configuration will be used. max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| keyId* | string | The id of the Api Key max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| userId | string | The id of the user which the api key belongs to. server-only. Eg: "some-user-id" max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| name | string | The name of the key max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| enabled | boolean | Whether the Api Key is enabled or not |
| remaining | number | The number of remaining requests ≥ -1000000000000≤ 1000000000000 |
| refillAmount | number | The refill amount ≥ -1000000000000≤ 1000000000000 |
| refillInterval | number | The refill interval ≥ -1000000000000≤ 1000000000000 |
| metadata | any | |
| expiresIn | number | null | Expiration time of the Api Key in seconds ≥ -1000000000000≤ 1000000000000 |
| rateLimitEnabled | boolean | Whether the key has rate limiting enabled. |
| rateLimitTimeWindow | number | The duration in milliseconds where each request is counted. server-only. Eg: 1000 ≥ -1000000000000≤ 1000000000000 |
| rateLimitMax | number | Maximum amount of requests allowed within a window. Once the `maxRequests` is reached, the request will be rejected until the `timeWindow` has passed, at which point the `timeWindow` will be reset. server-only. Eg: 100 ≥ -1000000000000≤ 1000000000000 |
| permissions | object | null | Update the permissions on the API Key. server-only. |
| field | type | notes |
|---|---|---|
| id* | string | ID |
| name | string | null | The name of the key |
| start | string | null | Shows the first few characters of the API key, including the prefix. This allows you to show those few characters in the UI to make it easier for users to identify the API key. |
| prefix | string | null | The API Key prefix. Stored as plain text. |
| userId* | string | The owner of the user id |
| refillInterval | number | null | The interval in milliseconds between refills of the `remaining` count. Example: 3600000 // refill every hour (3600000ms = 1h) |
| refillAmount | number | null | The amount to refill |
| lastRefillAt | string | null | The last refill date |
| enabled* | boolean | Sets if key is enabled or disabled = true |
| rateLimitEnabled* | boolean | Whether the key has rate limiting enabled |
| rateLimitTimeWindow | number | null | The duration in milliseconds |
| rateLimitMax | number | null | Maximum amount of requests allowed within a window |
| requestCount* | number | The number of requests made within the rate limit time window |
| remaining | number | null | Remaining requests (every time api key is used this should updated and should be updated on refill as well) |
| lastRequest | string | null | When last request occurred |
| expiresAt | string | null | Expiry date of a key |
| createdAt* | string<date-time> | created at |
| updatedAt* | string<date-time> | updated at |
| metadata | object | null | Extra metadata about the apiKey |
| permissions | string | null | Permissions for the api key (stored as JSON string) |
{
"id": "string",
"name": "string",
"start": "string",
"prefix": "string",
"userId": "string",
"refillInterval": 0,
"refillAmount": 0,
"lastRefillAt": "2026-01-01T00:00:00Z",
"enabled": true,
"rateLimitEnabled": false,
"rateLimitTimeWindow": 0,
"rateLimitMax": 0,
"requestCount": 0,
"remaining": 0,
"lastRequest": "2026-01-01T00:00:00Z",
"expiresAt": "2026-01-01T00:00:00Z",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z",
"metadata": {},
"permissions": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Delete an existing API key
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/api-key/delete" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"keyId":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/api-key/delete", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"keyId": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/api-key/delete", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"keyId": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| keyId* | string | The id of the API key to delete max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| success* | boolean | Indicates if the API key was successfully deleted |
{
"success": false
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}List all API keys for the authenticated user or for a specific organization
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/api-key/list" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/api-key/list", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/api-key/list", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| apiKeys* | array | array of
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| total* | number | Total number of API keys | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| limit | number | null | The limit used for pagination | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| offset | number | null | The offset used for pagination |
{
"apiKeys": [
{
"id": "string",
"name": "string",
"start": "string",
"prefix": "string",
"userId": "string",
"refillInterval": 0,
"refillAmount": 0,
"lastRefillAt": "2026-01-01T00:00:00Z",
"enabled": true,
"rateLimitEnabled": false,
"rateLimitTimeWindow": 0,
"rateLimitMax": 0,
"requestCount": 0,
"remaining": 0,
"lastRequest": "2026-01-01T00:00:00Z",
"expiresAt": "2026-01-01T00:00:00Z",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z",
"metadata": {},
"permissions": "string"
}
],
"total": 0,
"limit": 0,
"offset": 0
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}MCP
The signed-in user's MCP OAuth connections — the apps they authorized via the MCP authorization flow, with granted tool scopes, the per-connection PAID credit cap + free-tier share, usage, and expiry. (Authorized via /api/auth/mcp/*; this is the usage-joined read.)
curl -X GET "https://app.toolfactory.saastemly.com/api/mcp/connections" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/mcp/connections", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/mcp/connections", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes | |||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| connections* | array | array of
|
{
"connections": [
{
"clientId": "string",
"name": "string",
"scopes": [
"string"
],
"creditLimit": 0,
"rateLimitSharePct": 0,
"creditsUsed": 0,
"expiresAt": 0,
"createdAt": 0
}
]
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Set an MCP connection's per-connection credit cap / free-tier share, and/or narrow its granted scopes (the live token updates immediately).
curl -X POST "https://app.toolfactory.saastemly.com/api/mcp/connections/update" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"clientId":"string","creditLimit":0,"rateLimitSharePct":0,"scopes":["string"]}'const res = await fetch("https://app.toolfactory.saastemly.com/api/mcp/connections/update", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"clientId": "string",
"creditLimit": 0,
"rateLimitSharePct": 0,
"scopes": [
"string"
]
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/mcp/connections/update", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"clientId": "string",
"creditLimit": 0,
"rateLimitSharePct": 0,
"scopes": [
"string"
]
})
data = res.json()| field | type | notes |
|---|---|---|
| clientId* | string | min len 1max len 255pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| creditLimit | anyOf | |
| rateLimitSharePct | anyOf | |
| scopes | array | max items 20 array of string max len 64pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| ok* | boolean |
{
"ok": false
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Revoke an MCP connection — its token stops working immediately.
curl -X POST "https://app.toolfactory.saastemly.com/api/mcp/connections/revoke" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"clientId":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/mcp/connections/revoke", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"clientId": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/mcp/connections/revoke", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"clientId": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| clientId* | string | min len 1max len 255pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| ok* | boolean |
{
"ok": false
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Handle OAuth2 consent. Supports both URL parameter-based flows (consent_code in body) and cookie-based flows (signed cookie).
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/oauth2/consent" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"accept":false,"consent_code":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/oauth2/consent", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"accept": false,
"consent_code": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/oauth2/consent", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"accept": False,
"consent_code": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| accept* | boolean | Whether the user accepts or denies the consent request |
| consent_code | string | The consent code from the authorization request. Optional if using cookie-based flow. max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| redirectURI* | string<uri> | The URI to redirect to, either with an authorization code or an error |
{
"redirectURI": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Better Auth — .well-known oauth-authorization-server (MCP)
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/.well-known/oauth-authorization-server" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/.well-known/oauth-authorization-server", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/.well-known/oauth-authorization-server", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Better Auth — .well-known oauth-protected-resource (MCP)
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/.well-known/oauth-protected-resource" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/.well-known/oauth-protected-resource", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/.well-known/oauth-protected-resource", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Authorize an OAuth2 request using MCP
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/mcp/authorize" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/mcp/authorize", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/mcp/authorize", headers={"authorization": "Bearer <token>"})
data = res.json(){}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Better Auth — mcp token (MCP)
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/mcp/token" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/mcp/token", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/mcp/token", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={})
data = res.json()| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Register an OAuth2 application
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/mcp/register" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"redirect_uris":["string"],"token_endpoint_auth_method":"none","grant_types":["authorization_code"],"response_types":["code"],"client_name":"string","client_uri":"string","logo_uri":"string","scope":"string","contacts":["string"],"tos_uri":"string","policy_uri":"string","jwks_uri":"string","jwks":{},"metadata":{},"software_id":"string","software_version":"string","software_statement":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/mcp/register", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"redirect_uris": [
"string"
],
"token_endpoint_auth_method": "none",
"grant_types": [
"authorization_code"
],
"response_types": [
"code"
],
"client_name": "string",
"client_uri": "string",
"logo_uri": "string",
"scope": "string",
"contacts": [
"string"
],
"tos_uri": "string",
"policy_uri": "string",
"jwks_uri": "string",
"jwks": {},
"metadata": {},
"software_id": "string",
"software_version": "string",
"software_statement": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/mcp/register", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"redirect_uris": [
"string"
],
"token_endpoint_auth_method": "none",
"grant_types": [
"authorization_code"
],
"response_types": [
"code"
],
"client_name": "string",
"client_uri": "string",
"logo_uri": "string",
"scope": "string",
"contacts": [
"string"
],
"tos_uri": "string",
"policy_uri": "string",
"jwks_uri": "string",
"jwks": {},
"metadata": {},
"software_id": "string",
"software_version": "string",
"software_statement": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| redirect_uris* | array | max items 1000 array of string max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| token_endpoint_auth_method | string | "none" "client_secret_basic" "client_secret_post" |
| grant_types | array | max items 1000 array of enum "authorization_code" "implicit" "password" "client_credentials" "refresh_token" "urn:ietf:params:oauth:grant-type:jwt-bearer" "urn:ietf:params:oauth:grant-type:saml2-bearer" |
| response_types | array | max items 1000 array of enum "code" "token" |
| client_name | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| client_uri | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| logo_uri | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| scope | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| contacts | array | max items 1000 array of string max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| tos_uri | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| policy_uri | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| jwks_uri | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| jwks | object | |
| metadata | object | |
| software_id | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| software_version | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| software_statement | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| name* | string | Name of the OAuth2 application |
| icon | string | null | Icon URL for the application |
| metadata | object | null | Additional metadata for the application |
| clientId* | string | Unique identifier for the client |
| clientSecret | string | Secret key for the client. Not included for public clients. |
| redirectUrls* | array | List of allowed redirect URLs array of string<uri> |
| type* | string | Type of the client "web" "public" |
| authenticationScheme* | string | Authentication scheme used by the client "client_secret" "none" |
| disabled* | boolean | Whether the client is disabled false |
| userId | string | null | ID of the user who registered the client, null if registered anonymously |
| createdAt* | string<date-time> | Creation timestamp |
| updatedAt* | string<date-time> | Last update timestamp |
{
"name": "string",
"icon": "string",
"metadata": {},
"clientId": "string",
"clientSecret": "string",
"redirectUrls": [
"string"
],
"type": "web",
"authenticationScheme": "client_secret",
"disabled": false,
"userId": "string",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Better Auth — mcp get-session (MCP)
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/mcp/get-session" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/mcp/get-session", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/mcp/get-session", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Tools
Transcribe audio/video to subtitles (SRT/VTT) via Whisper on Workers AI. Auth + credit-gated.
curl -X POST "https://app.toolfactory.saastemly.com/api/transcribe" \
-H "content-type: multipart/form-data" \
-H "authorization: Bearer <token>" \
-d '{"file":"string","format":"srt"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/transcribe", {
method: "POST",
headers: {
"content-type": "multipart/form-data",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"file": "string",
"format": "srt"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/transcribe", headers={"content-type": "multipart/form-data", "authorization": "Bearer <token>"}, json={
"file": "string",
"format": "srt"
})
data = res.json()| field | type | notes |
|---|---|---|
| file* | string | the audio/video file (multipart upload) max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| format | string | output subtitle format (default srt) "srt" "vtt" "ass" "ssa" "sbv" "lrc" |
"string"
| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Bulk-convert subtitle files between formats (srt/vtt/ass/ssa/sbv/smi/lrc/sub). Auth + credit-gated (1 credit per converted file). Per-item + idempotent on a batch requestId + per-file id (a retry never double-charges).
curl -X POST "https://app.toolfactory.saastemly.com/api/subtitle/convert" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"to":"srt","from":"srt","requestId":"string","files":[{"id":"string","name":"string","content":"string"}]}'const res = await fetch("https://app.toolfactory.saastemly.com/api/subtitle/convert", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"to": "srt",
"from": "srt",
"requestId": "string",
"files": [
{
"id": "string",
"name": "string",
"content": "string"
}
]
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/subtitle/convert", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"to": "srt",
"from": "srt",
"requestId": "string",
"files": [
{
"id": "string",
"name": "string",
"content": "string"
}
]
})
data = res.json()| field | type | notes | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| to* | string | "srt" "vtt" "ass" "ssa" "sbv" "lrc" | ||||||||||||
| from | string | "srt" "vtt" "ass" "ssa" "sbv" "lrc" | ||||||||||||
| requestId | string | min len 8max len 128pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | ||||||||||||
| files* | array | min items 1max items 20 array of
|
| field | type | notes | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| results* | array | the per-file outcome, in input order array of
| |||||||||||||||||||||
| totalCharged* | integer | credits debited across the batch ≥ -9007199254740991≤ 9007199254740991 | |||||||||||||||||||||
| replayed* | boolean | true when the whole batch was a retry that moved no new money |
{
"results": [
{
"id": "string",
"name": "string",
"status": "done",
"content": "string",
"error": "string",
"charged": 0
}
],
"totalCharged": 0,
"replayed": false
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
{
"type": "about:blank",
"title": "string",
"status": 0,
"detail": "string",
"instance": "string",
"errors": {},
"error": "string"
}Sign in
Sign in with a social provider
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/sign-in/social" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"callbackURL":"string","newUserCallbackURL":"string","errorCallbackURL":"string","provider":"apple","disableRedirect":false,"idToken":{"token":"string","nonce":"string","accessToken":"string","refreshToken":"string","expiresAt":0,"user":{"name":{"firstName":"string","lastName":"string"},"email":"string"}},"scopes":["string"],"requestSignUp":false,"loginHint":"string","additionalData":{}}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/sign-in/social", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"callbackURL": "string",
"newUserCallbackURL": "string",
"errorCallbackURL": "string",
"provider": "apple",
"disableRedirect": false,
"idToken": {
"token": "string",
"nonce": "string",
"accessToken": "string",
"refreshToken": "string",
"expiresAt": 0,
"user": {
"name": {
"firstName": "string",
"lastName": "string"
},
"email": "string"
}
},
"scopes": [
"string"
],
"requestSignUp": false,
"loginHint": "string",
"additionalData": {}
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/sign-in/social", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"callbackURL": "string",
"newUserCallbackURL": "string",
"errorCallbackURL": "string",
"provider": "apple",
"disableRedirect": False,
"idToken": {
"token": "string",
"nonce": "string",
"accessToken": "string",
"refreshToken": "string",
"expiresAt": 0,
"user": {
"name": {
"firstName": "string",
"lastName": "string"
},
"email": "string"
}
},
"scopes": [
"string"
],
"requestSignUp": False,
"loginHint": "string",
"additionalData": {}
})
data = res.json()| field | type | notes | |||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| callbackURL | string | Callback URL to redirect to after the user has signed in max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | |||||||||||||||||||||||||||||||||||||||
| newUserCallbackURL | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | |||||||||||||||||||||||||||||||||||||||
| errorCallbackURL | string | Callback URL to redirect to if an error happens max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | |||||||||||||||||||||||||||||||||||||||
| provider* | anyOf | ||||||||||||||||||||||||||||||||||||||||
| disableRedirect | boolean | Disable automatic redirection to the provider. Useful for handling the redirection yourself | |||||||||||||||||||||||||||||||||||||||
| idToken | object |
| |||||||||||||||||||||||||||||||||||||||
| scopes | array | Array of scopes to request from the provider. This will override the default scopes passed. max items 1000 array of string max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | |||||||||||||||||||||||||||||||||||||||
| requestSignUp | boolean | Explicitly request sign-up. Useful when disableImplicitSignUp is true for this provider | |||||||||||||||||||||||||||||||||||||||
| loginHint | string | The login hint to use for the authorization code request max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | |||||||||||||||||||||||||||||||||||||||
| additionalData | object |
| field | type | notes |
|---|---|---|
| token | string | |
| user | User | |
| url | string | |
| redirect* | boolean |
{
"token": "string",
"user": {
"id": "string",
"name": "string",
"email": "string",
"emailVerified": false,
"image": "string",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z"
},
"url": "string",
"redirect": false
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Better Auth — callback {id} (Sign in)
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/callback/:id" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/callback/:id", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/callback/:id", headers={"authorization": "Bearer <token>"})
data = res.json()| name | type | notes | value |
|---|---|---|---|
| id* | string max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Better Auth — callback {id} (Sign in)
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/callback/:id" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"code":"string","error":"string","device_id":"string","error_description":"string","state":"string","user":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/callback/:id", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"code": "string",
"error": "string",
"device_id": "string",
"error_description": "string",
"state": "string",
"user": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/callback/:id", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"code": "string",
"error": "string",
"device_id": "string",
"error_description": "string",
"state": "string",
"user": "string"
})
data = res.json()| name | type | notes | value |
|---|---|---|---|
| id* | string max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| code | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| error | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| device_id | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| error_description | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| state | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| user | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Link a social account to the user
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/link-social" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"callbackURL":"string","provider":"apple","idToken":{"token":"string","nonce":"string","accessToken":"string","refreshToken":"string","scopes":["string"]},"requestSignUp":false,"scopes":["string"],"errorCallbackURL":"string","disableRedirect":false,"additionalData":{}}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/link-social", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"callbackURL": "string",
"provider": "apple",
"idToken": {
"token": "string",
"nonce": "string",
"accessToken": "string",
"refreshToken": "string",
"scopes": [
"string"
]
},
"requestSignUp": false,
"scopes": [
"string"
],
"errorCallbackURL": "string",
"disableRedirect": false,
"additionalData": {}
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/link-social", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"callbackURL": "string",
"provider": "apple",
"idToken": {
"token": "string",
"nonce": "string",
"accessToken": "string",
"refreshToken": "string",
"scopes": [
"string"
]
},
"requestSignUp": False,
"scopes": [
"string"
],
"errorCallbackURL": "string",
"disableRedirect": False,
"additionalData": {}
})
data = res.json()| field | type | notes | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| callbackURL | string | The URL to redirect to after the user has signed in max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | ||||||||||||||||||
| provider* | anyOf | |||||||||||||||||||
| idToken | object |
| ||||||||||||||||||
| requestSignUp | boolean | |||||||||||||||||||
| scopes | array | Additional scopes to request from the provider max items 1000 array of string max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | ||||||||||||||||||
| errorCallbackURL | string | The URL to redirect to if there is an error during the link process max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | ||||||||||||||||||
| disableRedirect | boolean | Disable automatic redirection to the provider. Useful for handling the redirection yourself | ||||||||||||||||||
| additionalData | object |
| field | type | notes |
|---|---|---|
| url | string | The authorization URL to redirect the user to |
| redirect* | boolean | Indicates if the user should be redirected to the authorization URL |
| status | boolean |
{
"url": "string",
"redirect": false,
"status": false
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}List all accounts linked to the user
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/list-accounts" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/list-accounts", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/list-accounts", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes |
|---|---|---|
| id* | string | |
| providerId* | string | |
| createdAt* | string<date-time> | |
| updatedAt* | string<date-time> | |
| accountId* | string | |
| userId* | string | |
| scopes* | array | array of string |
[
{
"id": "string",
"providerId": "string",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z",
"accountId": "string",
"userId": "string",
"scopes": [
"string"
]
}
]| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Unlink an account
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/unlink-account" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"providerId":"string","accountId":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/unlink-account", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"providerId": "string",
"accountId": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/unlink-account", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"providerId": "string",
"accountId": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| providerId* | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| accountId | string | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| status | boolean |
{
"status": false
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Refresh the access token using a refresh token
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/refresh-token" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"providerId":"string","accountId":"string","userId":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/refresh-token", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"providerId": "string",
"accountId": "string",
"userId": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/refresh-token", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"providerId": "string",
"accountId": "string",
"userId": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| providerId* | string | The provider ID for the OAuth provider max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| accountId | string | The account ID associated with the refresh token max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| userId | string | The user ID associated with the account max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| tokenType | string | |
| idToken | string | |
| accessToken | string | |
| refreshToken | string | |
| accessTokenExpiresAt | string<date-time> | |
| refreshTokenExpiresAt | string<date-time> |
{
"tokenType": "string",
"idToken": "string",
"accessToken": "string",
"refreshToken": "string",
"accessTokenExpiresAt": "2026-01-01T00:00:00Z",
"refreshTokenExpiresAt": "2026-01-01T00:00:00Z"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Get a valid access token, doing a refresh if needed
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/get-access-token" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"providerId":"string","accountId":"string","userId":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/get-access-token", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"providerId": "string",
"accountId": "string",
"userId": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/get-access-token", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"providerId": "string",
"accountId": "string",
"userId": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| providerId* | string | The provider ID for the OAuth provider max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| accountId | string | The account ID associated with the refresh token max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| userId | string | The user ID associated with the account max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| tokenType | string | |
| idToken | string | |
| accessToken | string | |
| accessTokenExpiresAt | string<date-time> |
{
"tokenType": "string",
"idToken": "string",
"accessToken": "string",
"accessTokenExpiresAt": "2026-01-01T00:00:00Z"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Get the account info provided by the provider
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/account-info" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/account-info", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/account-info", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| user* | object |
| ||||||||||||||||||
| data* | object | object |
{
"user": {
"id": "string",
"name": "string",
"email": "string",
"image": "string",
"emailVerified": false
},
"data": {}
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Account
Update the current user
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/update-user" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"name":"string","image":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/update-user", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"name": "string",
"image": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/update-user", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"name": "string",
"image": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| name | string | The name of the user max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| image | string | null | The image of the user max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| user | User |
{
"user": {
"id": "string",
"name": "string",
"email": "string",
"emailVerified": false,
"image": "string",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z"
}
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Delete the user
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/delete-user" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"callbackURL":"string","password":"string","token":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/delete-user", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"callbackURL": "string",
"password": "string",
"token": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/delete-user", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"callbackURL": "string",
"password": "string",
"token": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| callbackURL | string | The callback URL to redirect to after the user is deleted max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| password | string | The user's password. Required if session is not fresh max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| token | string | The deletion verification token max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| success* | boolean | Indicates if the operation was successful |
| message* | string | Status message of the deletion process "User deleted" "Verification email sent" |
{
"success": false,
"message": "User deleted"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Callback to complete user deletion with verification token
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/delete-user/callback?token=&callbackURL=" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/delete-user/callback?token=&callbackURL=", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/delete-user/callback?token=&callbackURL=", headers={"authorization": "Bearer <token>"})
data = res.json()| name | type | notes | value |
|---|---|---|---|
| token | string max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | The token to verify the deletion request max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | |
| callbackURL | string max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | The URL to redirect to after deletion max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| success* | boolean | Indicates if the deletion was successful |
| message* | string | Confirmation message "User deleted" |
{
"success": false,
"message": "User deleted"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Passkey
Generate registration options for a new passkey
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/passkey/generate-register-options?authenticatorAttachment=&name=&context=" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/passkey/generate-register-options?authenticatorAttachment=&name=&context=", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/passkey/generate-register-options?authenticatorAttachment=&name=&context=", headers={"authorization": "Bearer <token>"})
data = res.json()| name | type | notes | value |
|---|---|---|---|
| authenticatorAttachment | string max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | |
| name | string max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | |
| context | string max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| challenge | string | |||||||||||||
| rp | object |
| ||||||||||||
| user | object |
| ||||||||||||
| pubKeyCredParams | array | array of
| ||||||||||||
| timeout | number | |||||||||||||
| excludeCredentials | array | array of
| ||||||||||||
| authenticatorSelection | object |
| ||||||||||||
| attestation | string | |||||||||||||
| extensions | object |
{
"challenge": "string",
"rp": {
"name": "string",
"id": "string"
},
"user": {
"id": "string",
"name": "string",
"displayName": "string"
},
"pubKeyCredParams": [
{
"type": "string",
"alg": 0
}
],
"timeout": 0,
"excludeCredentials": [
{
"id": "string",
"type": "string",
"transports": [
"string"
]
}
],
"authenticatorSelection": {
"authenticatorAttachment": "string",
"requireResidentKey": false,
"userVerification": "string"
},
"attestation": "string",
"extensions": {}
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Generate authentication options for a passkey
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/passkey/generate-authenticate-options" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/passkey/generate-authenticate-options", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/passkey/generate-authenticate-options", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| challenge | string | |||||||||||||
| rp | object |
| ||||||||||||
| user | object |
| ||||||||||||
| timeout | number | |||||||||||||
| allowCredentials | array | array of
| ||||||||||||
| userVerification | string | |||||||||||||
| authenticatorSelection | object |
| ||||||||||||
| extensions | object |
{
"challenge": "string",
"rp": {
"name": "string",
"id": "string"
},
"user": {
"id": "string",
"name": "string",
"displayName": "string"
},
"timeout": 0,
"allowCredentials": [
{
"id": "string",
"type": "string",
"transports": [
"string"
]
}
],
"userVerification": "string",
"authenticatorSelection": {
"authenticatorAttachment": "string",
"requireResidentKey": false,
"userVerification": "string"
},
"extensions": {}
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Verify registration of a new passkey
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/passkey/verify-registration" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"name":"string","response":{"id":"string","rawId":"string","type":"public-key","authenticatorAttachment":"platform","clientExtensionResults":{},"response":{"clientDataJSON":"string","attestationObject":"string","transports":["string"],"authenticatorData":"string","publicKey":"string","publicKeyAlgorithm":0}}}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/passkey/verify-registration", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"name": "string",
"response": {
"id": "string",
"rawId": "string",
"type": "public-key",
"authenticatorAttachment": "platform",
"clientExtensionResults": {},
"response": {
"clientDataJSON": "string",
"attestationObject": "string",
"transports": [
"string"
],
"authenticatorData": "string",
"publicKey": "string",
"publicKeyAlgorithm": 0
}
}
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/passkey/verify-registration", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"name": "string",
"response": {
"id": "string",
"rawId": "string",
"type": "public-key",
"authenticatorAttachment": "platform",
"clientExtensionResults": {},
"response": {
"clientDataJSON": "string",
"attestationObject": "string",
"transports": [
"string"
],
"authenticatorData": "string",
"publicKey": "string",
"publicKeyAlgorithm": 0
}
}
})
data = res.json()| field | type | notes | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| name | string | max len 256pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ | ||||||||||||||||||||||||||||||||||||||||||
| response* | object |
|
| field | type | notes |
|---|---|---|
| id* | string | readOnly |
| name | string | |
| publicKey* | string | |
| userId* | string | |
| credentialID* | string | |
| counter* | number | |
| deviceType* | string | |
| backedUp* | boolean | |
| transports | string | |
| createdAt | string<date-time> | |
| aaguid | string |
{
"id": "string",
"name": "string",
"publicKey": "string",
"userId": "string",
"credentialID": "string",
"counter": 0,
"deviceType": "string",
"backedUp": false,
"transports": "string",
"createdAt": "2026-01-01T00:00:00Z",
"aaguid": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Verify authentication of a passkey
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/passkey/verify-authentication" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"response":{"id":"string","rawId":"string","type":"public-key","authenticatorAttachment":"platform","clientExtensionResults":{},"response":{"clientDataJSON":"string","authenticatorData":"string","signature":"string","userHandle":"string"}}}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/passkey/verify-authentication", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"response": {
"id": "string",
"rawId": "string",
"type": "public-key",
"authenticatorAttachment": "platform",
"clientExtensionResults": {},
"response": {
"clientDataJSON": "string",
"authenticatorData": "string",
"signature": "string",
"userHandle": "string"
}
}
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/passkey/verify-authentication", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"response": {
"id": "string",
"rawId": "string",
"type": "public-key",
"authenticatorAttachment": "platform",
"clientExtensionResults": {},
"response": {
"clientDataJSON": "string",
"authenticatorData": "string",
"signature": "string",
"userHandle": "string"
}
}
})
data = res.json()| field | type | notes | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| response* | object |
|
| field | type | notes |
|---|---|---|
| session | Session | |
| user | User |
{
"session": {
"id": "string",
"expiresAt": "2026-01-01T00:00:00Z",
"token": "string",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z",
"ipAddress": "string",
"userAgent": "string",
"userId": "string"
},
"user": {
"id": "string",
"name": "string",
"email": "string",
"emailVerified": false,
"image": "string",
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-01T00:00:00Z"
}
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}List all passkeys for the authenticated user
curl -X GET "https://app.toolfactory.saastemly.com/api/auth/passkey/list-user-passkeys" \ -H "authorization: Bearer <token>"
const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/passkey/list-user-passkeys", {
method: "GET",
headers: {
"authorization": "Bearer <token>"
}
});
const data = await res.json();import requests
res = requests.get("https://app.toolfactory.saastemly.com/api/auth/passkey/list-user-passkeys", headers={"authorization": "Bearer <token>"})
data = res.json()| field | type | notes |
|---|---|---|
| id* | string | readOnly |
| name | string | |
| publicKey* | string | |
| userId* | string | |
| credentialID* | string | |
| counter* | number | |
| deviceType* | string | |
| backedUp* | boolean | |
| transports | string | |
| createdAt | string<date-time> | |
| aaguid | string |
[
{
"id": "string",
"name": "string",
"publicKey": "string",
"userId": "string",
"credentialID": "string",
"counter": 0,
"deviceType": "string",
"backedUp": false,
"transports": "string",
"createdAt": "2026-01-01T00:00:00Z",
"aaguid": "string"
}
]| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Delete a specific passkey
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/passkey/delete-passkey" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"id":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/passkey/delete-passkey", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"id": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/passkey/delete-passkey", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"id": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| id* | string | The ID of the passkey to delete. Eg: "some-passkey-id" max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| status* | boolean | Indicates whether the deletion was successful |
{
"status": false
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}Update a specific passkey's name
curl -X POST "https://app.toolfactory.saastemly.com/api/auth/passkey/update-passkey" \
-H "content-type: application/json" \
-H "authorization: Bearer <token>" \
-d '{"id":"string","name":"string"}'const res = await fetch("https://app.toolfactory.saastemly.com/api/auth/passkey/update-passkey", {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": "Bearer <token>"
},
body: JSON.stringify({
"id": "string",
"name": "string"
})
});
const data = await res.json();import requests
res = requests.post("https://app.toolfactory.saastemly.com/api/auth/passkey/update-passkey", headers={"content-type": "application/json", "authorization": "Bearer <token>"}, json={
"id": "string",
"name": "string"
})
data = res.json()| field | type | notes |
|---|---|---|
| id* | string | The ID of the passkey which will be updated. Eg: "passkey-id" max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| name* | string | The new name which the passkey will be updated to. Eg: "my-new-passkey-name" min len 1max len 1024pattern ^[^\u0000-\u0008\u000b\u000c\u000e-\u001f]*$ |
| field | type | notes |
|---|---|---|
| passkey* | Passkey |
{
"passkey": {
"id": "string",
"name": "string",
"publicKey": "string",
"userId": "string",
"credentialID": "string",
"counter": 0,
"deviceType": "string",
"backedUp": false,
"transports": "string",
"createdAt": "2026-01-01T00:00:00Z",
"aaguid": "string"
}
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message* | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}| field | type | notes |
|---|---|---|
| message | string |
{
"message": "string"
}One contract → every layer
The same v4 contract is not a document but a function — it projects into each layer below. Change the contract, and all of them change.
Hardening — input-validation grade 🛡 A 96/100
Untrusted input is the attack surface. Every string wants a maxLength + a pattern (a character allowlist), every number a maximum, every array a maxItems, every object closed (additionalProperties:false) + typed, and no any/unknown — so malformed or oversized input can't break the system. 1 high · 14 medium · 0 low findings.
- medium object 'updateSession/body' allows additionalProperties → set additionalProperties:false to forbid unexpected keys
- medium object 'updateSession/body' has no defined properties (a free-form bag) → define properties with explicit types (a closed empty object additionalProperties:false is fine for no-input)
- medium object 'postOperation/body' allows additionalProperties → set additionalProperties:false to forbid unexpected keys
- medium object 'postOperation/body' has no defined properties (a free-form bag) → define properties with explicit types (a closed empty object additionalProperties:false is fine for no-input)
- high 'postOperation/body/metadata' has no `type` (any/unknown) → give it a concrete type + bounds — never accept unconstrained input
- medium object 'postOperation/body/permissions' allows additionalProperties → set additionalProperties:false to forbid unexpected keys
- medium object 'postOperation/body/permissions' has no defined properties (a free-form bag) → define properties with explicit types (a closed empty object additionalProperties:false is fine for no-input)
- high 'postOperation/body/metadata' has no `type` (any/unknown) → give it a concrete type + bounds — never accept unconstrained input
- medium object 'postOperation/body/permissions' allows additionalProperties → set additionalProperties:false to forbid unexpected keys
- medium object 'postOperation/body/permissions' has no defined properties (a free-form bag) → define properties with explicit types (a closed empty object additionalProperties:false is fine for no-input)
- medium object 'postOperation/body/jwks' allows additionalProperties → set additionalProperties:false to forbid unexpected keys
- medium object 'postOperation/body/jwks' has no defined properties (a free-form bag) → define properties with explicit types (a closed empty object additionalProperties:false is fine for no-input)
- medium object 'postOperation/body/metadata' allows additionalProperties → set additionalProperties:false to forbid unexpected keys
- medium object 'postOperation/body/metadata' has no defined properties (a free-form bag) → define properties with explicit types (a closed empty object additionalProperties:false is fine for no-input)
- medium object 'linkSocialAccount/body/additionalData' allows additionalProperties → set additionalProperties:false to forbid unexpected keys
- medium object 'linkSocialAccount/body/additionalData' has no defined properties (a free-form bag) → define properties with explicit types (a closed empty object additionalProperties:false is fine for no-input)
- medium object 'socialSignIn/body/additionalData' allows additionalProperties → set additionalProperties:false to forbid unexpected keys
- medium object 'socialSignIn/body/additionalData' has no defined properties (a free-form bag) → define properties with explicit types (a closed empty object additionalProperties:false is fine for no-input)
Facet panels — v4 operational facets
Advisory facets a v4 contract carries beyond shape — what a 3.x tool can't host. Rate limits: the declared per-op budget. Background costs (C024/C025): costs that accrue when an EVENT fires (a webhook / cron / queue), not when the route runs — with who gets charged.
Rate limits 68/72 operations metered
| operation | route | budget | key |
|---|---|---|---|
getOperation | GET api/auth/.well-known/oauth-authorization-server | 15 / 60s | ip |
getOperation | GET api/auth/.well-known/oauth-protected-resource | 15 / 60s | ip |
getOperation | GET api/auth/account-info | 15 / 60s | ip |
postOperation | POST api/auth/api-key/create | 15 / 60s | ip |
postOperation | POST api/auth/api-key/delete | 15 / 60s | ip |
getOperation | GET api/auth/api-key/get | 15 / 60s | ip |
getOperation | GET api/auth/api-key/list | 15 / 60s | ip |
postOperation | POST api/auth/api-key/update | 15 / 60s | ip |
getOperation | GET api/auth/callback/{id} | 15 / 60s | ip |
postOperation | POST api/auth/callback/{id} | 15 / 60s | ip |
getOperation | GET api/auth/delete-user/callback | 15 / 60s | ip |
deleteUser | POST api/auth/delete-user | 15 / 60s | ip |
getOperation | GET api/auth/error | 15 / 60s | ip |
postOperation | POST api/auth/get-access-token | 15 / 60s | ip |
linkSocialAccount | POST api/auth/link-social | 15 / 60s | ip |
listUserAccounts | GET api/auth/list-accounts | 15 / 60s | ip |
listUserSessions | GET api/auth/list-sessions | 15 / 60s | ip |
getOperation | GET api/auth/mcp/authorize | 15 / 60s | ip |
getOperation | GET api/auth/mcp/get-session | 15 / 60s | ip |
postOperation | POST api/auth/mcp/register | 15 / 60s | ip |
postOperation | POST api/auth/mcp/token | 15 / 60s | ip |
postOperation | POST api/auth/oauth2/consent | 15 / 60s | ip |
getOperation | GET api/auth/ok | 15 / 60s | ip |
postOperation | POST api/auth/passkey/delete-passkey | 15 / 60s | ip |
passkeyGenerateAuthenticateOptions | GET api/auth/passkey/generate-authenticate-options | 15 / 60s | ip |
generatePasskeyRegistrationOptions | GET api/auth/passkey/generate-register-options | 15 / 60s | ip |
getOperation | GET api/auth/passkey/list-user-passkeys | 15 / 60s | ip |
postOperation | POST api/auth/passkey/update-passkey | 15 / 60s | ip |
passkeyVerifyAuthentication | POST api/auth/passkey/verify-authentication | 15 / 60s | ip |
passkeyVerifyRegistration | POST api/auth/passkey/verify-registration | 15 / 60s | ip |
postOperation | POST api/auth/refresh-token | 15 / 60s | ip |
postOperation | POST api/auth/revoke-other-sessions | 15 / 60s | ip |
postOperation | POST api/auth/revoke-session | 15 / 60s | ip |
postOperation | POST api/auth/revoke-sessions | 15 / 60s | ip |
socialSignIn | POST api/auth/sign-in/social | 15 / 60s | ip |
signOut | POST api/auth/sign-out | 15 / 60s | ip |
postOperation | POST api/auth/unlink-account | 15 / 60s | ip |
updateSession | POST api/auth/update-session | 15 / 60s | ip |
updateUser | POST api/auth/update-user | 15 / 60s | ip |
getAutoTopup | GET api/billing/auto-topup | 30 / 60s | principal |
updateAutoTopup | POST api/billing/auto-topup | 30 / 60s | principal |
checkout | POST api/billing/checkout | 20 / 60s | principal |
setDefaultPaymentMethod | POST api/billing/methods/default | 30 / 60s | principal |
deletePaymentMethod | POST api/billing/methods/delete | 30 / 60s | principal |
listPaymentMethods | GET api/billing/methods | 60 / 60s | principal |
getPacks | GET api/billing/packs | 60 / 60s | ip |
getPaymentConfig | GET api/billing/payment-config | 60 / 60s | ip |
getPaymentHealth | GET api/billing/payment-health | 60 / 60s | principal |
createPaymentIntent | POST api/billing/payment-intent | 20 / 60s | principal |
getPlans | GET api/billing/plans | 60 / 60s | ip |
portal | POST api/billing/portal | 20 / 60s | principal |
getPurchaseQuote | GET api/billing/purchase-quote | 30 / 60s | principal |
getRefundQuote | GET api/billing/refund-quote | 30 / 60s | principal |
refundCredits | POST api/billing/refund | 10 / 60s | principal |
createSetupIntent | POST api/billing/setup-intent | 20 / 60s | principal |
subscribe | POST api/billing/subscribe | 20 / 60s | principal |
changePlan | POST api/billing/subscription-plan | 20 / 60s | principal |
cancelSubscription | POST api/billing/subscription | 20 / 60s | principal |
getSubscription | GET api/billing/subscription | 60 / 60s | principal |
listTransactions | GET api/credits/transactions | 60 / 60s | principal |
getCredits | GET api/credits | 60 / 60s | principal |
provisionKey | POST api/keys/provision | 30 / 60s | principal |
revokeKey | POST api/keys/revoke | 30 / 60s | principal |
getApiKeys | GET api/keys | 60 / 60s | principal |
getLogs | GET api/logs | 60 / 60s | principal |
revokeMcpConnection | POST api/mcp/connections/revoke | 30 / 60s | principal |
updateMcpConnection | POST api/mcp/connections/update | 30 / 60s | principal |
getMcpConnections | GET api/mcp/connections | 60 / 60s | principal |
Background-event costs 0 deferred
no background-triggered costs — every cost accrues on its own route.
Cost Explorer + Workflow Calculator
Every operation by declared cost + source (a contract fact, not telemetry). Tick a sequence of operations to build a WORKFLOW — see its cumulative cost + per-source breakdown + a monthly projection. Budget a user journey before you build it. No 3.x tool models cost at all.
ADA Resolution Playground
v4 dispatches a request to a NAMED operation by its computed signature, not by method+path (which need not be unique). Enter a request and watch it resolve — uniquely, to a collision, or to a runtime-dependent set. A question 3.x never had to ask.
Reachability — the contract refracted per viewer
Access is a contract facet (x-suluk-access). ● full · ◐ own rows only · · not reachable. The View as lens recomputes the visible operation set from this.
| operation | requires | Anonymous | Signed-in user | Admin |
|---|---|---|---|---|
| health | anyone | ● | ● | ● |
| getSession | anyone | ● | ● | ● |
| getCredits | authenticated | · | ● | ● |
| listTransactions | authenticated | · | ● | ● |
| getLogs | authenticated | · | ● | ● |
| getPacks | anyone | ● | ● | ● |
| getPlans | anyone | ● | ● | ● |
| checkout | authenticated | · | ● | ● |
| getPaymentConfig | anyone | ● | ● | ● |
| createPaymentIntent | authenticated | · | ● | ● |
| getPaymentHealth | authenticated | · | ● | ● |
| getApiKeys | authenticated | · | ● | ● |
| provisionKey | authenticated | · | ● | ● |
| revokeKey | authenticated | · | ● | ● |
| getMcpConnections | authenticated | · | ● | ● |
| updateMcpConnection | authenticated | · | ● | ● |
| revokeMcpConnection | authenticated | · | ● | ● |
| getPurchaseQuote | authenticated | · | ● | ● |
| createSetupIntent | authenticated | · | ● | ● |
| subscribe | authenticated | · | ● | ● |
| getSubscription | authenticated | · | ● | ● |
| cancelSubscription | authenticated | · | ● | ● |
| changePlan | authenticated | · | ● | ● |
| getRefundQuote | authenticated | · | ● | ● |
| refundCredits | authenticated | · | ● | ● |
| portal | authenticated | · | ● | ● |
| listPaymentMethods | authenticated | · | ● | ● |
| getAutoTopup | authenticated | · | ● | ● |
| updateAutoTopup | authenticated | · | ● | ● |
| setDefaultPaymentMethod | authenticated | · | ● | ● |
| deletePaymentMethod | authenticated | · | ● | ● |
| transcribe | authenticated | · | ● | ● |
| convertSubtitle | authenticated | · | ● | ● |
| socialSignIn | anyone | ● | ● | ● |
| getOperation | anyone | ● | ● | ● |
| postOperation | anyone | ● | ● | ● |
| signOut | authenticated | · | ● | ● |
| updateSession | authenticated | · | ● | ● |
| updateUser | authenticated | · | ● | ● |
| deleteUser | authenticated | · | ● | ● |
| listUserSessions | authenticated | · | ● | ● |
| postOperation | authenticated | · | ● | ● |
| postOperation | authenticated | · | ● | ● |
| postOperation | authenticated | · | ● | ● |
| linkSocialAccount | authenticated | · | ● | ● |
| listUserAccounts | authenticated | · | ● | ● |
| getOperation | authenticated | · | ● | ● |
| postOperation | authenticated | · | ● | ● |
| postOperation | authenticated | · | ● | ● |
| postOperation | authenticated | · | ● | ● |
| getOperation | authenticated | · | ● | ● |
| getOperation | anyone | ● | ● | ● |
| getOperation | anyone | ● | ● | ● |
| postOperation | authenticated | · | ● | ● |
| getOperation | authenticated | · | ● | ● |
| postOperation | authenticated | · | ● | ● |
| postOperation | authenticated | · | ● | ● |
| getOperation | authenticated | · | ● | ● |
| generatePasskeyRegistrationOptions | authenticated | · | ● | ● |
| passkeyGenerateAuthenticateOptions | anyone | ● | ● | ● |
| passkeyVerifyRegistration | authenticated | · | ● | ● |
| passkeyVerifyAuthentication | anyone | ● | ● | ● |
| getOperation | authenticated | · | ● | ● |
| postOperation | authenticated | · | ● | ● |
| postOperation | authenticated | · | ● | ● |
| postOperation | authenticated | · | ● | ● |
| getOperation | anyone | ● | ● | ● |
| getOperation | anyone | ● | ● | ● |
| getOperation | anyone | ● | ● | ● |
| postOperation | anyone | ● | ● | ● |
| postOperation | anyone | ● | ● | ● |
| getOperation | authenticated | · | ● | ● |
Models
ProblemDetails
| field | type | notes |
|---|---|---|
| type* | string<uri-reference> | = "about:blank" |
| title* | string | |
| status* | integer | |
| detail | string | |
| instance | string<uri-reference> | |
| errors | object | |
| error | string | deprecated |
User
| field | type | notes |
|---|---|---|
| id* | string | readOnly |
| name* | string | |
| email* | string | |
| emailVerified* | boolean | = false readOnly |
| image | string | |
| createdAt* | string<date-time> | |
| updatedAt* | string<date-time> |
Session
| field | type | notes |
|---|---|---|
| id* | string | readOnly |
| expiresAt* | string<date-time> | |
| token* | string | |
| createdAt* | string<date-time> | |
| updatedAt* | string<date-time> | |
| ipAddress | string | |
| userAgent | string | |
| userId* | string |
Account
| field | type | notes |
|---|---|---|
| id* | string | readOnly |
| accountId* | string | |
| providerId* | string | |
| userId* | string | |
| accessToken | string | |
| refreshToken | string | |
| idToken | string | |
| accessTokenExpiresAt | string<date-time> | |
| refreshTokenExpiresAt | string<date-time> | |
| scope | string | |
| password | string | |
| createdAt* | string<date-time> | |
| updatedAt* | string<date-time> |
Verification
| field | type | notes |
|---|---|---|
| id* | string | readOnly |
| identifier* | string | |
| value* | string | |
| expiresAt* | string<date-time> | |
| createdAt* | string<date-time> | |
| updatedAt* | string<date-time> |
Apikey
| field | type | notes |
|---|---|---|
| id* | string | readOnly |
| configId* | string | = "default" readOnly |
| name | string | readOnly |
| start | string | readOnly |
| referenceId* | string | readOnly |
| prefix | string | readOnly |
| key* | string | readOnly |
| refillInterval | number | readOnly |
| refillAmount | number | readOnly |
| lastRefillAt | string<date-time> | readOnly |
| enabled | boolean | = true readOnly |
| rateLimitEnabled | boolean | = true readOnly |
| rateLimitTimeWindow | number | = 86400000 readOnly |
| rateLimitMax | number | = 10 readOnly |
| requestCount | number | = 0 readOnly |
| remaining | number | readOnly |
| lastRequest | string<date-time> | readOnly |
| expiresAt | string<date-time> | readOnly |
| createdAt* | string<date-time> | readOnly |
| updatedAt* | string<date-time> | readOnly |
| permissions | string | readOnly |
| metadata | string |
Passkey
| field | type | notes |
|---|---|---|
| id* | string | readOnly |
| name | string | |
| publicKey* | string | |
| userId* | string | |
| credentialID* | string | |
| counter* | number | |
| deviceType* | string | |
| backedUp* | boolean | |
| transports | string | |
| createdAt | string<date-time> | |
| aaguid | string |
OauthApplication
| field | type | notes |
|---|---|---|
| id* | string | readOnly |
| name | string | |
| icon | string | |
| metadata | string | |
| clientId | string | |
| clientSecret | string | |
| redirectUrls | string | |
| type | string | |
| disabled | boolean | = false |
| userId | string | |
| createdAt | string<date-time> | |
| updatedAt | string<date-time> |
OauthAccessToken
| field | type | notes |
|---|---|---|
| id* | string | readOnly |
| accessToken | string | |
| refreshToken | string | |
| accessTokenExpiresAt | string<date-time> | |
| refreshTokenExpiresAt | string<date-time> | |
| clientId | string | |
| userId | string | |
| scopes | string | |
| createdAt | string<date-time> | |
| updatedAt | string<date-time> |
OauthConsent
| field | type | notes |
|---|---|---|
| id* | string | readOnly |
| clientId | string | |
| userId | string | |
| scopes | string | |
| createdAt | string<date-time> | |
| updatedAt | string<date-time> | |
| consentGiven | boolean |
Authentication
API Key authentication via cookie
Bearer token authentication