endpointr / docs

Endpointr API

basehttps://api.endpointr.com
authBearer JWT
endpoints838

Endpointr API

A self-hosted REST gateway. JWT Bearer auth on every call; per-customer encrypted vault for third-party credentials; auto-generated REST routes; first-class outbound + inbound webhooks; provider-failover for AI; stateless OAuth relay for user-scoped credentials.

One contract for everything you'd otherwise glue together yourself.

Quick start (60 seconds, zero to first 200)

1. Import this collection into Postman.
2. Edit the collection variables (right-click collection → *Edit* → *Variables*):
| Variable | Set to |
|---|---|
| baseUrl | https://api.endpointr.com (or http://localhost:8080 while developing) |
| api_key | Your customer's api_key (from scripts/create_customer.php or the admin UI) |
| token | Leave blank — auto-filled by the test script on the first POST /v1/token |
3. Run POST /v1/token in the *Auth* folder. The test script captures data.token into {{token}} and every other request inherits Authorization: Bearer {{token}}.
4. Set up a service — for example, OpenAI: PUT /v1/credentials/openai with body {"api_key":"sk-..."}. Done. Now POST /v1/ai/chat with {"prompt":"hi"} works.

That's the whole onboarding loop.

Postman variables (recommended)

The example bodies use {{double_brace}} placeholders for things only you can supply (OAuth tokens, account IDs, contact-book IDs). Define them once in your Postman environment and every example "just works." The most useful ones:

VariableWhere it's usedHow to get it
claude_oauth_tokenAI examples for provider: claude-cliclaude setup-token (Claude Max subscription)
missive_api_keyMissive passthrough modeMissive *Preferences → API → Create token* (missive_pat-…)
missive_account / missive_organization / missive_team / missive_user / missive_contact_book / missive_conversationMissive examples that reference those resourcesLook them up via the relevant GET /v1/mail/missive-* endpoint
google_oauth_tokenGoogle Tasks (direct access_token mode)OAuth flow (1h TTL); for testing, oauth playground
google_refresh_token / google_client_id / google_client_secretGoogle Tasks (refresh-triplet mode)Same OAuth playground; pick Tasks API v1 → tasks scope
meta_ad_account_idEvery /v1/marketing/* example with account_idGET /v1/marketing/ad-accounts after creds are set — pick one. Includes the act_ prefix.
meta_page_idLead-form / page examplesGET /v1/marketing/pages — the id of the Page you'll run lead-gen ads from.
meta_pixel_idConversions API examplesGET /v1/marketing/pixels?account_id={{meta_ad_account_id}}
meta_webhook_verify_tokenWebhook subscription examplesWhatever you set in the META_WEBHOOK_VERIFY_TOKEN env var (openssl rand -hex 32).

Anything you see as <placeholder> in a description (e.g. <conversation-id>) is shorthand for "fill this in" — usually a server-side ID you obtain by listing first.

Auth

  • POST /v1/token — issue (no auth; body {api_key})
  • PATCH /v1/token — rotate (revokes old jti, issues new)
  • DELETE /v1/token — revoke (logout)
  • POST /v1/keys — upload your PEM public key (≥ 2048 bits)
  • GET /v1/keys — fetch your stored public key

Tokens carry a jti checked against a revocation table on every request.

Credentials — vault, passthrough, hybrid

Most third-party credentials live in the encrypted service_credentials vault — set them once, never touch them again. Some integrations also let you supply credentials per-request so you can keep secrets entirely client-side.

Vault-only services

ServiceKeys
openaiapi_key, optional organization
anthropicapi_key
openrouterapi_key, optional referer, app_title
claude-clioauth_token (from claude setup-token)
openai-cliapi_key
stripeapi_key, optional webhook_secret
mailchimpapi_key (dc suffix in key), optional list_id
ipregistryapi_key
clearhausapi_key
meta-adsaccess_token, app_id, app_secret, optional business_id, optional api_version (default v24.0) — see Meta Marketing API — first-time setup for the full provisioning recipe

Hybrid services (vault OR per-request)

ServiceVault entryPer-request override field
AI providers (CLI variants)claude-cli.oauth_token / openai-cli.api_keyoauth_token / api_key in body
missivemissive.api_keyapi_key in body (write verbs) or query (read verbs)
google (Tasks)google.{client_id, client_secret, refresh_token}oauth_token OR same triplet in body (write verbs) / query (read verbs)
google-tagmanager (GTM)google-tagmanager.{client_id, client_secret, refresh_token}oauth_token OR same triplet in body (write verbs) / query (read verbs) — own slot (GTM consent carries edit + publish scopes); refresh_token minted by the admin page's "Connect Google Tag Manager" button
reddit-adsreddit-ads.{client_id, client_secret, refresh_token, user_agent?}oauth_token OR same triplet in body (write verbs) / query (read verbs) — see Reddit Ads API — first-time setup

Vault-only (handlers TBD)

ServiceVault entry
microsoft-graph{tenant_id, client_id, client_secret, refresh_token} — slot for upcoming Outlook / Calendar / To Do handlers; admins can stage credentials today, no API endpoint consumes them yet.

Google in vault mode. Store the refresh-triplet once; Endpointr exchanges it for a fresh access_token on every call. Per-request oauth_token is then optional — useful when a client already has a fresh access_token in hand.

When to pick which mode

  • Vault. Set-and-forget. Required for binding-driven automations (EventDispatcher) and inbound-webhook signature verification — those flows have no live caller to attach credentials to.
  • Per-request. Keeps secrets out of the server entirely. Good for browser/mobile clients with their own secret store, or environments where the credential rotates frequently.
  • Override-on-vault. Both modes coexist for hybrid services — the per-request value wins for that single call, the vault remains the fallback. The override field is stripped before the upstream call so it never leaks into Missive's payload, query strings, or webhook event records.

When you add credentials for an AI provider, that provider is auto-appended to the customer's provider priority list — see below.

Handler URL derivation

Handlers auto-register at /v1/<subdir>/<kebab-class-name>. Example: class StripeCustomersHandler in handlers/payments//v1/payments/stripe-customers.

REST verbs map to handler methods:

Method / pathHandler method
GET /v1/<g>/<name>getAll(customerId)
GET /v1/<g>/<name>/{id}get(customerId, id)
GET /v1/<g>/<name>/?…getByParam(customerId, query)
POST /v1/<g>/<name>create(customerId, data)
PUT /v1/<g>/<name>/{id}update(customerId, id, data)
DELETE /v1/<g>/<name>/{id}delete(customerId, id)

Only the verbs the handler implements are reachable. Unimplemented verbs return HTTP 500 with a descriptive error.

Response envelope

Every response is wrapped:

{
  "httpCode":  200,
  "status":    "HTTP/1.1 200 OK",
  "timestamp": "2026-04-21T12:00:00+00:00",
  "requestId": "9f3d8e1a2b…",
  "version":   "1.0.0",
  "data":      { /* handler return value */ }
}

X-Request-Id header mirrors requestId — include it in bug reports.

Webhooks

Every handler operation fires a typed event (e.g. StripeCustomersHandler.create, MissiveDraftsHandler.create). Register a listener with POST /v1/webhooks; the response returns a one-time secret. Receivers verify HMAC-SHA256 against the JSON body via the X-Endpointr-Signature: sha256=… header.

Sensitive fields (api_key, oauth_token, password, secret, etc.) are auto-redacted from webhook payloads — your hybrid-credential override never reaches subscribers.

AI handlers — unified multi-provider

Five endpoints, five providers. Pick one per request via the provider field, or leave it off and let the customer's priority list decide.

ProviderAuthChatVisionImage genStreamModels
openaiper-customer api_key
anthropicper-customer api_key
openrouterper-customer api_key
claude-clioauth_token (Claude Max sub)
openai-cliserver-side codex login (sub)

CLI providers ride one shared subscription on this server; API providers bill to the customer's own account.

### Endpoints
- POST /v1/ai/chat — text
- POST /v1/ai/conversation — text + server-side memory + per-customer Elasticsearch retrieval (narration / TTS tone)
- POST /v1/ai/vision — image-in + text-out
- POST /v1/ai/image — image generation (openai, openrouter)
- POST /v1/ai/stream — SSE chat stream
- POST /v1/ai/upload — upload an image, get a short-lived public URL for vision providers
- GET /v1/ai/models/?provider=… — list available models
- POST /v1/chat/completions (alias: POST /v1/ai/completions) — OpenAI-compatible, see below

model is always passed via the model field — no hardcoded default outside provider fallbacks.

OpenAI-compatible endpoint (/v1/chat/completions)

Drop-in replacement for OpenAI's POST /v1/chat/completions. Built for n8n's *OpenAI* credential, LangChain's ChatOpenAI, the official openai SDK, and anything else that speaks the OpenAI wire format — point them at {{baseUrl}}/v1 and your customer api_key works as the OpenAI key.

Difference from the rest of /v1/*Why
Auth: Authorization: Bearer <api_key> — the raw customer api_key, not a JWT.n8n's OpenAI credential has no refresh hook; flat keys is what OpenAI does too.
No response envelope. Body is the raw OpenAI shape ({id, object, created, model, choices, usage}); errors are {error: {message, type, code}}.LangChain / OpenAI SDK reads these fields directly.
Registered at two paths/v1/chat/completions (drop-in) and /v1/ai/completions (namespace match).Same handler; pick whichever lines up with your client.

Model routing. The model string picks the upstream:

Model prefixUpstreamNotes
anthropic/… or claude-*Anthropic (vault anthropic.api_key)Full translation: tools ↔ input_schema, tool_use ↔ tool_calls, system extraction, stop_reason mapping.
openai/… or gpt-* / o1-* / o3-* / o4-*OpenAI (vault openai.api_key)Near-passthrough.
anything elseOpenRouter (vault openrouter.api_key)Model string passed verbatim (google/gemini-2.5-flash, etc.).

Missing vault creds for the resolved upstream → 401 with a remediation hint.

Tool calling. Required for n8n's AI Agent node. Send OpenAI-shape tools + tool_choice. The response carries finish_reason: "tool_calls" with tool_calls[].function.arguments as a JSON-encoded string (do not parse server-side — clients reassemble). Continue the loop with {role: "tool", tool_call_id, content}. Parallel tool calls supported.

Streaming. "stream": truetext/event-stream with chat.completion.chunk frames and data: [DONE] sentinel. Anthropic streams are translated event-by-event; OpenAI / OpenRouter streams are near-passthrough.

Accepted but ignored (so n8n payloads don't trip a 400): logprobs, top_logprobs, n, seed, logit_bias, user, service_tier.

Setup recipe. One-time for the customer:
1. Generate an api_key (admin UI or scripts/create_customer.php).
2. PUT /v1/credentials/anthropic (and/or openai, openrouter) with the upstream provider's key.
3. In n8n: create an *OpenAI* credential → API Key = customer api_key, Base URL = https://api.endpointr.com/v1.

That's the whole loop. The customer's api_key is now their single credential for every n8n LLM node, AI Agent included.

Default models (when you omit model)

ProviderChat / Vision / StreamImage gen
anthropicclaude-sonnet-4-6
claude-cliclaude-sonnet-4-6
openaigpt-4o-minigpt-image-1
openai-cligpt-5
openrouteropenai/gpt-4o-minigoogle/gemini-2.5-flash-image-preview

Provider priority & failover

provider is optional on every AI endpoint. When it's omitted, the resolver consults the customer's provider priority list — an ordered slug array stored on customers.provider_priority and managed in the admin UI at /admin/customers/{id} → *Provider priority*.

Resolution is capability-aware:

1. Walk the priority list top-to-bottom.
2. Skip any slug that lacks credentials in service_credentials.
3. Skip any slug whose capability map doesn't include the requested operation (so claude-cli is skipped for image/models, openai-cli is skipped for everything except chat, etc.).
4. The first survivor handles the request. If 2+ survive, they're wrapped in a failover chain.

Failover (chat/vision/image/models): if the chosen provider returns 5xx, 401, 403, or 429, the next survivor is tried. Other 4xx responses surface immediately — they're the caller's fault and the next provider would also reject the same payload.

Failover (stream): none. Once SSE headers are out, the connection is committed. The first qualifying candidate handles the stream; mid-stream errors surface as SSE error frames, not retries.

Explicit provider always wins. Passing provider in the body bypasses the priority list and uses exactly that provider with no failover — useful when you specifically want to route a request to OpenAI's gpt-image-1 or OpenRouter's google/gemini-3-pro-image-preview ("Nano Banana Pro").

Image generation — model picker

ProviderModelNotes
openaigpt-image-1Default OpenAI image model
openaidall-e-3Older DALL·E
openroutergoogle/gemini-2.5-flash-image-preview"Nano Banana" — fast & cheap
openroutergoogle/gemini-3-pro-image-preview"Nano Banana Pro" — higher quality

OpenRouter image responses are normalized to {b64_json: "…"} so callers can swap providers without reshaping output.

Meta Marketing API — first-time setup

Endpointr's /v1/marketing/* endpoints are a thin canonicalised facade over Meta's Graph API. To use them you need a Meta App + a long-lived access token + an app_secret — none of which Endpointr can mint for you. This section is the end-to-end recipe.

Time budget: ~30 minutes for a brand-new Meta account; ~10 minutes if you already have a Business Portfolio.

Glossary (just enough to follow the steps)

TermWhat it is
Meta AppThe "client" Endpointr identifies as when calling Graph. Has an app_id + app_secret. Created at developers.facebook.com/apps.
Business Portfolio (formerly Business Manager)A container that owns ad accounts, Pages, pixels, and people. Created at business.facebook.com. The portfolio's id is the business_id credential.
System UserA non-human user inside a Business Portfolio that owns access tokens. Tokens minted by a System User never expire (vs. ~60 d for human-user tokens) and survive password resets. This is what production should use.
Access tokenThe bearer string Endpointr sends on every Graph call. Stored at service_credentials.meta-ads.access_token.
Verify tokenThe shared secret Meta echoes during webhook subscription handshakes. Set app-wide via the META_WEBHOOK_VERIFY_TOKEN env var.

Step 1 — Create the Meta App (5 minutes)

1. Go to developers.facebook.com/appsCreate app.
2. Use case: pick *Other* → Next.
3. App type: *Business* → Next.
4. Name / contact email: anything descriptive. Attach a Business Portfolio if you already have one (otherwise create one inline).
5. After creation, on the App Dashboard → Add products → click Set up on:
- Marketing API (required)
- Webhooks (only if you want inbound events — leadgen, account-status changes, etc.)
6. Open App Settings → Basic and grab:
- App IDapp_id in the credential
- App Secret (click *Show*) → app_secret in the credential

The app starts in Development mode, which is fine — Marketing API works in dev mode for any ad account the app owner / a System User has access to. You only need to flip to *Live* if you want third parties to log in via this app.

Step 2 — Create a Business Portfolio + System User (10 minutes)

Skip if you already have a Business Portfolio with a System User that owns the ad accounts you'll be managing.

1. Go to business.facebook.comCreate account. Fill in the legal name + your contact email.
2. Inside the new portfolio → Settings (⚙) → Business Settings.
3. Users → System Users → Add. Name it something like endpointr-api. Role: Admin.
4. With the System User selected → Add AssetsApps → tick the app you created in Step 1 → grant Full control.
5. Add Assets again → Ad Accounts → tick every ad account you want Endpointr to manage → grant Manage ad account.
6. Repeat for Pages (needed for lead-gen ads + Page-attached creatives) and Pixels (needed for Conversions API).
7. Copy the Business Portfolio ID from *Business Settings → Business Info* → that's the business_id credential (optional — leaving it blank means Endpointr discovers ad accounts via /me/adaccounts instead of /{business_id}/owned_ad_accounts; the latter is recommended for tokens that own many accounts).

Step 3 — Generate a long-lived System User access token (3 minutes)

Still in *Business Settings → Users → System Users*:

1. Click the System User you created → Generate new token.
2. App: pick the app from Step 1.
3. Token expiration: *Never*.
4. Permissions (tick all of these — missing scopes silently break specific endpoints):

| Scope | Used for |
|---|---|
| ads_management | All write operations on campaigns, ad sets, ads, creatives |
| ads_read | All read operations + insights |
| business_management | business_id-scoped account discovery, catalogs |
| leads_retrieval | /v1/marketing/leads |
| pages_show_list | /v1/marketing/pages |
| pages_read_engagement | Lead-gen webhook + Page-attached creatives |
| pages_manage_metadata | Subscribing the Page to webhooks |
| instagram_basic *(optional)* | Instagram ads via the connected IG business account |

5. Click Generate token. Copy it now — Meta only shows it once. That's the access_token credential.

Step 4 — Store the credentials in Endpointr (1 minute)

PUT {{baseUrl}}/v1/credentials/meta-ads
Authorization: Bearer {{token}}
Content-Type: application/json

{
  "access_token": "EAA...the-long-string-from-step-3",
  "app_id":       "1234567890123456",
  "app_secret":   "abcdef0123456789abcdef0123456789",
  "business_id":  "9876543210987654",
  "api_version":  "v24.0"
}

api_version is optional — defaults to whatever META_DEFAULT_API_VERSION (env) is set to (v24.0 out of the box). Pin per-customer when one partner needs an older version.

Step 5 — Smoke-test (30 seconds)

GET {{baseUrl}}/v1/marketing/auth
Authorization: Bearer {{token}}

Expected response:

{
  "data": {
    "debug_token": {
      "app_id":       "1234567890123456",
      "type":         "SYSTEM_USER",
      "expires_at":   0,
      "is_valid":     true,
      "scopes":       ["ads_management", "ads_read", "business_management", "..."]
    },
    "scopes": {
      "granted":  ["ads_management", "ads_read", "..."],
      "declined": []
    }
  }
}

If expires_at is not 0, you didn't generate a System User token — short-lived tokens will work for testing but die in ~60 days. If scopes.granted is missing one of the rows from Step 3, go back to the System User and add the missing permission.

Then list your accounts:

GET {{baseUrl}}/v1/marketing/ad-accounts

Pick one of the act_… ids from the response — that's your {{meta_ad_account_id}} Postman variable from here on.

Step 6 — (optional) wire up webhooks

If you want Meta to push events (leadgen submissions, ad-account status changes), you also need:

1. Set the META_WEBHOOK_VERIFY_TOKEN env var server-side. Generate with openssl rand -hex 32. This is one app-wide value — Meta echoes it during the handshake at GET /v1/webhooks/inbound/meta-ads.

2. In the Meta App Dashboard → Webhooks, add the same value as the *Verify Token* and https://api.endpointr.com/v1/webhooks/inbound/meta-ads as the *Callback URL*. Subscribe to the objects you care about (page, ad_account, etc.). Meta will hit the GET endpoint immediately to validate the token.

3. Subscribe to specific fields via Endpointr's wrapper (uses the same callback URL by default and persists the local mapping so inbound deliveries can be attributed to the right customer):

   POST {{baseUrl}}/v1/marketing/webhook-subscriptions
   Content-Type: application/json

   {
     "object":       "page",
     "object_id":    "{{meta_page_id}}",
     "fields":       ["leadgen"]
   }
   

4. Test the inbound endpoint from Meta App Dashboard → Webhooks → Test → pick leadgen. A row will appear in the meta_ad_webhook_events table within a second, and a leadgen_fetch job will be queued for the meta marketing worker. The full lead body lands in meta_ad_leads; subscribed MetaAds.page.leadgen outbound webhooks fire too.

Common gotchas

  • (#100) Param account_id… not supported — you forgot the act_ prefix on an account id. Endpointr auto-prefixes when you pass it as account_id in a body, but some Graph error paths surface the raw error before that normalisation. Always include act_ to be safe.
  • (#10) You do not have permission to perform this action — the System User isn't assigned the relevant asset (ad account / Page / pixel) with *Manage* role. Back to Step 2.5/2.6.
  • (#190) Error validating access token: Session has expired — short-lived token; regenerate as a System User token (Step 3, expiration *Never*).
  • (#368) The action attempted has been deemed abusive — usually Meta thinks the request is bot-generated. Make sure the System User token was generated against the same app you're calling from, and that appsecret_proof is on (it is by default in MetaGraphClient when app_secret is set in the vault).
  • Conversions API events don't show up in Events Manager — they take ~20 minutes to appear in the test events tab unless you pass test_event_code: 'TEST123…' (from Events Manager → *Test Events*). Production events appear under the normal aggregations after that delay.
  • Video upload stuck at processing forever — Meta's transcoder can occasionally fail silently. Re-upload with a different container/codec (H.264 + AAC in MP4 is the safest). The worker stops polling after 30 attempts (~5 minutes) and marks the asset failed with the last status payload.

Reddit Ads API — first-time setup

Full campaign management against the Reddit Ads API v3 (ads-api.reddit.com/api/v3) under /v1/redditads/*. Reading works for any developer app; endpoints that create/edit ads additionally require Reddit's Ads API approval — apply at redditforbusiness.com/api-partnership or via your Reddit account team.

Step 1 — Create the Reddit app (3 minutes)

1. Log in to Reddit with the account that manages the ads (or a dedicated integration account) and open reddit.com/prefs/apps.
2. Create app → type web app.
3. Set the redirect uri to the exact value shown next to the "Connect Reddit Ads" button on the customer's Endpointr admin page (https://<your-host>/admin/reddit/oauth/callback). Reddit allows exactly one redirect URI per app and requires an exact match.
4. Note the client_id (the string under the app name) and client_secret.

Step 2 — Store the credentials (1 minute)

Admin UI: customer page → add a reddit-ads credential with client_id + client_secret (leave user_agent blank for Endpointr's default — Reddit requires a descriptive User-Agent and blocks generic ones; set your own only if you want your app identified differently, format <platform>:<app id>:<version> (by /u/<username>)).

Or over the API:

PUT /v1/credentials/reddit-ads
{"client_id": "…", "client_secret": "…"}

Step 3 — Connect (30 seconds)

Click Connect Reddit Ads → on the customer's admin page. Reddit shows a consent screen requesting the adsread adsedit adsconversions adsdatadeletion scopes with duration=permanent; on approval the refresh_token is minted and written into the vault automatically — never type it by hand. (Escape hatch: if you already have a refresh_token from elsewhere, PUT the full triplet instead.)

Endpointr then exchanges the triplet for a short-lived (~24 h) access_token on every call, and persists any rotated refresh_token so the connection self-sustains. Vault-minted tokens are surfaced as refreshed_access_token in responses for client-side caching.

Step 4 — Smoke-test (30 seconds)

GET /v1/redditads/reddit-me            → the authenticated member
GET /v1/redditads/reddit-businesses    → business_id source
GET /v1/redditads/reddit-ad-accounts?business_id=<id>
GET /v1/redditads/reddit-campaigns?ad_account_id=<id>

Campaign build order

campaign (needs funding_instrument_id from /v1/redditads/reddit-funding-instruments?ad_account_id=…) → ad group (targeting/bid; validate values via /v1/redditads/reddit-targeting?resource=…) → post (profile_id from /v1/redditads/reddit-profiles?ad_account_id=…) → ad (binds post_id to ad_group_id). Create everything configured_status: PAUSED, flip to ACTIVE when ready.

Common gotchas

  • Write bodies are {"data": {...}} upstream — Endpointr wraps flat fields automatically; only pass an explicit data key if you're sending Reddit's wire shape yourself.
  • Updates are PATCH upstream — Endpointr's PUT /v1/redditads/…/{id} forwards as PATCH; send only the fields to change.
  • No delete for campaigns / ad groups / ads — archive or pause via {"configured_status": "PAUSED"|"ARCHIVED"}. DELETE exists only for custom audiences, product catalogs/feeds/sets.
  • Money is microcurrency (1,000,000 = 1 unit of the account currency); timestamps ISO 8601 UTC.
  • Pagination — lists take page.size/page.token (aliases page_size/page_token work around PHP's $_GET dot-mangling) and return pagination.next_url; pass that back as next_url to follow it.
  • 401/403 on writes with valid reads — the app lacks Reddit's Ads API developer approval (see the intro), or the token is missing the adsedit scope (re-connect).
  • invalid_grant on token refresh — the refresh_token was revoked (password change, app deauthorised) — click "Connect Reddit Ads" again.

Stubs — endpoints present but dependency-missing

EndpointInstall
/v1/rendering/html2-pdfcomposer require dompdf/dompdf
/v1/rendering/phantom-jsdeprecated; migrate to Playwright externally

These throw a clear RuntimeException explaining what to install.

Rebuilding this collection

When you add a handler or change an AI provider's behaviour, re-run:

php v1/generate_postman.php
php scripts/generate_docs.php

The first rewrites postman_collection.json (re-import into Postman or use *Update* on the existing collection). The second rewrites public/documentation/index.html from the same JSON, so both stay in sync.

MCP Servers

Every handler is also reachable as an MCP tool — one tool per (handler, verb) pair, named endpointr_<group>_<resource>_<verb>. Handlers are grouped into dedicated connectors below so a client only sees the tools it needs; the endpointr connector is the catch-all. Auth is OAuth2 + PKCE with Dynamic Client Registration — the consent screen asks for your api_key (the same one used at POST /v1/token), so there's no separate identity stack. Paste any connector URL into claude.ai/settings/connectors, or add it to Claude Code with the command on each card below.

MCPendpointr — https://mcp.endpointr.com
every handler except the dedicated connectors below

endpointr is a multi-tenant REST gateway. Each tool here mirrors one HTTP
endpoint under /v1/*. The tool name encodes the route shape:
endpointr_<group>_<resource>_<verb>
where verb is one of: list (GET collection), get (GET by id), query
(GET with query params), create (POST), update (PUT), delete (DELETE).

Tool arguments mirror the HTTP request body / path id / query string.
Most create/update tools accept an open body object — the schema lists
documented fields, but handlers commonly accept additional keys.

When the stored credential a call resolves carries an optional account_name
(e.g. distinguishing a "Conzent" Stripe account from a "Rexultz" one), the
response includes an _account object — {"name": …, "service": …} — naming
which account the call hit. Absent that field, no account_name was configured.

claude mcp add endpointr --transport http https://mcp.endpointr.com
MCPendpointr-tidycal — https://tidycal.calendars.mcp.endpointr.com
groups: tidycal

This connector is a TidyCal relay (https://tidycal.com/api) exposed through
endpointr — read AND write access to your TidyCal scheduling data (unlike
TidyCal's own read-only MCP). Each tool mirrors one TidyCal REST endpoint. Tool
names follow:
endpointr_tidycal_tidy_cal_<resource>_<verb>
where verb is one of: list (GET collection), query (GET with query params),
get (GET by id), create (POST/PATCH upstream), delete (DELETE).

Resources:
- me your account profile (list; GET /me).
- bookings query/get scheduled bookings; create books a slot on a
booking type (create REQUIRES booking_type_id in the
body, plus {name, email, timezone} and either starts_at
or a bookings[] array for packages).
- booking-cancel cancel a booking (create {booking_id, reason?}).
- booking-types query your booking types; create a new one
({title, description, duration_minutes, url_slug, …}).
- timeslots query bookable slots for a booking type — REQUIRES
booking_type_id, starts_at, ends_at (UTC).
- contacts query/create contacts.
- teams query/get teams you own or belong to.
- team-bookings query a team's bookings — REQUIRES team_id.
- team-users query/invite team members (create {team_id, email,
role_name?}); remove with a composite id teamId:teamUserId.
- team-booking-types query/create a team's booking types — REQUIRES team_id.
- accounts list the configured TidyCal account labels (see below).

MCP note: there is no query string over MCP, so resources that need a parent id
take it in the query arg (booking_type_id / team_id) for list verbs, in the
body for create, and as a composite parent:child id for get/delete.

Multiple TidyCal accounts. One login can hold several TidyCal accounts, each
stored under its own credential tidycal:<label> via
PUT /v1/credentials/tidycal:<label> {api_key:'…'} (e.g. tidycal:work). EVERY
tool (except accounts-list) accepts an optional top-level account argument
naming which account to target. When more than one account is configured,
account is REQUIRED — a call that omits it is rejected with the list of valid
labels. Call endpointr_tidycal_tidy_cal_accounts_list to see the labels. If only
a single unlabelled tidycal credential exists, account may be omitted.

Authentication. The connector OAuth establishes which endpointr customer you
are. The upstream TidyCal token (a personal access token; API access needs a
paid/Pro plan) is resolved per customer from the credential vault — store it
once via PUT /v1/credentials/tidycal {api_key:'…'}. Alternatively pass
api_key inline in any tool's body/query for per-request passthrough; it is
stripped before the request reaches TidyCal.

Tool arguments mirror the HTTP request body / path id / query string. Most
create tools accept an open body object — the schema lists documented fields,
but handlers forward additional keys to TidyCal verbatim.

claude mcp add tidycal --transport http https://tidycal.calendars.mcp.endpointr.com
MCPendpointr-missive — https://missive.mcp.endpointr.com
classes: Missive*

This connector is a Missive relay. Each tool mirrors one Missive REST
endpoint (https://public.missiveapp.com/v1/) exposed through endpointr.
Tool names follow:
endpointr_mail_missive_<resource>_<verb>
where verb is one of: query (GET list with query params), get (GET by id),
create (POST), update (PUT/PATCH upstream), delete (DELETE).

Resources: conversations, messages, contacts, drafts, posts (internal
comments), tasks — plus the id-discovery lists: organizations, teams, users,
shared-labels, contact-books (each takes no parameters).

Where ids come from (chain these): conversation id ← conversations list (no
params; 25 most recent from the All mailbox) or query (mailbox filters);
message id ← messages query with conversation=<id>; contact_book
contact-books list; organization ← organizations list; team / user /
shared-label ids ← their lists. Draft and task ids come from their create
responses — Missive has no list endpoint for either (upstream limitation).

Authentication. The connector OAuth establishes which endpointr customer
you are. The upstream Missive API key (a missive_pat-… token) is resolved
per customer from the credential vault — store it once via
PUT /v1/credentials/missive {api_key:…}. Alternatively, pass api_key
inline in any tool's body/query for per-request passthrough; it is stripped
before the request reaches Missive.

Tool arguments mirror the HTTP request body / path id / query string. Most
create/update tools accept an open body object — the schema lists
documented fields, but handlers commonly accept additional keys.

claude mcp add missive --transport http https://missive.mcp.endpointr.com
MCPendpointr-meta — https://meta.mcp.endpointr.com
groups: marketing, messaging

This connector bundles Meta's (Facebook/Instagram) Graph APIs exposed through
endpointr: the Marketing API (ads) and the Conversations API (Messenger +
Instagram messaging). Each tool mirrors one REST endpoint. Tool names follow:
endpointr_marketing_<resource>_<verb> (Marketing API — /v1/marketing/*)
endpointr_messaging_<resource>_<verb> (Conversations API — /v1/messaging/*)
where verb is one of: list (GET collection), query (GET with query params),
get (GET by id), create (POST), update (PUT), delete (DELETE).

Marketing API (group marketing, vault slot meta-ads):
- ad-accounts the ad accounts the token can see (list/get). business_id
in the credential scopes discovery to one Business Manager.
- campaigns / ad-sets / ads the campaign hierarchy — full CRUD.
- creatives / ad-images / ad-videos creative assets (image/video upload;
ad-videos supports sync + async upload).
- audiences custom + lookalike audiences.
- pages Facebook Pages the token manages.
- pixels datasets + the Conversions API (server-side events).
- catalogs Commerce product catalogs.
- leads lead ads + lead forms.
- insights performance metrics (spend, impressions, actions, ROAS).
- auth Meta token introspection (debug-token, granted scopes) and
short-lived -> long-lived extension (create {action:'extend'}).
- webhook-subscriptions activate inbound meta-ads webhooks.

Conversations API (group messaging, vault slot meta-messaging):
- channels connected Pages / Instagram accounts (list/get). Listing
discovers each Page's access token and caches it.
- conversations list/read Messenger + Instagram threads (pass page_id,
platform=messenger|instagram).
- messages read messages (query by conversation_id) AND the Send
API (create): text, attachments, templates, quick
replies, plus sender actions (typing_on/typing_off/
mark_seen via {action,page_id,recipient_id}).
- profile look up a messaging user's public profile by PSID/IGSID
(pass page_id).
- messenger-profile persistent menu, ice breakers, greeting, get-started
button per Page (query/create/delete).
- webhook-subs activate inbound meta-messaging webhooks per Page
(subscribed_apps).
- token introspect / extend the stored messaging Meta token.

Authentication. The connector OAuth establishes which endpointr customer you
are. The two surfaces use SEPARATE vault slots (a login can wire either or
both), each resolved per customer from the credential vault:
- Marketing: PUT /v1/credentials/meta-ads {access_token, app_id, app_secret,
business_id?, api_version?}
. Prefer a long-lived System User token (or
extend a short-lived user token via marketing/auth {action:'extend'}).
- Messaging: PUT /v1/credentials/meta-messaging {access_token, app_id,
app_secret}
. The token needs messaging scopes (pages_messaging,
pages_show_list, pages_manage_metadata, pages_read_engagement, and for
Instagram instagram_basic + instagram_manage_messages, plus
business_management). Per-Page access tokens are auto-discovered from
/me/accounts and cached encrypted — pass page_id on
conversation/message/profile calls so the right Page token is used.
app_secret backs HMAC verification of inbound webhook deliveries and token
extension. api_version pins the Graph version (default v24.0) per customer.

Tool arguments mirror the HTTP request body / path id / query string. Most
create/update tools accept an open body object — the schema lists documented
fields, but handlers forward additional keys to Meta verbatim.

claude mcp add meta --transport http https://meta.mcp.endpointr.com
MCPendpointr-cloudflare — https://cloudflare.mcp.endpointr.com
groups: cloudflare

This connector is a Cloudflare API relay (https://api.cloudflare.com/client/v4)
exposed through endpointr, scoped to managing zones (domains), DNS, Workers,
and Pages. Each tool mirrors one REST endpoint under /v1/cloudflare/*. Tool
names follow:
endpointr_cloudflare_<resource>_<verb>
where verb is one of: list (GET collection), query (GET with query params),
get (GET by id), create (POST), update (PUT/PATCH), delete (DELETE).

Resources:
- zones list/get/create/update/delete zones. Creating a zone is
how you ADD A NEW DOMAIN ({name, account_id?}). query
filters by name/status/account_id.
- dns-records DNS records, scoped to a zone. query lists them and
REQUIRES zone_id. create/update carry zone_id in the
body. get/delete take a composite id zoneId:recordId.
- workers Worker scripts (account-scoped). list/get/delete by
script name; create uploads an ES-module worker
({name, script, compatibility_date?, bindings?}).
- worker-routes route patterns that run a Worker, scoped to a zone.
query REQUIRES zone_id; delete takes zoneId:routeId.
- pages-projects Cloudflare Pages projects (account-scoped). list/get/
create/delete by project name.
- pages-deployments deployments per project. query REQUIRES project_name;
create triggers a deploy ({project_name, branch?});
get/delete take a composite id projectName:deploymentId.
- cf-accounts list/get the accounts the token can access — use this to
discover the account_id Workers/Pages need.
- token-verify verify the stored API token is active.
- cache-purge clear a zone's cache. create REQUIRES zone_id in the
body plus one mode: purge_everything:true, or a
files / tags / hosts / prefixes array (the last
three are Enterprise-only).
- cache-settings read/update a zone's cache config. query REQUIRES
zone_id and returns cache_level / browser_cache_ttl /
development_mode / always_online / sort_query_string_for_cache.
update takes the setting name as the id and
{zone_id, value} in the body (development_mode on/off,
cache_level aggressive/basic/simplified, browser_cache_ttl
in seconds).

MCP note: there is no query string over MCP, so resources that need a parent id
take it in the query arg (zone_id / project_name) for list verbs, in the
body for create/update, and as a composite parent:child id for get/delete.

Authentication. The connector OAuth establishes which endpointr customer you
are. The upstream Cloudflare token is resolved per customer from the credential
vault — store it once via PUT /v1/credentials/cloudflare
{api_token, account_id}
. Only API Token (Bearer) auth is supported; the token
needs the relevant Zone / Workers / Pages permissions. account_id is the
default account for account-scoped resources (Workers, Pages, accounts) and may
be overridden per-request via account_id.

Tool arguments mirror the HTTP request body / path id / query string. Most
create/update tools accept an open body object — the schema lists documented
fields, but handlers forward additional keys to Cloudflare verbatim.

claude mcp add cloudflare --transport http https://cloudflare.mcp.endpointr.com
MCPendpointr-openprovider — https://openprovider.mcp.endpointr.com
groups: openprovider

This connector is an OpenProvider relay (https://api.openprovider.eu/v1beta)
exposed through endpointr, scoped to domain registration and DNS. Served at
openprovider.mcp.endpointr.com. Each tool mirrors one OpenProvider REST endpoint
under /v1/openprovider/*. Tool names follow:
endpointr_openprovider_op_<resource>_<verb>
where verb is one of: query (GET with query params), get (GET by id),
create (POST), update (PUT), delete (DELETE).

Typical flow: op-customers create (get an owner handle) -> op-domain-check
(is the name free?) -> op-domains create (register) -> op-zones / op-dns-records
(set up DNS).

Resources:
- op-customers The reusable contact HANDLE (e.g. XX123456-XX) a domain
references as owner/admin/tech. Full CRUD; the id is the
handle string. create returns data.handle. You need at
least one before registering a domain. Required to create:
name, address, phone, email (see the tool's minimal body).
- op-domain-check create = check availability (+ price with with_price).
Body {domains:[{name,extension}], with_price?}. status
free = available, active = taken.
- op-domains query/get/create/update/delete domains. create with no
action REGISTERS ({domain:{name,extension}, owner_handle,
period, name_servers|ns_group}); create with action:'renew'
+ id + period renews. get/update/delete take the NUMERIC
domain id.
- op-zones DNS zones CRUD. id = the zone NAME (full domain, e.g.
example.com). get returns records inline. update is how you
EDIT/REMOVE records
— send them grouped under
records:{add,remove,replace,update} (records have no id;
matched by their {type,name,value} tuple).
- op-dns-records query = list a zone's records (name required = the domain).
create = APPEND records to a zone
({domain, records:[{type,name,value,ttl}]}). For edit/remove
use op-zones update.

MCP note: there is no query string over MCP, so list filters go in the query
arg; the path id goes in the id arg — a NUMERIC domain id (op-domains), a
customer HANDLE (op-customers), or a zone NAME/full domain (op-zones). op-dns-records
takes the zone in the body/query, not as a path id.

Every response is the OpenProvider envelope {code, data, desc, warnings}
code:0 is success and the useful payload is under data.

Authentication. The connector OAuth establishes which endpointr customer you
are. OpenProvider issues a short-lived Bearer token from username/password; the
relay logs in, caches the token, and re-logs-in on expiry, so you never handle
tokens. Store the login once via PUT /v1/credentials/openprovider
{username, password}
(optional ip, default 0.0.0.0 = any IP — set it if the
account restricts API access by IP). Vault-only: credentials are never accepted
per-request.

Tool arguments mirror the HTTP request body / path id / query string. Most
create/update tools accept an open body object — the schema lists documented
fields, but handlers forward additional keys to OpenProvider verbatim (so the
full CreateDomain / CreateCustomer / UpdateZone request shapes are supported).

claude mcp add openprovider --transport http https://openprovider.mcp.endpointr.com
MCPendpointr-calcom — https://calcom.calendars.mcp.endpointr.com
groups: calcom

This connector is a Cal.com API v2 relay (https://api.cal.com/v2) exposed
through endpointr, scoped to scheduling: event types, availability slots,
bookings, and availability schedules. Served at
calcom.calendars.mcp.endpointr.com. Each tool mirrors one REST endpoint under
/v1/calcom/*. Tool names follow:
endpointr_calcom_cal_com_<resource>_<verb>
where verb is one of: query (GET with query params), get (GET by id),
list (GET collection), create (POST), update (PUT/PATCH), delete (DELETE).

Typical flow: event-types (find an eventTypeId) -> slots (find an open time)
-> bookings create -> bookings query/get and cancel/reschedule.

Resources:
- event-types query = list bookable meeting types (filters: username,
eventSlug, orgSlug, ...). Each event type's numeric id is the
eventTypeId slots + bookings need. get = one by id.
- slots query = available times for an event type over a date range.
REQUIRES start + end (UTC, ISO 8601 / YYYY-MM-DD) AND an
event identifier (eventTypeId, or eventTypeSlug + username).
Optional timeZone, duration. Response data is grouped by
date.
- bookings query = list bookings (filters: status, attendeeEmail,
eventTypeId, limit, cursor, ...). get = one by its uid. create
= book a meeting ({start, eventTypeId, attendee:{name, email,
timeZone}}) OR act on an existing booking via an action field
+ bookingUid: action ∈ cancel | reschedule (needs new start)
| confirm | decline. Cal.com has no PUT/DELETE for bookings —
use create+action.
- schedules list/get/create/update/delete availability schedules (working
hours). create REQUIRES name + timeZone; isDefault
defaults false; availability defaults Mon-Fri 09:00-17:00.
- accounts list the configured Cal.com account labels (see below).

MCP note: there is no query string over MCP, so list filters go in the query
arg, the booking uid / schedule id / event type id go in the id arg (or, for
booking cancel/reschedule/confirm/decline, as bookingUid inside the create
body alongside action). The correct dated cal-api-version header is set by
the gateway per endpoint — you never send it.

Multiple Cal.com accounts. One login can hold several Cal.com accounts, each
stored under its own credential calcom:<label> via
PUT /v1/credentials/calcom:<label> {api_key:"cal_live_…"} (e.g. calcom:work).
EVERY tool (except accounts-list) accepts an optional top-level account
argument naming which account to target. When more than one account is
configured, account is REQUIRED — a call that omits it is rejected with the
list of valid labels. Call endpointr_calcom_cal_com_accounts_list to see the
labels. If only a single unlabelled calcom credential exists, account may be
omitted.

Authentication. The connector OAuth establishes which endpointr customer you
are. The upstream Cal.com API key is resolved per customer from the credential
vault — store it once via PUT /v1/credentials/calcom {api_key:"cal_live_…"}
(test keys start cal_…), or per account as calcom:<label> (see above).
Alternatively pass api_key inline in any tool's body/query for per-request
passthrough; it is stripped before the request reaches Cal.com.

Tool arguments mirror the HTTP request body / path id / query string. Most
create/update tools accept an open body object — the schema lists documented
fields, but handlers forward additional keys to Cal.com verbatim.

claude mcp add calcom --transport http https://calcom.calendars.mcp.endpointr.com
MCPendpointr-dinero — https://dinero.mcp.endpointr.com
groups: accounting

This connector is a Dinero accounting relay (api.dinero.dk) exposed through
endpointr. Each tool mirrors one Dinero REST endpoint. Tool names follow:
endpointr_accounting_dinero_<resource>_<verb>
where verb is one of: query (GET list / read sub-operation), get (GET by id),
list (GET collection), create (POST), update (PUT), delete (DELETE).

Resources: invoices (+ reminders), contacts (+ notes), products, accounts,
accounting-years, ledger-items, entries, vouchers (manual / purchase /
purchase credit notes), purchase-vouchers (payments), sales-credit-notes,
trade-offers, voucher-templates, reports, webhooks, integrations,
organizations (+ verification), state-of-account, business-goals, files,
attachments, sms, electronic-invoice, settings, countries, vat-types,
unified-vouchers.

Sub-operations and nested resources are folded onto an action field (and a
type field for the vouchers family) because the gateway publishes flat REST
routes only — e.g. book an invoice with create {action:'book', id:'…'}, read
a PDF with query {action:'pdf', id:'…'}. Each tool's description lists its
available actions and the ids they require. Reads and deletes of nested
resources (notes, reminders, payments, attachment files) are reached through
query/create with the relevant action, not the bare get/delete tool
(which only carries a single path id).

Authentication. The connector OAuth establishes which endpointr customer you
are. The upstream Dinero token uses Visma Connect OAuth2 (scopes
dineropublicapi:read dineropublicapi:write offline_access). Store it once
via PUT /v1/credentials/dinero {access_token, refresh_token, client_id,
client_secret, organization_id}
— the relay auto-refreshes the access_token
against connect.visma.com when it expires. Alternatively pass access_token
+ organization_id inline on any call (per-request passthrough; both are
stripped before the request reaches Dinero). organization_id is required
(inline or stored) since most Dinero paths are organisation-scoped.

Tool arguments mirror the HTTP request body / path id / query string. Most
create/update tools accept an open body object — the schema lists documented
fields, but handlers forward additional keys to Dinero verbatim.

claude mcp add dinero --transport http https://dinero.mcp.endpointr.com
MCPendpointr-analytics — https://analytics.mcp.endpointr.com
groups: searchconsole, analytics_data, analytics_admin

This connector bundles Google's measurement APIs — Search Console (read-only)
and Google Analytics 4 (Data reads + Admin reads AND writes) — exposed through
endpointr. Each tool mirrors one Google REST endpoint. Tool names follow:
endpointr_searchconsole_<resource>_<verb> (Search Console)
endpointr_analytics_data_ga4_<resource>_<verb> (GA4 Data API)
endpointr_analytics_admin_ga4_<resource>_<verb> (GA4 Admin API)
where verb is one of: list (GET collection), query (GET with query params),
get (GET by id), create (POST), update (PATCH), delete (DELETE).

Google Tag Manager is a SEPARATE connector (endpointr-tagmanager) — container,
tag, trigger and publish work lives there, not here.

Search Console (https://www.googleapis.com/webmasters/v3):
- sites list verified sites (list); get one by ?siteUrl=.
- search-analytics create = run a Search Analytics query (clicks,
impressions, CTR, position) for a property. Requires
siteUrl + startDate + endDate; dimensions optional.
- sitemaps query = list a property's sitemaps (requires siteUrl;
add feedpath for a single sitemap).
- url-inspection create = inspect one URL's index status on a property
(requires inspectionUrl + siteUrl).

GA4 Data API (https://analyticsdata.googleapis.com/v1beta):
- ga4-reports create = runReport (metrics × dimensions over dateRanges).
- ga4-realtime create = runRealtimeReport (last 30 minutes).
- ga4-metadata query = discover the dimensions/metrics a property exposes.
All require property (accepts properties/123456 or a bare 123456).

GA4 Admin API (https://analyticsadmin.googleapis.com/v1beta) — discovery AND
configuration. This is the half that SETS TRACKING UP on an account that
already exists:

- ga4-account-summaries list = every account + property in one call. Start
here; you need no ids to make this call.
- ga4-accounts list = accounts the user can access. An account CANNOT be
created through any Google API (a human must accept the
Terms of Service), so this list is the ceiling —
everything below hangs off one of these.
- ga4-properties query (requires ?account=) / get / create / update /
delete. create needs {account, displayName, timeZone};
set currencyCode too — GA4 defaults to USD and past data
is not restated. delete is a 63-day soft delete.
- ga4-data-streams query (requires ?property=) / get / create / update /
delete. Creating a WEB_DATA_STREAM is what MINTS THE
MEASUREMENT ID (webStreamData.measurementId, G-XXXXXXXXXX)
— the value a Google tag or a GTM googtag tag needs.
- ga4-key-events the GA4 UI's "conversions". query/get/create/update/
delete; create = {property, eventName, countingMethod}.
The event need not have fired yet.
- ga4-custom-dimensions / ga4-custom-metrics register event parameters so
they are queryable. A parameter with no custom dimension
is collected and then INVISIBLE — the step most tracking
setups miss. Archive (not delete) via
create {action:'archive', id}.
- ga4-google-ads-links query/create/update/delete. create = {property,
customerId} where customerId is the 10-digit GOOGLE ADS
id. This is what lets key events import as Ads
conversions.
- ga4-data-retention per-property singleton, addressed by the PROPERTY
id. New properties default to TWO_MONTHS; FOURTEEN_MONTHS
is almost always what you want, and it is not
retroactive.
- ga4-enhanced-measurement a WEB stream's automatic events (scrolls,
outbound clicks, site search, video, downloads, form
interactions). get/update by propertyId:streamId.
v1alpha — no v1beta equivalent exists.
- ga4-measurement-protocol-secrets list/get/create/delete per data stream
(needs BOTH property and stream). The secret + measurement
ID are the credentials for SERVER-SIDE events.

THE ID MODEL for the Admin tools: collection verbs (query/create) take the
parent as property (accepting properties/123456 or a bare 123456) — plus
stream one level down for measurement-protocol secrets. get/update/delete take
the target as id, either the full resource name Google returns in name
(properties/123/keyEvents/456) or the MCP-friendly colon composite of the same
ids in order (123:456).

UPDATES ARE PATCHES. Google rejects a patch that does not name the fields it
changes, so send only the fields you are changing and the relay derives
updateMask from your body's top-level keys. Pass update_mask explicitly to
override — required when targeting a nested leaf, e.g.
{update_mask:'webStreamData.defaultUri', webStreamData:{defaultUri:'https://…'}}.

A NEW SITE, END TO END: ga4-accounts (pick the account) -> ga4-properties create
-> ga4-data-streams create (copy the measurement ID out of the response) ->
ga4-data-retention update -> ga4-key-events create (one per conversion) ->
ga4-custom-dimensions create (one per parameter your tags will send) ->
ga4-google-ads-links create. Then install the measurement ID on the site —
that part lives in the tagmanager connector, or in your own deploy.

Authentication. The connector OAuth establishes which endpointr customer you
are. The upstream Google token is resolved per customer from the credential
vault — store a refresh-triplet once via PUT /v1/credentials/google
{client_id, client_secret, refresh_token}
and endpointr mints short-lived
access_tokens on every call. Alternatively pass oauth_token (a fresh
access_token) or the refresh-triplet inline on any call (per-request
passthrough; the credential fields are stripped before the request reaches
Google). The refresh_token must be minted with the scopes you need:
- https://www.googleapis.com/auth/webmasters.readonly (Search Console)
- https://www.googleapis.com/auth/analytics.readonly (Analytics Data+Admin reads)
- https://www.googleapis.com/auth/analytics.edit (GA4 Admin WRITES)

WRITING to the GA4 Admin API needs analytics.edit. Both Google connect flows
now request it: the shared google slot ("Connect with Google") and the
dedicated google-analytics slot ("Connect Google Analytics"), which exists for
customers who want GA4 write consent kept off the slot Tasks and Search Console
share. Every GA4 tool resolves its token as: credentials in the request bag ->
the google-analytics slot when the customer has one -> the google slot.

Widening the scope list grants nothing retroactively: a refresh_token minted
before analytics.edit was requested still carries only its original scopes, so a
write on such a grant comes back as Google's own 403 PERMISSION_DENIED until the
customer re-runs Connect once. Reads are unaffected either way.
When a token is minted from the vault, the new access_token is surfaced in the
response as refreshed_access_token so clients can cache it (~1h TTL).

Tool arguments mirror the HTTP request body / path id / query string. Most
create/update tools accept an open body object — the schema lists documented
fields, but handlers forward additional keys to Google verbatim (so the full
runReport / searchAnalytics request shapes are supported).

claude mcp add analytics --transport http https://analytics.mcp.endpointr.com
MCPendpointr-google-ads — https://googleads.mcp.endpointr.com
groups: googleads

This connector is a Google Ads API (v25, REST) relay exposed through
endpointr: FULL account management — reads via GAQL, writes via each
resource's :mutate endpoint (one operation per call), plus the special
service methods (conversion uploads, keyword planning, recommendations,
account creation, Customer Match, experiments). Each tool mirrors one
REST endpoint under /v1/googleads/*. Tool names follow:
endpointr_googleads_<resource>_<verb>
where verb is one of: list (GET collection), query (GET with query
params), create (POST), update (PUT), delete (DELETE).

Typical flow: accessible-customers list (discover which Ads accounts you
can reach) -> any resource's query tool (canned GAQL id-discovery; every
row's resourceName is the id its update/delete take) -> create/update/
delete. reports create runs raw GAQL for anything the canned lists don't
cover.

THE ID MODEL (read this first): update/delete tools take the target as
id, in any of three forms — the full resource name exactly as query
tools return it (customers/<cid>/<resource>/<rid>), the composite
<customer_id>:<resource_id> (the MCP-friendly form; put the account's 10
digits before the colon), or a bare id when customer_id rides in the
update body. Google's own composite resource ids keep their ~ (an ad is
<adGroupId>~<adId>; an asset link <assetId>~<fieldType>).

Resources, grouped:
- Discovery & reporting: accessible-customers (list = every account the
OAuth grant reaches directly; empty usually means MCC-only access —
set login_customer_id to the manager and query customer_client via
reports), reports (create = raw GAQL SELECT, REQUIRES customer_id +
query; pageSize/pageToken page up to 10,000 rows).
- Campaign structure: campaign-budgets, ad-campaigns, ad-groups,
ad-group-ads, keywords (ad-group criteria of type KEYWORD).
- Targeting & bidding: campaign-criteria (geo/language/schedule/device/
negatives), ad-group-criteria (all criterion types),
customer-negative-criteria, shared-sets + shared-criteria +
campaign-shared-sets (negative keyword lists), labels +
campaign/ad-group/ad-group-ad-labels, bidding-strategies (portfolio),
campaign-bid-modifiers, ad-group-bid-modifiers.
- Assets: assets (permanent — NO delete; detach links instead),
asset-sets + asset-set-assets + campaign/ad-group-asset-sets, and the
link tables customer-assets / campaign-assets / ad-group-assets.
- Performance Max: asset-groups, asset-group-assets,
asset-group-listing-group-filters, asset-group-signals.
- Conversions & goals: conversion-actions, conversion-uploads (create
only; action = click|call|adjustments; Google requires
partialFailure:true), conversion-value-rules + -rule-sets,
conversion-custom-variables, custom-conversion-goals,
customer-conversion-goals + campaign-conversion-goals,
conversion-goal-campaign-configs, goals + campaign-goal-configs
(v25 unified goals).
- Audiences: user-lists, offline-user-data-jobs (Customer Match:
create job -> action add_operations -> action run -> poll query),
ad-audiences (the audience resource), custom-audiences,
remarketing-actions.
- Planning: keyword-planning (create only; action = ideas|
historical_metrics|forecast_metrics), geo-targets (query the
constants table; create = suggest by location name, no customer_id),
recommendations (query + create with REQUIRED action apply|dismiss|
generate — apply changes live spend).
- Account ops: customers (query own row; create = NEW client account
under an MCC; update = the account itself), customer-user-accesses,
customer-user-access-invitations, customer-client-links (manager
side), customer-manager-links (client side; action move),
customer-labels (label MCC children), product-links +
product-link-invitations (Merchant Center / GA4 / Hotel Center —
update ACCEPTED|REJECTED answers incoming invitations), and billing:
billing-setups, account-budget-proposals, invoices,
payments-accounts.
- Experiments: experiments (create shell; action schedule|promote|end|
graduate), experiment-arms.
- Bulk: batch-jobs (create -> action add_operations with mixed
mutateOperations -> action run -> poll query -> ?job= results).

Cross-cutting: money/bids are in MICROS (1,000,000 = 1 unit of account
currency). Pause/enable anything via update {status:"PAUSED"|"ENABLED"}.
validate_only:true dry-runs any mutate without applying it.
update_mask overrides the auto-derived mask (derived recursively from
the fields you send, so nested objects mask only their set leaf paths).
partial_failure is accepted where Google supports it.

Authentication. The connector OAuth establishes which endpointr customer
you are. This uses its OWN vault slot (google-ads) — separate from the
google slot used by Tasks/Search Console/Analytics, so consent for one
never implicitly grants the other. Store a refresh-triplet once via
PUT /v1/credentials/google-ads {client_id, client_secret, refresh_token}
and endpointr mints short-lived access_tokens on every call — or click
"Connect Google Ads" on the customer's admin page to run the consent flow
and have refresh_token written automatically (never type it by hand).
Alternatively pass oauth_token (a fresh access_token) or the
refresh-triplet inline per-request (credential fields are stripped before
reaching Google). Required scope: https://www.googleapis.com/auth/adwords.

Beyond OAuth, every call ALSO needs:
- developer_token (REQUIRED) — issued once per Ads MANAGER account via
the Ads UI (Tools & Settings > API Center); never rotates. Store it in
the google-ads vault entry, or pass developer_token per-request.
- login_customer_id (optional) — the manager account's id, needed only
when you reach the target account THROUGH a manager rather than
owning it directly. Same hybrid resolution.

When a token is minted from the vault, the new access_token is surfaced in
the response as refreshed_access_token so clients can cache it (~1h TTL).

Tool arguments mirror the HTTP request body / path id / query string.
create/update tools accept an open body object — the schema lists
documented fields, but bodies are forwarded to Google verbatim, so the
full v25 request shape of every resource is supported. Over MCP there is
no query string: customer_id rides in the query arg on query verbs,
in the body on create/update, and inside the composite/full-resource
id on delete.

claude mcp add googleads --transport http https://googleads.mcp.endpointr.com
MCPendpointr-tagmanager — https://tagmanager.mcp.endpointr.com
groups: tagmanager

This connector is a Google Tag Manager API (v2) relay exposed through
endpointr: full container management — accounts, containers, workspaces,
tags, triggers, variables, built-in variables, folders, environments, and
versioning INCLUDING publish-to-live. Each tool mirrors one REST endpoint
under /v1/tagmanager/*. Tool names follow:
endpointr_tagmanager_gtm_<resource>_<verb>
where verb is one of: list (GET collection), query (GET with query params),
get (GET by id), create (POST), update (PUT), delete (DELETE).

THE ID MODEL (read this first): GTM addresses everything by a nested path
(accounts/{a}/containers/{c}/workspaces/{w}/tags/{t}). Collection verbs
(query/create) take the parent ids as account_id / container_id /
workspace_id (query arg on reads, body on writes). get/update/delete take
the target as id — either the full GTM path every resource carries, or
the colon composite of the same ids in order: a container is
accountId:containerId, a workspace accountId:containerId:workspaceId, a
tag/trigger/variable/folder appends its own id as the 4th part.

Typical flow: gtm-accounts list -> gtm-containers query ?account_id (the
numeric containerId is what the other tools take; publicId is the
GTM-XXXXXXX snippet id) -> gtm-workspaces query (find the Default
Workspace) -> create/edit tags, triggers and variables in that workspace ->
gtm-workspaces create {action:'create_version'} (snapshot) -> gtm-versions
create {action:'publish', version_id} (LIVE on the site).

Resources:
- gtm-accounts list = every GTM account the grant reaches (start
here); get by accountId. Read-only.
- gtm-containers query ?account_id = list; or look one up from its
public snippet id with ?destination_id=GTM-XXXXXXX (no
account needed). create needs {account_id, name,
usageContext:["web"]}. delete is IRREVERSIBLE.
- gtm-workspaces the editable draft layer every tag/trigger/variable
write happens in. create also folds the sub-methods:
{action:'create_version'} snapshots the workspace into
a version (response carries containerVersion.
containerVersionId), {action:'sync'} pulls
latest-version changes in.
- gtm-tags CRUD inside a workspace ({…ids, name, type,
parameter[], firingTriggerId[]}). Common types:
googtag (Google tag/GA4), gaawe (GA4 event), html
(custom HTML), awct (Ads conversion).
- gtm-triggers CRUD; types pageview, click, linkClick, customEvent,
scrollDepth, … A trigger's triggerId feeds tags'
firingTriggerId.
- gtm-variables user-defined variables CRUD; types v (data layer), c
(constant), jsm (custom JS), u (URL), … Reference as
{{Name}} from tag/trigger parameters.
- gtm-built-in-variables the one-click toggles (Page URL, Click
Classes, …): query = enabled ones, create = enable
({…ids, type:"pageUrl" | [types…]}), delete = disable
(id accountId:containerId:workspaceId:type).
- gtm-folders workspace organisation CRUD; file an entity by setting
parentFolderId on the entity itself.
- gtm-versions query ?account_id&container_id = version HEADERS;
get by a:c:versionId — or the specials a:c:live
(the published version) and a:c:latest. create is
action-only: {action:'publish'|'undelete'|'set_latest',
account_id, container_id, version_id} — PUBLISH PUSHES
LIVE. update renames; delete trashes a version.
- gtm-environments live/latest plus custom preview/staging environments
(auth codes + preview links). Full CRUD.

Cross-cutting: every response wraps the GTM payload as {data: …}. Mutable
resources carry a fingerprint — pass it on update/publish for
optimistic-concurrency protection (omitted = last write wins). Writes land
in a workspace and are NOT live until a version is created and published.

MCP note: there is no query string over MCP, so parent ids ride the query
arg on query verbs, the body on create/update, and the composite/full-path
id on get/delete.

Authentication. The connector OAuth establishes which endpointr customer
you are. This uses its OWN vault slot (google-tagmanager) — separate from
the google slot used by Tasks/Search Console/Analytics, so Tag Manager's
write+publish consent is granted independently. Store a refresh-triplet
once via PUT /v1/credentials/google-tagmanager {client_id, client_secret,
refresh_token}
and endpointr mints short-lived access_tokens on every call
— or click "Connect Google Tag Manager" on the customer's admin page to run
the consent flow and have refresh_token written automatically. Alternatively
pass oauth_token (a fresh access_token) or the refresh-triplet inline
per-request (credential fields are stripped before reaching Google).
Scopes: tagmanager.readonly, tagmanager.edit.containers,
tagmanager.edit.containerversions, tagmanager.publish,
tagmanager.delete.containers.

When a token is minted from the vault, the new access_token is surfaced in
the response as refreshed_access_token so clients can cache it (~1h TTL).

Tool arguments mirror the HTTP request body / path id / query string.
create/update tools accept an open body object — the schema lists
documented fields, but bodies are forwarded to Google verbatim, so the full
v2 request shape of every resource is supported.

claude mcp add tagmanager --transport http https://tagmanager.mcp.endpointr.com
MCPendpointr-reddit-ads — https://redditads.mcp.endpointr.com
groups: redditads

This connector is a Reddit Ads API (v3, https://ads-api.reddit.com/api/v3)
relay exposed through endpointr: full campaign management, audiences,
creatives, conversions (CAPI), product catalogs, targeting reference data,
and reporting. Each tool mirrors one or more REST endpoints under
/v1/redditads/*. Tool names follow:
endpointr_redditads_reddit_<resource>_<verb>
where verb is one of: list (GET collection), query (GET with query params),
get (GET by id), create (POST), update (PATCH upstream), delete (DELETE).

Id chain (chain these): reddit-me list (smoke test) -> reddit-businesses
list (business_id) -> reddit-ad-accounts query ?business_id (ad_account_id)
-> campaigns / ad-groups / ads / custom-audiences / saved-audiences /
lead-gen-forms / reports, all keyed by ad_account_id. Posts, structured
posts and creative assets hang off reddit-profiles (profile_id, from
?ad_account_id or ?business_id). Product feeds/sets/products hang off
reddit-product-catalogs (catalog_id, from ?business_id). Campaign create
also needs a funding_instrument_id (reddit-funding-instruments).

Campaign build order: campaign create (objective, funding_instrument_id)
-> ad-group create (campaign_id + targeting/bid; validate targeting values
with reddit-targeting first) -> post create (profile_id) -> ad create
(ad_group_id + post_id). Start everything configured_status=PAUSED and
flip to ACTIVE when ready.

Reddit API shapes the relay smooths over:
- Write bodies are auto-wrapped in Reddit's {"data": {...}} envelope —
pass flat fields (an explicit data key is forwarded as-is).
- Updates are PATCH upstream; send only the fields to change. There is
NO delete for campaigns/ad groups/ads — archive/pause via
update {configured_status: PAUSED|ARCHIVED}.
- Lists paginate via page.size/page.token (aliases page_size/
page_token accepted) and return pagination.next_url — pass that URL
back as a next_url query field to fetch the next page.
- Money is in MICROCURRENCY (1,000,000 = 1 unit of the account
currency); timestamps are ISO 8601 UTC.
- MCP note: parent ids ride the query arg on query verbs and the
body on create; get/update/delete take only the object id — Reddit
ids are globally unique, so no composite ids anywhere.

Authentication. The connector OAuth establishes which endpointr customer
you are. This uses its OWN vault slot (reddit-ads). Store client_id +
client_secret once and run the admin page's "Connect Reddit Ads" consent
flow to mint the refresh_token automatically — or PUT the full triplet via
PUT /v1/credentials/reddit-ads {client_id, client_secret, refresh_token}.
endpointr exchanges the triplet for a short-lived (~24h) access_token on
every call and persists any rotated refresh_token. Alternatively pass
oauth_token (a fresh access_token) or the refresh-triplet inline
per-request (credential fields are stripped before reaching Reddit). An
optional vault user_agent overrides the descriptive User-Agent endpointr
sends (Reddit blocks generic UAs). Scopes: adsread adsedit adsconversions
adsdatadeletion. Reads work for any authorized app; CREATE/EDIT endpoints
additionally require Reddit's Ads API developer approval.

When a token is minted from the vault, the new access_token is surfaced in
the response as refreshed_access_token so clients can cache it (~24h TTL).

Tool arguments mirror the HTTP request body / path id / query string. Most
create/update tools accept an open body object — the schema lists
documented fields, but handlers forward additional keys to Reddit verbatim.

claude mcp add redditads --transport http https://redditads.mcp.endpointr.com
MCPendpointr-sendmails — https://sendmails.outreach.mcp.endpointr.com
groups: sendmails

This connector is a SendMails.io relay (a hosted Acelle Mail —
https://app.sendmails.io/api/v1) exposed through endpointr: newsletter
campaigns, mail lists, and subscribers. Each tool mirrors one REST endpoint
under /v1/sendmails/*. Tool names follow:
endpointr_sendmails_sendmails_<resource>_<verb>
where verb is one of: list (GET collection, no params), query (GET with
params), get (GET by uid), create (POST), update (PATCH upstream), delete.

Typical newsletter flow: lists list (pick/create a list_uid) -> subscribers
create (add recipients) -> campaigns create (the newsletter: subject, html,
from) -> campaigns create {action:'run', uid} (start sending) -> campaigns
get (delivery statistics).

Resources:
- lists list = all mail lists, NO parameters — the id source: each
result's uid is the list_uid subscribers + campaigns
need. get by uid. create needs name, from_email, from_name
+ a contact block (company, address_1, city, zip, country_id,
email, phone?). Fold {action:'add_field', uid, type, label,
tag} to add a custom subscriber field. delete by uid.
- subscribers FULL CRUD (create/update/delete all write — not read-only).
query = subscribers on a list (REQUIRES list_uid; filters:
per_page, page, keyword?). get by uid. create adds one
({list_uid, EMAIL, FIRST_NAME?, LAST_NAME?, ...}). Fields are
keyed by their Acelle identifier (the uppercase field key:
EMAIL, FIRST_NAME, custom fields). Label a subscriber with the
built-in tag field — SINGULAR, a comma-separated STRING
(e.g. "vip,newsletter"); there is no tags array, and sending
tags is silently ignored. status field = subscribed|
unsubscribed. Fold {action:'subscribe'|'unsubscribe', uid} to
flip status. update by uid (PATCH, send only changed fields);
delete by uid.
- campaigns THE NEWSLETTER SURFACE. list = all campaigns, NO parameters
(each result's uid is the campaign id). get by uid returns
details + statistics (sent/open/click/bounce). create a
draft: {name, list_uid, subject, from_email, from_name,
reply_to, html, plain?, track_open?/track_click? ('yes'/'no'),
run_at? ('Y-m-d H:i:s' to schedule)}. Fold
{action:'run'|'pause'|'resume', uid} for the send lifecycle.
update a draft by uid; delete by uid.

Authentication. The connector OAuth establishes which endpointr customer you
are. The upstream api_token is resolved per customer from the vault — store
it once via PUT /v1/credentials/sendmails {api_token:"…"} (from the
SendMails.io dashboard, Account > API). Vault-only.

Tool arguments mirror the HTTP request body / path id / query string; extra
fields are forwarded to SendMails verbatim.

claude mcp add sendmails --transport http https://sendmails.outreach.mcp.endpointr.com
MCPendpointr-encharge — https://encharge.outreach.mcp.endpointr.com
groups: encharge

This connector is an Encharge.io relay (https://api.encharge.io/v1) exposed
through endpointr: people, tags, fields, segments, webhooks, account. Each
tool mirrors one REST endpoint under /v1/encharge/*. Tool names follow:
endpointr_encharge_encharge_<resource>_<verb>
where verb is one of: list (GET, no params), query (GET with params),
create (POST), update (PATCH upstream), delete (DELETE).

THE OUTREACH MODEL — read this first: Encharge's public API has NO
campaign/newsletter endpoints. Emails are sent by Flows built in the
Encharge UI; the API's job is getting people INTO those flows. To send
outreach: people create (upsert the person) -> tags create ({tag:"…",
email}) — a Flow triggered by that tag sends the email sequence.

Resources:
- account list = account info (peopleCount, timezone, …), NO parameters.
The connector smoke test — call it first.
- people query = fetch specific people by identifier (email / user_id /
id; emails comma-list for several; raw people array for
mixed identifiers). create = UPSERT one person object or a
people array (identified by email/userId/id; any extra keys
become field values). Fold {action:'unsubscribe', email} to
stop all email to a person. delete by id (the person's email
or Encharge id; add force=true for GDPR wipe — else archives).
- tags create = add tag(s) ({tag:"a,b", email}) — THE outreach
trigger. Fold {action:'remove', tag, email} to untag. Tags are
free strings; no upstream list — a person's current tags are on
their tags field via people query.
- fields list = all person fields, NO parameters (the field name is
the id). create ({name, type, title?} or a fields array),
update by fieldName, delete by fieldName.
- segments list = all segments, NO parameters (id source). query = people
in one segment (REQUIRES segment_id; limit/offset/attributes/
sort/order).
- webhooks create = subscribe to Encharge events ({eventType, targetUrl};
eventTypes: newUser, updatedUser, unsubscribedUser,
added-tag-<TAG>, removed-tag-<TAG>). delete by the numeric id
FROM THE CREATE RESPONSE — no upstream list (keep the id).

Authentication. The connector OAuth establishes which endpointr customer you
are. The upstream API key is resolved per customer from the vault — store it
once via PUT /v1/credentials/encharge {api_key:"…"} (Encharge > Settings >
Account). Sent as the X-Encharge-Token header. Vault-only.

Tool arguments mirror the HTTP request body / path id / query string; extra
person keys are forwarded to Encharge verbatim (they become field values).

claude mcp add encharge --transport http https://encharge.outreach.mcp.endpointr.com
MCPendpointr-sendr — https://sendr.outreach.mcp.endpointr.com
groups: sendr

This connector is a Sendr.io relay (https://api.sendr.io) exposed through
endpointr: signal-driven outreach — contact sheets feeding campaign
sequences, personalized pages (GIF / dynamic audio / lip-sync video), and a
unified reply inbox. Each tool mirrors one REST endpoint under /v1/sendr/*.
Tool names follow:
endpointr_sendr_sendr_<resource>_<verb>
where verb is one of: list (GET, no params), query (GET with params),
get (GET by id), create (POST), update (PATCH upstream), delete.

THE MODEL: a campaign runs on a SHEET of contacts. Campaigns are built in
the Sendr app and are READ-ONLY via the API — you add contacts by adding
ROWS to the campaign's sheet. Replies land in the INBOX. Personalized
pages are generated from PAGE TEMPLATES.

Resources & id chains:
- seat list = API-user/workspace info, NO parameters. The
connector smoke test — call it first. Also the seat id
source for inbox assignment.
- sheets list (no params) / query (offset, limit, name,
campaignId) / get. Each sheet's id feeds sheet-columns
and sheet-rows.
- sheet-columns query REQUIRES sheet_id — the sheet's field schema;
column names are the keys sheet-rows accepts.
- sheet-rows create REQUIRES sheet_id + the row fields (column
names) — HOW YOU ADD A CONTACT to a campaign.
- campaigns list (no params) / query (page, limit, search, status
DRAFT|ACTIVE|PAUSED) / get. READ-ONLY upstream — no
create/pause via API. Each campaign carries its
sheetId (feed sheet-rows to add contacts).
- page-templates list = templates (no params; the templateId source);
get = a template's VARIABLE TAGS (the keys for
variablesValues).
- pages create = generate a personalized page ({templateId,
variablesValues, gifSource?, videoBackgroundUrl?,
attributes?, webhookUrl?}) -> {pageId, pageUrl}. get by
pageId/slug = status (pending|done|failed) + generated
assets. Poll get until done, or use webhooks.
- dynamic-audio create = queue personalized audio ({audioUrl,
targetWord, replacementWord}) -> jobId.
- video create = queue personalized video (same + videoUrl;
mode merge|lipsync|video_only) -> jobId.
- webhooks list = workspace webhooks (no params). Sendr keys
webhooks BY URL, so mutations fold onto create:
create {name, url, events?} | {action:'update', url, …}
| {action:'delete', url} | {action:'toggle', url,
enabled} | {action:'reveal_secret', url}. Events:
page:pending/done/failed, engagement:page_view/
audio_play/video_play/button_click/meeting_booked, ….
- inbox list = 25 latest reply threads (no params) / query
(search, status unread|deleted, channels, campaign_ids,
tag_ids, starred, cursor, limit) / get by threadId.
create = act via action: send {thread_id, body} (reply),
read/unread, star/unstar, tag/untag {thread_id, tag_id},
delete/restore {thread_ids[]}, assign {thread_ids[],
seat_id}.
- inbox-messages query REQUIRES thread_id (+cursor/limit) = the thread's
messages; add message_id = attachment list; add
attachment_id = download (base64 + mime).
- inbox-tags list/query/create/update/delete — the tag_id source.
- inbox-campaigns list = campaigns that have inbox threads (the
campaign_ids filter source).
- analyze-website create {url} = about text + generated buyer personas.

Authentication. The connector OAuth establishes which endpointr customer you
are. The upstream API key is resolved per customer from the vault — store it
once via PUT /v1/credentials/sendr {api_key:"…"} (generate it at
app.sendr.io > Settings > API keys). Sent as the X-API-Key header. Vault-only.

Tool arguments mirror the HTTP request body / path id / query string; extra
fields are forwarded to Sendr verbatim.

claude mcp add sendr --transport http https://sendr.outreach.mcp.endpointr.com
MCPendpointr-leadshark — https://leadshark.mcp.endpointr.com
classes: Leadshark*

This connector is a LeadShark relay (https://apex.leadshark.io) exposed through
endpointr: LinkedIn enrichment, post-engagement AUTOMATIONS, lead capture,
scheduled posts, lead-magnet pages/links, engagement signals, and webhooks.
Each tool mirrors one REST endpoint under /v1/social/leadshark-*. Tool names:
endpointr_social_leadshark_<resource>_<verb>
where verb is one of: list (GET, no params), query (GET with params),
get (GET by id), create (POST), update (PUT), delete (DELETE).

THE AUTOMATION MODEL (the headline feature): an automation watches ONE
LinkedIn post and DMs/replies/connects with people who comment matching
keywords. To create one you need the post's URN + share URL, which come from
the posts tool:
1. leadshark-posts query -> pick a post; copy item.post_id (a URN like
urn:li:activity:7150…) and item.share_url.
2. leadshark-automations create {name, post_id:<URN>, linkedin_post_url:
<share_url>, keywords, dm_template, …}.
Use the FULL URN exactly — the numeric tail alone creates the row but
engagement won't bind. Pass only {name, post_id, linkedin_post_url} for a
webhook-only automation (comments stream to your webhooks; no LeadShark-side
actions). Toggle Running/Paused/Stopped via leadshark-automation-status.

Resources & id chains:
- enrich-person / enrich-company : query = enrich a LinkedIn profile/company
(real profile view; soft cap ~200-250/day).
- linkedin-search : create = LinkedIn people/company search.
- posts : query = your (or anyone's, via linkedin_id) LinkedIn
posts. THE id source for automations (post_id + share_url)
and the engagement tools (post_id URN).
- automations : list/query/get/create/update/delete. create binds to a
post (see above). automation-status update flips status.
automation-templates query = reusable templates; each id
is a template_id for scheduled-posts.
- leads : query = captured leads (email + ICP score when present).
- bookmarks / bookmark-tags : save LinkedIn profiles with tags/notes.
- scheduled-posts : list/create/update/delete. create {content,
scheduled_time} (15 min-90 days out) with an optional
automation pre-automation that activates on publish —
it accepts page_id (links_enabled:true) or template_id.
- post-stats : query = post metrics; summary=true = lifetime totals
(Apex). dashboard-activity query = activity rollups.
- discover (Apex): fresh lead-magnet posts to engage.
- signals (Apex) : signals = ranked hot leads; signal-events = raw events
(type + since/until or since_captured/until_captured).
- engagement (Pro+): post-reactions / post-comments / post-reposts REQUIRE
post_id (URN from posts). profile-viewers = Apex + Premium.
- links (Pro+) : list/get/create/delete tracking links; each slug feeds
link-analytics and link-events. Attach a page via page_id.
- pages (Pro+) : quiz lead-magnet pages CRUD; each id is a page_id for
links and pre-automations; page-stats / page-responses /
page-emails REQUIRE page_id.
- webhooks (Pro) : CRUD + webhook-test. event_types new_comment /
email_captured / lead_sent (Pro) + new_profile_visit /
new_like (Apex only). HMAC-SHA256 signed; secret shown once.

Tier gating (Pro / Pro+ / Apex) is enforced by LeadShark — a call outside your
plan returns 403 with an explanatory body. Rate limits: 250/hr, 1000/day,
100/min (429 on exceed — back off).

Authentication. The connector OAuth establishes which endpointr customer you
are. The upstream key is resolved per customer from the vault — store it once
via PUT /v1/credentials/leadshark {api_key:"…"} (LeadShark Settings → API
Access). Sent as the x-api-key header. You may also pass api_key inline in
any tool's body/query for per-request passthrough; it is stripped before the
request reaches LeadShark.

claude mcp add leadshark --transport http https://leadshark.mcp.endpointr.com
MCPendpointr-paypal — https://paypal.mcp.endpointr.com
classes: Paypal*

This connector is a PayPal REST API relay (https://api-m.paypal.com) exposed
through endpointr, scoped to READ-ONLY money reporting — invoices,
subscriptions, balance, and income. No write/refund/charge operations. Each
tool mirrors one PayPal endpoint. Tool names follow:
endpointr_payments_paypal_<resource>_<verb>
where verb is: list (GET collection / singleton), query (GET/search with
params), get (GET by id).

Resources:
- overview list = a computed snapshot in ONE call: balances +
invoices bucketed PAID / OUTSTANDING / OVERDUE + gross
income over the last 30 days, grouped per currency. Start
here for "how are we doing".
- invoices query = list invoices, or SEARCH when you pass filters
(status e.g. PAID|SENT|UNPAID|PAYMENT_PENDING|
MARKED_AS_PAID|PARTIALLY_PAID|CANCELLED, invoice_date_range,
due_date_range, recipient_email, total_amount_range,
plus page/page_size). get = one invoice by id. PayPal has
no "overdue" status — overdue = outstanding past its due date
(filter due_date_range, or read overview.invoices.overdue).
- transactions query = Transaction Search — the true "overall income"
source (every payment received, not just invoiced).
REQUIRES start_date+end_date (RFC3339), max 31 days apart;
page by month for longer ranges.
- balance list = current available money on the account, per currency.
- plans query = billing plans (the NAMED recurring products). This
is how you "search subscriptions by name/description" — pass
name/description (filtered client-side within the fetched
page). get = one plan by id (P-…).
- subscriptions get = one subscription by id (I-…): status, plan_id,
subscriber, next billing, last payment, outstanding balance.
PayPal has NO list/search for subscriptions — find the plan
by name via plans, read a subscriber's sub by its I-… id.
- accounts list = the configured PayPal account labels (see below).

balance, transactions, and overview's balance/income need the PayPal REST
app to have the "Transaction Search" feature enabled; data can lag live by ~3h.

Authentication. The connector OAuth establishes which endpointr customer you
are. The upstream PayPal credential is resolved per customer from the vault —
store it once via PUT /v1/credentials/paypal
{client_id, client_secret, mode}
(mode = live|sandbox; from a REST API app at
developer.paypal.com). The relay exchanges it for a short-lived access_token
(client-credentials) and caches it — you never handle tokens. Vault-only:
client_id/client_secret are never accepted per-request.

Multiple PayPal accounts. One login can hold several accounts, each stored
under paypal:<label> (e.g. paypal:conzent). EVERY tool accepts an optional
top-level account argument naming which to target; when more than one is
configured it is REQUIRED (a call that omits it is rejected with the list of
labels). Call the accounts-list tool to see them. A single unlabelled paypal
credential lets you omit account.

Money values are decimal major units with a currency code. Tool arguments
mirror the HTTP query string / path id.

claude mcp add paypal --transport http https://paypal.mcp.endpointr.com
MCPendpointr-stripe — https://stripe.mcp.endpointr.com
classes: Stripe*

This connector is a Stripe API relay (https://api.stripe.com/v1) exposed
through endpointr, scoped to the core payments resources. Each tool mirrors one
Stripe REST endpoint. Tool names follow:
endpointr_payments_stripe_<resource>_<verb>
where verb is one of: list (GET collection), query (GET with query params),
get (GET by id), create (POST), update (POST upstream), delete (DELETE).

Resources: payment-intents, charges, customers, payment-methods, products,
prices, subscriptions, invoices, refunds, payouts, balance, balance-transactions,
checkout-sessions, setup-intents, events, overview, accounts.

The overview resource is a computed business-health snapshot (MRR, ARR, active
subscriptions & customers, new subs and cancellations over 1/7/30 days, refunds,
monthly churn, ARPU, LTV) grouped per currency — call
endpointr_payments_stripe_overview_list.

Multiple Stripe accounts. One login can hold several Stripe accounts (e.g. two
companies), each stored under its own credential stripe:<label> via
PUT /v1/credentials/stripe:<label> {api_key:'sk_live_…'} (e.g. stripe:rexultz,
stripe:conzent). EVERY tool accepts an optional top-level account argument
naming which account to target, e.g.
endpointr_payments_stripe_overview_list {account:'rexultz'} or
endpointr_payments_stripe_subscriptions_query {account:'conzent', query:{status:'active'}}.
When more than one account is configured, account is REQUIRED — a call that
omits it is rejected with the list of valid labels. Call
endpointr_payments_stripe_accounts_list to see the configured labels. If only a
single unlabelled stripe credential exists, account may be omitted.

Stripe uses POST for both create and update, and it has non-CRUD verbs
(confirm, capture, cancel, finalize, pay, send, void, attach, detach, reverse,
expire). Those are folded onto the create tool via an action field plus the
target id — e.g. confirm a PaymentIntent with
create {action:'confirm', id:'pi_…', payment_method:'pm_…'}, finalize an
invoice with create {action:'finalize', id:'in_…'}, attach a PaymentMethod
with create {action:'attach', id:'pm_…', customer:'cus_…'}. Without an
action, create POSTs to the bare collection to create a new resource. Each
tool's description lists its available actions.

Amounts are in the currency's smallest unit (e.g. 1099 = $10.99; zero-decimal
currencies like JPY use the whole number). Nested fields use Stripe's object
shape in the body/query args (e.g. {metadata:{order_id:'…'}},
{items:[{price:'price_…'}]}) — the relay serialises them to Stripe's
bracket form.

Authentication. The connector OAuth establishes which endpointr customer you
are. The upstream Stripe secret key is resolved per customer from the credential
vault — store it once via PUT /v1/credentials/stripe {api_key:'sk_live_…'}
(test mode: sk_test_…). Vault-only: the secret key is never accepted
per-request.

Tool arguments mirror the HTTP request body / path id / query string. Most
create/update tools accept an open body object — the schema lists documented
fields, but handlers forward additional keys to Stripe verbatim.

claude mcp add stripe --transport http https://stripe.mcp.endpointr.com

Auth

POST/v1/token
no auth this endpoint does not require a Bearer token.

Issue a JWT from a valid api_key.

Body:
{ "api_key": "…" }

On success, this request auto-captures the JWT into the collection variable token, so every subsequent request gets Authorization: Bearer {{token}} automatically.

Content-Typeapplication/json
{
    "api_key": "YOUR_API_KEY"
}
curl -X POST 'https://api.endpointr.com/v1/token' \
  -H 'Content-Type: application/json' \
  -d '{
    "api_key": "YOUR_API_KEY"
}'
const response = await fetch('https://api.endpointr.com/v1/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
      "api_key": "YOUR_API_KEY"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/token');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"api_key\": \"YOUR_API_KEY\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/token

Introspect the current token without erroring on edge cases.

Returns {valid, reason, jti, expires_in, expired, revoked, user}. reason is one of:
- null — token is good
- missing — no Bearer header
- expired — past exp
- revoked — jti in revoked_tokens (another device called DELETE)
- bad_signature / malformed — not our token

Use this as a pre-flight: if expires_in < 60, call PATCH to rotate; if revoked, re-exchange your api_key with POST.

Tip: You can usually skip this — the JWT is self-describing. Base64-decode the middle segment client-side and read exp directly. The one thing only this endpoint can tell you is *revocation*.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/token' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/token', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/token');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PATCH/v1/token

Rotate the current token. Revokes the presented jti and issues a fresh one.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X PATCH 'https://api.endpointr.com/v1/token' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/token', {
  method: 'PATCH',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/token');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PATCH');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/token

Revoke the current token (logout).

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/token' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/token', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/token');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/keys

Upload your PEM public key (≥ 2048 bits).

Private keys MUST NOT be sent.

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "publicKey": "-----BEGIN PUBLIC KEY-----\n…\n-----END PUBLIC KEY-----"
}
curl -X POST 'https://api.endpointr.com/v1/keys' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "publicKey": "-----BEGIN PUBLIC KEY-----\n…\n-----END PUBLIC KEY-----"
}'
const response = await fetch('https://api.endpointr.com/v1/keys', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "publicKey": "-----BEGIN PUBLIC KEY-----\n…\n-----END PUBLIC KEY-----"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/keys');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"publicKey\": \"-----BEGIN PUBLIC KEY-----\\n…\\n-----END PUBLIC KEY-----\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/keys

Return the stored public key for the authenticated customer.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/keys' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/keys', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/keys');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Credentials

GET/v1/credentials/:service

Check whether creds exist for a service (does not return values).

:service — e.g. openai, anthropic, openrouter, claude-cli, openai-cli, stripe, mailchimp, ipregistry

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/credentials/:service' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/credentials/:service', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/credentials/:service');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/credentials/:service

Store the JSON credential map for a service. Body is the map itself.

AI providers — auto-appended to the customer's provider-priority list on save (admin can reorder via /admin/customers/{id} → Provider priority):
- openai: { "api_key": "sk-…", "organization": "org-…" } (organization optional)
- anthropic: { "api_key": "sk-ant-…" }
- openrouter: { "api_key": "sk-or-…", "referer": "https://yourapp.com", "app_title": "YourApp" } (referer + app_title optional, used for OpenRouter's app rankings)
- claude-cli: { "oauth_token": "sk-ant-oat01-…" } (from claude setup-token)
- openai-cli: { "api_key": "sk-…" }

Other services:
- stripe: { "api_key": "sk_live_…", "webhook_secret": "whsec_…" } (webhook_secret optional)
- mailchimp: { "api_key": "…-usX", "list_id": "abc" } (list_id optional)
- ipregistry: { "api_key": "…" }

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "api_key": "REPLACE_ME"
}
curl -X PUT 'https://api.endpointr.com/v1/credentials/:service' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "api_key": "REPLACE_ME"
}'
const response = await fetch('https://api.endpointr.com/v1/credentials/:service', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "api_key": "REPLACE_ME"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/credentials/:service');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"api_key\": \"REPLACE_ME\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/credentials/:service

Remove stored creds for a service. If the service is an AI provider in the priority list, it is also removed from priority.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/credentials/:service' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/credentials/:service', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/credentials/:service');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Vault

GET/v1/vault

List the caller's vault entries. Returns names and updated_at only — values stay sealed. Use GET /v1/vault/:name to fetch one.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/vault' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/vault', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/vault');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/vault/:name

Fetch the decrypted JSON object stored at :name. Unlike /v1/credentials/:service, this returns the actual values — that's the point of the vault.

Use case. Your SaaS calls this at runtime to retrieve secrets (Amazon SES SMTP, ShortPixel keys, etc.) instead of hardcoding them. Rotate by editing the vault entry once in /admin/customers/{id} → Vault; every app picks up the new value on its next fetch.

Naming. :name is customer-chosen, lowercase: [a-z0-9][a-z0-9_-]{0,63}. Pick something descriptive (e.g. ses-prod, shortpixel, mailgun-eu).

Response: { "name": "ses-prod", "data": { …whatever you stored… } }. 404 if the name is unknown for this customer.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/vault/:name' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/vault/:name', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/vault/:name');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/vault/:name

Create or replace the vault entry at :name. Body is the JSON object to store — any shape you like, since you'll be reading it back yourself.

Example — Amazon SES SMTP:

{
  "smtp_host": "email-smtp.us-east-1.amazonaws.com",
  "smtp_port": 587,
  "smtp_user": "AKIA…",
  "smtp_pass": "BJ…"
}

Example — ShortPixel:

{ "api_key": "…" }

Existing entries are overwritten in place. Encrypted at rest with the same XSalsa20-Poly1305 key as /v1/credentials/:service.

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "smtp_host": "email-smtp.us-east-1.amazonaws.com",
    "smtp_port": 587,
    "smtp_user": "AKIA…",
    "smtp_pass": "REPLACE_ME"
}
curl -X PUT 'https://api.endpointr.com/v1/vault/:name' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "smtp_host": "email-smtp.us-east-1.amazonaws.com",
    "smtp_port": 587,
    "smtp_user": "AKIA…",
    "smtp_pass": "REPLACE_ME"
}'
const response = await fetch('https://api.endpointr.com/v1/vault/:name', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "smtp_host": "email-smtp.us-east-1.amazonaws.com",
      "smtp_port": 587,
      "smtp_user": "AKIA…",
      "smtp_pass": "REPLACE_ME"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/vault/:name');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"smtp_host\": \"email-smtp.us-east-1.amazonaws.com\",\n    \"smtp_port\": 587,\n    \"smtp_user\": \"AKIA…\",\n    \"smtp_pass\": \"REPLACE_ME\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/vault/:name

Remove the vault entry at :name.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/vault/:name' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/vault/:name', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/vault/:name');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

AI

Chat

POST/v1/ai/chat

Text chat across providers. Canonical body is the minimum that works when you've stored credentials for at least one provider via PUT /v1/credentials/:service — Endpointr walks the customer's priority list, picks the first survivor, and falls over to the next on 5xx / 401 / 403 / 429.

Provider selection. provider is optional. Omit it to use the priority list (set under /admin/customers/{id} → Provider priority). Pass it explicitly to pin one provider with no failover. Valid values: openai, anthropic, openrouter, claude-cli, openai-cli.

Credentials. Resolution order:
1. Per-request override in the body — oauth_token (for claude-cli) or api_key (for openai-cli). API providers have no body override; use stored creds.
2. Stored cred via PUT /v1/credentials/:service. Schemas:
- openai{api_key, organization?}
- anthropic{api_key}
- openrouter{api_key, referer?, app_title?}
- claude-cli{oauth_token}
- openai-cli{api_key}
3. Server-wide env (CLAUDE_CODE_OAUTH_TOKEN, codex login state) — last resort.

Body shape. Send either prompt (string) or messages: [{role, content}, ...]. system is appended to the message list as a system role. json: true forces JSON output (provider permitting).

Sampling controls (temperature, max_tokens, top_p, stop) work for openai, anthropic, openrouter. The CLI providers ignore them — the underlying binaries don't expose those flags. If you need sampling control, pin provider: openai or provider: anthropic.

Other example bodies.

Explicit OpenAI with sampling controls:

{"provider":"openai","model":"gpt-4o-mini","temperature":0.2,"max_tokens":150,"prompt":"Summarize the goal of OAuth2 in one sentence."}

Explicit Anthropic via API key (set anthropic.api_key once via /v1/credentials/anthropic; no body creds needed):

{"provider":"anthropic","model":"claude-sonnet-4-6","prompt":"Why is HTTPS preferred over HTTP?"}

Claude-CLI subscription with body-supplied token:

{"provider":"claude-cli","model":"claude-sonnet-4-6","oauth_token":"{{claude_oauth_token}}","system":"You are concise.","prompt":"Write one sentence about rain."}

Messages array with multi-turn history:

{"messages":[{"role":"system","content":"You are concise."},{"role":"user","content":"What's 2+2?"},{"role":"assistant","content":"4."},{"role":"user","content":"And times 3?"}]}

Force JSON output:

{"prompt":"Return {\"colors\": [...]} with three primary colors.","json":true}

Minimal body: {"prompt":"Write one sentence about rain."}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "prompt": "Write one sentence about rain."
}
curl -X POST 'https://api.endpointr.com/v1/ai/chat' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "prompt": "Write one sentence about rain."
}'
const response = await fetch('https://api.endpointr.com/v1/ai/chat', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "prompt": "Write one sentence about rain."
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/ai/chat');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"prompt\": \"Write one sentence about rain.\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Chat (SSE stream)

POST/v1/ai/stream

Same shape as /v1/ai/chat, but proxies the provider's native SSE stream back to you. Returns text/event-stream.

No failover for streaming — once SSE headers are out, you can't switch providers mid-flight. The first qualifying candidate handles the stream; mid-stream errors surface as SSE error frames.

Supported: claude-cli, anthropic, openai, openrouter. openai-cli does not support streaming (501).

Tip. Postman shows the full response after it completes; use curl -N in a terminal to see chunks live.

Other example bodies.

Explicit OpenAI:

{"provider":"openai","model":"gpt-4o-mini","max_tokens":200,"prompt":"Write a haiku about rain."}

Claude-CLI with body-supplied token:

{"provider":"claude-cli","model":"claude-sonnet-4-6","oauth_token":"{{claude_oauth_token}}","prompt":"Write a haiku about rain.","max_tokens":200}

Minimal body: {"prompt":"Write a haiku about rain.","max_tokens":200}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "prompt": "Write a haiku about rain.",
    "max_tokens": 200
}
curl -X POST 'https://api.endpointr.com/v1/ai/stream' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "prompt": "Write a haiku about rain.",
    "max_tokens": 200
}'
const response = await fetch('https://api.endpointr.com/v1/ai/stream', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "prompt": "Write a haiku about rain.",
      "max_tokens": 200
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/ai/stream');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"prompt\": \"Write a haiku about rain.\",\n    \"max_tokens\": 200\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Conversation (SSE stream)

POST/v1/ai/conversation-stream

Streaming twin of POST /v1/ai/conversation. Same per-customer memory + Elasticsearch retrieval pipeline and narration-style system prompt — but the assistant reply is delivered as Server-Sent Events for low-latency voice / UI use cases.

Wire format (Content-Type: text/event-stream):
1. event: endpointr_retrieval — retrieval status + hits, emitted before the model starts.
2. Provider-native SSE frames — Anthropic content_block_delta, OpenAI choices[].delta.content, or ClaudeCli stream-json.
3. event: endpointr_done — final conversation id + usage metadata after the stream ends.

Memory is loaded before any output, so memory failures surface as a normal JSON 4xx/5xx. Once the retrieval frame is on the wire, errors become SSE error frames rather than HTTP errors.

Body fields are identical to /v1/ai/conversation (prompt OR messages, optional provider/model/temperature/max_tokens/stop/oauth_token/api_key/system/retrieval/memory). stream is implied — stream: false is rejected (use the non-streaming endpoint instead). json: true is rejected (incompatible with conversational narration output).

Tip. Postman buffers SSE bodies — you'll see the full stream after the request ends. Use curl -N to watch frames live.

Minimal body: {"prompt":"How do I refund a charge?","retrieval":{"k":4}}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "prompt": "How do I refund a charge?",
    "retrieval": {
        "k": 4
    }
}
curl -X POST 'https://api.endpointr.com/v1/ai/conversation-stream' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "prompt": "How do I refund a charge?",
    "retrieval": {
        "k": 4
    }
}'
const response = await fetch('https://api.endpointr.com/v1/ai/conversation-stream', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "prompt": "How do I refund a charge?",
      "retrieval": {
          "k": 4
      }
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/ai/conversation-stream');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"prompt\": \"How do I refund a charge?\",\n    \"retrieval\": {\n        \"k\": 4\n    }\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Conversation (with memory + retrieval)

POST/v1/ai/conversation

Conversational chat with server-side memory and per-customer Elasticsearch retrieval, tuned for narration / TTS / voice agents.

Unlike /v1/ai/chat, the response is plain spoken prose — no markdown, no bullet points, no headers. Think "two friends talking," not a chatbot dumping a formatted answer.

Memory scope. One thread per customer (the API-key holder identified by your JWT). No user_id to manage — your token *is* the identity. Thread isolation between customers is JWT-enforced.

Required. prompt OR messages (same shape as Chat). If both, messages wins.

Optional.
- provider, model, temperature, top_p, max_tokens, stop — passed through (same priority/failover as /v1/ai/chat).
- oauth_token / api_key — per-request credential override (same semantics as Chat).
- system — appended to (not replacing) the built-in narration prompt.
- retrieval{enabled?: true, k?: 4, num_candidates?: 50, min_score?: null}. Set enabled: false to skip ES entirely.
- memory{enabled?: true, reset?: false}. reset: true clears your history before this turn.

Rejected.
- json: true — incompatible with the conversational output style; use /v1/ai/chat.
- stream: true — not supported (memory persists after the full response).

Retrieval status (always answered; retrieval.status in response tells you what happened):
- ok — hits returned
- index_missing — no customer_{id}_documents index yet
- empty — index exists, no matching docs
- embedding_failed — fell back to BM25 only
- no_embed_provider — no provider with embed capability + creds; BM25-only fallback also unavailable
- error — ES network/cluster error
- disabled — caller passed retrieval.enabled: false or there was no user query to embed

Embeddings use OpenAI-compatible providers (openai, openrouter). Stored creds reused; no separate setup.

Memory cap. 30 messages or ~8000 estimated tokens, whichever hits first. Older turns drop off transparently.

Other example bodies.

Reset history mid-conversation:

{"prompt":"Let's start fresh — what's the weather like in Paris?","memory":{"reset":true}}

Disable retrieval (skip ES query, save 50-100ms):

{"prompt":"What's 2+2?","retrieval":{"enabled":false}}

With app-specific system guidance appended to the narration prompt:

{"prompt":"How do I refund?","system":"Always mention you're calling from EndpointrSupport.","retrieval":{"k":6}}

Minimal body: {"prompt":"How do I refund a charge?","retrieval":{"k":4}}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "prompt": "How do I refund a charge?",
    "retrieval": {
        "k": 4
    }
}
curl -X POST 'https://api.endpointr.com/v1/ai/conversation' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "prompt": "How do I refund a charge?",
    "retrieval": {
        "k": 4
    }
}'
const response = await fetch('https://api.endpointr.com/v1/ai/conversation', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "prompt": "How do I refund a charge?",
      "retrieval": {
          "k": 4
      }
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/ai/conversation');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"prompt\": \"How do I refund a charge?\",\n    \"retrieval\": {\n        \"k\": 4\n    }\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Image Generation

POST/v1/ai/image

Generate images from a prompt. Default uses openai + gpt-image-1 (a small, square 1024x1024 image), so the canonical body needs only a prompt.

Supported providers: openai, openrouter. The Claude/Codex subscriptions don't include image generation — claude-cli, openai-cli, and anthropic return 501.

OpenAI models: gpt-image-1 (default), dall-e-3. Optional fields: size (1024x1024 | 1792x1024 | 1024x1792 for dall-e-3), n (1-10), quality, style, response_format (url | b64_json).

OpenRouter models (delivered via /chat/completions with modalities: ["image","text"] under the hood):
- google/gemini-2.5-flash-image-preview — "Nano Banana," fast & cheap (default if you specify provider: openrouter and omit model).
- google/gemini-3-pro-image-preview — "Nano Banana Pro," higher quality.

Reference images. Pass reference_images as an array (max 16) of:
- public https URLs (jpeg / png / gif / webp), or
- data:image/png;base64,... URIs, or
- bare base64 payloads (PNG assumed by content sniff).

When present, OpenAI is routed through /v1/images/edits (multipart upload — gpt-image-1 accepts up to 16 references; dall-e-2 accepts 1; dall-e-3 does not support edits). OpenRouter passes them as image_url content parts on the user message — Gemini Nano Banana / Pro use them as visual context for the generation.

Response. {provider, model, images: [{url}|{b64_json}]}. OpenAI follows your response_format. OpenRouter always returns b64_json (data URIs are stripped server-side).

Other example bodies.

Explicit OpenAI with high quality + base64 response:

{"provider":"openai","model":"gpt-image-1","prompt":"A red fox in snow, photorealistic","size":"1024x1024","quality":"high","response_format":"b64_json"}

OpenRouter Gemini Nano Banana Pro:

{"provider":"openrouter","model":"google/gemini-3-pro-image-preview","prompt":"A red fox in snow, photorealistic"}

DALL-E 3 widescreen:

{"provider":"openai","model":"dall-e-3","prompt":"A futuristic cityscape at dusk","size":"1792x1024"}

Gemini Nano Banana with two reference images (style transfer):

{"provider":"openrouter","model":"google/gemini-2.5-flash-image-preview","prompt":"Render the subject from image 1 in the painterly style of image 2.","reference_images":["https://example.com/subject.jpg","https://example.com/style.jpg"]}

OpenAI gpt-image-1 edit with reference (composite multiple inputs):

{"provider":"openai","model":"gpt-image-1","prompt":"Combine these into one cohesive scene at golden hour.","reference_images":["https://example.com/a.png","https://example.com/b.png"]}

Minimal body: {"prompt":"A red fox in snow, photorealistic"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "prompt": "A red fox in snow, photorealistic"
}
curl -X POST 'https://api.endpointr.com/v1/ai/image' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "prompt": "A red fox in snow, photorealistic"
}'
const response = await fetch('https://api.endpointr.com/v1/ai/image', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "prompt": "A red fox in snow, photorealistic"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/ai/image');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"prompt\": \"A red fox in snow, photorealistic\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

List Models

GET/v1/ai/models?provider=openrouter

Lists the models available to the authenticated customer for a given provider.

provider is optional — falls back to the priority list with failover. Supported: anthropic, openai, openrouter. claude-cli and openai-cli (subscription providers) don't expose a model catalog endpoint and return 501.

Minimal query: {"provider":"openrouter"}

AuthorizationBearer YOUR_JWT_TOKEN
provideropenrouter
curl -X GET 'https://api.endpointr.com/v1/ai/models?provider=openrouter' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/ai/models?provider=openrouter', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/ai/models?provider=openrouter');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

OpenAI-compatible

POST/v1/chat/completions

OpenAI-compatible chat/completions endpoint. Drop this in anywhere you'd point an OpenAI client (LangChain ChatOpenAI, the official openai SDK, n8n's *OpenAI* credential, etc.) and your customer api_key replaces the OpenAI key.

Path. Registered at both /v1/chat/completions (drop-in — n8n's default Base URL https://api.openai.com/v1 becomes https://api.endpointr.com/v1 with no path tweaking) and /v1/ai/completions (namespace match with the rest of /v1/ai/*). Same handler.

Auth. Authorization: Bearer <api_key> — the customer's raw api_key, not the JWT. No /v1/token exchange, no hourly refresh: n8n's OpenAI credential can't refresh on its own, so this endpoint uses the same flat-key model as OpenAI itself. SHA-256 hash lookup against customers.api_key_hash.

Response shape. Raw OpenAI envelope ({id, object, created, model, choices, usage}) — bypasses the standard Endpointr {httpCode, status, data, …} wrapper. Errors come back as {error: {message, type, code}}.

Model routing. Pick a model and the proxy picks the upstream:
- anthropic/<model> or bare claude-*Anthropic API (uses the customer's vault anthropic.api_key). Full OpenAI ↔ Anthropic translation: messages, tools, tool_choice, tool_use blocks, system extraction, stop_reason mapping, usage field renaming.
- openai/<model> or bare gpt-* / o1-* / o3-* / o4-*OpenAI API (vault openai.api_key). Near-passthrough — request/response barely touched.
- everything else → OpenRouter (vault openrouter.api_key). Passes the model string verbatim (google/gemini-2.5-flash, mistralai/mixtral-8x7b-instruct, etc.).

Missing creds for the resolved upstream → 401 with a clear message pointing at /admin/customers/{id} → service credentials.

Tool calling. Full support, required for n8n's AI Agent node. Send OpenAI-shape tools: [{type:"function", function:{name, description, parameters}}] and tool_choice — get back finish_reason: "tool_calls" with tool_calls[].function.arguments as a JSON-encoded string (this is what LangChain expects; do not parse it server-side). Continue the loop by sending a follow-up {role:"tool", tool_call_id, content} message.

Streaming. Set "stream": true to get back Content-Type: text/event-stream. Frames are chat.completion.chunk JSON terminated by data: [DONE]. For Anthropic this is full event-level translation (text deltas → delta.content, tool_usedelta.tool_calls[] with function.arguments arriving as concatenated string fragments — your client reassembles). Postman buffers SSE until end; use curl -N to watch live.

Accepted-but-ignored so n8n payloads don't trip a 400: logprobs, top_logprobs, n, seed, logit_bias, user, service_tier. Honored: model, messages, tools, tool_choice, stream, temperature, top_p, max_tokens, stop, response_format (OpenAI/OpenRouter only), presence_penalty / frequency_penalty (OpenAI/OpenRouter only).

Anthropic note. max_tokens is required upstream — we default to 4096 if you omit it.

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "model": "anthropic/claude-sonnet-4-6",
    "messages": [
        {
            "role": "system",
            "content": "You are concise."
        },
        {
            "role": "user",
            "content": "Reply with exactly: pong"
        }
    ],
    "max_tokens": 50
}
curl -X POST 'https://api.endpointr.com/v1/chat/completions' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "model": "anthropic/claude-sonnet-4-6",
    "messages": [
        {
            "role": "system",
            "content": "You are concise."
        },
        {
            "role": "user",
            "content": "Reply with exactly: pong"
        }
    ],
    "max_tokens": 50
}'
const response = await fetch('https://api.endpointr.com/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "model": "anthropic/claude-sonnet-4-6",
      "messages": [
          {
              "role": "system",
              "content": "You are concise."
          },
          {
              "role": "user",
              "content": "Reply with exactly: pong"
          }
      ],
      "max_tokens": 50
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/chat/completions');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"model\": \"anthropic/claude-sonnet-4-6\",\n    \"messages\": [\n        {\n            \"role\": \"system\",\n            \"content\": \"You are concise.\"\n        },\n        {\n            \"role\": \"user\",\n            \"content\": \"Reply with exactly: pong\"\n        }\n    ],\n    \"max_tokens\": 50\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/chat/completions

Tool-call round-trip. Send tools + a prompt that should trigger one. Expect 200 with choices[0].finish_reason: "tool_calls", choices[0].message.content: null, and choices[0].message.tool_calls[0].function.arguments as a JSON-encoded string (e.g. "{\"city\":\"Copenhagen\"}").

Continue the conversation with a follow-up request adding two messages: the assistant turn you just received, then {role:"tool", tool_call_id:"<the id>", content:"15°C, light rain"}. The next response will have finish_reason: "stop" and the model's final answer.

Works identically on anthropic/*, openai/*, and OpenRouter-routed models — only the upstream wire differs.

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "model": "anthropic/claude-sonnet-4-6",
    "messages": [
        {
            "role": "user",
            "content": "What's the weather in Copenhagen?"
        }
    ],
    "tools": [
        {
            "type": "function",
            "function": {
                "name": "get_weather",
                "description": "Return current weather for a city.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "city": {
                            "type": "string",
                            "description": "City name"
                        }
                    },
                    "required": [
                        "city"
                    ]
                }
            }
        }
    ],
    "tool_choice": "auto"
}
curl -X POST 'https://api.endpointr.com/v1/chat/completions' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "model": "anthropic/claude-sonnet-4-6",
    "messages": [
        {
            "role": "user",
            "content": "What'\''s the weather in Copenhagen?"
        }
    ],
    "tools": [
        {
            "type": "function",
            "function": {
                "name": "get_weather",
                "description": "Return current weather for a city.",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "city": {
                            "type": "string",
                            "description": "City name"
                        }
                    },
                    "required": [
                        "city"
                    ]
                }
            }
        }
    ],
    "tool_choice": "auto"
}'
const response = await fetch('https://api.endpointr.com/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "model": "anthropic/claude-sonnet-4-6",
      "messages": [
          {
              "role": "user",
              "content": "What's the weather in Copenhagen?"
          }
      ],
      "tools": [
          {
              "type": "function",
              "function": {
                  "name": "get_weather",
                  "description": "Return current weather for a city.",
                  "parameters": {
                      "type": "object",
                      "properties": {
                          "city": {
                              "type": "string",
                              "description": "City name"
                          }
                      },
                      "required": [
                          "city"
                      ]
                  }
              }
          }
      ],
      "tool_choice": "auto"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/chat/completions');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"model\": \"anthropic/claude-sonnet-4-6\",\n    \"messages\": [\n        {\n            \"role\": \"user\",\n            \"content\": \"What's the weather in Copenhagen?\"\n        }\n    ],\n    \"tools\": [\n        {\n            \"type\": \"function\",\n            \"function\": {\n                \"name\": \"get_weather\",\n                \"description\": \"Return current weather for a city.\",\n                \"parameters\": {\n                    \"type\": \"object\",\n                    \"properties\": {\n                        \"city\": {\n                            \"type\": \"string\",\n                            \"description\": \"City name\"\n                        }\n                    },\n                    \"required\": [\n                        \"city\"\n                    ]\n                }\n            }\n        }\n    ],\n    \"tool_choice\": \"auto\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/chat/completions

Streaming variant. Same body as the non-streaming example with "stream": true. Returns Content-Type: text/event-stream and a sequence of data: {chat.completion.chunk} lines terminated by data: [DONE].

Postman buffers SSE bodies — you'll see the full stream after the request ends. To watch chunks live, use curl -N:

curl -N -X POST {{baseUrl}}/v1/chat/completions \
  -H "Authorization: Bearer {{api_key}}" \
  -H "Content-Type: application/json" \
  -d '{"model":"anthropic/claude-sonnet-4-6","messages":[{"role":"user","content":"Count from 1 to 5"}],"stream":true,"max_tokens":50}'

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "model": "anthropic/claude-sonnet-4-6",
    "messages": [
        {
            "role": "user",
            "content": "Count from 1 to 5"
        }
    ],
    "stream": true,
    "max_tokens": 50
}
curl -X POST 'https://api.endpointr.com/v1/chat/completions' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "model": "anthropic/claude-sonnet-4-6",
    "messages": [
        {
            "role": "user",
            "content": "Count from 1 to 5"
        }
    ],
    "stream": true,
    "max_tokens": 50
}'
const response = await fetch('https://api.endpointr.com/v1/chat/completions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "model": "anthropic/claude-sonnet-4-6",
      "messages": [
          {
              "role": "user",
              "content": "Count from 1 to 5"
          }
      ],
      "stream": true,
      "max_tokens": 50
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/chat/completions');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"model\": \"anthropic/claude-sonnet-4-6\",\n    \"messages\": [\n        {\n            \"role\": \"user\",\n            \"content\": \"Count from 1 to 5\"\n        }\n    ],\n    \"stream\": true,\n    \"max_tokens\": 50\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/ai/completions

Same handler as POST /v1/chat/completions — registered at this path too so it slots into the existing /v1/ai/* namespace alongside /v1/ai/chat, /v1/ai/stream, etc. Pick whichever path fits your routing.

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "model": "openai/gpt-4o-mini",
    "messages": [
        {
            "role": "user",
            "content": "Say hi in one word."
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/ai/completions' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "model": "openai/gpt-4o-mini",
    "messages": [
        {
            "role": "user",
            "content": "Say hi in one word."
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/ai/completions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "model": "openai/gpt-4o-mini",
      "messages": [
          {
              "role": "user",
              "content": "Say hi in one word."
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/ai/completions');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"model\": \"openai/gpt-4o-mini\",\n    \"messages\": [\n        {\n            \"role\": \"user\",\n            \"content\": \"Say hi in one word.\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Upload Image

POST/v1/ai/upload

Upload an image and get back a short-lived public URL you can pass to /v1/ai/vision as image_url.

Vision providers fetch image URLs from their own infrastructure (so the URL must be publicly reachable). This endpoint hands you exactly that — a URL on the Endpointr public uploads volume, valid for 30+ minutes.

Request:
- image_base64 — raw base64 (no data: URI prefix). The 1x1 PNG below is real and uploads cleanly; substitute your own bytes.
- format?png | jpeg | webp | gif. Optional — magic bytes are sniffed and trusted over a caller hint.

Limits. Max raw size 25 MB. Allowed formats above.

Response. {url, mime, format, bytes, expires_in: 1800, expires_at: <iso8601>}. The 32-hex token in the URL has 128 bits of entropy; URL is the secret (no auth on download — necessary because vision providers can't carry your JWT).

TTL. 30 minutes minimum. A cron job in the backup container deletes files older than 30 minutes every 5 minutes — effective lifetime is 30-35 minutes. After that the URL 404s.

Required body: image_base64.

Minimal body: {"image_base64":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII="}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=",
    "format": "png"
}
curl -X POST 'https://api.endpointr.com/v1/ai/upload' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=",
    "format": "png"
}'
const response = await fetch('https://api.endpointr.com/v1/ai/upload', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=",
      "format": "png"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/ai/upload');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"image_base64\": \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=\",\n    \"format\": \"png\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Video Generation

GET/v1/ai/video

List the 50 most recent video predictions for the authenticated customer (newest first). Same row shape as GET /v1/ai/video/{id}. Read-only — does not trigger any upstream poll.

_Requires stored credentials: atlascloud (PUT /v1/credentials/atlascloud)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/ai/video' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/ai/video', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/ai/video');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/ai/video/:id

Poll a video generation by our internal id (returned from POST as id) — never AtlasCloud's prediction_id. Cross-customer access returns 404.

Behavior. If status is terminal (completed/failed/timeout), returns the cached row instantly without hitting upstream. If still processing, fires one upstream poll, updates the row, and returns. On status transition we fire VideoHandler.complete (success) or VideoHandler.failed (failure/timeout).

Response.

{"id":123,"provider":"atlascloud","model":"bytedance/seedance-2.0/text-to-video","prediction_id":"pred_abc","status":"completed","prompt":"...","outputs":["https://cdn.atlascloud.ai/.../video.mp4"],"error":null,"created_at":"...","completed_at":"..."}

Output URLs are AtlasCloud-hosted. Treat them as time-limited; download and re-host if you need durable storage.

_Requires stored credentials: atlascloud (PUT /v1/credentials/atlascloud)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/ai/video/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/ai/video/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/ai/video/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/ai/video

Async video generation via AtlasCloud (fronts Google VEO 3.1 Lite/Fast/Pro and ByteDance Seedance 2.0).

Two-step flow. POST returns immediately with {id, prediction_id, status: "processing"}. Poll GET /v1/ai/video/{id} until status is completed, failed, or timeout. Completed predictions are cached server-side — subsequent GETs return instantly without hitting AtlasCloud.

Required body fields: model, prompt. The model string is forwarded verbatim to AtlasCloud.

URL-bearing fields (image, last_image, images[], reference_images[], reference_videos[], reference_audios[]) must be public https URLs or data: URIs. The server runs an SSRF check before forwarding — internal/private IPs are rejected as 400.

Param whitelist (union across families; AtlasCloud rejects per-model mismatches): duration, resolution, ratio/aspect_ratio, image, last_image, images, reference_images, reference_videos, reference_audios, generate_audio, web_search, watermark, return_last_frame, seed, negative_prompt. Anything outside this list is silently dropped.

Webhook events: VideoHandler.complete and VideoHandler.failed fire on the customer's poll that detects the transition (subscribe via POST /v1/webhooks). The standard .create/.get/.getAll events fire as usual.

Response. {id, provider:"atlascloud", model, prediction_id, status:"processing", created_at}. Save id and poll the GET endpoint.

---

Mode bodies:

*Seedance 2.0 — text-to-video (canonical):*

{"model":"bytedance/seedance-2.0/text-to-video","prompt":"A red fox running through snow at sunrise, cinematic.","ratio":"16:9","duration":5,"resolution":"1080p","generate_audio":true}

*Seedance 2.0 — image-to-video:*

{"model":"bytedance/seedance-2.0/image-to-video","prompt":"The subject turns slowly toward the camera.","image":"https://example.com/frame.jpg","ratio":"16:9","duration":5}

*Seedance 2.0 — reference-to-video (style + motion + audio):*

{"model":"bytedance/seedance-2.0/reference-to-video","prompt":"Match the references.","reference_images":["https://example.com/style.jpg"],"reference_videos":["https://example.com/motion.mp4"],"reference_audios":["https://example.com/music.mp3"]}

*Seedance 2.0 Fast variant* — same shapes with bytedance/seedance-2.0-fast/... (e.g. bytedance/seedance-2.0-fast/text-to-video).

*VEO 3.1 — text-to-video:*

{"model":"google/veo3.1/text-to-video","prompt":"Drone shot over alpine lake at golden hour.","aspect_ratio":"16:9","duration":8,"resolution":"1080p","seed":42,"negative_prompt":"blurry, low quality"}

*VEO 3.1 — image-to-video* (Lite / Fast / Pro):

{"model":"google/veo3.1-fast/image-to-video","prompt":"The subject slowly turns toward the camera.","image":"https://example.com/start.jpg","aspect_ratio":"16:9","duration":8}

*VEO 3.1 — start-end-frame-to-video* (Lite / Fast / Pro; both frames required):

{"model":"google/veo3.1/start-end-frame-to-video","prompt":"Smooth dolly between the two frames.","image":"https://example.com/start.jpg","last_image":"https://example.com/end.jpg","aspect_ratio":"16:9","duration":8}

*VEO 3.1 Pro — reference-to-video* (Pro tier only):

{"model":"google/veo3.1/reference-to-video","prompt":"Render the subject in the painterly style of the reference.","images":["https://example.com/subject.jpg","https://example.com/style.jpg"],"resolution":"1080p","generate_audio":true}

Errors. Create-time AtlasCloud 4xx/5xx surface as the same status with {error, upstream:{...}}. A *prediction* that fails after creation is not a create-time error — it surfaces on poll as status: "failed" with error text.

Minimal body: {"model":"bytedance/seedance-2.0/text-to-video","prompt":"A red fox running through snow at sunrise, cinematic.","ratio":"16:9","duration":5,"resolution":"1080p"}

_Requires stored credentials: atlascloud (PUT /v1/credentials/atlascloud)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "model": "bytedance/seedance-2.0/text-to-video",
    "prompt": "A red fox running through snow at sunrise, cinematic.",
    "ratio": "16:9",
    "duration": 5,
    "resolution": "1080p"
}
curl -X POST 'https://api.endpointr.com/v1/ai/video' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "model": "bytedance/seedance-2.0/text-to-video",
    "prompt": "A red fox running through snow at sunrise, cinematic.",
    "ratio": "16:9",
    "duration": 5,
    "resolution": "1080p"
}'
const response = await fetch('https://api.endpointr.com/v1/ai/video', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "model": "bytedance/seedance-2.0/text-to-video",
      "prompt": "A red fox running through snow at sunrise, cinematic.",
      "ratio": "16:9",
      "duration": 5,
      "resolution": "1080p"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/ai/video');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"model\": \"bytedance/seedance-2.0/text-to-video\",\n    \"prompt\": \"A red fox running through snow at sunrise, cinematic.\",\n    \"ratio\": \"16:9\",\n    \"duration\": 5,\n    \"resolution\": \"1080p\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Vision

POST/v1/ai/vision

Image + text in, text out. Same provider/credential semantics as /v1/ai/chat.

Supported providers: claude-cli, openai, anthropic, openrouter. openai-cli does not support vision (returns 501).

image_url is either:
- a public https URL (jpeg / png / gif / webp), or
- a data:image/png;base64,... URI for inline images, or
- the URL handed back by POST /v1/ai/upload (recommended for client-side uploads — see that endpoint).

Other example bodies.

Explicit Anthropic API:

{"provider":"anthropic","model":"claude-sonnet-4-6","prompt":"Describe what you see.","image_url":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Cat_November_2010-1a.jpg/640px-Cat_November_2010-1a.jpg","max_tokens":500}

Claude-CLI with body-supplied token:

{"provider":"claude-cli","model":"claude-sonnet-4-6","oauth_token":"{{claude_oauth_token}}","prompt":"What is in this image?","image_url":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Cat_November_2010-1a.jpg/640px-Cat_November_2010-1a.jpg","max_tokens":500}

OpenAI with a data-URI image (handy for tiny images you don't want to host):

{"provider":"openai","model":"gpt-4o-mini","prompt":"What's in this image?","image_url":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII="}

Minimal body: {"prompt":"What is in this image?","image_url":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Cat_November_2010-1a.jpg/640px-Cat_November_2010-1a.jpg","max_tokens":500}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "prompt": "What is in this image?",
    "image_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Cat_November_2010-1a.jpg/640px-Cat_November_2010-1a.jpg",
    "max_tokens": 500
}
curl -X POST 'https://api.endpointr.com/v1/ai/vision' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "prompt": "What is in this image?",
    "image_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Cat_November_2010-1a.jpg/640px-Cat_November_2010-1a.jpg",
    "max_tokens": 500
}'
const response = await fetch('https://api.endpointr.com/v1/ai/vision', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "prompt": "What is in this image?",
      "image_url": "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Cat_November_2010-1a.jpg/640px-Cat_November_2010-1a.jpg",
      "max_tokens": 500
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/ai/vision');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"prompt\": \"What is in this image?\",\n    \"image_url\": \"https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Cat_November_2010-1a.jpg/640px-Cat_November_2010-1a.jpg\",\n    \"max_tokens\": 500\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Webhooks

GET/v1/webhooks/events

List every event name that a handler can emit.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/webhooks/events' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/webhooks/events', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/webhooks/events');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/webhooks

List webhooks registered under your account.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/webhooks' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/webhooks', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/webhooks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/webhooks

Register a webhook URL for an event.

Response includes a secret (returned once) — receivers verify HMAC-SHA256 against the JSON body via the X-Endpointr-Signature: sha256=… header.

Meta Marketing events fired by the inbound /v1/webhooks/inbound/meta-ads endpoint use names of the shape MetaAds.<object>.<field> — e.g. MetaAds.page.leadgen, MetaAds.ad_account.adsstatus. Subscribe to those exactly the same way as any other event name.

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "event": "HeaderMarkdownExtractor.getByParam",
    "url": "https://receiver.example.com/webhook"
}
curl -X POST 'https://api.endpointr.com/v1/webhooks' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "event": "HeaderMarkdownExtractor.getByParam",
    "url": "https://receiver.example.com/webhook"
}'
const response = await fetch('https://api.endpointr.com/v1/webhooks', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "event": "HeaderMarkdownExtractor.getByParam",
      "url": "https://receiver.example.com/webhook"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/webhooks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"event\": \"HeaderMarkdownExtractor.getByParam\",\n    \"url\": \"https://receiver.example.com/webhook\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/webhooks/:id

Delete one of your webhooks.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/webhooks/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/webhooks/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/webhooks/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/webhooks/inbound/meta-ads?hub.mode=subscribe&hub.verify_token=%E2%80%A6&hub.challenge=12345
no auth this endpoint does not require a Bearer token.

Meta's webhook subscription handshake. NOT JWT-protected — Meta hits this with hub.mode=subscribe&hub.verify_token=…&hub.challenge=… and expects the challenge echoed back as plain text.

The verify token is set app-wide via the META_WEBHOOK_VERIFY_TOKEN env var (must match what you pass to POST /v1/marketing/webhook-subscriptions). 403 on mismatch.

hub.modesubscribe
hub.verify_token
hub.challenge12345
curl -X GET 'https://api.endpointr.com/v1/webhooks/inbound/meta-ads?hub.mode=subscribe&hub.verify_token=%E2%80%A6&hub.challenge=12345'
const response = await fetch('https://api.endpointr.com/v1/webhooks/inbound/meta-ads?hub.mode=subscribe&hub.verify_token=%E2%80%A6&hub.challenge=12345', {
  method: 'GET'
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/webhooks/inbound/meta-ads?hub.mode=subscribe&hub.verify_token=%E2%80%A6&hub.challenge=12345');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/webhooks/inbound/meta-ads
no auth this endpoint does not require a Bearer token.

Meta's webhook delivery endpoint. NOT JWT-protected — authenticity is verified via HMAC-SHA256 of the raw body against the per-customer app_secret (stored at slug meta-ads).

Flow per delivery: persist raw event → resolve customer by matching entry[].id against meta_ad_webhook_subs.object_id → verify HMAC → fan out via the standard WebhookManager (event name MetaAds.<object>.<field>) → enqueue a leadgen_fetch worker job for any leadgen field changes (so the full lead body lands in meta_ad_leads).

Always responds 200 — even on signature mismatch or unknown object_id — to prevent Meta retry storms. Forensics: unmatched/unverified deliveries are kept in meta_ad_webhook_events with customer_id=NULL for audit.

curl -X POST 'https://api.endpointr.com/v1/webhooks/inbound/meta-ads'
const response = await fetch('https://api.endpointr.com/v1/webhooks/inbound/meta-ads', {
  method: 'POST'
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/webhooks/inbound/meta-ads');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Profylr

Company lookup

GET/v1/profylr/company?domain=example.com&country=dk&refresh=false

Implemented. Resolve a domain to a company profile built from two independent legs:

1. Crawler leg/v1/crawlers/* waterfall (social-profiles → phone → email → cvr → cms) against the website. Zero API tokens unless SCRAPEDO_API_KEY is set, in which case fetches go through scrape.do (spends credits — needed to clear Cloudflare managed-challenge pages).
2. Enrichment leg — the customer's admin-ordered enrichment_priority list is walked with a company-shaped query; the first provider that returns a hit wins. The raw response lands at company.enrichment.data._raw for inspection while we map fields.

Renamed from /v1/profylr/domain. Endpointr now treats contact (person) and company as two top-level entities — same async machinery, separate routes.

Selectors.
- ?domain=example.com (or ?url=https://example.com/anything — the host is extracted). One is required; a bad/missing value → 400.
- ?country=dk|no|se|… (optional, ISO-2). A .com may actually be a Danish/Swedish/Norwegian company; passing country does two things: it tells the CVR crawler which org-number format to look for (DK 8-digit CVR / NO 9-digit / SE 10-digit, all prefix-guarded against PII false positives) and it sets scrape.do's geoCode so the page is fetched from that country's IP (helps when content is geo-routed). Different countries are *different* enqueued jobs (separate idempotency).
- ?refresh=true (also ?bypass_cache=true / ?force=true) bypasses the TTL freshness check and enqueues a fresh crawl regardless. A cached company doc, if present, is still returned in this response (marked freshness: "stale"); the refreshed copy will land on the next poll.

Async (profylr_jobs). Both legs run in the enrichment worker, so the response is immediate. freshness is reconciled against the job queue's real state — pending/stale are only ever reported while a job is genuinely queued or running:
- pending — no data yet; a company_crawl job is queued/running. Poll again, or use sync=1.
- stale — cached doc returned now; a refresh is queued/running.
- fresh — cached within TTL (PROFYLR_DOMAIN_TTL_DAYS, default 30); nothing enqueued.
- not_foundterminal. The pipeline completed (or no provider is runnable for this query) and no data was found. Polling will not change the answer; refresh=1 re-runs the work.
- failedterminal. The background job exhausted its retries. Plain polls do NOT revive it (that would retry forever); refresh=1 revives explicitly.

Sync mode. ?sync=1 (alias ?wait=1) holds the request open until the enqueued job finishes — ?timeout= seconds, 1-25, default 20 — and returns the final profile in one round-trip. The response carries sync: {requested, waited_ms, outcome: completed|failed|timeout|no_wait}. Requires the enrichment worker to be running; without it the wait times out and the regular async answer is returned.

data: {domain, country, refresh, freshness, found, job_status, enqueued, es_status, company, checked_at, note}. found says whether the doc carries actual findings (not just bookkeeping); job_status mirrors the queue row (pending|claimed|completed|failed) or null. company is the profylr_companies doc (or null until the first crawl lands) with the crawler output under web_crawl and the provider waterfall under enrichment.

403 if the domain is in profylr_optouts (when PROFYLR_ENFORCE_OPTOUT=true).

Required query: domain.

Minimal query: {"domain":"example.com"}

AuthorizationBearer YOUR_JWT_TOKEN
domainexample.com
countrydk
refreshfalse
curl -X GET 'https://api.endpointr.com/v1/profylr/company?domain=example.com&country=dk&refresh=false' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/profylr/company?domain=example.com&country=dk&refresh=false', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/profylr/company?domain=example.com&country=dk&refresh=false');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

DSR (export / erase)

POST/v1/profylr/dsr

Specified, not yet implemented — present so the collection documents the Phase 1 contract (docs/api.md).

Data Subject Request. operation: export (full machine-readable bundle of every field held, with provenance) or erase (tombstone + removal across all ES indices and the job queue). identifier is an email, LinkedIn slug/URL, GitHub username, or person_id.

202 accepted — processed asynchronously and audited · 401 missing/invalid/revoked JWT · 404 no such data subject. Runbook: docs/compliance/dsr-handling-runbook.md.

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "operation": "export",
    "identifier": "jane@example.com"
}
curl -X POST 'https://api.endpointr.com/v1/profylr/dsr' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "operation": "export",
    "identifier": "jane@example.com"
}'
const response = await fetch('https://api.endpointr.com/v1/profylr/dsr', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "operation": "export",
      "identifier": "jane@example.com"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/profylr/dsr');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"operation\": \"export\",\n    \"identifier\": \"jane@example.com\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Opt-out

POST/v1/profylr/opt-out

Specified, not yet implemented — present so the collection documents the Phase 1 contract (docs/api.md).

Register an opt-out. scopeemail | domain | person; value is the address / domain / person_id. Omit channels to opt out of every channel, or pass an array of channel keys to scope it. reason is free text for the audit trail.

201 recorded · 401 missing/invalid/revoked JWT. Enforced by the lookup API and every drafter; writes a profylr_audit row.

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "scope": "email",
    "value": "jane@example.com",
    "channels": [
        "email",
        "linkedin"
    ],
    "reason": "Subject requested removal"
}
curl -X POST 'https://api.endpointr.com/v1/profylr/opt-out' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "scope": "email",
    "value": "jane@example.com",
    "channels": [
        "email",
        "linkedin"
    ],
    "reason": "Subject requested removal"
}'
const response = await fetch('https://api.endpointr.com/v1/profylr/opt-out', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "scope": "email",
      "value": "jane@example.com",
      "channels": [
          "email",
          "linkedin"
      ],
      "reason": "Subject requested removal"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/profylr/opt-out');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"scope\": \"email\",\n    \"value\": \"jane@example.com\",\n    \"channels\": [\n        \"email\",\n        \"linkedin\"\n    ],\n    \"reason\": \"Subject requested removal\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Person lookup

GET/v1/profylr/person?email=jane%40example.com&country=dk&refresh=false

Implemented (email selector). Resolve a person by email and attach the token-free domain crawl as the basis.

Selectors.
- ?email=jane@acme.com (required). Validated; the canonical person doc is keyed by email (a ULID person_id is minted; deterministic dedupe by email). ?linkedin=, ?github=, ?name=&company= are in docs/api.md but return a clear 400 in this phase (probabilistic identity *merge* is deferred).
- ?country=dk|no|se|… (optional, ISO-2). Forwarded to the domain crawl (CVR pattern + scrape.do geoCode).
- ?refresh=true bypasses the TTL freshness check and enqueues a fresh crawl.

Email → domain. If the email domain is a generic free-mail provider (gmail, outlook, yahoo, proton, …; extend via PROFYLR_GENERIC_EMAIL_DOMAINS), the waterfall is skipped (domain_enrichment: "skipped_generic"). Otherwise the employer domain is crawled asynchronously and linked.

Async (profylr_jobs). Same freshness model as Company lookup, reconciled against the job queue's real state: pending/stale only while a job is genuinely queued or running; not_found (pipeline completed, nothing found) and failed (job exhausted retries) are terminal — polling will not change them, refresh=1 re-runs. Provider slugs the worker cannot actually attempt (unwired / no stored credentials / unsupported query shape) never count toward freshness, so the endpoint cannot ask you to poll for work that will never run.

Sync mode. ?sync=1 (alias ?wait=1) holds the request open until the job finishes — ?timeout= seconds, 1-25, default 20 — and returns the final profile in one round-trip; the response carries sync: {requested, waited_ms, outcome: completed|failed|timeout|no_wait}. Requires the enrichment worker to be running.

data: {email, domain, domain_generic, domain_enrichment, email_enrichment, country, refresh, freshness, found, job_status, enqueued, es_status, person, checked_at, note}. personal_context is never returned (hard rule).

403 if the email or its domain is opted out.

Required query: email.

Minimal query: {"email":"jane@example.com"}

AuthorizationBearer YOUR_JWT_TOKEN
emailjane@example.com
countrydk
refreshfalse
curl -X GET 'https://api.endpointr.com/v1/profylr/person?email=jane%40example.com&country=dk&refresh=false' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/profylr/person?email=jane%40example.com&country=dk&refresh=false', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/profylr/person?email=jane%40example.com&country=dk&refresh=false');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Ping (bridge health check)

GET/v1/profylr/ping

Implemented. Bridge smoke test for the Profylr module: proves composer PSR-4 autoload, the reflection-router route, and the shared endpointr JWT middleware all line up. Returns the standard envelope with data: {profylr:"ok", version, php, customer_id, generated_at}.

Auth. Standard endpointr JWT — Authorization: Bearer {{token}} (issue via POST /v1/token with your api_key). No/expired/revoked JWT → 401. This is the *same* JWT as the rest of /v1/*; Profylr is not on profylr.endpointr.com (that vhost is the operator console only).

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/profylr/ping' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/profylr/ping', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/profylr/ping');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Payments

Clearhaus Settlements

GET/v1/payments/clearhaus-settlements

List settlements.

_Requires stored credentials: clearhaus (PUT /v1/credentials/clearhaus)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/payments/clearhaus-settlements' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/payments/clearhaus-settlements', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/payments/clearhaus-settlements');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/payments/clearhaus-settlements/:id

Fetch settlement by id.

_Requires stored credentials: clearhaus (PUT /v1/credentials/clearhaus)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/payments/clearhaus-settlements/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/payments/clearhaus-settlements/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/payments/clearhaus-settlements/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

PayPal — Accounts

GET/v1/payments/paypal-accounts

List the configured PayPal account labels (stored as paypal:<label>). Pass one as account on any PayPal tool. No upstream call.

_Requires stored credentials: paypal (PUT /v1/credentials/paypal)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/payments/paypal-accounts' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/payments/paypal-accounts', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/payments/paypal-accounts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

PayPal — Balance (money on account)

GET/v1/payments/paypal-balance

Current available balance per currency — how much money is on the account. Optional query: currency_code, as_of_time (RFC3339, historical). Needs the app's Transaction Search feature; can lag live by ~3h.

_Requires stored credentials: paypal (PUT /v1/credentials/paypal)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/payments/paypal-balance' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/payments/paypal-balance', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/payments/paypal-balance');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

PayPal — Billing Plans (search subscriptions by name)

GET/v1/payments/paypal-plans/:id

Get one billing plan by its id (P-…) — name, description, status, billing cycles, pricing.

_Requires stored credentials: paypal (PUT /v1/credentials/paypal)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/payments/paypal-plans/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/payments/paypal-plans/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/payments/paypal-plans/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/payments/paypal-plans?name=Pro&page_size=20

List billing plans — the NAMED recurring products. This is how you search subscriptions by name/description: pass name/description (filtered client-side within the fetched page; widen with page_size/page). Passthrough: product_id, page, page_size, total_required. Then read a subscriber's subscription via the subscriptions get.

Minimal query: {"name":"Pro","page_size":20}

_Requires stored credentials: paypal (PUT /v1/credentials/paypal)._

AuthorizationBearer YOUR_JWT_TOKEN
namePro
page_size20
curl -X GET 'https://api.endpointr.com/v1/payments/paypal-plans?name=Pro&page_size=20' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/payments/paypal-plans?name=Pro&page_size=20', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/payments/paypal-plans?name=Pro&page_size=20');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

PayPal — Invoices

GET/v1/payments/paypal-invoices/:id

Get one invoice by its id (e.g. INV2-XXXX-XXXX-XXXX-XXXX).

_Requires stored credentials: paypal (PUT /v1/credentials/paypal)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/payments/paypal-invoices/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/payments/paypal-invoices/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/payments/paypal-invoices/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/payments/paypal-invoices?status=UNPAID&page_size=20&total_required=true

List invoices, or search when you pass filters. Filters (search): status (PAID|MARKED_AS_PAID|PARTIALLY_PAID|SENT|UNPAID|PAYMENT_PENDING|SCHEDULED|CANCELLED|REFUNDED), invoice_date_range/due_date_range ({start,end}), recipient_email, total_amount_range, invoice_number. Pagination: page, page_size, total_required. Overdue = outstanding past its due date (filter due_date_range, or use Overview). account selects the PayPal account.

Minimal query: {"status":"UNPAID","page_size":20,"total_required":"true"}

_Requires stored credentials: paypal (PUT /v1/credentials/paypal)._

AuthorizationBearer YOUR_JWT_TOKEN
statusUNPAID
page_size20
total_requiredtrue
curl -X GET 'https://api.endpointr.com/v1/payments/paypal-invoices?status=UNPAID&page_size=20&total_required=true' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/payments/paypal-invoices?status=UNPAID&page_size=20&total_required=true', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/payments/paypal-invoices?status=UNPAID&page_size=20&total_required=true');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

PayPal — Overview (income snapshot)

GET/v1/payments/paypal-overview

One-call money snapshot: current balances, invoices bucketed paid / outstanding / overdue, and gross income over the last 30 days, grouped per currency. Start here. Balance/income need the app's Transaction Search feature; each source degrades independently into warnings. account selects which PayPal account (multi-account).

_Requires stored credentials: paypal (PUT /v1/credentials/paypal)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/payments/paypal-overview' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/payments/paypal-overview', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/payments/paypal-overview');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

PayPal — Subscriptions

GET/v1/payments/paypal-subscriptions/:id

Get one subscription by its id (I-…): status, plan_id, subscriber, billing_info (next billing time, last payment, outstanding balance). PayPal has no list/search for subscriptions — find the plan by name via the plans tool, then read a subscriber's sub by its I-… id.

_Requires stored credentials: paypal (PUT /v1/credentials/paypal)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/payments/paypal-subscriptions/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/payments/paypal-subscriptions/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/payments/paypal-subscriptions/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/payments/paypal-transactions?start_date=2026-06-01T00%3A00%3A00Z&end_date=2026-06-30T23%3A59%3A59Z&fields=all

Transaction Search — the true overall income source (all payments received). Required: start_date + end_date (RFC3339), max 31 days apart; page by month for longer ranges. Optional: transaction_status (S|P|D|V), fields (default all), page, page_size (max 500). Needs the app's Transaction Search feature; data lags ~3h.

Minimal query: {"start_date":"2026-06-01T00:00:00Z","end_date":"2026-06-30T23:59:59Z","fields":"all"}

_Requires stored credentials: paypal (PUT /v1/credentials/paypal)._

AuthorizationBearer YOUR_JWT_TOKEN
start_date2026-06-01T00:00:00Z
end_date2026-06-30T23:59:59Z
fieldsall
curl -X GET 'https://api.endpointr.com/v1/payments/paypal-transactions?start_date=2026-06-01T00%3A00%3A00Z&end_date=2026-06-30T23%3A59%3A59Z&fields=all' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/payments/paypal-transactions?start_date=2026-06-01T00%3A00%3A00Z&end_date=2026-06-30T23%3A59%3A59Z&fields=all', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/payments/paypal-transactions?start_date=2026-06-01T00%3A00%3A00Z&end_date=2026-06-30T23%3A59%3A59Z&fields=all');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Stripe

Accounting

Dinero — Accounting years

GET/v1/accounting/dinero-accounting-years

List accounting years. action=possible_end_dates returns valid end dates for a new year.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-accounting-years' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-accounting-years', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-accounting-years');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-accounting-years

Create an accounting year. action=possible_end_dates computes candidate end dates instead.

Minimal body: {"StartDate":"2026-01-01","EndDate":"2026-12-31"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "StartDate": "2026-01-01",
    "EndDate": "2026-12-31"
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-accounting-years' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "StartDate": "2026-01-01",
    "EndDate": "2026-12-31"
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-accounting-years', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "StartDate": "2026-01-01",
      "EndDate": "2026-12-31"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-accounting-years');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"StartDate\": \"2026-01-01\",\n    \"EndDate\": \"2026-12-31\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Attachments

GET/v1/accounting/dinero-attachments/:id

List the attachments bound to a document {documentGuid}. The guid is the Guid of an invoice/voucher/credit-note — get it from that resource's own list tool (invoices, vouchers, purchase-vouchers, sales-credit-notes).

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-attachments/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-attachments/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-attachments/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-attachments

action: bind (document_guid, document_type — bind the default file), add_file (document_guid, file_guid, file_name), delete_file (document_guid, file_guid).

Required body: document_guid.

Minimal body: {"document_guid":"<doc-guid>"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "action": "add_file",
    "document_guid": "<doc-guid>",
    "file_guid": "<file-guid>",
    "file_name": "receipt.pdf"
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-attachments' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "action": "add_file",
    "document_guid": "<doc-guid>",
    "file_guid": "<file-guid>",
    "file_name": "receipt.pdf"
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-attachments', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "action": "add_file",
      "document_guid": "<doc-guid>",
      "file_guid": "<file-guid>",
      "file_name": "receipt.pdf"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-attachments');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"action\": \"add_file\",\n    \"document_guid\": \"<doc-guid>\",\n    \"file_guid\": \"<file-guid>\",\n    \"file_name\": \"receipt.pdf\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Business goals

GET/v1/accounting/dinero-business-goals?year_key=%3Caccounting-year-key%3E

Get business goals for an accounting year (year_key). action: performance (yearly performance), monthly (monthly goals).

Required query: year_key.

Minimal query: {"year_key":"<accounting-year-key>"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
year_key<accounting-year-key>
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-business-goals?year_key=%3Caccounting-year-key%3E' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-business-goals?year_key=%3Caccounting-year-key%3E', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-business-goals?year_key=%3Caccounting-year-key%3E');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-business-goals

Set yearly business goals for an accounting year (year_key).

Required body: year_key.

Minimal body: {"year_key":"<accounting-year-key>"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "year_key": "<accounting-year-key>",
    "RevenueGoal": 1000000
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-business-goals' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "year_key": "<accounting-year-key>",
    "RevenueGoal": 1000000
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-business-goals', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "year_key": "<accounting-year-key>",
      "RevenueGoal": 1000000
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-business-goals');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"year_key\": \"<accounting-year-key>\",\n    \"RevenueGoal\": 1000000\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Chart of accounts

GET/v1/accounting/dinero-accounts?kind=entry

List chart-of-accounts entries. kind = entry (default) | purchase | deposit; for deposit add internal=1 for internal deposit accounts. Forwards fields, categoryFilter.

Minimal query: {"kind":"entry"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
kindentry
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-accounts?kind=entry' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-accounts?kind=entry', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-accounts?kind=entry');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-accounts

Create an account. action = entry (default) | deposit.

Minimal body: {"action":"entry","Name":"New revenue account","AccountNumber":1010}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "action": "entry",
    "Name": "New revenue account",
    "AccountNumber": 1010
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-accounts' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "action": "entry",
    "Name": "New revenue account",
    "AccountNumber": 1010
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-accounts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "action": "entry",
      "Name": "New revenue account",
      "AccountNumber": 1010
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-accounts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"action\": \"entry\",\n    \"Name\": \"New revenue account\",\n    \"AccountNumber\": 1010\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Contacts

GET/v1/accounting/dinero-contacts/:id

Get a single contact by its {guid}.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-contacts/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-contacts/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-contacts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/accounting/dinero-contacts?page=0&pageSize=100

List contacts (forwards fields, queryFilter, changesSince, page, pageSize). action=notes lists/gets a contact's notes (contact_guid [+note_guid]). api_version=v2 uses the v2 contacts list.

Minimal query: {"page":0,"pageSize":100}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
page0
pageSize100
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-contacts?page=0&pageSize=100' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-contacts?page=0&pageSize=100', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-contacts?page=0&pageSize=100');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-contacts

Create a contact. action=restore undeletes (id). action=note adds a note (contact_guid). action=note_delete removes a note (contact_guid,note_guid).

Minimal body: {"Name":"Acme ApS","CountryKey":"DK","Email":"billing@acme.dk","IsPerson":false,"IsMember":false,"UseCvr":false}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "Name": "Acme ApS",
    "CountryKey": "DK",
    "Email": "billing@acme.dk",
    "IsPerson": false,
    "IsMember": false,
    "UseCvr": false
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-contacts' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "Name": "Acme ApS",
    "CountryKey": "DK",
    "Email": "billing@acme.dk",
    "IsPerson": false,
    "IsMember": false,
    "UseCvr": false
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-contacts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "Name": "Acme ApS",
      "CountryKey": "DK",
      "Email": "billing@acme.dk",
      "IsPerson": false,
      "IsMember": false,
      "UseCvr": false
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-contacts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"Name\": \"Acme ApS\",\n    \"CountryKey\": \"DK\",\n    \"Email\": \"billing@acme.dk\",\n    \"IsPerson\": false,\n    \"IsMember\": false,\n    \"UseCvr\": false\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/accounting/dinero-contacts/:id

Update a contact by {guid} (api_version=v2 for v2). action=note updates a note (contact_guid, {guid}=note guid).

Minimal body: {"Name":"Acme ApS","Email":"ap@acme.dk"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "Name": "Acme ApS",
    "Email": "ap@acme.dk"
}
curl -X PUT 'https://api.endpointr.com/v1/accounting/dinero-contacts/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "Name": "Acme ApS",
    "Email": "ap@acme.dk"
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-contacts/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "Name": "Acme ApS",
      "Email": "ap@acme.dk"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-contacts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"Name\": \"Acme ApS\",\n    \"Email\": \"ap@acme.dk\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/accounting/dinero-contacts/:id

Delete a contact by {guid}.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/accounting/dinero-contacts/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-contacts/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-contacts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Countries

GET/v1/accounting/dinero-countries

List supported countries (global; not organisation-scoped).

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-countries' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-countries', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-countries');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Electronic invoice

GET/v1/accounting/dinero-electronic-invoice?ean=5790000000000

Validate an e-invoice recipient (forwards the recipient identifiers, e.g. ean/cvr).

Minimal query: {"ean":"5790000000000"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
ean5790000000000
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-electronic-invoice?ean=5790000000000' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-electronic-invoice?ean=5790000000000', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-electronic-invoice?ean=5790000000000');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Entries (general ledger)

GET/v1/accounting/dinero-entries?fromDate=2026-01-01&toDate=2026-12-31

List accounting entries (forwards fromDate, toDate, accountNumber, includePrimo). action=changes returns entries changed since a cursor.

Minimal query: {"fromDate":"2026-01-01","toDate":"2026-12-31"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
fromDate2026-01-01
toDate2026-12-31
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-entries?fromDate=2026-01-01&toDate=2026-12-31' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-entries?fromDate=2026-01-01&toDate=2026-12-31', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-entries?fromDate=2026-01-01&toDate=2026-12-31');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Files

GET/v1/accounting/dinero-files/:id

Get file metadata by {fileGuid}.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-files/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-files/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-files/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/accounting/dinero-files

List uploaded files available for attaching.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-files' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-files', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-files');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-files

Register/upload a file. action=attachment creates it as an attachment.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
[]
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-files' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '[]'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-files', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify([])
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-files');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '[]');
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Integrations

GET/v1/accounting/dinero-integrations

List integrations. action: mobilepay_access (MobilePay accounting access), pensopay (PensoPay status).

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-integrations' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-integrations', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-integrations');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-integrations

Integration lifecycle. action: mobilepay_start (mobilepay_version v1|v2), mobilepay_activate, mobilepay_deactivate, pensopay_start, pensopay_reactivate, pensopay_deactivate, pensopay_mobilepay (PUT).

Required body: action.

Minimal body: {"action":"mobilepay_start"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "action": "mobilepay_start"
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-integrations' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "action": "mobilepay_start"
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-integrations', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "action": "mobilepay_start"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-integrations');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"action\": \"mobilepay_start\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Invoices

GET/v1/accounting/dinero-invoices/:id

Get a single invoice by its {guid}.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-invoices/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-invoices/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-invoices/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/accounting/dinero-invoices?page=0&pageSize=100&statusFilter=Booked

List invoices, or a read sub-operation via action: pdf (id), payments (id), mailouts (id), templates, validate_trustpilot (contact_guid), email_template (id), pre_reminder_template (id), reminders (voucher_guid [+reminder_id|next]), reminder_template (voucher_guid). Without action, lists invoices (forwards Dinero filters such as page, pageSize, statusFilter, startDate, endDate, freeTextSearch, changesSince).

Minimal query: {"page":0,"pageSize":100,"statusFilter":"Booked"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
page0
pageSize100
statusFilterBooked
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-invoices?page=0&pageSize=100&statusFilter=Booked' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-invoices?page=0&pageSize=100&statusFilter=Booked', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-invoices?page=0&pageSize=100&statusFilter=Booked');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-invoices

Create a draft invoice, or a write sub-operation via action: book (id), fetch, email (id), pre_reminder (id), e_invoice (id), add_payment (id), generate_creditnote (id); reminders: reminder_create/reminder_fetch/reminder_email (voucher_guid), reminder_update/reminder_delete/reminder_book/reminder_e_reminder (voucher_guid + reminder_id). Fields other than action/id/voucher_guid/reminder_id are forwarded to Dinero verbatim.

Minimal body: {"ContactGuid":"<contact-guid>","Currency":"DKK","Language":"da-DK","Date":"2026-01-15","ProductLines":[{"BaseAmountValue":1000,"Quantity":1,"AccountNumber":1000,"Unit":"parts","Description":"Consulting","LineType":"Product"}]}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "ContactGuid": "<contact-guid>",
    "Currency": "DKK",
    "Language": "da-DK",
    "Date": "2026-01-15",
    "ProductLines": [
        {
            "BaseAmountValue": 1000,
            "Quantity": 1,
            "AccountNumber": 1000,
            "Unit": "parts",
            "Description": "Consulting",
            "LineType": "Product"
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-invoices' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "ContactGuid": "<contact-guid>",
    "Currency": "DKK",
    "Language": "da-DK",
    "Date": "2026-01-15",
    "ProductLines": [
        {
            "BaseAmountValue": 1000,
            "Quantity": 1,
            "AccountNumber": 1000,
            "Unit": "parts",
            "Description": "Consulting",
            "LineType": "Product"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-invoices', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "ContactGuid": "<contact-guid>",
      "Currency": "DKK",
      "Language": "da-DK",
      "Date": "2026-01-15",
      "ProductLines": [
          {
              "BaseAmountValue": 1000,
              "Quantity": 1,
              "AccountNumber": 1000,
              "Unit": "parts",
              "Description": "Consulting",
              "LineType": "Product"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-invoices');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"ContactGuid\": \"<contact-guid>\",\n    \"Currency\": \"DKK\",\n    \"Language\": \"da-DK\",\n    \"Date\": \"2026-01-15\",\n    \"ProductLines\": [\n        {\n            \"BaseAmountValue\": 1000,\n            \"Quantity\": 1,\n            \"AccountNumber\": 1000,\n            \"Unit\": \"parts\",\n            \"Description\": \"Consulting\",\n            \"LineType\": \"Product\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/accounting/dinero-invoices/:id

Update a draft invoice (PUT v1.2). Include the current Timestamp for optimistic concurrency.

Minimal body: {"Timestamp":"<timestamp>","ProductLines":[]}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "Timestamp": "<timestamp>",
    "ProductLines": []
}
curl -X PUT 'https://api.endpointr.com/v1/accounting/dinero-invoices/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "Timestamp": "<timestamp>",
    "ProductLines": []
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-invoices/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "Timestamp": "<timestamp>",
      "ProductLines": []
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-invoices/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"Timestamp\": \"<timestamp>\",\n    \"ProductLines\": []\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/accounting/dinero-invoices/:id

Delete a draft invoice by {guid}.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/accounting/dinero-invoices/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-invoices/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-invoices/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Ledger items

POST/v1/accounting/dinero-ledger-items

Create ledger items (POST v1.2). action: ledgers (create ledgers), update (PUT — payload identifies the line, no id segment), book, status, delete.

Minimal body: [{"AccountNumber":1000,"Amount":1000,"BalancingAccountNumber":55000,"Description":"Manual posting","VoucherDate":"2026-01-15"}]

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
[
    {
        "AccountNumber": 1000,
        "Amount": 1000,
        "BalancingAccountNumber": 55000,
        "Description": "Manual posting",
        "VoucherDate": "2026-01-15"
    }
]
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-ledger-items' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '[
    {
        "AccountNumber": 1000,
        "Amount": 1000,
        "BalancingAccountNumber": 55000,
        "Description": "Manual posting",
        "VoucherDate": "2026-01-15"
    }
]'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-ledger-items', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify([
      {
          "AccountNumber": 1000,
          "Amount": 1000,
          "BalancingAccountNumber": 55000,
          "Description": "Manual posting",
          "VoucherDate": "2026-01-15"
      }
  ])
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-ledger-items');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "[\n    {\n        \"AccountNumber\": 1000,\n        \"Amount\": 1000,\n        \"BalancingAccountNumber\": 55000,\n        \"Description\": \"Manual posting\",\n        \"VoucherDate\": \"2026-01-15\"\n    }\n]");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Organizations

GET/v1/accounting/dinero-organizations

List organisations the token can access (global). api_version=v1.1 for the newer list. action=is_verified returns the current org's verification status.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-organizations' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-organizations', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-organizations');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-organizations

Create an organisation (global). action: update / delete (current org), delete_verification (remove org verification).

Minimal body: {"Name":"My Company ApS","VatNumber":"12345678","CountryKey":"DK"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "Name": "My Company ApS",
    "VatNumber": "12345678",
    "CountryKey": "DK"
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-organizations' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "Name": "My Company ApS",
    "VatNumber": "12345678",
    "CountryKey": "DK"
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-organizations', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "Name": "My Company ApS",
      "VatNumber": "12345678",
      "CountryKey": "DK"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-organizations');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"Name\": \"My Company ApS\",\n    \"VatNumber\": \"12345678\",\n    \"CountryKey\": \"DK\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Products

GET/v1/accounting/dinero-products/:id

Get a single product by its {guid}.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-products/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-products/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-products/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/accounting/dinero-products?page=0&pageSize=100

List products (forwards fields, freeTextSearch, queryFilter, changesSince, page, pageSize).

Minimal query: {"page":0,"pageSize":100}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
page0
pageSize100
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-products?page=0&pageSize=100' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-products?page=0&pageSize=100', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-products?page=0&pageSize=100');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-products

Create a product.

Minimal body: {"Name":"Consulting hour","BaseAmountValue":1000,"AccountNumber":1000,"Unit":"hours","Quantity":1}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "Name": "Consulting hour",
    "BaseAmountValue": 1000,
    "AccountNumber": 1000,
    "Unit": "hours",
    "Quantity": 1
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-products' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "Name": "Consulting hour",
    "BaseAmountValue": 1000,
    "AccountNumber": 1000,
    "Unit": "hours",
    "Quantity": 1
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-products', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "Name": "Consulting hour",
      "BaseAmountValue": 1000,
      "AccountNumber": 1000,
      "Unit": "hours",
      "Quantity": 1
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-products');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"Name\": \"Consulting hour\",\n    \"BaseAmountValue\": 1000,\n    \"AccountNumber\": 1000,\n    \"Unit\": \"hours\",\n    \"Quantity\": 1\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/accounting/dinero-products/:id

Update a product by {guid}.

Minimal body: {"Name":"Consulting hour","BaseAmountValue":1200}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "Name": "Consulting hour",
    "BaseAmountValue": 1200
}
curl -X PUT 'https://api.endpointr.com/v1/accounting/dinero-products/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "Name": "Consulting hour",
    "BaseAmountValue": 1200
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-products/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "Name": "Consulting hour",
      "BaseAmountValue": 1200
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-products/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"Name\": \"Consulting hour\",\n    \"BaseAmountValue\": 1200\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/accounting/dinero-products/:id

Delete a product by {guid}.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/accounting/dinero-products/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-products/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-products/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Purchase voucher payments

GET/v1/accounting/dinero-purchase-vouchers?id=%3Cpurchase-voucher-id%3E

List a purchase voucher's payments (id required). action=v2 uses the v2 payments view.

Required query: id.

Minimal query: {"id":"<purchase-voucher-id>"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
id<purchase-voucher-id>
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-purchase-vouchers?id=%3Cpurchase-voucher-id%3E' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-purchase-vouchers?id=%3Cpurchase-voucher-id%3E', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-purchase-vouchers?id=%3Cpurchase-voucher-id%3E');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-purchase-vouchers

Add a payment to a purchase voucher (id). action=calculations previews a payment; action=delete_payment removes one (id, payment_id, timestamp).

Required body: id.

Minimal body: {"id":"<purchase-voucher-id>"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "id": "<purchase-voucher-id>",
    "Amount": 1250,
    "PaymentDate": "2026-01-20",
    "DepositAccountNumber": 55000
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-purchase-vouchers' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "id": "<purchase-voucher-id>",
    "Amount": 1250,
    "PaymentDate": "2026-01-20",
    "DepositAccountNumber": 55000
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-purchase-vouchers', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "id": "<purchase-voucher-id>",
      "Amount": 1250,
      "PaymentDate": "2026-01-20",
      "DepositAccountNumber": 55000
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-purchase-vouchers');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"id\": \"<purchase-voucher-id>\",\n    \"Amount\": 1250,\n    \"PaymentDate\": \"2026-01-20\",\n    \"DepositAccountNumber\": 55000\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Reports

GET/v1/accounting/dinero-reports?report=result&year=2026

Financial reports for an accounting year. report = saldo | result | primo | balance; year = accounting-year key. Extra params (date ranges) are forwarded.

Required query: report, year.

Minimal query: {"report":"result","year":"2026"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
reportresult
year2026
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-reports?report=result&year=2026' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-reports?report=result&year=2026', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-reports?report=result&year=2026');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — SMS

POST/v1/accounting/dinero-sms

action=validate checks SMS settings; otherwise sends a voucher SMS (voucher_guid required).

Required body: voucher_guid.

Minimal body: {"voucher_guid":"<voucher-guid>"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "voucher_guid": "<voucher-guid>",
    "PhoneNumber": "+4512345678"
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-sms' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "voucher_guid": "<voucher-guid>",
    "PhoneNumber": "+4512345678"
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-sms', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "voucher_guid": "<voucher-guid>",
      "PhoneNumber": "+4512345678"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-sms');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"voucher_guid\": \"<voucher-guid>\",\n    \"PhoneNumber\": \"+4512345678\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Sales credit notes

GET/v1/accounting/dinero-sales-credit-notes/:id

Get a single credit note by {guid}.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/accounting/dinero-sales-credit-notes?page=0&pageSize=100

List credit notes. action: sales (sales list), settings (sales settings), pdf (id), payments (id), mailouts (id), email_template (id).

Minimal query: {"page":0,"pageSize":100}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
page0
pageSize100
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes?page=0&pageSize=100' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes?page=0&pageSize=100', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes?page=0&pageSize=100');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-sales-credit-notes

Create a credit note. action: book (id), email (id), e_creditnote (id), fetch, add_payment (id), delete_payment (id, payment_guid).

Minimal body: {"ContactGuid":"<contact-guid>","Currency":"DKK","Date":"2026-01-15","ProductLines":[{"BaseAmountValue":-500,"Quantity":1,"AccountNumber":1000,"Unit":"parts","Description":"Refund"}]}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "ContactGuid": "<contact-guid>",
    "Currency": "DKK",
    "Date": "2026-01-15",
    "ProductLines": [
        {
            "BaseAmountValue": -500,
            "Quantity": 1,
            "AccountNumber": 1000,
            "Unit": "parts",
            "Description": "Refund"
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "ContactGuid": "<contact-guid>",
    "Currency": "DKK",
    "Date": "2026-01-15",
    "ProductLines": [
        {
            "BaseAmountValue": -500,
            "Quantity": 1,
            "AccountNumber": 1000,
            "Unit": "parts",
            "Description": "Refund"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "ContactGuid": "<contact-guid>",
      "Currency": "DKK",
      "Date": "2026-01-15",
      "ProductLines": [
          {
              "BaseAmountValue": -500,
              "Quantity": 1,
              "AccountNumber": 1000,
              "Unit": "parts",
              "Description": "Refund"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"ContactGuid\": \"<contact-guid>\",\n    \"Currency\": \"DKK\",\n    \"Date\": \"2026-01-15\",\n    \"ProductLines\": [\n        {\n            \"BaseAmountValue\": -500,\n            \"Quantity\": 1,\n            \"AccountNumber\": 1000,\n            \"Unit\": \"parts\",\n            \"Description\": \"Refund\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/accounting/dinero-sales-credit-notes/:id

Update a credit note by {guid} (PUT v1.2).

Minimal body: {"Timestamp":"<timestamp>","ProductLines":[]}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "Timestamp": "<timestamp>",
    "ProductLines": []
}
curl -X PUT 'https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "Timestamp": "<timestamp>",
    "ProductLines": []
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "Timestamp": "<timestamp>",
      "ProductLines": []
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"Timestamp\": \"<timestamp>\",\n    \"ProductLines\": []\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/accounting/dinero-sales-credit-notes/:id

Delete a credit note by {guid}.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-sales-credit-notes/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Settings

POST/v1/accounting/dinero-settings

Update organisation access settings (PUT settings/access upstream).

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
[]
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-settings' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '[]'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-settings', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify([])
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-settings');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '[]');
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — State of account

GET/v1/accounting/dinero-state-of-account/:id

Get the statement data for a contact {guid}.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-state-of-account/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-state-of-account/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-state-of-account/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/accounting/dinero-state-of-account?id=%3Ccontact-guid%3E

Get a contact's statement of account. action=pdf (with id) returns the PDF (base64).

Required query: id.

Minimal query: {"id":"<contact-guid>"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
id<contact-guid>
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-state-of-account?id=%3Ccontact-guid%3E' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-state-of-account?id=%3Ccontact-guid%3E', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-state-of-account?id=%3Ccontact-guid%3E');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-state-of-account

Email a contact's statement of account (id = contact guid).

Required body: id.

Minimal body: {"id":"<contact-guid>"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "id": "<contact-guid>",
    "Receiver": "ap@acme.dk"
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-state-of-account' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "id": "<contact-guid>",
    "Receiver": "ap@acme.dk"
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-state-of-account', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "id": "<contact-guid>",
      "Receiver": "ap@acme.dk"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-state-of-account');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"id\": \"<contact-guid>\",\n    \"Receiver\": \"ap@acme.dk\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Trade offers (quotes)

GET/v1/accounting/dinero-trade-offers/:id

Get a single trade offer by {guid}.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-trade-offers/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-trade-offers/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-trade-offers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/accounting/dinero-trade-offers?page=0&pageSize=100

List trade offers. action: email_template (id, email_version v1|v2 default v2), mailouts (id).

Minimal query: {"page":0,"pageSize":100}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
page0
pageSize100
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-trade-offers?page=0&pageSize=100' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-trade-offers?page=0&pageSize=100', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-trade-offers?page=0&pageSize=100');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-trade-offers

Create a trade offer. action: email (id, email_version), generate_invoice (id), fetch.

Minimal body: {"ContactGuid":"<contact-guid>","Currency":"DKK","Date":"2026-01-15","ProductLines":[]}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "ContactGuid": "<contact-guid>",
    "Currency": "DKK",
    "Date": "2026-01-15",
    "ProductLines": []
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-trade-offers' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "ContactGuid": "<contact-guid>",
    "Currency": "DKK",
    "Date": "2026-01-15",
    "ProductLines": []
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-trade-offers', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "ContactGuid": "<contact-guid>",
      "Currency": "DKK",
      "Date": "2026-01-15",
      "ProductLines": []
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-trade-offers');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"ContactGuid\": \"<contact-guid>\",\n    \"Currency\": \"DKK\",\n    \"Date\": \"2026-01-15\",\n    \"ProductLines\": []\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/accounting/dinero-trade-offers/:id

Update a trade offer by {guid} (PUT v1.2).

Minimal body: {"ProductLines":[]}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "ProductLines": []
}
curl -X PUT 'https://api.endpointr.com/v1/accounting/dinero-trade-offers/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "ProductLines": []
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-trade-offers/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "ProductLines": []
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-trade-offers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"ProductLines\": []\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/accounting/dinero-trade-offers/:id

Delete a trade offer by {guid}.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/accounting/dinero-trade-offers/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-trade-offers/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-trade-offers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Unified vouchers

GET/v1/accounting/dinero-unified-vouchers?page=0&pageSize=100

List vouchers across types in a unified view (forwards filters/paging).

Minimal query: {"page":0,"pageSize":100}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
page0
pageSize100
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-unified-vouchers?page=0&pageSize=100' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-unified-vouchers?page=0&pageSize=100', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-unified-vouchers?page=0&pageSize=100');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — VAT types

GET/v1/accounting/dinero-vat-types

List the organisation's VAT types.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-vat-types' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-vat-types', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-vat-types');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Voucher templates

GET/v1/accounting/dinero-voucher-templates?action=types

action: types (default — list template types), get (template_type, id), deposits (template_type, voucher_id).

Minimal query: {"action":"types"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
actiontypes
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-voucher-templates?action=types' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-voucher-templates?action=types', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-voucher-templates?action=types');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-voucher-templates

Create a template (year_key, template_type). action: update (year_key, template_type, id — PUT), book (template_type, id), similar_exists (year_key, template_type).

Required body: year_key, template_type.

Minimal body: {"year_key":"<accounting-year-key>","template_type":"manuel"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "year_key": "<accounting-year-key>",
    "template_type": "manuel"
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-voucher-templates' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "year_key": "<accounting-year-key>",
    "template_type": "manuel"
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-voucher-templates', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "year_key": "<accounting-year-key>",
      "template_type": "manuel"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-voucher-templates');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"year_key\": \"<accounting-year-key>\",\n    \"template_type\": \"manuel\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Vouchers (manual / purchase)

GET/v1/accounting/dinero-vouchers?type=purchase&id=%3Cguid%3E

Read a voucher. Required type = manual | purchase | purchase_creditnote. Pass id (guid), or for purchase file_guid to look up by attached file.

Required query: type, id.

Minimal query: {"type":"purchase","id":"<guid>"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
typepurchase
id<guid>
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-vouchers?type=purchase&id=%3Cguid%3E' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-vouchers?type=purchase&id=%3Cguid%3E', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-vouchers?type=purchase&id=%3Cguid%3E');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-vouchers

Create a voucher. Required type = manual | purchase | purchase_creditnote. action: book/update/delete (all types), and for purchase: fetch, similar, generate_creditnote. version overrides the create/update API version (purchase create defaults v1.2, update v1.1).

Required body: type.

Minimal body: {"type":"purchase"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "type": "purchase",
    "ContactGuid": "<supplier-guid>",
    "Date": "2026-01-15",
    "Lines": []
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-vouchers' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "type": "purchase",
    "ContactGuid": "<supplier-guid>",
    "Date": "2026-01-15",
    "Lines": []
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-vouchers', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "type": "purchase",
      "ContactGuid": "<supplier-guid>",
      "Date": "2026-01-15",
      "Lines": []
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-vouchers');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"type\": \"purchase\",\n    \"ContactGuid\": \"<supplier-guid>\",\n    \"Date\": \"2026-01-15\",\n    \"Lines\": []\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Dinero — Webhooks

GET/v1/accounting/dinero-webhooks

List webhook subscriptions. action=events lists subscribable event types.

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/accounting/dinero-webhooks' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-webhooks', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-webhooks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/accounting/dinero-webhooks

Subscribe to a webhook event. action=unsubscribe removes a subscription.

Minimal body: {"Event":"CreateSalesInvoice","CallbackUrl":"https://example.com/hook"}

_Requires stored credentials: dinero (PUT /v1/credentials/dinero)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "Event": "CreateSalesInvoice",
    "CallbackUrl": "https://example.com/hook"
}
curl -X POST 'https://api.endpointr.com/v1/accounting/dinero-webhooks' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "Event": "CreateSalesInvoice",
    "CallbackUrl": "https://example.com/hook"
}'
const response = await fetch('https://api.endpointr.com/v1/accounting/dinero-webhooks', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "Event": "CreateSalesInvoice",
      "CallbackUrl": "https://example.com/hook"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/accounting/dinero-webhooks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"Event\": \"CreateSalesInvoice\",\n    \"CallbackUrl\": \"https://example.com/hook\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Bindings

Bindings

GET/v1/bindings/bindings

List all your bindings.

A binding wires a trigger event (e.g. stripe.customer.created) to an action on another provider (e.g. paypal.customer.create) via a path-based mapping.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/bindings/bindings' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/bindings/bindings', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/bindings/bindings');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/bindings/bindings/:id

Retrieve one binding by id.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/bindings/bindings/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/bindings/bindings/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/bindings/bindings/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/bindings/bindings

Create a binding. mapping values of the form $.foo.bar pull from the event data; literal strings/numbers are passed through.

Example below: when a customer is created in Stripe, mirror them into PayPal, keeping the Stripe id as an external reference.

Required body: trigger_event, action.

Minimal body: {"trigger_event":"stripe.customer.created","action":"paypal.customer.create"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "trigger_event": "stripe.customer.created",
    "action": "paypal.customer.create",
    "enabled": true,
    "mapping": {
        "email": "$.object.email",
        "name": "$.object.name",
        "external_reference": "$.object.id"
    }
}
curl -X POST 'https://api.endpointr.com/v1/bindings/bindings' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "trigger_event": "stripe.customer.created",
    "action": "paypal.customer.create",
    "enabled": true,
    "mapping": {
        "email": "$.object.email",
        "name": "$.object.name",
        "external_reference": "$.object.id"
    }
}'
const response = await fetch('https://api.endpointr.com/v1/bindings/bindings', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "trigger_event": "stripe.customer.created",
      "action": "paypal.customer.create",
      "enabled": true,
      "mapping": {
          "email": "$.object.email",
          "name": "$.object.name",
          "external_reference": "$.object.id"
      }
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/bindings/bindings');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"trigger_event\": \"stripe.customer.created\",\n    \"action\": \"paypal.customer.create\",\n    \"enabled\": true,\n    \"mapping\": {\n        \"email\": \"\$.object.email\",\n        \"name\": \"\$.object.name\",\n        \"external_reference\": \"\$.object.id\"\n    }\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/bindings/bindings/:id

Update a binding (PUT replaces all fields).

Minimal body: {"trigger_event":"stripe.customer.created","action":"paypal.customer.create","enabled":false,"mapping":[]}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "trigger_event": "stripe.customer.created",
    "action": "paypal.customer.create",
    "enabled": false,
    "mapping": []
}
curl -X PUT 'https://api.endpointr.com/v1/bindings/bindings/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "trigger_event": "stripe.customer.created",
    "action": "paypal.customer.create",
    "enabled": false,
    "mapping": []
}'
const response = await fetch('https://api.endpointr.com/v1/bindings/bindings/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "trigger_event": "stripe.customer.created",
      "action": "paypal.customer.create",
      "enabled": false,
      "mapping": []
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/bindings/bindings/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"trigger_event\": \"stripe.customer.created\",\n    \"action\": \"paypal.customer.create\",\n    \"enabled\": false,\n    \"mapping\": []\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/bindings/bindings/:id

Delete a binding.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/bindings/bindings/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/bindings/bindings/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/bindings/bindings/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Inbound Webhooks

GET/v1/bindings/inbound-webhooks

List your provider webhook endpoints. Each row is an opaque token that addresses a (customer, provider, secret) tuple.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/bindings/inbound-webhooks' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/bindings/inbound-webhooks', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/bindings/inbound-webhooks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/bindings/inbound-webhooks

Register an inbound webhook endpoint. The response returns a token — paste {baseUrl}/v1/inbound/{provider}/{token} into the provider's webhook settings dashboard.

The secret you supply is the signing secret from the provider (e.g. Stripe's whsec_…, QuickPay's private key, or PayPal's webhook_id). Incoming webhooks are signature-verified against it before being dispatched to bindings.

Required body: provider, secret.

Minimal body: {"provider":"stripe","secret":"whsec_..."}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "provider": "stripe",
    "secret": "whsec_...",
    "label": "Production Stripe"
}
curl -X POST 'https://api.endpointr.com/v1/bindings/inbound-webhooks' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "provider": "stripe",
    "secret": "whsec_...",
    "label": "Production Stripe"
}'
const response = await fetch('https://api.endpointr.com/v1/bindings/inbound-webhooks', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "provider": "stripe",
      "secret": "whsec_...",
      "label": "Production Stripe"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/bindings/inbound-webhooks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"provider\": \"stripe\",\n    \"secret\": \"whsec_...\",\n    \"label\": \"Production Stripe\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/bindings/inbound-webhooks/:id

Delete an endpoint by its token. Use the token string as the path id.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/bindings/inbound-webhooks/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/bindings/inbound-webhooks/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/bindings/inbound-webhooks/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cal.com

Cal.com — Accounts (labels)

GET/v1/calcom/cal-com-accounts

List the Cal.com account labels configured for this customer (stored as calcom:<label>). Returns {accounts:[…], has_default:bool} — never secrets. Pass one of these labels as the account selector on the other Cal.com tools. This tool takes no account selector.

_Requires stored credentials: calcom (PUT /v1/credentials/calcom)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/calcom/cal-com-accounts' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/calcom/cal-com-accounts', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/calcom/cal-com-accounts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cal.com — Bookings

GET/v1/calcom/cal-com-bookings/:id

Fetch one booking by its uid (the uid string from a create/list response, e.g. bmDs8j…).

_Requires stored credentials: calcom (PUT /v1/credentials/calcom)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/calcom/cal-com-bookings/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/calcom/cal-com-bookings/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/calcom/cal-com-bookings/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/calcom/cal-com-bookings?status=upcoming&limit=10

List bookings, newest first. All filters optional; call with {} to list all.

Filters: status (upcoming|recurring|past|cancelled|unconfirmed), attendeeEmail, attendeeName, eventTypeId, eventTypeIds (comma-separated), bookingUid, afterStart, beforeEnd (date strings), afterCreatedAt/beforeCreatedAt, cursor (pagination), limit (default 50).

Auth: vault calcom — or calcom:<label> + an account selector for multiple accounts.

Minimal query: {"status":"upcoming","limit":"10"}

_Requires stored credentials: calcom (PUT /v1/credentials/calcom)._

AuthorizationBearer YOUR_JWT_TOKEN
statusupcoming
limit10
curl -X GET 'https://api.endpointr.com/v1/calcom/cal-com-bookings?status=upcoming&limit=10' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/calcom/cal-com-bookings?status=upcoming&limit=10', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/calcom/cal-com-bookings?status=upcoming&limit=10');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/calcom/cal-com-bookings

Create a booking, or act on an existing booking via an action field. Without action, this creates a booking.

Create a booking (no action). Required: start (ISO 8601 UTC), eventTypeId, and attendee {name, email, timeZone}. Get eventTypeId from /v1/calcom/cal-com-event-types and a valid start from /v1/calcom/cal-com-slots.

Minimal:

{"start":"2026-08-13T09:00:00Z","eventTypeId":123,"attendee":{"name":"John Doe","email":"john@example.com","timeZone":"America/New_York"}}

Alt event identifier: eventTypeSlug + username instead of eventTypeId. Optional: lengthInMinutes (variable-length event types), guests (extra attendee emails), location/meetingUrl, bookingFieldsResponses, metadata.

Act on an existing booking — pass action + bookingUid:

Cancel (reason optional):

{"action":"cancel","bookingUid":"bmDs8j…","cancellationReason":"User requested"}

Reschedule (needs a new start; reschedulingReason optional):
{"action":"reschedule","bookingUid":"bmDs8j…","start":"2026-08-13T10:00:00Z"}

Confirm / decline an unconfirmed booking:
{"action":"confirm","bookingUid":"bmDs8j…"}

Multi-account: pass the top-level account selector on any of these.

Minimal body: {"start":"2026-08-13T09:00:00Z","eventTypeId":123,"attendee":{"name":"John Doe","email":"john@example.com","timeZone":"America/New_York"}}

_Requires stored credentials: calcom (PUT /v1/credentials/calcom)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "start": "2026-08-13T09:00:00Z",
    "eventTypeId": 123,
    "attendee": {
        "name": "John Doe",
        "email": "john@example.com",
        "timeZone": "America/New_York"
    }
}
curl -X POST 'https://api.endpointr.com/v1/calcom/cal-com-bookings' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "start": "2026-08-13T09:00:00Z",
    "eventTypeId": 123,
    "attendee": {
        "name": "John Doe",
        "email": "john@example.com",
        "timeZone": "America/New_York"
    }
}'
const response = await fetch('https://api.endpointr.com/v1/calcom/cal-com-bookings', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "start": "2026-08-13T09:00:00Z",
      "eventTypeId": 123,
      "attendee": {
          "name": "John Doe",
          "email": "john@example.com",
          "timeZone": "America/New_York"
      }
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/calcom/cal-com-bookings');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"start\": \"2026-08-13T09:00:00Z\",\n    \"eventTypeId\": 123,\n    \"attendee\": {\n        \"name\": \"John Doe\",\n        \"email\": \"john@example.com\",\n        \"timeZone\": \"America/New_York\"\n    }\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cal.com — Event Types

GET/v1/calcom/cal-com-event-types/:id

Fetch one event type by its numeric id (full config: length, locations, booking fields, schedule).

_Requires stored credentials: calcom (PUT /v1/credentials/calcom)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/calcom/cal-com-event-types/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/calcom/cal-com-event-types/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/calcom/cal-com-event-types/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/calcom/cal-com-event-types?username=jane

List Cal.com event types — the bookable meeting types. This is the discovery step: each event type's numeric id is the eventTypeId that /v1/calcom/cal-com-slots and /v1/calcom/cal-com-bookings require.

All filters are optional; call with no params to list everything visible to the stored API key. Filters: username, usernames (comma-separated), eventSlug (needs username), orgSlug, orgId, sortCreatedAt (asc|desc).

Auth: vault calcom (PUT /v1/credentials/calcom {api_key}), or calcom:<label> + an account selector for multiple accounts.

Minimal query: {"username":"jane"}

_Requires stored credentials: calcom (PUT /v1/credentials/calcom)._

AuthorizationBearer YOUR_JWT_TOKEN
usernamejane
curl -X GET 'https://api.endpointr.com/v1/calcom/cal-com-event-types?username=jane' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/calcom/cal-com-event-types?username=jane', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/calcom/cal-com-event-types?username=jane');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cal.com — Schedules (availability)

GET/v1/calcom/cal-com-schedules

List the availability schedules (working-hours definitions) on the account. Event types reference a schedule to decide when they're bookable. Multi-account: pass account to choose which stored credential to read.

_Requires stored credentials: calcom (PUT /v1/credentials/calcom)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/calcom/cal-com-schedules' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/calcom/cal-com-schedules', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/calcom/cal-com-schedules');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/calcom/cal-com-schedules/:id

Fetch one schedule by its numeric id (name, timeZone, availability windows, overrides).

_Requires stored credentials: calcom (PUT /v1/credentials/calcom)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/calcom/cal-com-schedules/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/calcom/cal-com-schedules/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/calcom/cal-com-schedules/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/calcom/cal-com-schedules

Create an availability schedule. Required: name, timeZone (IANA). isDefault defaults to false if omitted; availability is optional and defaults to Mon–Fri 09:00–17:00.

Minimal:

{"name":"Working hours","timeZone":"Europe/Copenhagen"}

With explicit windows and made the default:

{"name":"Working hours","timeZone":"Europe/Copenhagen","isDefault":true,"availability":[{"days":["Monday","Tuesday","Wednesday","Thursday","Friday"],"startTime":"09:00","endTime":"17:00"}]}

Required body: name, timeZone.

Minimal body: {"name":"Working hours","timeZone":"Europe/Copenhagen"}

_Requires stored credentials: calcom (PUT /v1/credentials/calcom)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "Working hours",
    "timeZone": "Europe/Copenhagen",
    "isDefault": true,
    "availability": [
        {
            "days": [
                "Monday",
                "Tuesday",
                "Wednesday",
                "Thursday",
                "Friday"
            ],
            "startTime": "09:00",
            "endTime": "17:00"
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/calcom/cal-com-schedules' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "Working hours",
    "timeZone": "Europe/Copenhagen",
    "isDefault": true,
    "availability": [
        {
            "days": [
                "Monday",
                "Tuesday",
                "Wednesday",
                "Thursday",
                "Friday"
            ],
            "startTime": "09:00",
            "endTime": "17:00"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/calcom/cal-com-schedules', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "Working hours",
      "timeZone": "Europe/Copenhagen",
      "isDefault": true,
      "availability": [
          {
              "days": [
                  "Monday",
                  "Tuesday",
                  "Wednesday",
                  "Thursday",
                  "Friday"
              ],
              "startTime": "09:00",
              "endTime": "17:00"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/calcom/cal-com-schedules');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"Working hours\",\n    \"timeZone\": \"Europe/Copenhagen\",\n    \"isDefault\": true,\n    \"availability\": [\n        {\n            \"days\": [\n                \"Monday\",\n                \"Tuesday\",\n                \"Wednesday\",\n                \"Thursday\",\n                \"Friday\"\n            ],\n            \"startTime\": \"09:00\",\n            \"endTime\": \"17:00\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/calcom/cal-com-schedules/:id

Update a schedule by id (PATCH upstream — only the fields you send change). Send any of name, timeZone, isDefault, availability, overrides.

Minimal body: {"name":"Working hours (updated)","availability":[{"days":["Monday","Tuesday","Wednesday","Thursday"],"startTime":"10:00","endTime":"18:00"}]}

_Requires stored credentials: calcom (PUT /v1/credentials/calcom)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "Working hours (updated)",
    "availability": [
        {
            "days": [
                "Monday",
                "Tuesday",
                "Wednesday",
                "Thursday"
            ],
            "startTime": "10:00",
            "endTime": "18:00"
        }
    ]
}
curl -X PUT 'https://api.endpointr.com/v1/calcom/cal-com-schedules/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "Working hours (updated)",
    "availability": [
        {
            "days": [
                "Monday",
                "Tuesday",
                "Wednesday",
                "Thursday"
            ],
            "startTime": "10:00",
            "endTime": "18:00"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/calcom/cal-com-schedules/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "Working hours (updated)",
      "availability": [
          {
              "days": [
                  "Monday",
                  "Tuesday",
                  "Wednesday",
                  "Thursday"
              ],
              "startTime": "10:00",
              "endTime": "18:00"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/calcom/cal-com-schedules/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"Working hours (updated)\",\n    \"availability\": [\n        {\n            \"days\": [\n                \"Monday\",\n                \"Tuesday\",\n                \"Wednesday\",\n                \"Thursday\"\n            ],\n            \"startTime\": \"10:00\",\n            \"endTime\": \"18:00\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/calcom/cal-com-schedules/:id

Delete a schedule by id.

_Requires stored credentials: calcom (PUT /v1/credentials/calcom)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/calcom/cal-com-schedules/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/calcom/cal-com-schedules/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/calcom/cal-com-schedules/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cal.com — Slots (availability)

GET/v1/calcom/cal-com-slots?eventTypeId=123&start=2026-08-13&end=2026-08-20&timeZone=America%2FNew_York

Get available time slots for an event type over a date range. The response data is grouped by date (e.g. {"2026-08-13":[{"start":"…"}, …]}).

Required: start + end (ISO 8601 or YYYY-MM-DD, UTC) and an event identifier — either eventTypeId, or eventTypeSlug together with username.

Optional: timeZone (IANA, e.g. America/New_York — slot times come back in this zone), duration (minutes, for variable-length event types).

Minimal:

?eventTypeId=123&start=2026-08-13&end=2026-08-20&timeZone=America/New_York

By slug instead of id:

?eventTypeSlug=intro-call&username=jane&start=2026-08-13&end=2026-08-20

Auth: vault calcom — or calcom:<label> + an account selector for multiple accounts.

Required query: start, end.

Minimal query: {"eventTypeId":"123","start":"2026-08-13","end":"2026-08-20","timeZone":"America/New_York"}

_Requires stored credentials: calcom (PUT /v1/credentials/calcom)._

AuthorizationBearer YOUR_JWT_TOKEN
eventTypeId123
start2026-08-13
end2026-08-20
timeZoneAmerica/New_York
curl -X GET 'https://api.endpointr.com/v1/calcom/cal-com-slots?eventTypeId=123&start=2026-08-13&end=2026-08-20&timeZone=America%2FNew_York' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/calcom/cal-com-slots?eventTypeId=123&start=2026-08-13&end=2026-08-20&timeZone=America%2FNew_York', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/calcom/cal-com-slots?eventTypeId=123&start=2026-08-13&end=2026-08-20&timeZone=America%2FNew_York');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cloudflare

Cloudflare — Accounts

GET/v1/cloudflare/cf-accounts

List the Cloudflare accounts the stored token can access. Use this to discover the account_id for Workers/Pages.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/cloudflare/cf-accounts' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/cf-accounts', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/cf-accounts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/cloudflare/cf-accounts/:id

Get an account by {account_id}.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/cloudflare/cf-accounts/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/cf-accounts/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/cf-accounts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cloudflare — Cache purge

POST/v1/cloudflare/cache-purge

Purge (clear) a zone's cache. zone_id required in the body (from the Cloudflare Zones tool). Pick one mode: purge_everything:true clears the whole cache, or a non-empty files / tags / hosts / prefixes array does a selective purge (tags/hosts/prefixes are Enterprise-only). Over MCP the zone travels in the body.

Required body: zone_id.

Minimal body: {"zone_id":"<zone-id>"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "zone_id": "<zone-id>",
    "purge_everything": true
}
curl -X POST 'https://api.endpointr.com/v1/cloudflare/cache-purge' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "zone_id": "<zone-id>",
    "purge_everything": true
}'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/cache-purge', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "zone_id": "<zone-id>",
      "purge_everything": true
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/cache-purge');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"zone_id\": \"<zone-id>\",\n    \"purge_everything\": true\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cloudflare — Cache settings

GET/v1/cloudflare/cache-settings?zone_id=%3Czone-id%3E

Read a zone's cache-related settings (cache_level, browser_cache_ttl, development_mode, always_online, sort_query_string_for_cache). zone_id required (from the Cloudflare Zones tool).

Required query: zone_id.

Minimal query: {"zone_id":"<zone-id>"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
zone_id<zone-id>
curl -X GET 'https://api.endpointr.com/v1/cloudflare/cache-settings?zone_id=%3Czone-id%3E' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/cache-settings?zone_id=%3Czone-id%3E', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/cache-settings?zone_id=%3Czone-id%3E');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/cloudflare/cache-settings/:id

Update one cache setting. The path {id} is the setting name; zone_id + value required in the body. development_mode/always_online/sort_query_string_for_cache take on/off; cache_level takes aggressive/basic/simplified; browser_cache_ttl takes an integer of seconds.

Required body: zone_id, value.

Minimal body: {"zone_id":"<zone-id>","value":"on"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "zone_id": "<zone-id>",
    "value": "on"
}
curl -X PUT 'https://api.endpointr.com/v1/cloudflare/cache-settings/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "zone_id": "<zone-id>",
    "value": "on"
}'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/cache-settings/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "zone_id": "<zone-id>",
      "value": "on"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/cache-settings/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"zone_id\": \"<zone-id>\",\n    \"value\": \"on\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cloudflare — DNS records

GET/v1/cloudflare/dns-records/:id

Get one DNS record. Id is the composite zoneId:recordId (over MCP the zone can't travel in the query).

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/cloudflare/dns-records/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/dns-records/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/dns-records/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/cloudflare/dns-records?zone_id=%3Czone-id%3E&type=A

List DNS records for a zone. zone_id is required — get it from the Cloudflare Zones tool (…_zones_query / …_zones_get). Optional filters: type, name, content, page, per_page.

Required query: zone_id.

Minimal query: {"zone_id":"<zone-id>"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
zone_id<zone-id>
typeA
curl -X GET 'https://api.endpointr.com/v1/cloudflare/dns-records?zone_id=%3Czone-id%3E&type=A' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/dns-records?zone_id=%3Czone-id%3E&type=A', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/dns-records?zone_id=%3Czone-id%3E&type=A');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/cloudflare/dns-records

Create a DNS record. zone_id required in the body alongside the record fields.

Required body: zone_id.

Minimal body: {"zone_id":"<zone-id>"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "zone_id": "<zone-id>",
    "type": "A",
    "name": "www.example.com",
    "content": "203.0.113.10",
    "ttl": 3600,
    "proxied": false
}
curl -X POST 'https://api.endpointr.com/v1/cloudflare/dns-records' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "zone_id": "<zone-id>",
    "type": "A",
    "name": "www.example.com",
    "content": "203.0.113.10",
    "ttl": 3600,
    "proxied": false
}'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/dns-records', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "zone_id": "<zone-id>",
      "type": "A",
      "name": "www.example.com",
      "content": "203.0.113.10",
      "ttl": 3600,
      "proxied": false
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/dns-records');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"zone_id\": \"<zone-id>\",\n    \"type\": \"A\",\n    \"name\": \"www.example.com\",\n    \"content\": \"203.0.113.10\",\n    \"ttl\": 3600,\n    \"proxied\": false\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/cloudflare/dns-records/:id

Update a DNS record by {record_id} (PUT — send the full record). zone_id required in the body.

Required body: zone_id.

Minimal body: {"zone_id":"<zone-id>"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "zone_id": "<zone-id>",
    "type": "A",
    "name": "www.example.com",
    "content": "203.0.113.20"
}
curl -X PUT 'https://api.endpointr.com/v1/cloudflare/dns-records/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "zone_id": "<zone-id>",
    "type": "A",
    "name": "www.example.com",
    "content": "203.0.113.20"
}'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/dns-records/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "zone_id": "<zone-id>",
      "type": "A",
      "name": "www.example.com",
      "content": "203.0.113.20"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/dns-records/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"zone_id\": \"<zone-id>\",\n    \"type\": \"A\",\n    \"name\": \"www.example.com\",\n    \"content\": \"203.0.113.20\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/cloudflare/dns-records/:id

Delete a DNS record. Id is the composite zoneId:recordId.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/cloudflare/dns-records/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/dns-records/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/dns-records/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cloudflare — Pages deployments

GET/v1/cloudflare/pages-deployments/:id

Get one deployment. Id is the composite projectName:deploymentId.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/cloudflare/pages-deployments/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/pages-deployments/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/pages-deployments/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/cloudflare/pages-deployments?project_name=my-site

List deployments for a Pages project. project_name is required (from the Cloudflare Pages projects tool — …_pages-projects_list).

Required query: project_name.

Minimal query: {"project_name":"my-site"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
project_namemy-site
curl -X GET 'https://api.endpointr.com/v1/cloudflare/pages-deployments?project_name=my-site' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/pages-deployments?project_name=my-site', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/pages-deployments?project_name=my-site');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/cloudflare/pages-deployments

Trigger a new deployment for a project. project_name required; optional branch.

Required body: project_name.

Minimal body: {"project_name":"my-site"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "project_name": "my-site"
}
curl -X POST 'https://api.endpointr.com/v1/cloudflare/pages-deployments' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "project_name": "my-site"
}'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/pages-deployments', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "project_name": "my-site"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/pages-deployments');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"project_name\": \"my-site\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/cloudflare/pages-deployments/:id

Delete a deployment. Id is the composite projectName:deploymentId.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/cloudflare/pages-deployments/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/pages-deployments/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/pages-deployments/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cloudflare — Pages projects

GET/v1/cloudflare/pages-projects

List Pages projects on the account.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/cloudflare/pages-projects' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/pages-projects', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/pages-projects');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/cloudflare/pages-projects/:id

Get a Pages project by {project_name}.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/cloudflare/pages-projects/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/pages-projects/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/pages-projects/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/cloudflare/pages-projects

Create a Pages project. name required. Optional production_branch, build_config, account_id override.

Required body: name.

Minimal body: {"name":"my-site"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "my-site",
    "production_branch": "main"
}
curl -X POST 'https://api.endpointr.com/v1/cloudflare/pages-projects' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "my-site",
    "production_branch": "main"
}'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/pages-projects', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "my-site",
      "production_branch": "main"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/pages-projects');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"my-site\",\n    \"production_branch\": \"main\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/cloudflare/pages-projects/:id

Delete a Pages project by {project_name}.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/cloudflare/pages-projects/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/pages-projects/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/pages-projects/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cloudflare — Token verify

GET/v1/cloudflare/token-verify

Verify the stored API token is valid/active. Returns {id, status, …}.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/cloudflare/token-verify' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/token-verify', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/token-verify');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cloudflare — Worker routes

GET/v1/cloudflare/worker-routes?zone_id=%3Czone-id%3E

List Worker routes for a zone. zone_id is required (from the Cloudflare Zones tool).

Required query: zone_id.

Minimal query: {"zone_id":"<zone-id>"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
zone_id<zone-id>
curl -X GET 'https://api.endpointr.com/v1/cloudflare/worker-routes?zone_id=%3Czone-id%3E' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/worker-routes?zone_id=%3Czone-id%3E', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/worker-routes?zone_id=%3Czone-id%3E');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/cloudflare/worker-routes

Create a route binding a URL pattern to a script. zone_id required in the body.

Required body: zone_id, pattern.

Minimal body: {"zone_id":"<zone-id>","pattern":"example.com/*"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "zone_id": "<zone-id>",
    "pattern": "example.com/*",
    "script": "my-worker"
}
curl -X POST 'https://api.endpointr.com/v1/cloudflare/worker-routes' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "zone_id": "<zone-id>",
    "pattern": "example.com/*",
    "script": "my-worker"
}'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/worker-routes', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "zone_id": "<zone-id>",
      "pattern": "example.com/*",
      "script": "my-worker"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/worker-routes');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"zone_id\": \"<zone-id>\",\n    \"pattern\": \"example.com/*\",\n    \"script\": \"my-worker\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/cloudflare/worker-routes/:id

Update a route by {route_id}. zone_id required in the body.

Required body: zone_id.

Minimal body: {"zone_id":"<zone-id>"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "zone_id": "<zone-id>",
    "pattern": "example.com/api/*",
    "script": "my-worker"
}
curl -X PUT 'https://api.endpointr.com/v1/cloudflare/worker-routes/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "zone_id": "<zone-id>",
    "pattern": "example.com/api/*",
    "script": "my-worker"
}'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/worker-routes/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "zone_id": "<zone-id>",
      "pattern": "example.com/api/*",
      "script": "my-worker"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/worker-routes/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"zone_id\": \"<zone-id>\",\n    \"pattern\": \"example.com/api/*\",\n    \"script\": \"my-worker\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/cloudflare/worker-routes/:id

Delete a route. Id is the composite zoneId:routeId.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/cloudflare/worker-routes/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/worker-routes/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/worker-routes/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cloudflare — Workers

GET/v1/cloudflare/workers

List Worker scripts on the account (account_id defaults to the stored credential; override it with an id from the Cloudflare Accounts tool).

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/cloudflare/workers' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/workers', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/workers');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/cloudflare/workers/:id

Get a Worker script's settings/bindings by {script_name}.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/cloudflare/workers/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/workers/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/workers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/cloudflare/workers

Upload (create or replace) a Worker script as an ES module. name + script (the JS) are required. Optional compatibility_date, compatibility_flags, bindings (KV/vars/secrets/etc.), and account_id override.

Required body: name, script.

Minimal body: {"name":"my-worker","script":"export default { async fetch(req, env, ctx) { return new Response('hello'); } }"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "my-worker",
    "script": "export default { async fetch(req, env, ctx) { return new Response('hello'); } }",
    "compatibility_date": "2024-01-01"
}
curl -X POST 'https://api.endpointr.com/v1/cloudflare/workers' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "my-worker",
    "script": "export default { async fetch(req, env, ctx) { return new Response('\''hello'\''); } }",
    "compatibility_date": "2024-01-01"
}'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/workers', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "my-worker",
      "script": "export default { async fetch(req, env, ctx) { return new Response('hello'); } }",
      "compatibility_date": "2024-01-01"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/workers');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"my-worker\",\n    \"script\": \"export default { async fetch(req, env, ctx) { return new Response('hello'); } }\",\n    \"compatibility_date\": \"2024-01-01\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/cloudflare/workers/:id

Delete a Worker script by {script_name}.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/cloudflare/workers/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/workers/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/workers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cloudflare — Zones (domains)

GET/v1/cloudflare/zones

List all zones (domains) on the account.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/cloudflare/zones' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/zones', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/zones');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/cloudflare/zones/:id

Get one zone by {zone_id}.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/cloudflare/zones/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/zones/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/zones/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/cloudflare/zones?name=acme.com

List zones filtered by name, status, account_id, with page/per_page paging.

Minimal query: {"name":"acme.com"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
nameacme.com
curl -X GET 'https://api.endpointr.com/v1/cloudflare/zones?name=acme.com' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/zones?name=acme.com', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/zones?name=acme.com');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/cloudflare/zones

Add a domain. Creates a zone. name is the domain; account_id falls back to the stored credential. Optional type (full|partial).

Required body: name.

Minimal body: {"name":"example.com"}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "example.com"
}
curl -X POST 'https://api.endpointr.com/v1/cloudflare/zones' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "example.com"
}'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/zones', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "example.com"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/zones');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"example.com\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/cloudflare/zones/:id

Edit a zone by {zone_id} (PATCH). e.g. pause/unpause.

Minimal body: {"paused":true}

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "paused": true
}
curl -X PUT 'https://api.endpointr.com/v1/cloudflare/zones/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "paused": true
}'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/zones/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "paused": true
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/zones/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"paused\": true\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/cloudflare/zones/:id

Delete a zone by {zone_id}.

_Requires stored credentials: cloudflare (PUT /v1/credentials/cloudflare)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/cloudflare/zones/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cloudflare/zones/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cloudflare/zones/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Crawlers

AI Page Extractor

GET/v1/crawlers/ai-page-extractor?url=https%3A%2F%2Fexample.com&type=BUSINESS_SUMMARY

Same as POST but via query string.

Required query: url, type.

Minimal query: {"url":"https://example.com","type":"BUSINESS_SUMMARY"}

AuthorizationBearer YOUR_JWT_TOKEN
urlhttps://example.com
typeBUSINESS_SUMMARY
curl -X GET 'https://api.endpointr.com/v1/crawlers/ai-page-extractor?url=https%3A%2F%2Fexample.com&type=BUSINESS_SUMMARY' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/crawlers/ai-page-extractor?url=https%3A%2F%2Fexample.com&type=BUSINESS_SUMMARY', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/ai-page-extractor?url=https%3A%2F%2Fexample.com&type=BUSINESS_SUMMARY');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/crawlers/ai-page-extractor

Read a page and answer ONE structured question about the business behind it, named by type. Judgment-only — for phone numbers, emails, logos, socials or CMS use the deterministic sibling crawlers instead, which are faster and cannot be wrong.

Params. url (required). type (required, see below). geoCode optional ISO-2, forwarded to the fetch proxy. icp required for CONTENT_ICP_ALIGNMENT only.

type
- BUSINESS_SUMMARY — what they sell and to whom.
- BUSINESS_MODELECOMMERCE|LOCAL_SERVICE|B2B_SERVICE|PUBLISHER_AFFILIATE|SAAS|MARKETPLACE|BRANDING_ONLY. Fetch this first when building keywords — it decides the shape of everything downstream, and for PUBLISHER_AFFILIATE the money term is legitimately informational, which inverts the usual intent filter.
- SERVICES_OFFERED — what they sell, in the site's own wording, unnormalised.
- MONEY_KEYWORD_CANDIDATES — what a ready-to-buy customer would type. Hypotheses only.
- SERVICE_AREA{country, area, cities}, each independently nullable.
- ICP — the buyer the site appears built for. Designed to be shown back to the owner as a question.
- CONTENT_ICP_ALIGNMENT — scores the page against a supplied icp. The only type taking an input.
- VALUE_PROPOSITION — verbatim or unknown; never composed.
- INDUSTRY_CLASSIFICATION — GICS, deepest verifiable level, null below.
- PAGE_LANGUAGE — BCP-47.

status — the field to branch on:
- ok — answered. See data.
- fetch_failed — the page was never read. NOT the same as an empty answer — the question is unanswered, and data is null rather than a shrug. Bot interstitials are caught here rather than being extracted from.
- rejected_untraceable — the model returned a derived value that could not name what it was derived from. Discarded rather than downgraded; violations[] lists the paths.
- unparseable — no JSON object came back.

Verification is per field, not per response. Each carries verification: quoted (page says it, exact text in evidence) · derived (inferred; derived_from names the inputs) · unknown (not establishable). Only quoted is safe to reproduce verbatim to a customer. unknown is a correct answer, not a failure — a thin page that does not say where a business operates returns null rather than a plausible town.

validated is always false. Nothing here has been checked against anything outside the page itself. Keyword candidates in particular must pass real search-volume and search-intent checks before reaching any customer-facing document.

fetch.renderedfalse means JS-injected content was never seen, so a thin answer may be the fetch rather than the site. Pair with prompt_version when caching: a prompt revision invalidates a stored answer.

Status codes. 201 ok · 400 missing/invalid url or type, or a missing icp. A site that cannot be fetched is a 201 with status: fetch_failed, never an error.

Required body: url, type.

Minimal body: {"url":"https://example.com","type":"BUSINESS_MODEL"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "url": "https://example.com",
    "type": "BUSINESS_MODEL"
}
curl -X POST 'https://api.endpointr.com/v1/crawlers/ai-page-extractor' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "url": "https://example.com",
    "type": "BUSINESS_MODEL"
}'
const response = await fetch('https://api.endpointr.com/v1/crawlers/ai-page-extractor', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "url": "https://example.com",
      "type": "BUSINESS_MODEL"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/ai-page-extractor');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"url\": \"https://example.com\",\n    \"type\": \"BUSINESS_MODEL\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

CMP Detector

GET/v1/crawlers/cmp-crawler?url=https%3A%2F%2Fexample.com

Same as POST but via query string.

Required query: url.

Minimal query: {"url":"https://example.com"}

AuthorizationBearer YOUR_JWT_TOKEN
urlhttps://example.com
curl -X GET 'https://api.endpointr.com/v1/crawlers/cmp-crawler?url=https%3A%2F%2Fexample.com' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/crawlers/cmp-crawler?url=https%3A%2F%2Fexample.com', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/cmp-crawler?url=https%3A%2F%2Fexample.com');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/crawlers/cmp-crawler

Detect the Consent Management Platform (cookie banner) on a URL, and inventory the cookies set before any consent interaction.

Params. url (required). geoCode optional ISO-2 country, forwarded to the fetch proxy for geo-routed sites.

verdict — the field to branch on:
- identified — CMP named. See primary.
- tcf_unidentified — a TCF consent API is present but the vendor could not be named.
- generic_banner — a cookie banner exists but matches no known vendor.
- none — page read, no banner found.
- fetch_failed — the page was never read. NOT the same as none — the question is unanswered, and detected is null rather than false.

verdict_reliablefalse when the verdict is none and the fetch was not JS-rendered. Most CMPs inject themselves with JavaScript, so a non-rendered none only means "no CMP in the initial HTML". Filter on this before treating none as "this site has no cookie banner".

primary{slug, name, vendor, tcf_cmp_id, tcf, type, confidence, id_source}. type is saas | cms_plugin | oss | native. A decoded TC string (id_source: tcstring) is authoritative and outranks every HTML heuristic; it also names IAB-certified CMPs that have no web fingerprint at all. cmps[] holds every candidate, best first.

cookies{total, tracker_count, trackers_before_consent, categories, complete, items[]}. Analytics/advertising cookies present here were set with no consent given. complete is always false: cookies planted by third-party iframes/pixels are not observable, so this is a floor, not a census. Cookie values are never returned — names, attributes and classification only.

fetch.cookie_capturerendered (page's own jar, includes JS-set trackers) · set-cookie-only (response headers only) · unavailable. Without a rendering proxy configured, expect set-cookie-only.

Status codes. 201 ok · 400 missing/invalid url. A site that cannot be fetched is a 200/201 with verdict: fetch_failed, never an error.

Required body: url.

Minimal body: {"url":"https://example.com"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "url": "https://example.com"
}
curl -X POST 'https://api.endpointr.com/v1/crawlers/cmp-crawler' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "url": "https://example.com"
}'
const response = await fetch('https://api.endpointr.com/v1/crawlers/cmp-crawler', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "url": "https://example.com"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/cmp-crawler');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"url\": \"https://example.com\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

CMS Detector

GET/v1/crawlers/cms-crawler?url=https%3A%2F%2Fexample.com

Same as POST but via query string.

Required query: url.

Minimal query: {"url":"https://example.com"}

AuthorizationBearer YOUR_JWT_TOKEN
urlhttps://example.com
curl -X GET 'https://api.endpointr.com/v1/crawlers/cms-crawler?url=https%3A%2F%2Fexample.com' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/crawlers/cms-crawler?url=https%3A%2F%2Fexample.com', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/cms-crawler?url=https%3A%2F%2Fexample.com');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/crawlers/cms-crawler

Detect CMS used by the target URL.

Required body: url.

Minimal body: {"url":"https://example.com"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "url": "https://example.com"
}
curl -X POST 'https://api.endpointr.com/v1/crawlers/cms-crawler' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "url": "https://example.com"
}'
const response = await fetch('https://api.endpointr.com/v1/crawlers/cms-crawler', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "url": "https://example.com"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/cms-crawler');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"url\": \"https://example.com\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

CVR Crawler

GET/v1/crawlers/cvr-crawler?url=https%3A%2F%2Fexample.dk

GET variant.

Required query: url.

Minimal query: {"url":"https://example.dk"}

AuthorizationBearer YOUR_JWT_TOKEN
urlhttps://example.dk
curl -X GET 'https://api.endpointr.com/v1/crawlers/cvr-crawler?url=https%3A%2F%2Fexample.dk' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/crawlers/cvr-crawler?url=https%3A%2F%2Fexample.dk', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/cvr-crawler?url=https%3A%2F%2Fexample.dk');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/crawlers/cvr-crawler

Extract Danish CVR numbers.

Required body: url.

Minimal body: {"url":"https://example.dk"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "url": "https://example.dk"
}
curl -X POST 'https://api.endpointr.com/v1/crawlers/cvr-crawler' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "url": "https://example.dk"
}'
const response = await fetch('https://api.endpointr.com/v1/crawlers/cvr-crawler', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "url": "https://example.dk"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/cvr-crawler');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"url\": \"https://example.dk\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Email Crawler

GET/v1/crawlers/email-crawler?url=https%3A%2F%2Fexample.com

GET variant.

Required query: url.

Minimal query: {"url":"https://example.com"}

AuthorizationBearer YOUR_JWT_TOKEN
urlhttps://example.com
curl -X GET 'https://api.endpointr.com/v1/crawlers/email-crawler?url=https%3A%2F%2Fexample.com' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/crawlers/email-crawler?url=https%3A%2F%2Fexample.com', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/email-crawler?url=https%3A%2F%2Fexample.com');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/crawlers/email-crawler

Extract email addresses from a page.

Required body: url.

Minimal body: {"url":"https://example.com"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "url": "https://example.com"
}
curl -X POST 'https://api.endpointr.com/v1/crawlers/email-crawler' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "url": "https://example.com"
}'
const response = await fetch('https://api.endpointr.com/v1/crawlers/email-crawler', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "url": "https://example.com"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/email-crawler');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"url\": \"https://example.com\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/crawlers/link-crawler

Extract same-host anchor links (up to 500).

Required body: url.

Minimal body: {"url":"https://example.com"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "url": "https://example.com"
}
curl -X POST 'https://api.endpointr.com/v1/crawlers/link-crawler' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "url": "https://example.com"
}'
const response = await fetch('https://api.endpointr.com/v1/crawlers/link-crawler', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "url": "https://example.com"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/link-crawler');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"url\": \"https://example.com\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Logo Extractor

GET/v1/crawlers/logo-crawler?url=https%3A%2F%2Fexample.com

GET variant.

Required query: url.

Minimal query: {"url":"https://example.com"}

AuthorizationBearer YOUR_JWT_TOKEN
urlhttps://example.com
curl -X GET 'https://api.endpointr.com/v1/crawlers/logo-crawler?url=https%3A%2F%2Fexample.com' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/crawlers/logo-crawler?url=https%3A%2F%2Fexample.com', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/logo-crawler?url=https%3A%2F%2Fexample.com');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/crawlers/logo-crawler

Extract the logo URL from a page.

Required body: url.

Minimal body: {"url":"https://example.com"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "url": "https://example.com"
}
curl -X POST 'https://api.endpointr.com/v1/crawlers/logo-crawler' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "url": "https://example.com"
}'
const response = await fetch('https://api.endpointr.com/v1/crawlers/logo-crawler', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "url": "https://example.com"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/logo-crawler');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"url\": \"https://example.com\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Phone Crawler

GET/v1/crawlers/phone-crawler?url=https%3A%2F%2Fexample.com

GET variant.

Required query: url.

Minimal query: {"url":"https://example.com"}

AuthorizationBearer YOUR_JWT_TOKEN
urlhttps://example.com
curl -X GET 'https://api.endpointr.com/v1/crawlers/phone-crawler?url=https%3A%2F%2Fexample.com' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/crawlers/phone-crawler?url=https%3A%2F%2Fexample.com', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/phone-crawler?url=https%3A%2F%2Fexample.com');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/crawlers/phone-crawler

Extract phone numbers (tel: + textual).

Required body: url.

Minimal body: {"url":"https://example.com"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "url": "https://example.com"
}
curl -X POST 'https://api.endpointr.com/v1/crawlers/phone-crawler' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "url": "https://example.com"
}'
const response = await fetch('https://api.endpointr.com/v1/crawlers/phone-crawler', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "url": "https://example.com"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/phone-crawler');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"url\": \"https://example.com\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Social Profiles

GET/v1/crawlers/social-profiles-crawler?url=https%3A%2F%2Fexample.com

GET variant.

Required query: url.

Minimal query: {"url":"https://example.com"}

AuthorizationBearer YOUR_JWT_TOKEN
urlhttps://example.com
curl -X GET 'https://api.endpointr.com/v1/crawlers/social-profiles-crawler?url=https%3A%2F%2Fexample.com' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/crawlers/social-profiles-crawler?url=https%3A%2F%2Fexample.com', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/social-profiles-crawler?url=https%3A%2F%2Fexample.com');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/crawlers/social-profiles-crawler

Extract social-media profile URLs.

Required body: url.

Minimal body: {"url":"https://example.com"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "url": "https://example.com"
}
curl -X POST 'https://api.endpointr.com/v1/crawlers/social-profiles-crawler' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "url": "https://example.com"
}'
const response = await fetch('https://api.endpointr.com/v1/crawlers/social-profiles-crawler', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "url": "https://example.com"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/crawlers/social-profiles-crawler');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"url\": \"https://example.com\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Cvr

CVR — Account labels

GET/v1/cvr/account-labels

List the CVR credential labels configured for this customer, as {accounts: [...], default: bool}. One login may hold several CVR agreements, each stored as cvr:<label>. When more than one is configured, every other CVR tool REQUIRES an explicit account argument naming which to use — call this first to see the valid labels. default: true means an unlabelled cvr credential also exists.

_Requires stored credentials: cvr (PUT /v1/credentials/cvr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/cvr/account-labels' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cvr/account-labels', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cvr/account-labels');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

CVR — Company lookup

GET/v1/cvr/company-lookup/:id

Retrieve detailed official Danish CVR information about a company using its 8-digit CVR number. Returns the full normalised record: name and previous names, start/end dates, status, company form, primary and secondary industries, address, phone/email/website, employee count, advertising protection (reklamebeskyttelse), registered management roles, and production units (P-numbers). Also returns owners, split into legal (the ejerregister — who directly holds the shares, often a holding company, each carrying its own cvr so the chain can be walked) and beneficial (reelle ejere — the natural persons who ultimately own or control the company), with capital and voting percentages where CVR records them. Ownership is personal data and is available on this single-company lookup only — never on the bulk listing tools. The {id} path segment is the CVR number.

_Requires stored credentials: cvr (PUT /v1/credentials/cvr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/cvr/company-lookup/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cvr/company-lookup/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cvr/company-lookup/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

CVR — Credential test

GET/v1/cvr/credential-test

Verify the stored CVR credentials by running one minimal authenticated query against the Danish CVR API. Returns {ok: true, indexed_companies} on success, or {ok: false, error, message} with a safe error code (e.g. CVR_AUTHENTICATION_FAILED, CVR_CREDENTIAL_NOT_FOUND) on failure. Never returns the credentials themselves.

_Requires stored credentials: cvr (PUT /v1/credentials/cvr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/cvr/credential-test' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cvr/credential-test', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cvr/credential-test');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

CVR — Industry stats

GET/v1/cvr/industry-stats?from_date=2026-08-01&to_date=2026-08-12&company_forms=ApS&active_only=1&size=50

Count newly started Danish companies grouped by industry (branchekode) over a date range, so you can see which kinds of company are being registered most frequently. Returns {total_companies, industries: [{code, name, count}]}, largest first. from_date and to_date are inclusive YYYY-MM-DD. size caps how many industry buckets come back (default 50, max 500).

Required query: from_date, to_date.

Minimal query: {"from_date":"2026-08-01","to_date":"2026-08-12"}

_Requires stored credentials: cvr (PUT /v1/credentials/cvr)._

AuthorizationBearer YOUR_JWT_TOKEN
from_date2026-08-01
to_date2026-08-12
company_formsApS
active_only1
size50
curl -X GET 'https://api.endpointr.com/v1/cvr/industry-stats?from_date=2026-08-01&to_date=2026-08-12&company_forms=ApS&active_only=1&size=50' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cvr/industry-stats?from_date=2026-08-01&to_date=2026-08-12&company_forms=ApS&active_only=1&size=50', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cvr/industry-stats?from_date=2026-08-01&to_date=2026-08-12&company_forms=ApS&active_only=1&size=50');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

CVR — Lead finder

GET/v1/cvr/lead-finder?from_date=2026-08-11&to_date=2026-08-12&company_forms=ApS&industry_codes=620100&postal_codes=1000-2999&exclude_holding_companies=1&require_website=&limit=100&offset=0

Find newly registered Danish companies as sales leads: the new-company search plus first-stage lead filtering. All exclusions are deterministic and structured — exclude_holding_companies (default true) drops holding/investment industry codes, and dissolved/bankrupt/inactive companies are always excluded. require_website keeps only companies with a registered website. Each lead carries lead_signals (e.g. new_company, aps, website_available, phone_available), which are restatements of facts in the record, not scores. The response's excluded object states exactly what was filtered out.

Required query: from_date, to_date.

Minimal query: {"from_date":"2026-08-11","to_date":"2026-08-12"}

_Requires stored credentials: cvr (PUT /v1/credentials/cvr)._

AuthorizationBearer YOUR_JWT_TOKEN
from_date2026-08-11
to_date2026-08-12
company_formsApS
industry_codes620100
postal_codes1000-2999
exclude_holding_companies1
require_website
limit100
offset0
curl -X GET 'https://api.endpointr.com/v1/cvr/lead-finder?from_date=2026-08-11&to_date=2026-08-12&company_forms=ApS&industry_codes=620100&postal_codes=1000-2999&exclude_holding_companies=1&require_website=&limit=100&offset=0' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cvr/lead-finder?from_date=2026-08-11&to_date=2026-08-12&company_forms=ApS&industry_codes=620100&postal_codes=1000-2999&exclude_holding_companies=1&require_website=&limit=100&offset=0', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cvr/lead-finder?from_date=2026-08-11&to_date=2026-08-12&company_forms=ApS&industry_codes=620100&postal_codes=1000-2999&exclude_holding_companies=1&require_website=&limit=100&offset=0');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

CVR — New companies

GET/v1/cvr/new-companies?from_date=2026-08-11&to_date=2026-08-12&company_forms=ApS&industry_codes=620100&postal_codes=1000-2999&municipalities=K%C3%B8benhavn&active_only=1&limit=100&offset=0&cursor=

Search the official Danish CVR register for companies whose official company START DATE falls within a given date range. Useful for finding newly established Danish companies, lead generation, market research and company monitoring. from_date and to_date are inclusive YYYY-MM-DD dates in Danish (Europe/Copenhagen) local time. Optional filters: company_forms (e.g. ["ApS","A/S","ENK"] or the numeric CVR form codes), industry_codes (branchekode), postal_codes (exact "2100" or a range "1000-2999"), municipalities, active_only (default true — excludes dissolved/bankrupt companies). Page with limit (max 500) + offset, or pass back next_cursor as cursor for result sets deeper than 10000.

Required query: from_date, to_date.

Minimal query: {"from_date":"2026-08-11","to_date":"2026-08-12"}

_Requires stored credentials: cvr (PUT /v1/credentials/cvr)._

AuthorizationBearer YOUR_JWT_TOKEN
from_date2026-08-11
to_date2026-08-12
company_formsApS
industry_codes620100
postal_codes1000-2999
municipalitiesKøbenhavn
active_only1
limit100
offset0
cursor
curl -X GET 'https://api.endpointr.com/v1/cvr/new-companies?from_date=2026-08-11&to_date=2026-08-12&company_forms=ApS&industry_codes=620100&postal_codes=1000-2999&municipalities=K%C3%B8benhavn&active_only=1&limit=100&offset=0&cursor=' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cvr/new-companies?from_date=2026-08-11&to_date=2026-08-12&company_forms=ApS&industry_codes=620100&postal_codes=1000-2999&municipalities=K%C3%B8benhavn&active_only=1&limit=100&offset=0&cursor=', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cvr/new-companies?from_date=2026-08-11&to_date=2026-08-12&company_forms=ApS&industry_codes=620100&postal_codes=1000-2999&municipalities=K%C3%B8benhavn&active_only=1&limit=100&offset=0&cursor=');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

CVR — Prospects

GET/v1/cvr/prospects?days=7&since=&purpose=outreach&channel=linkedin&company_forms=ApS&industry_codes=620100&exclude_industry_codes=642120&postal_codes=1000-2999&municipalities=K%C3%B8benhavn&require_phone=&has_website=&limit=50&offset=0&cursor=

Find newly registered Danish companies that may lawfully be contacted for sales or marketing. Unlike endpointr_cvr_new_companies_query, which mirrors the register verbatim, this endpoint applies marketing-safety defaults: it excludes reklamebeskyttede companies (~74% of new registrations, and contacting them is unlawful), ceased and dissolved companies, holding companies (branche 642120, ~18%, never real prospects), and our own trade — reklamebureauer (731110), computerprogrammering (620100) and andre informationstjenester (639900) are competitors, not buyers.

Paginate with days or since, NOT by start date. The endpoint returns companies by when they first appeared in this dataset, which is the only reliable way to see each company exactly once — the register backfills for days after a company's official start date, so start-date windows silently miss 40-50% of new registrations. from_date/to_date switch to start-date semantics and exist only for backfill audits.

Every row carries a contactable object stating which channels are lawful for that specific company. Use it rather than inferring from phone or email. email is always false: Danish markedsføringsloven § 10 requires prior consent for electronic marketing to companies, and the presence of an email address in the register is not consent. Physical post and B2B phone calls are permitted for companies without reklamebeskyttelse.

excluded_counts reports how many candidates each default removed, so a short list is explainable from the response. Pass purpose: "research" to disable all filtering and see the register as-is.

Each row carries channel — the best lawful route to that company: linkedin when a decision maker was resolved to a profile, otherwise phone or postal. Filter with channel to pull one bucket at a time. contact.person is the registered Direktion (or beneficial owner) from CVR, which is who to approach: a company registered weeks ago has no gatekeeper. signals states buying signals as facts — note that no_website and generic_email are POSITIVE signals for web/SEO services, not disqualifiers, and the LinkedIn lookup keys off the person's name so a missing website never blocks it.

Minimal query: {"days":7,"purpose":"outreach","channel":"linkedin","company_forms":["ApS"],"industry_codes":["620100"],"exclude_industry_codes":["642120"],"postal_codes":["1000-2999"],"municipalities":["K\u00f8benhavn"],"require_phone":false,"limit":50,"offset":0}

_Requires stored credentials: cvr (PUT /v1/credentials/cvr)._

AuthorizationBearer YOUR_JWT_TOKEN
days7
since
purposeoutreach
channellinkedin
company_formsApS
industry_codes620100
exclude_industry_codes642120
postal_codes1000-2999
municipalitiesKøbenhavn
require_phone
has_website
limit50
offset0
cursor
curl -X GET 'https://api.endpointr.com/v1/cvr/prospects?days=7&since=&purpose=outreach&channel=linkedin&company_forms=ApS&industry_codes=620100&exclude_industry_codes=642120&postal_codes=1000-2999&municipalities=K%C3%B8benhavn&require_phone=&has_website=&limit=50&offset=0&cursor=' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cvr/prospects?days=7&since=&purpose=outreach&channel=linkedin&company_forms=ApS&industry_codes=620100&exclude_industry_codes=642120&postal_codes=1000-2999&municipalities=K%C3%B8benhavn&require_phone=&has_website=&limit=50&offset=0&cursor=', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cvr/prospects?days=7&since=&purpose=outreach&channel=linkedin&company_forms=ApS&industry_codes=620100&exclude_industry_codes=642120&postal_codes=1000-2999&municipalities=K%C3%B8benhavn&require_phone=&has_website=&limit=50&offset=0&cursor=');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

CVR — Recent companies

GET/v1/cvr/recent-companies?days=1&company_forms=ApS&industry_codes=620100&active_only=1&limit=100&offset=0

Newly started Danish companies from the last days calendar days — the convenience form of the new-companies search for questions like "give me all Danish companies created during the last 24 hours". The date window is computed in Danish (Europe/Copenhagen) local time and echoed back as from_date/to_date, so you never have to work out the boundary yourself. days defaults to 1 (today) and may not exceed 365.

Minimal query: {"days":1,"company_forms":["ApS"],"industry_codes":["620100"],"active_only":true,"limit":100,"offset":0}

_Requires stored credentials: cvr (PUT /v1/credentials/cvr)._

AuthorizationBearer YOUR_JWT_TOKEN
days1
company_formsApS
industry_codes620100
active_only1
limit100
offset0
curl -X GET 'https://api.endpointr.com/v1/cvr/recent-companies?days=1&company_forms=ApS&industry_codes=620100&active_only=1&limit=100&offset=0' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/cvr/recent-companies?days=1&company_forms=ApS&industry_codes=620100&active_only=1&limit=100&offset=0', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/cvr/recent-companies?days=1&company_forms=ApS&industry_codes=620100&active_only=1&limit=100&offset=0');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Generals

CVR Lookup

GET/v1/generals/cvr?cvr=12345678&country=dk&agent=Endpointr

Look up a company by its registration number via cvrapi. Required: cvr — the registration number you want to look up (user-supplied; not from another tool). country is optional and defaults to dk; set it to no for a Norwegian org number. Optional agent (a caller label). Example call: {"query":{"cvr":"12345678"}}.

Required query: cvr.

Minimal query: {"cvr":"12345678"}

AuthorizationBearer YOUR_JWT_TOKEN
cvr12345678
countrydk
agentEndpointr
curl -X GET 'https://api.endpointr.com/v1/generals/cvr?cvr=12345678&country=dk&agent=Endpointr' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/generals/cvr?cvr=12345678&country=dk&agent=Endpointr', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/generals/cvr?cvr=12345678&country=dk&agent=Endpointr');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

DNS

GET/v1/generals/dns?ip=1.1.1.1&action=reverse

Reverse DNS lookup or IP validity check.

Required query: ip.

Minimal query: {"ip":"1.1.1.1"}

AuthorizationBearer YOUR_JWT_TOKEN
ip1.1.1.1
actionreverse
curl -X GET 'https://api.endpointr.com/v1/generals/dns?ip=1.1.1.1&action=reverse' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/generals/dns?ip=1.1.1.1&action=reverse', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/generals/dns?ip=1.1.1.1&action=reverse');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

IP Geo (ipregistry)

GET/v1/generals/ip?ip=1.1.1.1

IP metadata via ipregistry (requires credentials).

Required query: ip.

Minimal query: {"ip":"1.1.1.1"}

_Requires stored credentials: ipregistry (PUT /v1/credentials/ipregistry)._

AuthorizationBearer YOUR_JWT_TOKEN
ip1.1.1.1
curl -X GET 'https://api.endpointr.com/v1/generals/ip?ip=1.1.1.1' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/generals/ip?ip=1.1.1.1', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/generals/ip?ip=1.1.1.1');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Redirect Inspector

GET/v1/generals/redirect?url=https%3A%2F%2Fexample.com&action=chain

action=has|chain|to; follows hops with SSRF re-validation at each step.

Required query: url.

Minimal query: {"url":"https://example.com"}

AuthorizationBearer YOUR_JWT_TOKEN
urlhttps://example.com
actionchain
curl -X GET 'https://api.endpointr.com/v1/generals/redirect?url=https%3A%2F%2Fexample.com&action=chain' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/generals/redirect?url=https%3A%2F%2Fexample.com&action=chain', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/generals/redirect?url=https%3A%2F%2Fexample.com&action=chain');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Remote User-Agent

GET/v1/generals/remote

Returns a randomly weighted user-agent string.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/generals/remote' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/generals/remote', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/generals/remote');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/generals/remote

GetByParam operation.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/generals/remote' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/generals/remote', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/generals/remote');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

VAT Validator

GET/v1/generals/vat?countrycode=DK&vatno=12345678

Validate an EU VAT number via VIES. Both required: countrycode (ISO-2 member state, e.g. DK, DE) and vatno (the number WITHOUT the country prefix, digits/letters only). Returns validity plus the registered name/address when the member state exposes them. Example call: {"query":{"countrycode":"DK","vatno":"12345678"}}.

Required query: countrycode, vatno.

Minimal query: {"countrycode":"DK","vatno":"12345678"}

AuthorizationBearer YOUR_JWT_TOKEN
countrycodeDK
vatno12345678
curl -X GET 'https://api.endpointr.com/v1/generals/vat?countrycode=DK&vatno=12345678' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/generals/vat?countrycode=DK&vatno=12345678', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/generals/vat?countrycode=DK&vatno=12345678');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Google Analytics — Admin

GA4 Admin — Account Summaries

GET/v1/analytics/admin/ga4-account-summaries

Start here to discover what GA4 properties a customer has. One call returns every account AND every property beneath it, each with its human-readable displayName — no need to already know an account id (unlike Properties below, which requires one). pageSize/pageToken are forwarded.

Auth — hybrid (creds in the query, or vault). Scope: analytics.readonly.

Response. {data: {accountSummaries: [{account:"accounts/123", displayName, propertySummaries: [{property:"properties/456", displayName, propertyType}]}]}, refreshed_access_token?} — feed the numeric id from propertySummaries[].property into Reports/Realtime/Metadata/Data Streams as ?property=, and account into Properties when creating a new one.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-account-summaries' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-account-summaries', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-account-summaries');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GA4 Admin — Accounts

GET/v1/analytics/admin/ga4-accounts

List the GA4 accounts the authenticated user can access (top of the account → property → data-stream tree). Prefer Account Summaries above when you just want to browse — it returns properties in the same call.

An account cannot be created through the API (Google requires a human to accept the Terms of Service), so this list is the ceiling: everything endpointr can configure hangs off one of these.

Auth — hybrid (creds in the query, or vault). Scope: analytics.readonly.

Response. {data: {accounts: [{name:"accounts/123", displayName, …}]}, refreshed_access_token?} — feed accounts/{id} into Properties as ?account=.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-accounts' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-accounts', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-accounts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GA4 Admin — Custom Dimensions

GET/v1/analytics/admin/ga4-custom-dimensions/:id

Fetch one custom dimension. id = propertyId:dimensionId or the full resource name properties/123456/customDimensions/789.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-custom-dimensions/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-custom-dimensions/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-custom-dimensions/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/analytics/admin/ga4-custom-dimensions?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D

List the property's custom dimensions. Required: property.

Scope: analytics.readonly.

Minimal query: {"oauth_token":"{{google_oauth_token}}","property":"{{ga4_property_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
property{{ga4_property_id}}
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-custom-dimensions?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-custom-dimensions?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-custom-dimensions?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/analytics/admin/ga4-custom-dimensions

Register an event parameter (or user property) so it becomes queryable in reports. A parameter your tag sends without a matching custom dimension is collected and then invisible — this is the most commonly missed step in a tracking setup.

Required: property, parameterName (exactly as the tag sends it), displayName, scope (EVENT | USER | ITEM). Optional description, disallowAdsPersonalization (USER scope only).

Quota is per property (50 event-scoped / 25 user-scoped on a standard property) and dimensions cannot be deleted — only archived.

Archive instead of delete: POST {action:'archive', id:'propertyId:dimensionId'}.

Scope: analytics.edit.

Minimal body: {"property":"{{ga4_property_id}}","parameterName":"form_name","displayName":"Form name","scope":"EVENT"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "property": "{{ga4_property_id}}",
    "parameterName": "form_name",
    "displayName": "Form name",
    "scope": "EVENT"
}
curl -X POST 'https://api.endpointr.com/v1/analytics/admin/ga4-custom-dimensions' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "property": "{{ga4_property_id}}",
    "parameterName": "form_name",
    "displayName": "Form name",
    "scope": "EVENT"
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-custom-dimensions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "property": "{{ga4_property_id}}",
      "parameterName": "form_name",
      "displayName": "Form name",
      "scope": "EVENT"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-custom-dimensions');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"property\": \"{{ga4_property_id}}\",\n    \"parameterName\": \"form_name\",\n    \"displayName\": \"Form name\",\n    \"scope\": \"EVENT\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/analytics/admin/ga4-custom-dimensions/:id

Update a custom dimension (PATCH) — displayName and description only; parameterName and scope are immutable. id = propertyId:dimensionId or the full resource name.

Scope: analytics.edit.

Minimal body: {"displayName":"Form name (v2)"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "displayName": "Form name (v2)"
}
curl -X PUT 'https://api.endpointr.com/v1/analytics/admin/ga4-custom-dimensions/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "displayName": "Form name (v2)"
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-custom-dimensions/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "displayName": "Form name (v2)"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-custom-dimensions/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"displayName\": \"Form name (v2)\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GA4 Admin — Custom Metrics

GET/v1/analytics/admin/ga4-custom-metrics/:id

Fetch one custom metric. id = propertyId:metricId or the full resource name properties/123456/customMetrics/789.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-custom-metrics/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-custom-metrics/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-custom-metrics/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/analytics/admin/ga4-custom-metrics?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D

List the property's custom metrics. Required: property.

Scope: analytics.readonly.

Minimal query: {"oauth_token":"{{google_oauth_token}}","property":"{{ga4_property_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
property{{ga4_property_id}}
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-custom-metrics?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-custom-metrics?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-custom-metrics?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/analytics/admin/ga4-custom-metrics

Register a numeric event parameter so it can be summed/averaged in reports — the numeric twin of a custom dimension.

Required: property, parameterName, displayName, measurementUnit (STANDARD | CURRENCY | FEET | METERS | KILOMETERS | MILES | MILLISECONDS | SECONDS | MINUTES | HOURS). scope is EVENT.

Like dimensions, metrics are archived, never deleted: POST {action:'archive', id:'propertyId:metricId'}.

Scope: analytics.edit.

Minimal body: {"property":"{{ga4_property_id}}","parameterName":"quote_value","displayName":"Quote value","scope":"EVENT","measurementUnit":"CURRENCY"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "property": "{{ga4_property_id}}",
    "parameterName": "quote_value",
    "displayName": "Quote value",
    "scope": "EVENT",
    "measurementUnit": "CURRENCY"
}
curl -X POST 'https://api.endpointr.com/v1/analytics/admin/ga4-custom-metrics' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "property": "{{ga4_property_id}}",
    "parameterName": "quote_value",
    "displayName": "Quote value",
    "scope": "EVENT",
    "measurementUnit": "CURRENCY"
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-custom-metrics', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "property": "{{ga4_property_id}}",
      "parameterName": "quote_value",
      "displayName": "Quote value",
      "scope": "EVENT",
      "measurementUnit": "CURRENCY"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-custom-metrics');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"property\": \"{{ga4_property_id}}\",\n    \"parameterName\": \"quote_value\",\n    \"displayName\": \"Quote value\",\n    \"scope\": \"EVENT\",\n    \"measurementUnit\": \"CURRENCY\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/analytics/admin/ga4-custom-metrics/:id

Update a custom metric (PATCH) — displayName / description. id = propertyId:metricId or the full resource name.

Scope: analytics.edit.

Minimal body: {"displayName":"Quote value (DKK)"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "displayName": "Quote value (DKK)"
}
curl -X PUT 'https://api.endpointr.com/v1/analytics/admin/ga4-custom-metrics/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "displayName": "Quote value (DKK)"
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-custom-metrics/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "displayName": "Quote value (DKK)"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-custom-metrics/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"displayName\": \"Quote value (DKK)\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GA4 Admin — Data Retention

GET/v1/analytics/admin/ga4-data-retention/:id

Read the retention settings by property id (the same singleton as the list form). id = the numeric property id or the full properties/123456.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-data-retention/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-data-retention/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-data-retention/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/analytics/admin/ga4-data-retention?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D

Read a property's data retention settings. A per-property singleton, addressed by the PROPERTY id. Required: property.

Scope: analytics.readonly.

Minimal query: {"oauth_token":"{{google_oauth_token}}","property":"{{ga4_property_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
property{{ga4_property_id}}
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-data-retention?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-data-retention?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-data-retention?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/analytics/admin/ga4-data-retention/:id

Set retention. id is the PROPERTY id (123456 or properties/123456) — the settings have no id of their own.

eventDataRetention: TWO_MONTHS | FOURTEEN_MONTHS on a standard property (360 adds TWENTY_FIVE_MONTHS | THIRTY_EIGHT_MONTHS | FIFTY_MONTHS). resetUserDataOnNewActivity: boolean.

GA4 creates every property at TWO_MONTHS, which silently caps explorations at ~60 days. Bumping it is part of standing a property up — and it is not retroactive, so it only helps from the day it is set.

Scope: analytics.edit.

Minimal body: {"eventDataRetention":"FOURTEEN_MONTHS","resetUserDataOnNewActivity":true}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "eventDataRetention": "FOURTEEN_MONTHS",
    "resetUserDataOnNewActivity": true
}
curl -X PUT 'https://api.endpointr.com/v1/analytics/admin/ga4-data-retention/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "eventDataRetention": "FOURTEEN_MONTHS",
    "resetUserDataOnNewActivity": true
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-data-retention/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "eventDataRetention": "FOURTEEN_MONTHS",
      "resetUserDataOnNewActivity": true
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-data-retention/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"eventDataRetention\": \"FOURTEEN_MONTHS\",\n    \"resetUserDataOnNewActivity\": true\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GA4 Admin — Data Streams

GET/v1/analytics/admin/ga4-data-streams/:id

Fetch one data stream. id = propertyId:streamId or the full resource name properties/123456/dataStreams/789.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-data-streams/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-data-streams/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-data-streams/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/analytics/admin/ga4-data-streams?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D

List the data streams (web / iOS / Android) under a property — where the measurement ID / firebase app id lives. Required: property (properties/123456 or bare 123456). pageSize/pageToken are forwarded.

Auth — hybrid. Scope: analytics.readonly.

Required query: property.

Minimal query: {"property":"{{ga4_property_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
property{{ga4_property_id}}
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-data-streams?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-data-streams?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-data-streams?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/analytics/admin/ga4-data-streams

Step 2 of standing tracking up — this is what mints the measurement ID. Create a data stream under a property.

Required: property, type (WEB_DATA_STREAM | ANDROID_APP_DATA_STREAM | IOS_APP_DATA_STREAM), displayName. A web stream also needs webStreamData: {defaultUri}.

The response carries webStreamData.measurementId (G-XXXXXXXXXX) — the value a Google tag, or a GTM googtag tag's tagId parameter, needs. You cannot choose it.

Scope: analytics.edit.

Minimal body: {"property":"{{ga4_property_id}}","type":"WEB_DATA_STREAM","displayName":"example.com","webStreamData":{"defaultUri":"https://example.com"}}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "property": "{{ga4_property_id}}",
    "type": "WEB_DATA_STREAM",
    "displayName": "example.com",
    "webStreamData": {
        "defaultUri": "https://example.com"
    }
}
curl -X POST 'https://api.endpointr.com/v1/analytics/admin/ga4-data-streams' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "property": "{{ga4_property_id}}",
    "type": "WEB_DATA_STREAM",
    "displayName": "example.com",
    "webStreamData": {
        "defaultUri": "https://example.com"
    }
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-data-streams', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "property": "{{ga4_property_id}}",
      "type": "WEB_DATA_STREAM",
      "displayName": "example.com",
      "webStreamData": {
          "defaultUri": "https://example.com"
      }
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-data-streams');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"property\": \"{{ga4_property_id}}\",\n    \"type\": \"WEB_DATA_STREAM\",\n    \"displayName\": \"example.com\",\n    \"webStreamData\": {\n        \"defaultUri\": \"https://example.com\"\n    }\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/analytics/admin/ga4-data-streams/:id

Update a data stream (PATCH). displayName masks itself; changing the URL means naming the nested leaf explicitly:

{"update_mask":"webStreamData.defaultUri","webStreamData":{"defaultUri":"https://example.com"}}

id = propertyId:streamId or the full resource name.

Scope: analytics.edit.

Minimal body: {"displayName":"example.com (web)"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "displayName": "example.com (web)"
}
curl -X PUT 'https://api.endpointr.com/v1/analytics/admin/ga4-data-streams/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "displayName": "example.com (web)"
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-data-streams/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "displayName": "example.com (web)"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-data-streams/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"displayName\": \"example.com (web)\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/analytics/admin/ga4-data-streams/:id

Delete a data stream. Hard delete — the measurement ID stops collecting immediately and is not reissued. id = propertyId:streamId or the full resource name.

Scope: analytics.edit.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/analytics/admin/ga4-data-streams/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-data-streams/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-data-streams/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GA4 Admin — Enhanced Measurement (alpha)

GET/v1/analytics/admin/ga4-enhanced-measurement/:id

Read a WEB stream's enhanced-measurement settings — the events GA4 collects automatically with no tag work (scrolls, outbound clicks, site search, video, file downloads, form interactions).

id = propertyId:streamId or the full properties/123456/dataStreams/789 (the /enhancedMeasurementSettings leaf is appended for you).

v1alpha — this resource has no v1beta equivalent, so it does not carry the v1beta stability guarantee.

Scope: analytics.readonly.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-enhanced-measurement/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-enhanced-measurement/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-enhanced-measurement/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/analytics/admin/ga4-enhanced-measurement/:id

Toggle a WEB stream's automatic events (PATCH). id = propertyId:streamId or the full stream resource name.

Booleans: streamEnabled, scrollsEnabled, outboundClicksEnabled, siteSearchEnabled, videoEngagementEnabled, fileDownloadsEnabled, pageChangesEnabled, formInteractionsEnabled. Plus searchQueryParameter (comma-separated, default q,s,search,query,keyword) and uriQueryParameter.

streamEnabled: false switches the whole feature off for the stream — the individual toggles keep their values but stop collecting.

v1alpha. Scope: analytics.edit.

Minimal body: {"formInteractionsEnabled":true,"searchQueryParameter":"q,s,search,query,keyword"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "formInteractionsEnabled": true,
    "searchQueryParameter": "q,s,search,query,keyword"
}
curl -X PUT 'https://api.endpointr.com/v1/analytics/admin/ga4-enhanced-measurement/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "formInteractionsEnabled": true,
    "searchQueryParameter": "q,s,search,query,keyword"
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-enhanced-measurement/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "formInteractionsEnabled": true,
      "searchQueryParameter": "q,s,search,query,keyword"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-enhanced-measurement/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"formInteractionsEnabled\": true,\n    \"searchQueryParameter\": \"q,s,search,query,keyword\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/analytics/admin/ga4-google-ads-links

Link the property to a Google Ads account — what lets GA4 key events be imported as Ads conversions and feeds Ads its remarketing audiences.

Required: property, customerId — the Google Ads customer id, 10 digits, no dashes (not an endpointr customer). Optional adsPersonalizationEnabled (default true).

Google only links accounts the grant administers on BOTH sides: a 403 here almost always means editor-on-GA4 but not admin-on-Ads.

Scope: analytics.edit.

Minimal body: {"property":"{{ga4_property_id}}","customerId":"{{google_ads_customer_id}}","adsPersonalizationEnabled":true}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "property": "{{ga4_property_id}}",
    "customerId": "{{google_ads_customer_id}}",
    "adsPersonalizationEnabled": true
}
curl -X POST 'https://api.endpointr.com/v1/analytics/admin/ga4-google-ads-links' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "property": "{{ga4_property_id}}",
    "customerId": "{{google_ads_customer_id}}",
    "adsPersonalizationEnabled": true
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-google-ads-links', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "property": "{{ga4_property_id}}",
      "customerId": "{{google_ads_customer_id}}",
      "adsPersonalizationEnabled": true
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-google-ads-links');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"property\": \"{{ga4_property_id}}\",\n    \"customerId\": \"{{google_ads_customer_id}}\",\n    \"adsPersonalizationEnabled\": true\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GA4 Admin — Key Events (conversions)

GET/v1/analytics/admin/ga4-key-events/:id

Fetch one key event. id = propertyId:keyEventId or the full resource name properties/123456/keyEvents/789.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-key-events/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-key-events/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-key-events/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/analytics/admin/ga4-key-events?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D

List the property's key events — what the GA4 UI calls conversions (the API renamed conversionEventskeyEvents). Required: property.

Scope: analytics.readonly.

Minimal query: {"oauth_token":"{{google_oauth_token}}","property":"{{ga4_property_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
property{{ga4_property_id}}
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-key-events?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-key-events?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-key-events?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/analytics/admin/ga4-key-events

Mark an event as a key event — the step that makes it optimisable (and, once the property is linked to Google Ads, importable as a conversion).

Required: property, eventName. Optional countingMethod (ONCE_PER_EVENT default | ONCE_PER_SESSION) and defaultValue: {numericValue, currencyCode} for events that carry no value of their own.

The event does not have to have fired yet — marking generate_lead before the tag exists is normal, and is how you configure measurement ahead of the site work.

Scope: analytics.edit.

Minimal body: {"property":"{{ga4_property_id}}","eventName":"generate_lead","countingMethod":"ONCE_PER_EVENT"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "property": "{{ga4_property_id}}",
    "eventName": "generate_lead",
    "countingMethod": "ONCE_PER_EVENT"
}
curl -X POST 'https://api.endpointr.com/v1/analytics/admin/ga4-key-events' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "property": "{{ga4_property_id}}",
    "eventName": "generate_lead",
    "countingMethod": "ONCE_PER_EVENT"
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-key-events', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "property": "{{ga4_property_id}}",
      "eventName": "generate_lead",
      "countingMethod": "ONCE_PER_EVENT"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-key-events');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"property\": \"{{ga4_property_id}}\",\n    \"eventName\": \"generate_lead\",\n    \"countingMethod\": \"ONCE_PER_EVENT\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/analytics/admin/ga4-key-events/:id

Update a key event (PATCH) — in practice countingMethod or defaultValue. id = propertyId:keyEventId or the full resource name.

Scope: analytics.edit.

Minimal body: {"countingMethod":"ONCE_PER_SESSION"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "countingMethod": "ONCE_PER_SESSION"
}
curl -X PUT 'https://api.endpointr.com/v1/analytics/admin/ga4-key-events/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "countingMethod": "ONCE_PER_SESSION"
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-key-events/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "countingMethod": "ONCE_PER_SESSION"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-key-events/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"countingMethod\": \"ONCE_PER_SESSION\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/analytics/admin/ga4-key-events/:id

Unmark a key event (the underlying event keeps being collected — it just stops counting as a conversion). id = propertyId:keyEventId or the full resource name.

Scope: analytics.edit.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/analytics/admin/ga4-key-events/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-key-events/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-key-events/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GA4 Admin — Measurement Protocol Secrets

GET/v1/analytics/admin/ga4-measurement-protocol-secrets/:id

Fetch one secret. id = propertyId:streamId:secretId or the full resource name.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-measurement-protocol-secrets/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-measurement-protocol-secrets/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-measurement-protocol-secrets/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/analytics/admin/ga4-measurement-protocol-secrets?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D&stream=%7B%7Bga4_stream_id%7D%7D

List a data stream's Measurement Protocol secrets. Required: BOTH property and stream (the numeric stream id from ga4-data-streams).

Scope: analytics.readonly.

Minimal query: {"oauth_token":"{{google_oauth_token}}","property":"{{ga4_property_id}}","stream":"{{ga4_stream_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
property{{ga4_property_id}}
stream{{ga4_stream_id}}
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-measurement-protocol-secrets?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D&stream=%7B%7Bga4_stream_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-measurement-protocol-secrets?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D&stream=%7B%7Bga4_stream_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-measurement-protocol-secrets?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D&stream=%7B%7Bga4_stream_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/analytics/admin/ga4-measurement-protocol-secrets

Mint a Measurement Protocol secret for a stream. The secret plus the stream's measurement ID are the credentials for sending events to GA4 server-side — offline conversions, backend purchases, anything the browser tag cannot see.

Required: property, stream, displayName. Google generates the value and returns it as secretValue; treat it like a password — anyone holding it can write events into the property.

Scope: analytics.edit.

Minimal body: {"property":"{{ga4_property_id}}","stream":"{{ga4_stream_id}}","displayName":"Server-side events"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "property": "{{ga4_property_id}}",
    "stream": "{{ga4_stream_id}}",
    "displayName": "Server-side events"
}
curl -X POST 'https://api.endpointr.com/v1/analytics/admin/ga4-measurement-protocol-secrets' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "property": "{{ga4_property_id}}",
    "stream": "{{ga4_stream_id}}",
    "displayName": "Server-side events"
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-measurement-protocol-secrets', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "property": "{{ga4_property_id}}",
      "stream": "{{ga4_stream_id}}",
      "displayName": "Server-side events"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-measurement-protocol-secrets');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"property\": \"{{ga4_property_id}}\",\n    \"stream\": \"{{ga4_stream_id}}\",\n    \"displayName\": \"Server-side events\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/analytics/admin/ga4-measurement-protocol-secrets/:id

Revoke a secret. id = propertyId:streamId:secretId or the full resource name.

Scope: analytics.edit.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/analytics/admin/ga4-measurement-protocol-secrets/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-measurement-protocol-secrets/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-measurement-protocol-secrets/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GA4 Admin — Properties

GET/v1/analytics/admin/ga4-properties/:id

Fetch a single property by its numeric id (the 123456 in properties/123456). Auth in the query (?oauth_token=… or refresh-triplet).

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-properties/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-properties/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-properties/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/analytics/admin/ga4-properties?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&account=%7B%7Bga4_account_id%7D%7D

List the properties under a GA4 account. Required: account (accounts/123456 or a bare 123456) — the Admin API has no unfiltered property list, so this becomes a parent:accounts/{id} filter. Extra query params (e.g. pageSize, pageToken, showDeleted) are forwarded.

Auth — hybrid. Scope: analytics.readonly.

Required query: account.

Minimal query: {"account":"{{ga4_account_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
account{{ga4_account_id}}
curl -X GET 'https://api.endpointr.com/v1/analytics/admin/ga4-properties?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&account=%7B%7Bga4_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-properties?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&account=%7B%7Bga4_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-properties?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&account=%7B%7Bga4_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/analytics/admin/ga4-properties

Step 1 of standing tracking up. Create a property under an existing account.

Required: account (or parent, accounts/123456), displayName, timeZone (IANA, e.g. Europe/Copenhagen). Strongly recommended: currencyCode (ISO-4217 — GA4 defaults to USD and it cannot be changed retroactively for past data), industryCategory.

propertyType defaults to PROPERTY_TYPE_ORDINARY. The response's name (properties/123456) is the id every other GA4 tool takes.

Next: create a WEB data stream to mint the measurement ID.

Scope: analytics.edit.

Minimal body: {"account":"{{ga4_account_id}}","displayName":"Example.com \u2014 Web","timeZone":"Europe/Copenhagen","currencyCode":"DKK","industryCategory":"BUSINESS_AND_INDUSTRIAL_MARKETS"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account": "{{ga4_account_id}}",
    "displayName": "Example.com — Web",
    "timeZone": "Europe/Copenhagen",
    "currencyCode": "DKK",
    "industryCategory": "BUSINESS_AND_INDUSTRIAL_MARKETS"
}
curl -X POST 'https://api.endpointr.com/v1/analytics/admin/ga4-properties' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account": "{{ga4_account_id}}",
    "displayName": "Example.com — Web",
    "timeZone": "Europe/Copenhagen",
    "currencyCode": "DKK",
    "industryCategory": "BUSINESS_AND_INDUSTRIAL_MARKETS"
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-properties', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account": "{{ga4_account_id}}",
      "displayName": "Example.com — Web",
      "timeZone": "Europe/Copenhagen",
      "currencyCode": "DKK",
      "industryCategory": "BUSINESS_AND_INDUSTRIAL_MARKETS"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-properties');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account\": \"{{ga4_account_id}}\",\n    \"displayName\": \"Example.com — Web\",\n    \"timeZone\": \"Europe/Copenhagen\",\n    \"currencyCode\": \"DKK\",\n    \"industryCategory\": \"BUSINESS_AND_INDUSTRIAL_MARKETS\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/analytics/admin/ga4-properties/:id

Update a property (PATCH). Send only the fields you're changing — updateMask is derived from the body's top-level keys, or pass update_mask explicitly (comma-separated) to override.

Updatable: displayName, timeZone, currencyCode, industryCategory. id = the numeric property id or the full properties/123456.

Scope: analytics.edit.

Minimal body: {"displayName":"Example.com \u2014 Web (2026)"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "displayName": "Example.com — Web (2026)"
}
curl -X PUT 'https://api.endpointr.com/v1/analytics/admin/ga4-properties/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "displayName": "Example.com — Web (2026)"
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-properties/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "displayName": "Example.com — Web (2026)"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-properties/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"displayName\": \"Example.com — Web (2026)\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/analytics/admin/ga4-properties/:id

Move a property to the trash. Soft delete: GA4 keeps it for 63 days (restorable from the UI, still counting against the account's property quota) and then purges it permanently. id = the numeric property id or the full properties/123456.

Scope: analytics.edit.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/analytics/admin/ga4-properties/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/admin/ga4-properties/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/admin/ga4-properties/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Google Analytics — Data

GA4 Data — Metadata

GET/v1/analytics/data/ga4-metadata?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D

List the dimensions and metrics available for a property (including custom ones) so you can discover valid name values before building a report. Required: property.

Auth — hybrid (creds in the query). Scope: analytics.readonly.

Required query: property.

Minimal query: {"property":"{{ga4_property_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
property{{ga4_property_id}}
curl -X GET 'https://api.endpointr.com/v1/analytics/data/ga4-metadata?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/analytics/data/ga4-metadata?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/data/ga4-metadata?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&property=%7B%7Bga4_property_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GA4 Data — Realtime Report

POST/v1/analytics/data/ga4-realtime

Activity in the last 30 minutes for a property (no dateRanges; a narrower dimension/metric set than the standard report — e.g. metric activeUsers, dimensions country, deviceCategory, unifiedScreenName).

Required: property. Other body fields forward to properties/{id}:runRealtimeReport.

Auth — hybrid (see Run Report). Scope: analytics.readonly.

Required body: property.

Minimal body: {"property":"{{ga4_property_id}}"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "oauth_token": "{{google_oauth_token}}",
    "property": "{{ga4_property_id}}",
    "dimensions": [
        {
            "name": "country"
        }
    ],
    "metrics": [
        {
            "name": "activeUsers"
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/analytics/data/ga4-realtime' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "oauth_token": "{{google_oauth_token}}",
    "property": "{{ga4_property_id}}",
    "dimensions": [
        {
            "name": "country"
        }
    ],
    "metrics": [
        {
            "name": "activeUsers"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/data/ga4-realtime', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "oauth_token": "{{google_oauth_token}}",
      "property": "{{ga4_property_id}}",
      "dimensions": [
          {
              "name": "country"
          }
      ],
      "metrics": [
          {
              "name": "activeUsers"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/data/ga4-realtime');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"oauth_token\": \"{{google_oauth_token}}\",\n    \"property\": \"{{ga4_property_id}}\",\n    \"dimensions\": [\n        {\n            \"name\": \"country\"\n        }\n    ],\n    \"metrics\": [\n        {\n            \"name\": \"activeUsers\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GA4 Data — Run Report

POST/v1/analytics/data/ga4-reports

Run a GA4 report: metrics × dimensions over one or more dateRanges for a property.

Required: property, and a report definition (at minimum dateRanges + metrics). Body fields other than property (and the OAuth creds) are forwarded verbatim to properties/{id}:runReport.

Common fields: dimensions:[{name}], metrics:[{name}], dateRanges:[{startDate,endDate}] (dates YYYY-MM-DD or relative like 7daysAgo/today), dimensionFilter, metricFilter, orderBys, limit, offset.

Auth — hybrid. Store creds once via PUT /v1/credentials/google; or carry oauth_token / the refresh-triplet in the body. Scope: analytics.readonly.

Required body: property.

Minimal body: {"property":"{{ga4_property_id}}"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "oauth_token": "{{google_oauth_token}}",
    "property": "{{ga4_property_id}}",
    "dateRanges": [
        {
            "startDate": "28daysAgo",
            "endDate": "today"
        }
    ],
    "dimensions": [
        {
            "name": "country"
        }
    ],
    "metrics": [
        {
            "name": "activeUsers"
        },
        {
            "name": "sessions"
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/analytics/data/ga4-reports' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "oauth_token": "{{google_oauth_token}}",
    "property": "{{ga4_property_id}}",
    "dateRanges": [
        {
            "startDate": "28daysAgo",
            "endDate": "today"
        }
    ],
    "dimensions": [
        {
            "name": "country"
        }
    ],
    "metrics": [
        {
            "name": "activeUsers"
        },
        {
            "name": "sessions"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/analytics/data/ga4-reports', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "oauth_token": "{{google_oauth_token}}",
      "property": "{{ga4_property_id}}",
      "dateRanges": [
          {
              "startDate": "28daysAgo",
              "endDate": "today"
          }
      ],
      "dimensions": [
          {
              "name": "country"
          }
      ],
      "metrics": [
          {
              "name": "activeUsers"
          },
          {
              "name": "sessions"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/analytics/data/ga4-reports');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"oauth_token\": \"{{google_oauth_token}}\",\n    \"property\": \"{{ga4_property_id}}\",\n    \"dateRanges\": [\n        {\n            \"startDate\": \"28daysAgo\",\n            \"endDate\": \"today\"\n        }\n    ],\n    \"dimensions\": [\n        {\n            \"name\": \"country\"\n        }\n    ],\n    \"metrics\": [\n        {\n            \"name\": \"activeUsers\"\n        },\n        {\n            \"name\": \"sessions\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Google Tag Manager

GTM — Accounts

GET/v1/tagmanager/gtm-accounts

Takes no parameters — start here. Lists every GTM account the authenticated grant reaches; each result's accountId is the account_id every other gtm-* tool needs.

Auth — hybrid. Store creds once via PUT /v1/credentials/google-tagmanager (or the admin page's Connect button) and send nothing; or carry oauth_token / the refresh-triplet per-request. Scope: tagmanager.readonly.

Response. {data: {account: [{accountId, name, path, …}]}, refreshed_access_token?}.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-accounts' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-accounts', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-accounts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/tagmanager/gtm-accounts/:id

Fetch one account. id = the numeric accountId or the full path accounts/{id}.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-accounts/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-accounts/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-accounts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GTM — Built-in Variables

GET/v1/tagmanager/gtm-built-in-variables?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D

List the built-in variables currently ENABLED in a workspace (Page URL, Click Classes, …). Required: account_id, container_id, workspace_id.

Built-ins aren't CRUD upstream — they are toggles: create = enable, delete = disable.

Required query: account_id, container_id, workspace_id.

Minimal query: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}","workspace_id":"{{gtm_workspace_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{gtm_account_id}}
container_id{{gtm_container_id}}
workspace_id{{gtm_workspace_id}}
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-built-in-variables?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-built-in-variables?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-built-in-variables?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tagmanager/gtm-built-in-variables

ENABLE one or more built-in variables. Required: the three parent ids + type — one type string or an array.

Common types: pageUrl, pageHostname, pagePath, referrer, event, clickElement, clickClasses, clickId, clickUrl, clickText, formId, formUrl, errorMessage, scrollDepthThreshold, videoStatus, containerId, containerVersion, randomNumber, environmentName.

Required body: account_id, container_id, workspace_id, type.

Minimal body: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}","workspace_id":"{{gtm_workspace_id}}","type":["pageUrl","clickClasses"]}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "workspace_id": "{{gtm_workspace_id}}",
    "type": [
        "pageUrl",
        "clickClasses"
    ]
}
curl -X POST 'https://api.endpointr.com/v1/tagmanager/gtm-built-in-variables' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "workspace_id": "{{gtm_workspace_id}}",
    "type": [
        "pageUrl",
        "clickClasses"
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-built-in-variables', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{gtm_account_id}}",
      "container_id": "{{gtm_container_id}}",
      "workspace_id": "{{gtm_workspace_id}}",
      "type": [
          "pageUrl",
          "clickClasses"
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-built-in-variables');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{gtm_account_id}}\",\n    \"container_id\": \"{{gtm_container_id}}\",\n    \"workspace_id\": \"{{gtm_workspace_id}}\",\n    \"type\": [\n        \"pageUrl\",\n        \"clickClasses\"\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/tagmanager/gtm-built-in-variables/:id

DISABLE one built-in variable. id = accountId:containerId:workspaceId:type — the 4th part is the TYPE (built-ins have no per-variable id), e.g. 1:2:3:clickClasses.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/tagmanager/gtm-built-in-variables/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-built-in-variables/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-built-in-variables/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GTM — Containers

GET/v1/tagmanager/gtm-containers/:id

Fetch one container. id = accountId:containerId or the full GTM path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-containers/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-containers/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-containers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/tagmanager/gtm-containers?account_id=%7B%7Bgtm_account_id%7D%7D

List the containers under an account (account_id required) — or resolve one from its public snippet id with destination_id=GTM-XXXXXXX (:lookup; no account needed — handy when all you have is the snippet on a page; tag_id looks up by a GA4 measurement id instead).

Each result's numeric containerId is the container_id the workspace/version/environment tools take; publicId is the GTM-XXXXXXX id the site installs.

Auth — hybrid (vault google-tagmanager, or creds in the query). Scope: tagmanager.readonly.

Minimal query: {"account_id":"{{gtm_account_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{gtm_account_id}}
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-containers?account_id=%7B%7Bgtm_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-containers?account_id=%7B%7Bgtm_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-containers?account_id=%7B%7Bgtm_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tagmanager/gtm-containers

Create a container under an account. Required: account_id, name, usageContext (array of platforms: web | android | ios | server | amp).

Auth — hybrid. Scope: tagmanager.edit.containers.

Required body: account_id, name, usageContext.

Minimal body: {"account_id":"{{gtm_account_id}}","name":"example.com","usageContext":["web"]}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{gtm_account_id}}",
    "name": "example.com",
    "usageContext": [
        "web"
    ]
}
curl -X POST 'https://api.endpointr.com/v1/tagmanager/gtm-containers' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{gtm_account_id}}",
    "name": "example.com",
    "usageContext": [
        "web"
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-containers', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{gtm_account_id}}",
      "name": "example.com",
      "usageContext": [
          "web"
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-containers');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{gtm_account_id}}\",\n    \"name\": \"example.com\",\n    \"usageContext\": [\n        \"web\"\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/tagmanager/gtm-containers/:id

Update a container (full-resource PUT — GTM replaces what you send). id = accountId:containerId or the full path. Pass the container's fingerprint for optimistic concurrency.

Minimal body: {"name":"example.com (renamed)"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "example.com (renamed)"
}
curl -X PUT 'https://api.endpointr.com/v1/tagmanager/gtm-containers/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "example.com (renamed)"
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-containers/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "example.com (renamed)"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-containers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"example.com (renamed)\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/tagmanager/gtm-containers/:id

Delete a container. IRREVERSIBLE — the container, all its versions and workspaces are gone. id = accountId:containerId or the full path. Scope: tagmanager.delete.containers.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/tagmanager/gtm-containers/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-containers/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-containers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GTM — Environments

GET/v1/tagmanager/gtm-environments/:id

Fetch one environment. id = accountId:containerId:environmentId or the full GTM path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-environments/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-environments/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-environments/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/tagmanager/gtm-environments?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D

List a container's environments — the built-in Live/Latest rows plus custom preview/staging environments (each with its authorizationCode and preview link). Required: account_id, container_id.

Required query: account_id, container_id.

Minimal query: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{gtm_account_id}}
container_id{{gtm_container_id}}
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-environments?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-environments?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-environments?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tagmanager/gtm-environments

Create a CUSTOM environment (staging/QA — its snippet serves whatever version you publish to it). Required: account_id, container_id, name. Optional: description, enableDebug, url (the site it fronts).

Required body: account_id, container_id, name.

Minimal body: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}","name":"Staging"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "name": "Staging",
    "enableDebug": true,
    "url": "https://staging.example.com"
}
curl -X POST 'https://api.endpointr.com/v1/tagmanager/gtm-environments' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "name": "Staging",
    "enableDebug": true,
    "url": "https://staging.example.com"
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-environments', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{gtm_account_id}}",
      "container_id": "{{gtm_container_id}}",
      "name": "Staging",
      "enableDebug": true,
      "url": "https://staging.example.com"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-environments');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{gtm_account_id}}\",\n    \"container_id\": \"{{gtm_container_id}}\",\n    \"name\": \"Staging\",\n    \"enableDebug\": true,\n    \"url\": \"https://staging.example.com\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/tagmanager/gtm-environments/:id

Update an environment (full-resource PUT; pass fingerprint for concurrency). id = accountId:containerId:environmentId or the full path.

Minimal body: {"name":"Staging","enableDebug":true}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "Staging",
    "enableDebug": true
}
curl -X PUT 'https://api.endpointr.com/v1/tagmanager/gtm-environments/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "Staging",
    "enableDebug": true
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-environments/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "Staging",
      "enableDebug": true
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-environments/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"Staging\",\n    \"enableDebug\": true\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/tagmanager/gtm-environments/:id

Delete a CUSTOM environment (the built-in Live/Latest rows can't be deleted). id = accountId:containerId:environmentId or the full path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/tagmanager/gtm-environments/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-environments/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-environments/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GTM — Folders

GET/v1/tagmanager/gtm-folders/:id

Fetch one folder. id = accountId:containerId:workspaceId:folderId or the full GTM path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-folders/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-folders/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-folders/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/tagmanager/gtm-folders?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D

List the folders in a workspace. Required: account_id, container_id, workspace_id. File a tag/trigger/variable into a folder by setting parentFolderId on the entity itself (via its update tool).

Required query: account_id, container_id, workspace_id.

Minimal query: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}","workspace_id":"{{gtm_workspace_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{gtm_account_id}}
container_id{{gtm_container_id}}
workspace_id{{gtm_workspace_id}}
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-folders?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-folders?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-folders?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tagmanager/gtm-folders

Create a folder in a workspace. Required: the three parent ids + name.

Required body: account_id, container_id, workspace_id, name.

Minimal body: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}","workspace_id":"{{gtm_workspace_id}}","name":"Analytics"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "workspace_id": "{{gtm_workspace_id}}",
    "name": "Analytics"
}
curl -X POST 'https://api.endpointr.com/v1/tagmanager/gtm-folders' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "workspace_id": "{{gtm_workspace_id}}",
    "name": "Analytics"
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-folders', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{gtm_account_id}}",
      "container_id": "{{gtm_container_id}}",
      "workspace_id": "{{gtm_workspace_id}}",
      "name": "Analytics"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-folders');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{gtm_account_id}}\",\n    \"container_id\": \"{{gtm_container_id}}\",\n    \"workspace_id\": \"{{gtm_workspace_id}}\",\n    \"name\": \"Analytics\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/tagmanager/gtm-folders/:id

Rename a folder (full-resource PUT). id = accountId:containerId:workspaceId:folderId or the full path.

Minimal body: {"name":"Analytics"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "Analytics"
}
curl -X PUT 'https://api.endpointr.com/v1/tagmanager/gtm-folders/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "Analytics"
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-folders/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "Analytics"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-folders/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"Analytics\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/tagmanager/gtm-folders/:id

Delete a folder (entities inside it survive, unfiled). id = accountId:containerId:workspaceId:folderId or the full path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/tagmanager/gtm-folders/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-folders/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-folders/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GTM — Tags

GET/v1/tagmanager/gtm-tags/:id

Fetch one tag. id = accountId:containerId:workspaceId:tagId or the full GTM path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-tags/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-tags/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-tags/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/tagmanager/gtm-tags?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D

List the tags in a workspace. Required: account_id, container_id, workspace_id.

Auth — hybrid. Scope: tagmanager.readonly.

Required query: account_id, container_id, workspace_id.

Minimal query: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}","workspace_id":"{{gtm_workspace_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{gtm_account_id}}
container_id{{gtm_container_id}}
workspace_id{{gtm_workspace_id}}
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-tags?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-tags?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-tags?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tagmanager/gtm-tags

Create a tag in a workspace. Required: the three parent ids, name, type. Everything else is the GTM tag resource, forwarded verbatim.

Common types: googtag (Google tag — GA4 config), gaawe (GA4 event), html (custom HTML), img (custom image/pixel), awct (Google Ads conversion), sp (Google Ads remarketing).

Tag settings ride parameter ([{type:'template'|'boolean'|'integer'|'list'|'map', key, value}]); firing rules ride firingTriggerId (array of trigger ids from gtm-triggers — 2147479553 is the built-in All Pages trigger id).

Custom-HTML example:

{"account_id":"…","container_id":"…","workspace_id":"…","name":"Plausible","type":"html","parameter":[{"type":"template","key":"html","value":"<script defer data-domain=\"example.com\" src=\"https://plausible.io/js/script.js\"></script>"}],"firingTriggerId":["2147479553"]}

Required body: account_id, container_id, workspace_id, name, type.

Minimal body: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}","workspace_id":"{{gtm_workspace_id}}","name":"GA4 Configuration","type":"googtag"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "workspace_id": "{{gtm_workspace_id}}",
    "name": "GA4 Configuration",
    "type": "googtag",
    "parameter": [
        {
            "type": "template",
            "key": "tagId",
            "value": "G-XXXXXXXXXX"
        }
    ],
    "firingTriggerId": [
        "2147479553"
    ]
}
curl -X POST 'https://api.endpointr.com/v1/tagmanager/gtm-tags' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "workspace_id": "{{gtm_workspace_id}}",
    "name": "GA4 Configuration",
    "type": "googtag",
    "parameter": [
        {
            "type": "template",
            "key": "tagId",
            "value": "G-XXXXXXXXXX"
        }
    ],
    "firingTriggerId": [
        "2147479553"
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-tags', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{gtm_account_id}}",
      "container_id": "{{gtm_container_id}}",
      "workspace_id": "{{gtm_workspace_id}}",
      "name": "GA4 Configuration",
      "type": "googtag",
      "parameter": [
          {
              "type": "template",
              "key": "tagId",
              "value": "G-XXXXXXXXXX"
          }
      ],
      "firingTriggerId": [
          "2147479553"
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-tags');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{gtm_account_id}}\",\n    \"container_id\": \"{{gtm_container_id}}\",\n    \"workspace_id\": \"{{gtm_workspace_id}}\",\n    \"name\": \"GA4 Configuration\",\n    \"type\": \"googtag\",\n    \"parameter\": [\n        {\n            \"type\": \"template\",\n            \"key\": \"tagId\",\n            \"value\": \"G-XXXXXXXXXX\"\n        }\n    ],\n    \"firingTriggerId\": [\n        \"2147479553\"\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/tagmanager/gtm-tags/:id

Update a tag (full-resource PUT — send the complete tag, not a diff; read it first, mutate, PUT back with its fingerprint). id = accountId:containerId:workspaceId:tagId or the full path.

Minimal body: {"name":"GA4 Configuration","type":"googtag","parameter":[{"type":"template","key":"tagId","value":"G-XXXXXXXXXX"}]}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "GA4 Configuration",
    "type": "googtag",
    "parameter": [
        {
            "type": "template",
            "key": "tagId",
            "value": "G-XXXXXXXXXX"
        }
    ]
}
curl -X PUT 'https://api.endpointr.com/v1/tagmanager/gtm-tags/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "GA4 Configuration",
    "type": "googtag",
    "parameter": [
        {
            "type": "template",
            "key": "tagId",
            "value": "G-XXXXXXXXXX"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-tags/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "GA4 Configuration",
      "type": "googtag",
      "parameter": [
          {
              "type": "template",
              "key": "tagId",
              "value": "G-XXXXXXXXXX"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-tags/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"GA4 Configuration\",\n    \"type\": \"googtag\",\n    \"parameter\": [\n        {\n            \"type\": \"template\",\n            \"key\": \"tagId\",\n            \"value\": \"G-XXXXXXXXXX\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/tagmanager/gtm-tags/:id

Delete a tag from the workspace. id = accountId:containerId:workspaceId:tagId or the full path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/tagmanager/gtm-tags/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-tags/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-tags/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GTM — Triggers

GET/v1/tagmanager/gtm-triggers/:id

Fetch one trigger. id = accountId:containerId:workspaceId:triggerId or the full GTM path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-triggers/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-triggers/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-triggers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/tagmanager/gtm-triggers?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D

List the triggers in a workspace. Required: account_id, container_id, workspace_id. A trigger's triggerId is what tags reference in firingTriggerId.

Auth — hybrid. Scope: tagmanager.readonly.

Required query: account_id, container_id, workspace_id.

Minimal query: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}","workspace_id":"{{gtm_workspace_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{gtm_account_id}}
container_id{{gtm_container_id}}
workspace_id{{gtm_workspace_id}}
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-triggers?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-triggers?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-triggers?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tagmanager/gtm-triggers

Create a trigger in a workspace. Required: the three parent ids, name, type.

Common types: pageview, domReady, windowLoaded, click, linkClick, formSubmission, customEvent, historyChange, timer, scrollDepth, elementVisibility, youTubeVideo.

Conditions ride filter / customEventFilter / autoEventFilter: [{type:'equals'|'contains'|'startsWith'|'cssSelector'|…, parameter:[{type:'template',key:'arg0',value:'{{Page URL}}'},{type:'template',key:'arg1',value:'/checkout'}]}].

Custom-event example:

{"account_id":"…","container_id":"…","workspace_id":"…","name":"Purchase event","type":"customEvent","customEventFilter":[{"type":"equals","parameter":[{"type":"template","key":"arg0","value":"{{_event}}"},{"type":"template","key":"arg1","value":"purchase"}]}]}

Required body: account_id, container_id, workspace_id, name, type.

Minimal body: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}","workspace_id":"{{gtm_workspace_id}}","name":"Checkout pages","type":"pageview"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "workspace_id": "{{gtm_workspace_id}}",
    "name": "Checkout pages",
    "type": "pageview",
    "filter": [
        {
            "type": "contains",
            "parameter": [
                {
                    "type": "template",
                    "key": "arg0",
                    "value": "{{Page URL}}"
                },
                {
                    "type": "template",
                    "key": "arg1",
                    "value": "/checkout"
                }
            ]
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/tagmanager/gtm-triggers' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "workspace_id": "{{gtm_workspace_id}}",
    "name": "Checkout pages",
    "type": "pageview",
    "filter": [
        {
            "type": "contains",
            "parameter": [
                {
                    "type": "template",
                    "key": "arg0",
                    "value": "{{Page URL}}"
                },
                {
                    "type": "template",
                    "key": "arg1",
                    "value": "/checkout"
                }
            ]
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-triggers', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{gtm_account_id}}",
      "container_id": "{{gtm_container_id}}",
      "workspace_id": "{{gtm_workspace_id}}",
      "name": "Checkout pages",
      "type": "pageview",
      "filter": [
          {
              "type": "contains",
              "parameter": [
                  {
                      "type": "template",
                      "key": "arg0",
                      "value": "{{Page URL}}"
                  },
                  {
                      "type": "template",
                      "key": "arg1",
                      "value": "/checkout"
                  }
              ]
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-triggers');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{gtm_account_id}}\",\n    \"container_id\": \"{{gtm_container_id}}\",\n    \"workspace_id\": \"{{gtm_workspace_id}}\",\n    \"name\": \"Checkout pages\",\n    \"type\": \"pageview\",\n    \"filter\": [\n        {\n            \"type\": \"contains\",\n            \"parameter\": [\n                {\n                    \"type\": \"template\",\n                    \"key\": \"arg0\",\n                    \"value\": \"{{Page URL}}\"\n                },\n                {\n                    \"type\": \"template\",\n                    \"key\": \"arg1\",\n                    \"value\": \"/checkout\"\n                }\n            ]\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/tagmanager/gtm-triggers/:id

Update a trigger (full-resource PUT; read → mutate → PUT back with fingerprint). id = accountId:containerId:workspaceId:triggerId or the full path.

Minimal body: {"name":"Checkout pages","type":"pageview"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "Checkout pages",
    "type": "pageview"
}
curl -X PUT 'https://api.endpointr.com/v1/tagmanager/gtm-triggers/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "Checkout pages",
    "type": "pageview"
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-triggers/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "Checkout pages",
      "type": "pageview"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-triggers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"Checkout pages\",\n    \"type\": \"pageview\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/tagmanager/gtm-triggers/:id

Delete a trigger. Tags still referencing its id keep a dangling firingTriggerId. id = accountId:containerId:workspaceId:triggerId or the full path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/tagmanager/gtm-triggers/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-triggers/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-triggers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GTM — Variables

GET/v1/tagmanager/gtm-variables/:id

Fetch one variable. id = accountId:containerId:workspaceId:variableId or the full GTM path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-variables/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-variables/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-variables/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/tagmanager/gtm-variables?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D

List the USER-DEFINED variables in a workspace (the one-click built-ins live on gtm-built-in-variables). Required: account_id, container_id, workspace_id.

Auth — hybrid. Scope: tagmanager.readonly.

Required query: account_id, container_id, workspace_id.

Minimal query: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}","workspace_id":"{{gtm_workspace_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{gtm_account_id}}
container_id{{gtm_container_id}}
workspace_id{{gtm_workspace_id}}
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-variables?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-variables?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-variables?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D&workspace_id=%7B%7Bgtm_workspace_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tagmanager/gtm-variables

Create a user-defined variable in a workspace. Required: the three parent ids, name, type.

Common types: v (data layer variable), c (constant), jsm (custom JavaScript), k (1st-party cookie), u (URL component), d (DOM element), remm (regex lookup), smm (lookup table).

Settings ride parameter — a data layer variable takes [{type:'template',key:'name',value:'ecommerce.value'},{type:'integer',key:'dataLayerVersion',value:'2'}]. Reference the variable from tags/triggers as {{Its Name}}.

Required body: account_id, container_id, workspace_id, name, type.

Minimal body: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}","workspace_id":"{{gtm_workspace_id}}","name":"DL - purchase value","type":"v"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "workspace_id": "{{gtm_workspace_id}}",
    "name": "DL - purchase value",
    "type": "v",
    "parameter": [
        {
            "type": "template",
            "key": "name",
            "value": "ecommerce.value"
        },
        {
            "type": "integer",
            "key": "dataLayerVersion",
            "value": "2"
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/tagmanager/gtm-variables' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "workspace_id": "{{gtm_workspace_id}}",
    "name": "DL - purchase value",
    "type": "v",
    "parameter": [
        {
            "type": "template",
            "key": "name",
            "value": "ecommerce.value"
        },
        {
            "type": "integer",
            "key": "dataLayerVersion",
            "value": "2"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-variables', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{gtm_account_id}}",
      "container_id": "{{gtm_container_id}}",
      "workspace_id": "{{gtm_workspace_id}}",
      "name": "DL - purchase value",
      "type": "v",
      "parameter": [
          {
              "type": "template",
              "key": "name",
              "value": "ecommerce.value"
          },
          {
              "type": "integer",
              "key": "dataLayerVersion",
              "value": "2"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-variables');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{gtm_account_id}}\",\n    \"container_id\": \"{{gtm_container_id}}\",\n    \"workspace_id\": \"{{gtm_workspace_id}}\",\n    \"name\": \"DL - purchase value\",\n    \"type\": \"v\",\n    \"parameter\": [\n        {\n            \"type\": \"template\",\n            \"key\": \"name\",\n            \"value\": \"ecommerce.value\"\n        },\n        {\n            \"type\": \"integer\",\n            \"key\": \"dataLayerVersion\",\n            \"value\": \"2\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/tagmanager/gtm-variables/:id

Update a variable (full-resource PUT; read → mutate → PUT back with fingerprint). id = accountId:containerId:workspaceId:variableId or the full path.

Minimal body: {"name":"DL - purchase value","type":"v","parameter":[{"type":"template","key":"name","value":"ecommerce.value"}]}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "DL - purchase value",
    "type": "v",
    "parameter": [
        {
            "type": "template",
            "key": "name",
            "value": "ecommerce.value"
        }
    ]
}
curl -X PUT 'https://api.endpointr.com/v1/tagmanager/gtm-variables/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "DL - purchase value",
    "type": "v",
    "parameter": [
        {
            "type": "template",
            "key": "name",
            "value": "ecommerce.value"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-variables/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "DL - purchase value",
      "type": "v",
      "parameter": [
          {
              "type": "template",
              "key": "name",
              "value": "ecommerce.value"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-variables/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"DL - purchase value\",\n    \"type\": \"v\",\n    \"parameter\": [\n        {\n            \"type\": \"template\",\n            \"key\": \"name\",\n            \"value\": \"ecommerce.value\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/tagmanager/gtm-variables/:id

Delete a variable. id = accountId:containerId:workspaceId:variableId or the full path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/tagmanager/gtm-variables/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-variables/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-variables/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GTM — Versions (publish)

GET/v1/tagmanager/gtm-versions/:id

Fetch one FULL version (every tag/trigger/variable snapshotted in it). id = accountId:containerId:versionId, or the specials accountId:containerId:live (the currently-published version) and accountId:containerId:latest (newest version's header), or a full GTM path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-versions/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-versions/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-versions/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/tagmanager/gtm-versions?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D

List a container's version HEADERS (lightweight — id, name, counts; not the full contents). Required: account_id, container_id. Add includeDeleted=true for trashed versions.

Auth — hybrid. Scope: tagmanager.readonly.

Required query: account_id, container_id.

Minimal query: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{gtm_account_id}}
container_id{{gtm_container_id}}
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-versions?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-versions?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-versions?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tagmanager/gtm-versions

Version lifecycle actions — action required (versions are CREATED from a workspace via gtm-workspaces create_version, not here):

  • publish — push the version LIVE on every page carrying the snippet: {action:'publish', account_id, container_id, version_id, fingerprint?}. THE deploy step.
  • undelete — restore a trashed version.
  • set_latest — mark a version as the container's "latest" (what new workspaces base on) without publishing it.

The version target is account_id + container_id + version_id, or the version's full path.

Required body: action.

Minimal body: {"action":"publish"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "action": "publish",
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "version_id": "{{gtm_version_id}}"
}
curl -X POST 'https://api.endpointr.com/v1/tagmanager/gtm-versions' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "action": "publish",
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "version_id": "{{gtm_version_id}}"
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-versions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "action": "publish",
      "account_id": "{{gtm_account_id}}",
      "container_id": "{{gtm_container_id}}",
      "version_id": "{{gtm_version_id}}"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-versions');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"action\": \"publish\",\n    \"account_id\": \"{{gtm_account_id}}\",\n    \"container_id\": \"{{gtm_container_id}}\",\n    \"version_id\": \"{{gtm_version_id}}\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/tagmanager/gtm-versions/:id

Edit a version's name / description (full-resource PUT; contents are immutable). id = accountId:containerId:versionId or the full path.

Minimal body: {"name":"GA4 rollout","description":"Added GA4 config + purchase event"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "GA4 rollout",
    "description": "Added GA4 config + purchase event"
}
curl -X PUT 'https://api.endpointr.com/v1/tagmanager/gtm-versions/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "GA4 rollout",
    "description": "Added GA4 config + purchase event"
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-versions/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "GA4 rollout",
      "description": "Added GA4 config + purchase event"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-versions/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"GA4 rollout\",\n    \"description\": \"Added GA4 config + purchase event\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/tagmanager/gtm-versions/:id

Trash a version (restorable via create {action:'undelete'} until GTM purges it). The LIVE version can't be deleted. id = accountId:containerId:versionId or the full path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/tagmanager/gtm-versions/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-versions/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-versions/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

GTM — Workspaces

GET/v1/tagmanager/gtm-workspaces/:id

Fetch one workspace. id = accountId:containerId:workspaceId or the full GTM path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-workspaces/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-workspaces/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-workspaces/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/tagmanager/gtm-workspaces?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D

List the workspaces of a container (account_id + container_id required) — the editable draft layer every tag/trigger/variable write happens in. Every container has a "Default Workspace"; its workspaceId is what the tag/trigger/variable tools take as workspace_id.

Auth — hybrid. Scope: tagmanager.readonly.

Required query: account_id, container_id.

Minimal query: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}"}

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{gtm_account_id}}
container_id{{gtm_container_id}}
curl -X GET 'https://api.endpointr.com/v1/tagmanager/gtm-workspaces?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-workspaces?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-workspaces?account_id=%7B%7Bgtm_account_id%7D%7D&container_id=%7B%7Bgtm_container_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tagmanager/gtm-workspaces

Three calls fold onto POST via action:

1. No action — create a workspace: {account_id, container_id, name, description?}.
2. action:'create_version' — snapshot the workspace into an immutable container version (this is how EVERY publish starts): {action, account_id, container_id, workspace_id, name?, notes?}. The response's containerVersion.containerVersionId is what gtm-versions publish takes as version_id. The workspace is consumed (GTM deletes it on versioning).
3. action:'sync' — pull latest-version changes into a stale workspace before versioning: {action, account_id, container_id, workspace_id} (response lists any merge conflicts).

Auth — hybrid. Scopes: tagmanager.edit.containers (create/sync), tagmanager.edit.containerversions (create_version).

Create-version example:

{"action":"create_version","account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}","workspace_id":"{{gtm_workspace_id}}","name":"GA4 rollout","notes":"Added GA4 config + purchase event"}

Minimal body: {"account_id":"{{gtm_account_id}}","container_id":"{{gtm_container_id}}","name":"agent-changes"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "name": "agent-changes"
}
curl -X POST 'https://api.endpointr.com/v1/tagmanager/gtm-workspaces' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{gtm_account_id}}",
    "container_id": "{{gtm_container_id}}",
    "name": "agent-changes"
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-workspaces', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{gtm_account_id}}",
      "container_id": "{{gtm_container_id}}",
      "name": "agent-changes"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-workspaces');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{gtm_account_id}}\",\n    \"container_id\": \"{{gtm_container_id}}\",\n    \"name\": \"agent-changes\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/tagmanager/gtm-workspaces/:id

Rename a workspace / edit its description (full-resource PUT). id = accountId:containerId:workspaceId or the full path.

Minimal body: {"name":"agent-changes","description":"Automated edits"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "agent-changes",
    "description": "Automated edits"
}
curl -X PUT 'https://api.endpointr.com/v1/tagmanager/gtm-workspaces/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "agent-changes",
    "description": "Automated edits"
}'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-workspaces/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "agent-changes",
      "description": "Automated edits"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-workspaces/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"agent-changes\",\n    \"description\": \"Automated edits\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/tagmanager/gtm-workspaces/:id

Delete a workspace and every unversioned change in it. id = accountId:containerId:workspaceId or the full path.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/tagmanager/gtm-workspaces/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tagmanager/gtm-workspaces/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tagmanager/gtm-workspaces/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Graphics

Combine

POST/v1/graphics/combine

Two modes: overlay (base + overlay + x,y + opacity) or mosaic (images[] + layout + gap).

Minimal body: {"base_image_base64":"<base64>","overlay_image_base64":"<base64>","x":0,"y":0,"opacity":1,"format":"png"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "base_image_base64": "<base64>",
    "overlay_image_base64": "<base64>",
    "x": 0,
    "y": 0,
    "opacity": 1,
    "format": "png"
}
curl -X POST 'https://api.endpointr.com/v1/graphics/combine' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "base_image_base64": "<base64>",
    "overlay_image_base64": "<base64>",
    "x": 0,
    "y": 0,
    "opacity": 1,
    "format": "png"
}'
const response = await fetch('https://api.endpointr.com/v1/graphics/combine', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "base_image_base64": "<base64>",
      "overlay_image_base64": "<base64>",
      "x": 0,
      "y": 0,
      "opacity": 1,
      "format": "png"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/graphics/combine');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"base_image_base64\": \"<base64>\",\n    \"overlay_image_base64\": \"<base64>\",\n    \"x\": 0,\n    \"y\": 0,\n    \"opacity\": 1,\n    \"format\": \"png\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Convert Format

POST/v1/graphics/convert

Convert image between png/jpeg/webp/gif. jpeg auto-flattens transparency.

Required body: image_base64, format.

Minimal body: {"image_base64":"<base64>","format":"jpeg"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "image_base64": "<base64>",
    "format": "jpeg",
    "quality": 85
}
curl -X POST 'https://api.endpointr.com/v1/graphics/convert' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "image_base64": "<base64>",
    "format": "jpeg",
    "quality": 85
}'
const response = await fetch('https://api.endpointr.com/v1/graphics/convert', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "image_base64": "<base64>",
      "format": "jpeg",
      "quality": 85
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/graphics/convert');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"image_base64\": \"<base64>\",\n    \"format\": \"jpeg\",\n    \"quality\": 85\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Crop

POST/v1/graphics/crop

Crop a rectangle from an image. Overflow is clamped.

Required body: image_base64, x, y, width, height.

Minimal body: {"image_base64":"<base64>","x":0,"y":0,"width":200,"height":200}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "image_base64": "<base64>",
    "x": 0,
    "y": 0,
    "width": 200,
    "height": 200,
    "format": "png"
}
curl -X POST 'https://api.endpointr.com/v1/graphics/crop' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "image_base64": "<base64>",
    "x": 0,
    "y": 0,
    "width": 200,
    "height": 200,
    "format": "png"
}'
const response = await fetch('https://api.endpointr.com/v1/graphics/crop', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "image_base64": "<base64>",
      "x": 0,
      "y": 0,
      "width": 200,
      "height": 200,
      "format": "png"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/graphics/crop');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"image_base64\": \"<base64>\",\n    \"x\": 0,\n    \"y\": 0,\n    \"width\": 200,\n    \"height\": 200,\n    \"format\": \"png\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Resize

POST/v1/graphics/resize

Resize an image. mode: fit | cover | stretch.

Required body: image_base64, width, height.

Minimal body: {"image_base64":"<base64>","width":300,"height":200}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "image_base64": "<base64>",
    "width": 300,
    "height": 200,
    "mode": "cover",
    "format": "png",
    "quality": 85
}
curl -X POST 'https://api.endpointr.com/v1/graphics/resize' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "image_base64": "<base64>",
    "width": 300,
    "height": 200,
    "mode": "cover",
    "format": "png",
    "quality": 85
}'
const response = await fetch('https://api.endpointr.com/v1/graphics/resize', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "image_base64": "<base64>",
      "width": 300,
      "height": 200,
      "mode": "cover",
      "format": "png",
      "quality": 85
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/graphics/resize');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"image_base64\": \"<base64>\",\n    \"width\": 300,\n    \"height\": 200,\n    \"mode\": \"cover\",\n    \"format\": \"png\",\n    \"quality\": 85\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sponsored Text

GET/v1/graphics/text2-img?text=Sponsored

GET variant.

Minimal query: {"text":"Sponsored"}

AuthorizationBearer YOUR_JWT_TOKEN
textSponsored
curl -X GET 'https://api.endpointr.com/v1/graphics/text2-img?text=Sponsored' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/graphics/text2-img?text=Sponsored', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/graphics/text2-img?text=Sponsored');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/graphics/text2-img

Generate a 200x25 sponsored-label PNG. Empty text picks a random default.

Minimal body: {"text":"Sponsored"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "text": "Sponsored"
}
curl -X POST 'https://api.endpointr.com/v1/graphics/text2-img' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "text": "Sponsored"
}'
const response = await fetch('https://api.endpointr.com/v1/graphics/text2-img', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "text": "Sponsored"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/graphics/text2-img');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"text\": \"Sponsored\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Images

Host Image (24h)

POST/v1/images/host

General-purpose image hosting. Stores an image on the Endpointr public uploads volume and returns a URL that's reachable from anywhere on the internet for up to 24 hours, then auto-deleted.

When to use this vs /v1/ai/upload. The AI uploader is built for the vision flow: 30-minute TTL, base64-only. This endpoint is the general-purpose variant — longer TTL, three input modes, ideal for shrinking AI prompts in a long agent session (host once, reference by URL on every turn instead of re-encoding the same kilobytes of base64 over and over).

Request — pick exactly one input mode:
- image_base64 — raw base64 (no data: URI prefix). Simplest path for MCP / JSON-RPC clients that can't send multipart.
- image_url — a public https URL we'll fetch and mirror. SSRF-validated — internal/private IPs are rejected as 400. Useful for re-hosting a one-shot Meta CDN URL that's about to expire.
- file — multipart file=@path/to.png upload. HTTP-only (can't be expressed over MCP); cleanest for curl --form from a shell.
- format?png | jpeg | webp | gif. Optional — magic bytes are sniffed and trusted over a caller hint.

Limits. Max raw size 25 MB. Allowed formats above.

Response. {url, mime, format, bytes, expires_in: 86400, expires_at: <iso8601>}. The 32-hex token in the URL has 128 bits of entropy; URL is the secret (no auth on download — necessary because vision providers / Meta / etc. can't carry your JWT).

TTL. 24 hours minimum. An hourly cron in the backup container deletes files older than 24 hours — effective lifetime is 24-25 hours. After that the URL 404s.

Other example bodies.

Mirror a remote image:

{"image_url":"https://example.com/cat.jpg"}

Curl with a local file (HTTP only, won't work over MCP):

curl -X POST {{baseUrl}}/v1/images/host \
  -H "Authorization: Bearer {{token}}" \
  -F file=@/path/to/cat.jpg

Minimal body: {"image_base64":"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=","format":"png"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=",
    "format": "png"
}
curl -X POST 'https://api.endpointr.com/v1/images/host' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=",
    "format": "png"
}'
const response = await fetch('https://api.endpointr.com/v1/images/host', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=",
      "format": "png"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/images/host');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"image_base64\": \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=\",\n    \"format\": \"png\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Mail

Mailchimp

GET/v1/mail/mailchimp

List configured Mailchimp audiences.

_Requires stored credentials: mailchimp (PUT /v1/credentials/mailchimp)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/mail/mailchimp' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/mailchimp', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/mailchimp');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/mail/mailchimp

Subscribe an email to a list.

Required body: email.

Minimal body: {"email":"user@example.com"}

_Requires stored credentials: mailchimp (PUT /v1/credentials/mailchimp)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "email": "user@example.com",
    "list_id": "abc123",
    "merge_fields": {
        "FNAME": "Alice"
    }
}
curl -X POST 'https://api.endpointr.com/v1/mail/mailchimp' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "email": "user@example.com",
    "list_id": "abc123",
    "merge_fields": {
        "FNAME": "Alice"
    }
}'
const response = await fetch('https://api.endpointr.com/v1/mail/mailchimp', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "email": "user@example.com",
      "list_id": "abc123",
      "merge_fields": {
          "FNAME": "Alice"
      }
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/mailchimp');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"email\": \"user@example.com\",\n    \"list_id\": \"abc123\",\n    \"merge_fields\": {\n        \"FNAME\": \"Alice\"\n    }\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/mail/mailchimp/:id

Update a member (id = email).

Minimal body: {"list_id":"abc123","merge_fields":{"FNAME":"Bob"}}

_Requires stored credentials: mailchimp (PUT /v1/credentials/mailchimp)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "list_id": "abc123",
    "merge_fields": {
        "FNAME": "Bob"
    }
}
curl -X PUT 'https://api.endpointr.com/v1/mail/mailchimp/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "list_id": "abc123",
    "merge_fields": {
        "FNAME": "Bob"
    }
}'
const response = await fetch('https://api.endpointr.com/v1/mail/mailchimp/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "list_id": "abc123",
      "merge_fields": {
          "FNAME": "Bob"
      }
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/mailchimp/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"list_id\": \"abc123\",\n    \"merge_fields\": {\n        \"FNAME\": \"Bob\"\n    }\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/mail/mailchimp/:id?list_id=abc123

Remove a subscriber (id = email, list_id in query).

Minimal query: {"list_id":"abc123"}

_Requires stored credentials: mailchimp (PUT /v1/credentials/mailchimp)._

AuthorizationBearer YOUR_JWT_TOKEN
list_idabc123
curl -X DELETE 'https://api.endpointr.com/v1/mail/mailchimp/:id?list_id=abc123' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/mailchimp/:id?list_id=abc123', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/mailchimp/:id?list_id=abc123');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Missive — Contact books

GET/v1/mail/missive-contact-books

Takes no parameters. Lists the contact books the token can access. Each result's id is the contact_book the contacts query/create tools require (Missive scopes contacts to a book).

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/mail/missive-contact-books' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-contact-books', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-contact-books');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Missive — Contacts

GET/v1/mail/missive-contacts/:id

Fetch a single contact (full memberships + infos). Auth: vault default, OR ?api_key={{missive_api_key}}.

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/mail/missive-contacts/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-contacts/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-contacts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/mail/missive-contacts?contact_book=%7B%7Bmissive_contact_book%7D%7D&limit=50

List contacts. Missive scopes contacts to a contact book, so contact_book is required — get it from the contact-books list tool (takes no parameters).

Optional: search (matches name + email), order (asc | desc), limit, offset, modified_since (unix), include_deleted (bool).

Auth — vault default.

Auth — per-request passthrough:

?contact_book={{missive_contact_book}}&limit=50&api_key={{missive_api_key}}

Other example queries.

Full-text search for an email or name fragment:

?contact_book={{missive_contact_book}}&search=alice%40example.com

Delta sync since last poll:

?contact_book={{missive_contact_book}}&modified_since=1764460800&include_deleted=true

Required query: contact_book.

Minimal query: {"contact_book":"{{missive_contact_book}}"}

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
contact_book{{missive_contact_book}}
limit50
curl -X GET 'https://api.endpointr.com/v1/mail/missive-contacts?contact_book=%7B%7Bmissive_contact_book%7D%7D&limit=50' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-contacts?contact_book=%7B%7Bmissive_contact_book%7D%7D&limit=50', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-contacts?contact_book=%7B%7Bmissive_contact_book%7D%7D&limit=50');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/mail/missive-contacts

Create one or more contacts.

Body shape. Pass either:
- a single contact object — we wrap it into Missive's {contacts: [...]} envelope, or
- a contacts array — we forward as-is.

Each entry needs at least contact_book.

Auth — vault default (canonical body above).

Auth — per-request passthrough:

{
  "api_key":"{{missive_api_key}}",
  "contact_book":"{{missive_contact_book}}",
  "first_name":"Alice",
  "last_name":"Doe",
  "infos":[{"kind":"email","value":"alice@example.com"}]
}

Other example bodies.

Batch import (array form):

{
  "contacts":[
    {"contact_book":"{{missive_contact_book}}","first_name":"Alice","last_name":"Doe","infos":[{"kind":"email","value":"alice@example.com"}]},
    {"contact_book":"{{missive_contact_book}}","first_name":"Bob", "last_name":"Roe","infos":[{"kind":"email","value":"bob@example.com"}]}
  ]
}

With multiple infos and a starred flag:

{
  "contact_book":"{{missive_contact_book}}",
  "first_name":"Alice",
  "last_name":"Doe",
  "starred":true,
  "infos":[
    {"kind":"email","value":"alice@example.com","label":"work"},
    {"kind":"phone","value":"+15555550100","label":"mobile"}
  ]
}

Required body: contact_book.

Minimal body: {"contact_book":"{{missive_contact_book}}"}

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "contact_book": "{{missive_contact_book}}",
    "first_name": "Alice",
    "last_name": "Doe",
    "infos": [
        {
            "kind": "email",
            "value": "alice@example.com"
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/mail/missive-contacts' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "contact_book": "{{missive_contact_book}}",
    "first_name": "Alice",
    "last_name": "Doe",
    "infos": [
        {
            "kind": "email",
            "value": "alice@example.com"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-contacts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "contact_book": "{{missive_contact_book}}",
      "first_name": "Alice",
      "last_name": "Doe",
      "infos": [
          {
              "kind": "email",
              "value": "alice@example.com"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-contacts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"contact_book\": \"{{missive_contact_book}}\",\n    \"first_name\": \"Alice\",\n    \"last_name\": \"Doe\",\n    \"infos\": [\n        {\n            \"kind\": \"email\",\n            \"value\": \"alice@example.com\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/mail/missive-contacts/:id

Update a single contact (PATCH upstream). Body fields merge onto the existing contact.

Auth — vault default (canonical body above).

Auth — per-request passthrough:

{"api_key":"{{missive_api_key}}","first_name":"Alice (updated)"}

Other example bodies.

Add a phone number (replaces the infos array — fetch + merge client-side if you want to preserve existing entries):

{"infos":[{"kind":"email","value":"alice@example.com"},{"kind":"phone","value":"+15555550100"}]}

Star a contact:

{"starred":true}

Minimal body: {"first_name":"Alice (updated)"}

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "first_name": "Alice (updated)"
}
curl -X PUT 'https://api.endpointr.com/v1/mail/missive-contacts/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "first_name": "Alice (updated)"
}'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-contacts/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "first_name": "Alice (updated)"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-contacts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"first_name\": \"Alice (updated)\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Missive — Conversations

GET/v1/mail/missive-conversations

Takes no parameters — the conversation-id discovery entry point. Returns the 25 most recent conversations from the "All" mailbox (Missive requires a mailbox scope; this defaults it for you). Each result's id is the conversation id that messages/posts/drafts/tasks reference. Use the query tool for a different mailbox, filters, or pagination.

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/mail/missive-conversations' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-conversations', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-conversations');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/mail/missive-conversations/:id

Fetch a single conversation (with metadata). Auth: vault default, OR ?api_key={{missive_api_key}}.

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/mail/missive-conversations/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-conversations/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-conversations/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/mail/missive-conversations?limit=10&inbox=true

List conversations — the "last n mails" entry point.

limit (1-50; default 25) plus any combination of scope filters: inbox, all, assigned, closed, snoozed, flagged, trashed, junked, drafts, shared_label, team_inbox, team_closed, team_all, organization, email, domain, contact_organization. until is a unix timestamp for pagination (cursor on last_activity_at).

Auth — vault (canonical query above). Store creds once.

Auth — per-request passthrough. Add api_key to the query string:

?limit=10&inbox=true&api_key={{missive_api_key}}

Other example queries.

Last 25 conversations assigned to anyone:

?assigned=true&limit=25

Conversations from a specific email address (handy for support context):

?email=customer@example.com&all=true&limit=20

Paginate further back in time:

?inbox=true&limit=25&until=1761955200

Minimal query: {"limit":"10","inbox":"true"}

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
limit10
inboxtrue
curl -X GET 'https://api.endpointr.com/v1/mail/missive-conversations?limit=10&inbox=true' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-conversations?limit=10&inbox=true', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-conversations?limit=10&inbox=true');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Missive — Drafts

POST/v1/mail/missive-drafts

Create a draft (and optionally send it). Body is the inner shape of Missive's drafts object — we wrap it as {drafts: ...} upstream.

Required: from_field (sender) OR conversation (replying inside an existing thread).

Useful optional fields: subject, to_fields[], cc_fields[], bcc_fields[], body (HTML), attachments[], references[], send (bool), send_at (unix), close, add_to_inbox, add_to_team_inbox, add_users[], add_assignees[], add_shared_labels[], organization, team, account, quote_previous_message.

Auth — vault mode (canonical body above). Store the api_key once via PUT /v1/credentials/missive {api_key:...}; never send it again.

Auth — per-request passthrough. Add api_key to this body and skip the vault entry entirely:

{
  "api_key":"{{missive_api_key}}",
  "from_field":{"address":"me@example.com","name":"Me"},
  "to_fields":[{"address":"you@example.com","name":"You"}],
  "subject":"Hello from Endpointr",
  "body":"<p>Sent via Endpointr passthrough.</p>",
  "send":false
}

Other example bodies.

Reply inside an existing conversation (omits from_field):

{"conversation":"{{missive_conversation}}","body":"<p>Got it — circling back next week.</p>","send":true}

Send immediately and assign to a teammate:

{
  "from_field":{"address":"me@example.com","name":"Me"},
  "to_fields":[{"address":"you@example.com"}],
  "subject":"Welcome",
  "body":"<p>Hi!</p>",
  "send":true,
  "organization":"{{missive_organization}}",
  "add_assignees":["{{missive_user}}"]
}

Schedule for later (send_at is unix seconds):

{"from_field":{"address":"me@example.com"},"to_fields":[{"address":"you@example.com"}],"subject":"Reminder","body":"<p>Heads up</p>","send_at":1764547200}

Minimal body: {"from_field":{"address":"me@example.com","name":"Me"},"to_fields":[{"address":"you@example.com","name":"You"}],"subject":"Hello from Endpointr","body":"<p>This was created via the Missive REST API relay.</p>","send":false}

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "from_field": {
        "address": "me@example.com",
        "name": "Me"
    },
    "to_fields": [
        {
            "address": "you@example.com",
            "name": "You"
        }
    ],
    "subject": "Hello from Endpointr",
    "body": "<p>This was created via the Missive REST API relay.</p>",
    "send": false
}
curl -X POST 'https://api.endpointr.com/v1/mail/missive-drafts' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "from_field": {
        "address": "me@example.com",
        "name": "Me"
    },
    "to_fields": [
        {
            "address": "you@example.com",
            "name": "You"
        }
    ],
    "subject": "Hello from Endpointr",
    "body": "<p>This was created via the Missive REST API relay.</p>",
    "send": false
}'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-drafts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "from_field": {
          "address": "me@example.com",
          "name": "Me"
      },
      "to_fields": [
          {
              "address": "you@example.com",
              "name": "You"
          }
      ],
      "subject": "Hello from Endpointr",
      "body": "<p>This was created via the Missive REST API relay.</p>",
      "send": false
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-drafts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"from_field\": {\n        \"address\": \"me@example.com\",\n        \"name\": \"Me\"\n    },\n    \"to_fields\": [\n        {\n            \"address\": \"you@example.com\",\n            \"name\": \"You\"\n        }\n    ],\n    \"subject\": \"Hello from Endpointr\",\n    \"body\": \"<p>This was created via the Missive REST API relay.</p>\",\n    \"send\": false\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/mail/missive-drafts/:id

Delete a draft by id. The draft id comes from the create response (drafts.id) — Missive has no list-drafts endpoint (draft *conversations* can be found via the conversations query with drafts=true, but that yields conversation ids, not draft ids). Auth: vault by default, OR ?api_key={{missive_api_key}} for passthrough.

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/mail/missive-drafts/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-drafts/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-drafts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Missive — Messages

GET/v1/mail/missive-messages/:id

Fetch a single message (full headers + body). Auth: vault default, OR ?api_key={{missive_api_key}}.

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/mail/missive-messages/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-messages/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-messages/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/mail/missive-messages?conversation=%7B%7Bmissive_conversation%7D%7D&limit=20

Read messages. Missive has no global message-list endpoint, so this dispatches based on the query you send:

  • ?conversation=<id>&limit=… → list messages within a conversation
  • ?email_message_id=<rfc822> → look up by RFC-822 Message-ID header

Auth — vault default.

Auth — per-request passthrough:

?conversation={{missive_conversation}}&limit=20&api_key={{missive_api_key}}

Other example queries.

Look up a specific email by its Message-ID header (great for inbound webhook correlation):

?email_message_id=<CAEbf...@mail.gmail.com>

Minimal query: {"conversation":"{{missive_conversation}}","limit":"20"}

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
conversation{{missive_conversation}}
limit20
curl -X GET 'https://api.endpointr.com/v1/mail/missive-messages?conversation=%7B%7Bmissive_conversation%7D%7D&limit=20' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-messages?conversation=%7B%7Bmissive_conversation%7D%7D&limit=20', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-messages?conversation=%7B%7Bmissive_conversation%7D%7D&limit=20');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Missive — Organizations

GET/v1/mail/missive-organizations

Takes no parameters. Lists the organizations the token belongs to. Each result's id is the organization the conversations filter takes, the teams/users/shared-labels lists accept, and tasks/drafts routing uses.

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/mail/missive-organizations' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-organizations', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-organizations');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Missive — Posts (comments)

POST/v1/mail/missive-posts

Append a post — Missive's term for an internal comment or system message rendered inside a conversation. Useful for: writing internal notes from automations, surfacing third-party alerts (PagerDuty, Stripe, etc.) into the team's inbox, or annotating conversations with context.

Required:
- conversation (existing thread) OR references (find-or-create — Missive matches against email Message-IDs)
- notification — object with title + body. Missive uses this for the in-app notification card; *not* shown in the conversation body.
- One of text, markdown, or attachments — the actual post content.

Forwarded as-is: username (custom display name), add_users, add_assignees, add_to_inbox, add_to_team_inbox, close, organization, team.

Auth — vault default (canonical body above).

Auth — per-request passthrough:

{
  "api_key":"{{missive_api_key}}",
  "conversation":"{{missive_conversation}}",
  "notification":{"title":"New comment","body":"Posted from Endpointr"},
  "markdown":"Heads up — see the new attachment."
}

Other example bodies.

Find-or-create a conversation by Message-ID:

{
  "references":["<order-1234@orders.example.com>"],
  "notification":{"title":"Stripe alert","body":"Refund requested for charge ch_xxx"},
  "text":"Refund $42.50 was issued by Alice in Stripe. Original charge: ch_3PqXyz."
}

Markdown comment with assignees and team-inbox routing:

{
  "conversation":"{{missive_conversation}}",
  "notification":{"title":"Escalation","body":"Customer needs review"},
  "markdown":"**Escalating** — repeated 5xx since 14:02 UTC. Logs: https://...",
  "add_assignees":["{{missive_user}}"],
  "add_to_team_inbox":true,
  "team":"{{missive_team}}"
}

Required body: notification.

Minimal body: {"notification":{"title":"New comment","body":"Posted from Endpointr"}}

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "conversation": "{{missive_conversation}}",
    "notification": {
        "title": "New comment",
        "body": "Posted from Endpointr"
    },
    "markdown": "Heads up — see the new attachment."
}
curl -X POST 'https://api.endpointr.com/v1/mail/missive-posts' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "conversation": "{{missive_conversation}}",
    "notification": {
        "title": "New comment",
        "body": "Posted from Endpointr"
    },
    "markdown": "Heads up — see the new attachment."
}'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-posts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "conversation": "{{missive_conversation}}",
      "notification": {
          "title": "New comment",
          "body": "Posted from Endpointr"
      },
      "markdown": "Heads up — see the new attachment."
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-posts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"conversation\": \"{{missive_conversation}}\",\n    \"notification\": {\n        \"title\": \"New comment\",\n        \"body\": \"Posted from Endpointr\"\n    },\n    \"markdown\": \"Heads up — see the new attachment.\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Missive — Shared labels

GET/v1/mail/missive-shared-labels

Takes no parameters. Lists all shared labels. Each result's id is the label id the conversations shared_label filter and drafts/posts add_shared_labels take.

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/mail/missive-shared-labels' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-shared-labels', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-shared-labels');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/mail/missive-shared-labels?organization=%7B%7Bmissive_organization%7D%7D

List shared labels filtered to one organization (id from the organizations list tool).

Minimal query: {"organization":"{{missive_organization}}"}

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
organization{{missive_organization}}
curl -X GET 'https://api.endpointr.com/v1/mail/missive-shared-labels?organization=%7B%7Bmissive_organization%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-shared-labels?organization=%7B%7Bmissive_organization%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-shared-labels?organization=%7B%7Bmissive_organization%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Missive — Tasks (conversation tasks)

POST/v1/mail/missive-tasks

Create a Missive task. Conversation- or organization-scoped — distinct from Google Tasks (/v1/tasks/google-tasks), which are personal to-dos on a Google account.

Required: title.

For standalone tasks also required: organization plus at least one of team, assignees, or add_users.

For sub-tasks (tasks attached to a specific conversation): subtask: true plus either conversation or references.

Auth — vault default (canonical body above).

Auth — per-request passthrough:

{
  "api_key":"{{missive_api_key}}",
  "title":"Follow up with customer",
  "organization":"{{missive_organization}}",
  "assignees":["{{missive_user}}"],
  "due_at":1764547200
}

Other example bodies.

Sub-task tied to a conversation:

{
  "title":"Send refund confirmation",
  "subtask":true,
  "conversation":"{{missive_conversation}}"
}

Team-assigned task with due date one week from now:

{
  "title":"Review Q2 numbers",
  "organization":"{{missive_organization}}",
  "team":"{{missive_team}}",
  "due_at":1765152000
}

Required body: title.

Minimal body: {"title":"Follow up with customer"}

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "title": "Follow up with customer",
    "organization": "{{missive_organization}}",
    "assignees": [
        "{{missive_user}}"
    ],
    "due_at": 1764547200
}
curl -X POST 'https://api.endpointr.com/v1/mail/missive-tasks' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "title": "Follow up with customer",
    "organization": "{{missive_organization}}",
    "assignees": [
        "{{missive_user}}"
    ],
    "due_at": 1764547200
}'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-tasks', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "title": "Follow up with customer",
      "organization": "{{missive_organization}}",
      "assignees": [
          "{{missive_user}}"
      ],
      "due_at": 1764547200
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-tasks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"title\": \"Follow up with customer\",\n    \"organization\": \"{{missive_organization}}\",\n    \"assignees\": [\n        \"{{missive_user}}\"\n    ],\n    \"due_at\": 1764547200\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/mail/missive-tasks/:id

Update a task (PATCH upstream). The task id comes from the create response — Missive has no list-tasks endpoint (upstream limitation), so keep the id from creation. Updatable fields: state (todo | in_progress | closed), title, due_at.

Auth — vault default (canonical body above).

Auth — per-request passthrough:

{"api_key":"{{missive_api_key}}","state":"closed"}

Other example bodies.

Mark in-progress and bump due date:

{"state":"in_progress","due_at":1765843200}

Rename:

{"title":"Follow up — pinged Alice"}

Minimal body: {"state":"in_progress"}

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "state": "in_progress"
}
curl -X PUT 'https://api.endpointr.com/v1/mail/missive-tasks/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "state": "in_progress"
}'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-tasks/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "state": "in_progress"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-tasks/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"state\": \"in_progress\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/mail/missive-tasks/:id

Mark a task done. The task id comes from the create response (no list-tasks endpoint upstream). Missive has no real DELETE on tasks; this transitions to state: "closed" upstream. Auth: vault default, OR ?api_key={{missive_api_key}}.

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/mail/missive-tasks/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-tasks/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-tasks/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Missive — Teams

GET/v1/mail/missive-teams

Takes no parameters. Lists all teams. Each result's id is the team id the conversations filters (team_inbox/team_all/team_closed), tasks (team), and drafts routing take.

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/mail/missive-teams' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-teams', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-teams');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/mail/missive-teams?organization=%7B%7Bmissive_organization%7D%7D

List teams filtered to one organization (id from the organizations list tool).

Minimal query: {"organization":"{{missive_organization}}"}

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
organization{{missive_organization}}
curl -X GET 'https://api.endpointr.com/v1/mail/missive-teams?organization=%7B%7Bmissive_organization%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-teams?organization=%7B%7Bmissive_organization%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-teams?organization=%7B%7Bmissive_organization%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Missive — Users

GET/v1/mail/missive-users

Takes no parameters. Lists all users. Each result's id is the user id that drafts (add_assignees) and tasks (assignees) reference.

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/mail/missive-users' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-users', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-users');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/mail/missive-users?organization=%7B%7Bmissive_organization%7D%7D

List users filtered to one organization (id from the organizations list tool).

Minimal query: {"organization":"{{missive_organization}}"}

_Requires stored credentials: missive (PUT /v1/credentials/missive)._

AuthorizationBearer YOUR_JWT_TOKEN
organization{{missive_organization}}
curl -X GET 'https://api.endpointr.com/v1/mail/missive-users?organization=%7B%7Bmissive_organization%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/mail/missive-users?organization=%7B%7Bmissive_organization%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/mail/missive-users?organization=%7B%7Bmissive_organization%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Meta Conversation

Channels (connected Pages / IG)

GET/v1/messaging/channels

List the Facebook Pages (and linked Instagram business accounts) the stored token can message through. Walks /me/accounts and caches each Page's access token encrypted in meta_msg_channels so later message/send/profile calls can use the correct Page token. Pass ?refresh=true to force re-discovery. Page tokens are never returned in the response.

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/messaging/channels' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/messaging/channels', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/channels');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/messaging/channels/:id

Fetch one connected channel by Page id (discovers + caches if not seen yet).

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/messaging/channels/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/messaging/channels/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/channels/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Conversations

GET/v1/messaging/conversations/:id

Fetch one conversation by id. Local mirror first; ?refresh=true&page_id=<id> forces live.

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/messaging/conversations/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/messaging/conversations/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/conversations/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/messaging/conversations?page_id=%3Cpage-id%3E&platform=messenger&refresh=false

List conversations (threads) on a Page.

?page_id=<id> is required. ?platform=messenger (default) or instagram. Reads from the local mirror by default; ?refresh=true forces a Graph round-trip and re-mirrors.

Response: {collection:[Conversation…], source:"local"|"live"}.

Required query: page_id.

Minimal query: {"page_id":"<page-id>"}

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

AuthorizationBearer YOUR_JWT_TOKEN
page_id<page-id>
platformmessenger
refreshfalse
curl -X GET 'https://api.endpointr.com/v1/messaging/conversations?page_id=%3Cpage-id%3E&platform=messenger&refresh=false' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/messaging/conversations?page_id=%3Cpage-id%3E&platform=messenger&refresh=false', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/conversations?page_id=%3Cpage-id%3E&platform=messenger&refresh=false');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Messages (read + Send API)

GET/v1/messaging/messages/:id

Fetch one message by Meta message id. Pass ?page_id=<id> for the Page token.

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/messaging/messages/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/messaging/messages/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/messages/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/messaging/messages?conversation_id=%3Cconversation-id%3E&page_id=%3Cpage-id%3E&refresh=false

List messages in a conversation.

?conversation_id=<id> is required; pass ?page_id=<id> so the Page token resolves. ?refresh=true forces a live Graph pull. Inbound webhook messages also land in this same store.

Required query: conversation_id.

Minimal query: {"conversation_id":"<conversation-id>"}

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

AuthorizationBearer YOUR_JWT_TOKEN
conversation_id<conversation-id>
page_id<page-id>
refreshfalse
curl -X GET 'https://api.endpointr.com/v1/messaging/messages?conversation_id=%3Cconversation-id%3E&page_id=%3Cpage-id%3E&refresh=false' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/messaging/messages?conversation_id=%3Cconversation-id%3E&page_id=%3Cpage-id%3E&refresh=false', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/messages?conversation_id=%3Cconversation-id%3E&page_id=%3Cpage-id%3E&refresh=false');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/messaging/messages

Send a message via the Send API (POST /{page-id}/messages).

Required: page_id, recipient ({id:<psid>}) or recipient_id, and message.

Message shapes:
- Text: {message:{text:"hi"}}
- Attachment: {message:{attachment:{type:"image",payload:{url:"…",is_reusable:true}}}}
- Quick replies: {message:{text:"Pick",quick_replies:[…]}}
- Template: {message:{attachment:{type:"template",payload:{…}}}}

Optional: messaging_type (RESPONSE default | UPDATE | MESSAGE_TAG), tag (sets messaging_type=MESSAGE_TAG — needed outside the 24h window), platform (messenger|instagram).

Sender actions via the same POST: {action:'typing_on'|'typing_off'|'mark_seen', page_id, recipient_id}.

Sent messages are mirrored into the local store with direction:"out".

Required body: page_id, recipient_id, message.

Minimal body: {"page_id":"<page-id>","message":{"text":"Hello from Endpointr \ud83d\udc4b"}}

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "page_id": "<page-id>",
    "recipient": {
        "id": "<psid>"
    },
    "message": {
        "text": "Hello from Endpointr 👋"
    },
    "messaging_type": "RESPONSE"
}
curl -X POST 'https://api.endpointr.com/v1/messaging/messages' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "page_id": "<page-id>",
    "recipient": {
        "id": "<psid>"
    },
    "message": {
        "text": "Hello from Endpointr 👋"
    },
    "messaging_type": "RESPONSE"
}'
const response = await fetch('https://api.endpointr.com/v1/messaging/messages', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "page_id": "<page-id>",
      "recipient": {
          "id": "<psid>"
      },
      "message": {
          "text": "Hello from Endpointr 👋"
      },
      "messaging_type": "RESPONSE"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/messages');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"page_id\": \"<page-id>\",\n    \"recipient\": {\n        \"id\": \"<psid>\"\n    },\n    \"message\": {\n        \"text\": \"Hello from Endpointr 👋\"\n    },\n    \"messaging_type\": \"RESPONSE\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Messenger Profile (menu / ice breakers)

GET/v1/messaging/messenger-profile?page_id=%3Cpage-id%3E&fields=persistent_menu%2Cice_breakers%2Cgreeting%2Cget_started

Read Messenger Profile properties for a Page.

?page_id=<id> required. ?fields= defaults to persistent_menu,get_started,greeting,ice_breakers,whitelisted_domains,account_linking_url.

Required query: page_id.

Minimal query: {"page_id":"<page-id>"}

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

AuthorizationBearer YOUR_JWT_TOKEN
page_id<page-id>
fieldspersistent_menu,ice_breakers,greeting,get_started
curl -X GET 'https://api.endpointr.com/v1/messaging/messenger-profile?page_id=%3Cpage-id%3E&fields=persistent_menu%2Cice_breakers%2Cgreeting%2Cget_started' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/messaging/messenger-profile?page_id=%3Cpage-id%3E&fields=persistent_menu%2Cice_breakers%2Cgreeting%2Cget_started', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/messenger-profile?page_id=%3Cpage-id%3E&fields=persistent_menu%2Cice_breakers%2Cgreeting%2Cget_started');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/messaging/messenger-profile

Set Messenger Profile properties. Pass page_id plus any of: persistent_menu, ice_breakers, greeting, get_started, whitelisted_domains. The body (minus page_id) is forwarded to Graph as JSON.

Required body: page_id.

Minimal body: {"page_id":"<page-id>"}

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "page_id": "<page-id>",
    "get_started": {
        "payload": "GET_STARTED"
    },
    "greeting": [
        {
            "locale": "default",
            "text": "Hi {{user_first_name}}! How can we help?"
        }
    ],
    "ice_breakers": [
        {
            "question": "What are your hours?",
            "payload": "HOURS"
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/messaging/messenger-profile' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "page_id": "<page-id>",
    "get_started": {
        "payload": "GET_STARTED"
    },
    "greeting": [
        {
            "locale": "default",
            "text": "Hi {{user_first_name}}! How can we help?"
        }
    ],
    "ice_breakers": [
        {
            "question": "What are your hours?",
            "payload": "HOURS"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/messaging/messenger-profile', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "page_id": "<page-id>",
      "get_started": {
          "payload": "GET_STARTED"
      },
      "greeting": [
          {
              "locale": "default",
              "text": "Hi {{user_first_name}}! How can we help?"
          }
      ],
      "ice_breakers": [
          {
              "question": "What are your hours?",
              "payload": "HOURS"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/messenger-profile');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"page_id\": \"<page-id>\",\n    \"get_started\": {\n        \"payload\": \"GET_STARTED\"\n    },\n    \"greeting\": [\n        {\n            \"locale\": \"default\",\n            \"text\": \"Hi {{user_first_name}}! How can we help?\"\n        }\n    ],\n    \"ice_breakers\": [\n        {\n            \"question\": \"What are your hours?\",\n            \"payload\": \"HOURS\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/messaging/messenger-profile/:id

Clear Messenger Profile properties. Body {page_id, fields:["persistent_menu","ice_breakers"]} (or DELETE /v1/messaging/messenger-profile/<field>?page_id=<id> for a single field).

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/messaging/messenger-profile/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/messaging/messenger-profile/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/messenger-profile/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Token (introspection)

GET/v1/messaging/token?action=debug-token

Inspect the stored Meta messaging token.

  • No params → debug_token + granted/declined scopes in one call. Best smoke-test after PUT /v1/credentials/meta-messaging.
  • ?action=debug-token → Graph /debug_token.
  • ?action=scopes/me/permissions granted vs declined.

Required scopes for messaging: pages_messaging, pages_show_list, pages_manage_metadata, pages_read_engagement, plus instagram_basic + instagram_manage_messages for Instagram DMs, and business_management.

Minimal query: {"action":"debug-token"}

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

AuthorizationBearer YOUR_JWT_TOKEN
actiondebug-token
curl -X GET 'https://api.endpointr.com/v1/messaging/token?action=debug-token' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/messaging/token?action=debug-token', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/token?action=debug-token');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/messaging/token

Body {action:"extend"} exchanges a short-lived user token for a ~60-day one (fb_exchange_token). Requires app_id/app_secret. Harmless on System User tokens (already long-lived). For production prefer a System User token — it never expires.

Minimal body: {"action":"extend"}

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "action": "extend"
}
curl -X POST 'https://api.endpointr.com/v1/messaging/token' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "action": "extend"
}'
const response = await fetch('https://api.endpointr.com/v1/messaging/token', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "action": "extend"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/token');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"action\": \"extend\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

User profile lookup

GET/v1/messaging/profile/:id

Look up a messaging user's public profile by PSID/IGSID.

GET /v1/messaging/profile/<psid>?page_id=<id>[&fields=first_name,last_name,profile_pic,locale]. Requires the Page token of the Page the user is conversing with — pass page_id. The <psid> comes from a conversation participant or an inbound webhook event.

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/messaging/profile/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/messaging/profile/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/profile/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Webhook subscriptions (per Page)

GET/v1/messaging/webhook-subs

List the per-Page messaging webhook subscriptions (/{page-id}/subscribed_apps) plus the local meta_msg_webhook_subs rows that map (object, object_id=page_id) → customer for inbound routing.

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/messaging/webhook-subs' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/messaging/webhook-subs', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/webhook-subs');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/messaging/webhook-subs

Activate messaging webhooks for a Page (POST /{page-id}/subscribed_apps).

{object_id:'<page-id>', object:'page'|'instagram',
 fields:['messages','messaging_postbacks','message_reads','message_reactions','messaging_referrals'],
 callback_url?:'…'}

callback_url defaults to /v1/webhooks/inbound/meta-messaging on the current host.

One-time app setup: the app-level callback URL + verify token (env META_WEBHOOK_VERIFY_TOKEN) for the page/instagram objects is configured once in the Meta App Dashboard. This endpoint is the per-Page activation that routes that Page's events to the app.

Required body: object_id.

Minimal body: {"object_id":"<page-id>"}

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "object_id": "<page-id>",
    "object": "page",
    "fields": [
        "messages",
        "messaging_postbacks",
        "message_reads"
    ]
}
curl -X POST 'https://api.endpointr.com/v1/messaging/webhook-subs' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "object_id": "<page-id>",
    "object": "page",
    "fields": [
        "messages",
        "messaging_postbacks",
        "message_reads"
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/messaging/webhook-subs', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "object_id": "<page-id>",
      "object": "page",
      "fields": [
          "messages",
          "messaging_postbacks",
          "message_reads"
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/webhook-subs');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"object_id\": \"<page-id>\",\n    \"object\": \"page\",\n    \"fields\": [\n        \"messages\",\n        \"messaging_postbacks\",\n        \"message_reads\"\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/messaging/webhook-subs/:id

Deactivate messaging webhooks for a Page. :id is the Page id (object_id).

_Requires stored credentials: meta-messaging (PUT /v1/credentials/meta-messaging)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/messaging/webhook-subs/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/messaging/webhook-subs/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/messaging/webhook-subs/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Meta Marketing

Ad Sets

GET/v1/marketing/ad-sets/:id

Fetch one ad set.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/ad-sets/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/ad-sets/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ad-sets/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/marketing/ad-sets?account_id=%7B%7Bmeta_ad_account_id%7D%7D&refresh=false

List ad sets. Filter by account_id (all ad sets in an account), campaign_id (ad sets in one campaign), or both. ?refresh=true forces a live Graph pull.

Minimal query: {"account_id":"{{meta_ad_account_id}}","refresh":"false"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{meta_ad_account_id}}
refreshfalse
curl -X GET 'https://api.endpointr.com/v1/marketing/ad-sets?account_id=%7B%7Bmeta_ad_account_id%7D%7D&refresh=false' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/ad-sets?account_id=%7B%7Bmeta_ad_account_id%7D%7D&refresh=false', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ad-sets?account_id=%7B%7Bmeta_ad_account_id%7D%7D&refresh=false');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/marketing/ad-sets

Create an ad set.

Required: account_id, campaign_id, name, billing_event (e.g. IMPRESSIONS, LINK_CLICKS), optimization_goal (e.g. LINK_CLICKS, REACH, CONVERSIONS, LEAD_GENERATION), targeting (full Meta targeting spec — see https://developers.facebook.com/docs/marketing-api/audiences/reference/targeting-specs).

Budgeting at the ad-set level: daily_budget OR lifetime_budget (cannot have both). Optional: bid_amount, bid_strategy, start_time, end_time, attribution_spec, promoted_object, destination_type.

Sub-verbs like campaigns: set_status, activate, pause, archive.

Required body: account_id.

Minimal body: {"account_id":"{{meta_ad_account_id}}"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{meta_ad_account_id}}",
    "campaign_id": "120000000000000",
    "name": "EU desktop — clicks",
    "status": "PAUSED",
    "billing_event": "IMPRESSIONS",
    "optimization_goal": "LINK_CLICKS",
    "daily_budget": 2500,
    "targeting": {
        "geo_locations": {
            "countries": [
                "DK",
                "SE",
                "NO"
            ]
        },
        "age_min": 25,
        "age_max": 55,
        "publisher_platforms": [
            "facebook",
            "instagram"
        ]
    },
    "start_time": "2026-06-01T00:00:00+0000"
}
curl -X POST 'https://api.endpointr.com/v1/marketing/ad-sets' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{meta_ad_account_id}}",
    "campaign_id": "120000000000000",
    "name": "EU desktop — clicks",
    "status": "PAUSED",
    "billing_event": "IMPRESSIONS",
    "optimization_goal": "LINK_CLICKS",
    "daily_budget": 2500,
    "targeting": {
        "geo_locations": {
            "countries": [
                "DK",
                "SE",
                "NO"
            ]
        },
        "age_min": 25,
        "age_max": 55,
        "publisher_platforms": [
            "facebook",
            "instagram"
        ]
    },
    "start_time": "2026-06-01T00:00:00+0000"
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/ad-sets', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{meta_ad_account_id}}",
      "campaign_id": "120000000000000",
      "name": "EU desktop — clicks",
      "status": "PAUSED",
      "billing_event": "IMPRESSIONS",
      "optimization_goal": "LINK_CLICKS",
      "daily_budget": 2500,
      "targeting": {
          "geo_locations": {
              "countries": [
                  "DK",
                  "SE",
                  "NO"
              ]
          },
          "age_min": 25,
          "age_max": 55,
          "publisher_platforms": [
              "facebook",
              "instagram"
          ]
      },
      "start_time": "2026-06-01T00:00:00+0000"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ad-sets');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{meta_ad_account_id}}\",\n    \"campaign_id\": \"120000000000000\",\n    \"name\": \"EU desktop — clicks\",\n    \"status\": \"PAUSED\",\n    \"billing_event\": \"IMPRESSIONS\",\n    \"optimization_goal\": \"LINK_CLICKS\",\n    \"daily_budget\": 2500,\n    \"targeting\": {\n        \"geo_locations\": {\n            \"countries\": [\n                \"DK\",\n                \"SE\",\n                \"NO\"\n            ]\n        },\n        \"age_min\": 25,\n        \"age_max\": 55,\n        \"publisher_platforms\": [\n            \"facebook\",\n            \"instagram\"\n        ]\n    },\n    \"start_time\": \"2026-06-01T00:00:00+0000\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/marketing/ad-sets/:id

Patch fields on an ad set.

Find ids: the sibling query tool on this resource lists them (each row's resourceName is the id; needs only customer_id).

Find ids: the sibling query tool on this resource lists them (each row's resourceName is the id; needs only customer_id).

Find ids: the sibling query tool on this resource lists them (each row's resourceName is the id; needs only customer_id).

Find ids: the sibling query tool on this resource lists them (each row's resourceName is the id; needs only customer_id).

Find ids: the sibling query tool on this resource lists them (each row's resourceName is the id; needs only customer_id).

Minimal body: {"name":"EU desktop \u2014 clicks (updated)","daily_budget":4000}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "EU desktop — clicks (updated)",
    "daily_budget": 4000
}
curl -X PUT 'https://api.endpointr.com/v1/marketing/ad-sets/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "EU desktop — clicks (updated)",
    "daily_budget": 4000
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/ad-sets/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "EU desktop — clicks (updated)",
      "daily_budget": 4000
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ad-sets/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"EU desktop — clicks (updated)\",\n    \"daily_budget\": 4000\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/marketing/ad-sets/:id

Delete an ad set.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/marketing/ad-sets/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/ad-sets/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ad-sets/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Ad accounts

GET/v1/marketing/ad-accounts

List every ad account the stored token can see.

Discovery walks /me/adaccounts by default. If a business_id is stored in the credential, walks /{business_id}/owned_ad_accounts instead — useful for Business Manager-scoped tokens that own more accounts than they personally have access to.

Every account is mirrored into meta_ad_objects (level=account) so subsequent reads can come from the local store.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/ad-accounts' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/ad-accounts', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ad-accounts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/marketing/ad-accounts/:id

Fetch one ad account. :id accepts either act_1234567 (Graph-format) or bare 1234567 (the prefix is added automatically).

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/ad-accounts/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/ad-accounts/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ad-accounts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Ad creatives

GET/v1/marketing/creatives/:id

Fetch one creative.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/creatives/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/creatives/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/creatives/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/marketing/creatives?account_id=%7B%7Bmeta_ad_account_id%7D%7D

List creatives under one ad account. ?account_id=act_… required.

Required query: account_id.

Minimal query: {"account_id":"{{meta_ad_account_id}}"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{meta_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/marketing/creatives?account_id=%7B%7Bmeta_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/creatives?account_id=%7B%7Bmeta_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/creatives?account_id=%7B%7Bmeta_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/marketing/creatives

Create an ad creative. Meta creatives are effectively immutable — there is no PUT.

Minimal link-ad shape: {account_id, name, object_story_spec:{page_id, link_data:{message, link, name, call_to_action:{type:'LEARN_MORE', value:{link}}}}}.

For image-ad: include image_hash (from POST /v1/marketing/ad-images). For video-ad: include video_id (from POST /v1/marketing/ad-videos).

Dry-run: {action:'validate', ...} — runs Meta's dry_run=true validation and returns {valid: bool, errors?} without creating anything.

Required body: account_id.

Minimal body: {"account_id":"{{meta_ad_account_id}}"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{meta_ad_account_id}}",
    "name": "Spring landing creative",
    "object_story_spec": {
        "page_id": "0",
        "link_data": {
            "message": "Save 30% this week.",
            "link": "https://example.com/spring",
            "name": "Spring Sale",
            "call_to_action": {
                "type": "SHOP_NOW",
                "value": {
                    "link": "https://example.com/spring"
                }
            }
        }
    }
}
curl -X POST 'https://api.endpointr.com/v1/marketing/creatives' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{meta_ad_account_id}}",
    "name": "Spring landing creative",
    "object_story_spec": {
        "page_id": "0",
        "link_data": {
            "message": "Save 30% this week.",
            "link": "https://example.com/spring",
            "name": "Spring Sale",
            "call_to_action": {
                "type": "SHOP_NOW",
                "value": {
                    "link": "https://example.com/spring"
                }
            }
        }
    }
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/creatives', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{meta_ad_account_id}}",
      "name": "Spring landing creative",
      "object_story_spec": {
          "page_id": "0",
          "link_data": {
              "message": "Save 30% this week.",
              "link": "https://example.com/spring",
              "name": "Spring Sale",
              "call_to_action": {
                  "type": "SHOP_NOW",
                  "value": {
                      "link": "https://example.com/spring"
                  }
              }
          }
      }
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/creatives');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{meta_ad_account_id}}\",\n    \"name\": \"Spring landing creative\",\n    \"object_story_spec\": {\n        \"page_id\": \"0\",\n        \"link_data\": {\n            \"message\": \"Save 30% this week.\",\n            \"link\": \"https://example.com/spring\",\n            \"name\": \"Spring Sale\",\n            \"call_to_action\": {\n                \"type\": \"SHOP_NOW\",\n                \"value\": {\n                    \"link\": \"https://example.com/spring\"\n                }\n            }\n        }\n    }\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/marketing/creatives/:id

Delete a creative.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/marketing/creatives/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/creatives/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/creatives/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Ad images (upload)

GET/v1/marketing/ad-images?account_id=%7B%7Bmeta_ad_account_id%7D%7D

List uploaded ad images for one account. Optional hashes (JSON array of image hashes) narrows the search.

Required query: account_id.

Minimal query: {"account_id":"{{meta_ad_account_id}}"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{meta_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/marketing/ad-images?account_id=%7B%7Bmeta_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/ad-images?account_id=%7B%7Bmeta_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ad-images?account_id=%7B%7Bmeta_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/marketing/ad-images

Upload an image to Meta's ad-account image library. Sync — single multipart POST.

Three ways to supply the bytes (pick one):
- image_base64: raw base64 (no data: URI prefix). Magic bytes are sniffed; PNG/JPEG/GIF/WEBP allowed.
- image_url: any public https URL. Endpointr fetches once, then uploads.
- upload_url: a URL returned by POST /v1/ai/upload (i.e. already on Endpointr's uploads volume).

Response: {id, hash, account_id, asset_id, url, width, height, raw}. The hash is what you attach to creatives. The 1x1 PNG below uploads cleanly — substitute your real bytes.

Required body: account_id.

Minimal body: {"account_id":"{{meta_ad_account_id}}"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{meta_ad_account_id}}",
    "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=",
    "name": "spring-hero.png"
}
curl -X POST 'https://api.endpointr.com/v1/marketing/ad-images' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{meta_ad_account_id}}",
    "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=",
    "name": "spring-hero.png"
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/ad-images', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{meta_ad_account_id}}",
      "image_base64": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=",
      "name": "spring-hero.png"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ad-images');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{meta_ad_account_id}}\",\n    \"image_base64\": \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=\",\n    \"name\": \"spring-hero.png\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/marketing/ad-images/:id

Delete an image by hash. Pass ?account_id=act_… as a query param; the URL id is the image hash.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/marketing/ad-images/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/ad-images/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ad-images/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Ad videos (upload, sync + async)

GET/v1/marketing/ad-videos/:id

Status check. :id accepts either an Endpointr asset_id (numeric, returned at upload time) or a Meta video_id.

Response for an asset_id: {asset_id, video_id, upload_state, upload_progress, error?, raw} where upload_state ∈ pending|uploading|processing|ready|failed.

Response for a video_id: the canonical AdVideo shape with the live Graph status.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/ad-videos/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/ad-videos/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ad-videos/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/marketing/ad-videos?account_id=%7B%7Bmeta_ad_account_id%7D%7D

List videos in an ad account.

Required query: account_id.

Minimal query: {"account_id":"{{meta_ad_account_id}}"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{meta_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/marketing/ad-videos?account_id=%7B%7Bmeta_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/ad-videos?account_id=%7B%7Bmeta_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ad-videos?account_id=%7B%7Bmeta_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/marketing/ad-videos

Upload an ad video. Picks sync vs async by byte size automatically.

Supply the bytes one of three ways: video_base64, video_url, or upload_url (same semantics as image upload).

  • <50 MB → synchronous single-call upload. Response: {asset_id, video_id, upload_state, mode:'sync', status, raw}. upload_state is usually processing (Meta transcodes for a few seconds; subsequent GET will show ready).
  • ≥50 MB → resumable upload queued as a video_upload job. Response is 202 {asset_id, job_id, upload_state:'pending', mode:'async', bytes}. Poll GET /v1/marketing/ad-videos/<asset_id> to follow upload_progress (0-99) and upload_state. The worker chains a status-poll job after the transfer finishes — final ready lands without further client action.

Optional: title, description, name, mime (defaults to video/mp4).

Required body: account_id.

Minimal body: {"account_id":"{{meta_ad_account_id}}"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{meta_ad_account_id}}",
    "video_url": "https://example.com/marketing/spring-promo.mp4",
    "title": "Spring promo",
    "description": "Hero video — 15s vertical cut"
}
curl -X POST 'https://api.endpointr.com/v1/marketing/ad-videos' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{meta_ad_account_id}}",
    "video_url": "https://example.com/marketing/spring-promo.mp4",
    "title": "Spring promo",
    "description": "Hero video — 15s vertical cut"
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/ad-videos', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{meta_ad_account_id}}",
      "video_url": "https://example.com/marketing/spring-promo.mp4",
      "title": "Spring promo",
      "description": "Hero video — 15s vertical cut"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ad-videos');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{meta_ad_account_id}}\",\n    \"video_url\": \"https://example.com/marketing/spring-promo.mp4\",\n    \"title\": \"Spring promo\",\n    \"description\": \"Hero video — 15s vertical cut\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/marketing/ad-videos/:id

Delete a video by its Meta video_id.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/marketing/ad-videos/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/ad-videos/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ad-videos/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Ads

GET/v1/marketing/ads/:id

Fetch one ad.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/ads/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/ads/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ads/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/marketing/ads?account_id=%7B%7Bmeta_ad_account_id%7D%7D

List ads. Filter by account_id, adset_id, or campaign_id. Use ?action=preview&id=<ad_id>&ad_format=DESKTOP_FEED_STANDARD to fetch HTML previews for any ad format (mobile/desktop/story/reels).

Minimal query: {"account_id":"{{meta_ad_account_id}}"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{meta_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/marketing/ads?account_id=%7B%7Bmeta_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/ads?account_id=%7B%7Bmeta_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ads?account_id=%7B%7Bmeta_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/marketing/ads

Create an ad.

Required: account_id, name, adset_id, creative_id (or an inline creative spec object).

Optional: status (default Meta-side is ACTIVE; pass PAUSED while staging), tracking_specs.

Sub-verbs: set_status, activate, pause, archive, preview ({action:'preview', id, ad_format}).

Required body: account_id, adset_id, creative_id.

Minimal body: {"account_id":"{{meta_ad_account_id}}","adset_id":"120000000000000","creative_id":"120000000000000"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{meta_ad_account_id}}",
    "name": "Carousel — spring",
    "adset_id": "120000000000000",
    "creative_id": "120000000000000",
    "status": "PAUSED"
}
curl -X POST 'https://api.endpointr.com/v1/marketing/ads' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{meta_ad_account_id}}",
    "name": "Carousel — spring",
    "adset_id": "120000000000000",
    "creative_id": "120000000000000",
    "status": "PAUSED"
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/ads', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{meta_ad_account_id}}",
      "name": "Carousel — spring",
      "adset_id": "120000000000000",
      "creative_id": "120000000000000",
      "status": "PAUSED"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ads');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{meta_ad_account_id}}\",\n    \"name\": \"Carousel — spring\",\n    \"adset_id\": \"120000000000000\",\n    \"creative_id\": \"120000000000000\",\n    \"status\": \"PAUSED\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/marketing/ads/:id

Patch an ad.

Minimal body: {"name":"Carousel \u2014 spring (v2)"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "Carousel — spring (v2)"
}
curl -X PUT 'https://api.endpointr.com/v1/marketing/ads/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "Carousel — spring (v2)"
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/ads/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "Carousel — spring (v2)"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ads/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"Carousel — spring (v2)\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/marketing/ads/:id

Delete an ad.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/marketing/ads/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/ads/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/ads/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Audiences (custom + lookalike)

GET/v1/marketing/audiences/:id

Fetch one audience by id (incl. lookalike spec, rule, approximate size).

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/audiences/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/audiences/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/audiences/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/marketing/audiences?account_id=%7B%7Bmeta_ad_account_id%7D%7D

List custom + lookalike audiences for an ad account.

Required query: account_id.

Minimal query: {"account_id":"{{meta_ad_account_id}}"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{meta_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/marketing/audiences?account_id=%7B%7Bmeta_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/audiences?account_id=%7B%7Bmeta_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/audiences?account_id=%7B%7Bmeta_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/marketing/audiences

Create / mutate audiences.

Default action — create custom audience:

{account_id, name, subtype:'CUSTOM', description?,
 customer_file_source:'USER_PROVIDED_ONLY', retention_days?, rule?}

Lookalike (action:'lookalike'): {account_id, name, origin_audience_id, lookalike_spec:{type:'similarity', country:'DK', ratio:0.01}}.

Add users (action:'add_users'): {id, schema:['EMAIL']|['EMAIL','PHONE']|…, users:['jane@a.com', ...]}. Plain-text — SHA-256 hashing happens server-side; already-hashed values are detected and left alone. Phone numbers are stripped to digits before hashing.

Remove users (action:'remove_users'): same shape as add_users.

Required body: account_id.

Minimal body: {"account_id":"{{meta_ad_account_id}}"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{meta_ad_account_id}}",
    "name": "Customers who purchased in last 30d",
    "subtype": "CUSTOM",
    "customer_file_source": "USER_PROVIDED_ONLY",
    "retention_days": 180
}
curl -X POST 'https://api.endpointr.com/v1/marketing/audiences' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{meta_ad_account_id}}",
    "name": "Customers who purchased in last 30d",
    "subtype": "CUSTOM",
    "customer_file_source": "USER_PROVIDED_ONLY",
    "retention_days": 180
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/audiences', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{meta_ad_account_id}}",
      "name": "Customers who purchased in last 30d",
      "subtype": "CUSTOM",
      "customer_file_source": "USER_PROVIDED_ONLY",
      "retention_days": 180
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/audiences');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{meta_ad_account_id}}\",\n    \"name\": \"Customers who purchased in last 30d\",\n    \"subtype\": \"CUSTOM\",\n    \"customer_file_source\": \"USER_PROVIDED_ONLY\",\n    \"retention_days\": 180\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/marketing/audiences/:id

Delete an audience.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/marketing/audiences/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/audiences/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/audiences/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Auth (token introspection)

GET/v1/marketing/auth?action=debug-token

Inspect the stored Meta token.

  • No params → both debug_token + granted/declined scopes in one call. Best smoke-test after PUT /v1/credentials/meta-ads.
  • ?action=debug-token → calls Graph's /debug_token: returns scopes, expires_at, issued_at, ad-account/business ids the token can see.
  • ?action=scopes → calls /me/permissions: granted vs declined arrays.

Reading the response

{
  "data": {
    "debug_token": {
      "app_id":     "1234567890123456",
      "type":       "SYSTEM_USER",
      "expires_at": 0,
      "is_valid":   true,
      "scopes":     ["ads_management", "ads_read", "..."]
    },
    "scopes": {
      "granted":  ["ads_management", "ads_read", "..."],
      "declined": []
    }
  }
}
  • expires_at: 0 — System User token (never expires). ✅ Use this for production.
  • expires_at: <future-unix> — short-lived user token. POST {action:"extend"} to swap it for a ~60-day token, OR follow the setup guide to switch to a System User token.
  • Missing scope? Re-issue the token from Meta Business Settings → System Users → *Generate new token* with the scope ticked. The full required set:
  • ads_management, ads_read — every campaign/adset/ad/creative read & write
  • business_management — business-scoped account discovery, catalogs
  • leads_retrieval — lead form data
  • pages_show_list, pages_read_engagement, pages_manage_metadata — pages + leadgen webhooks
  • instagram_basic (optional) — Instagram ads via the connected IG business account

See the Meta Marketing API — first-time setup section in the collection description for the full provisioning walkthrough (creating the app, the Business Portfolio, the System User, and minting the token).

Minimal query: {"action":"debug-token"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
actiondebug-token
curl -X GET 'https://api.endpointr.com/v1/marketing/auth?action=debug-token' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/auth?action=debug-token', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/auth?action=debug-token');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/marketing/auth

Body {action: "extend"} exchanges a short-lived user token (1-2 h) for a long-lived (~60 d) one via Meta's fb_exchange_token grant. Requires app_id and app_secret in the stored credential. Harmless on System User tokens (they're already long-lived).

The response carries the new access_token — store it via PUT /v1/credentials/meta-ads to make it the active token.

Recommendation: for production, don't rely on this — generate a System User token from Meta Business Settings instead. System User tokens never expire and don't need periodic refresh. The extend flow is for development scenarios where you only have user-token access (e.g. testing with your own Facebook account during local dev).

Minimal body: {"action":"extend"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "action": "extend"
}
curl -X POST 'https://api.endpointr.com/v1/marketing/auth' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "action": "extend"
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/auth', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "action": "extend"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/auth');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"action\": \"extend\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Campaigns

GET/v1/marketing/campaigns/:id

Fetch one campaign by id. Local mirror first; ?refresh=true forces live.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/campaigns/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/campaigns/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/campaigns/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/marketing/campaigns?account_id=%7B%7Bmeta_ad_account_id%7D%7D&refresh=false

List campaigns under one ad account.

?account_id=act_… is required. By default reads from the local mirror; pass ?refresh=true to force a Graph round-trip and re-mirror.

Response: {collection: [Campaign…], source: "local"|"live"}.

Required query: account_id.

Minimal query: {"account_id":"{{meta_ad_account_id}}"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{meta_ad_account_id}}
refreshfalse
curl -X GET 'https://api.endpointr.com/v1/marketing/campaigns?account_id=%7B%7Bmeta_ad_account_id%7D%7D&refresh=false' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/campaigns?account_id=%7B%7Bmeta_ad_account_id%7D%7D&refresh=false', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/campaigns?account_id=%7B%7Bmeta_ad_account_id%7D%7D&refresh=false');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/marketing/campaigns

Create a campaign.

Required: account_id, name, objective (e.g. OUTCOME_TRAFFIC, OUTCOME_SALES, OUTCOME_AWARENESS, OUTCOME_LEADS, OUTCOME_ENGAGEMENT, OUTCOME_APP_PROMOTION). Meta also requires special_ad_categories — pass an empty array [] if none apply (Endpointr fills it in for you when the field is omitted entirely).

Optional: status (PAUSED is the recommended default — created campaigns shouldn't go live unreviewed), daily_budget / lifetime_budget (Meta minor units — cents, øre, etc.), bid_strategy, buying_type, start_time, stop_time.

Sub-verbs via the same POST:
{action:'set_status', id, status:'ACTIVE'|'PAUSED'|'ARCHIVED'|'DELETED'}
{action:'activate', id} — shorthand for status=ACTIVE
{action:'pause', id} — shorthand for status=PAUSED
{action:'archive', id} — shorthand for status=ARCHIVED

Required body: account_id.

Minimal body: {"account_id":"{{meta_ad_account_id}}"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{meta_ad_account_id}}",
    "name": "Spring sale — search ads",
    "objective": "OUTCOME_TRAFFIC",
    "status": "PAUSED",
    "special_ad_categories": [],
    "daily_budget": 5000
}
curl -X POST 'https://api.endpointr.com/v1/marketing/campaigns' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{meta_ad_account_id}}",
    "name": "Spring sale — search ads",
    "objective": "OUTCOME_TRAFFIC",
    "status": "PAUSED",
    "special_ad_categories": [],
    "daily_budget": 5000
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/campaigns', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{meta_ad_account_id}}",
      "name": "Spring sale — search ads",
      "objective": "OUTCOME_TRAFFIC",
      "status": "PAUSED",
      "special_ad_categories": [],
      "daily_budget": 5000
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/campaigns');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{meta_ad_account_id}}\",\n    \"name\": \"Spring sale — search ads\",\n    \"objective\": \"OUTCOME_TRAFFIC\",\n    \"status\": \"PAUSED\",\n    \"special_ad_categories\": [],\n    \"daily_budget\": 5000\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/marketing/campaigns/:id

Patch any updatable field on a campaign. Pass only the fields you want to change — Endpointr never echoes back unchanged fields to Graph (avoids accidental resets).

Minimal body: {"name":"Spring sale \u2014 updated","daily_budget":7500}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "Spring sale — updated",
    "daily_budget": 7500
}
curl -X PUT 'https://api.endpointr.com/v1/marketing/campaigns/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "Spring sale — updated",
    "daily_budget": 7500
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/campaigns/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "Spring sale — updated",
      "daily_budget": 7500
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/campaigns/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"Spring sale — updated\",\n    \"daily_budget\": 7500\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/marketing/campaigns/:id

Hard-delete a campaign in Graph and soft-delete (deleted_at) in the local mirror.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/marketing/campaigns/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/campaigns/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/campaigns/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Catalogs (Commerce)

GET/v1/marketing/catalogs/:id

Fetch one catalog.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/catalogs/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/catalogs/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/catalogs/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/marketing/catalogs

List business-owned product catalogs (requires business_id in stored creds).

Sub-actions:
- ?action=product_sets&id=<catalog_id>
- ?action=products&id=<catalog_id>
- ?action=diagnostics&id=<catalog_id>

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/catalogs' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/catalogs', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/catalogs');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Insights (performance metrics)

GET/v1/marketing/insights?level=campaign&ids=120000000000000&date_preset=last_7d

Pull performance metrics.

Required:
- levelaccount|campaign|adset|ad
- ids — CSV list, e.g. 120000000000000,120000000000001

Date selection (pick one):
- date_preset — e.g. today, yesterday, last_7d, last_30d, this_month, last_month, this_quarter, maximum
- time_range — JSON {"since":"YYYY-MM-DD","until":"YYYY-MM-DD"}

Optional:
- time_increment1 (daily bucket; default for backfills), all_days, 7
- fields — CSV (default: impressions,reach,clicks,spend,cpm,cpc,ctr,actions,action_values,date_start,date_stop)
- breakdowns — CSV (e.g. age,gender, country, publisher_platform,platform_position)
- action_breakdowns — CSV (e.g. action_type)
- cachedtrue (default) writes results into meta_ad_insights_snapshots. false always live.

Response: {level, ids, date_range, breakdowns, rows:[…], sample_raw:[…], cached}.

Required query: level, ids.

Minimal query: {"level":"campaign","ids":"120000000000000"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
levelcampaign
ids120000000000000
date_presetlast_7d
curl -X GET 'https://api.endpointr.com/v1/marketing/insights?level=campaign&ids=120000000000000&date_preset=last_7d' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/insights?level=campaign&ids=120000000000000&date_preset=last_7d', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/insights?level=campaign&ids=120000000000000&date_preset=last_7d');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/marketing/insights

POST equivalent of the GET. Body shape is the same params — use POST when arrays/objects (e.g. breakdowns, time_range) are easier to express in JSON than URL-encoded.

Required body: level, ids.

Minimal body: {"level":"campaign","ids":["120000000000000","120000000000001"]}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "level": "campaign",
    "ids": [
        "120000000000000",
        "120000000000001"
    ],
    "time_range": {
        "since": "2026-05-01",
        "until": "2026-05-21"
    },
    "breakdowns": [
        "age",
        "gender"
    ],
    "cached": true
}
curl -X POST 'https://api.endpointr.com/v1/marketing/insights' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "level": "campaign",
    "ids": [
        "120000000000000",
        "120000000000001"
    ],
    "time_range": {
        "since": "2026-05-01",
        "until": "2026-05-21"
    },
    "breakdowns": [
        "age",
        "gender"
    ],
    "cached": true
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/insights', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "level": "campaign",
      "ids": [
          "120000000000000",
          "120000000000001"
      ],
      "time_range": {
          "since": "2026-05-01",
          "until": "2026-05-21"
      },
      "breakdowns": [
          "age",
          "gender"
      ],
      "cached": true
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/insights');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"level\": \"campaign\",\n    \"ids\": [\n        \"120000000000000\",\n        \"120000000000001\"\n    ],\n    \"time_range\": {\n        \"since\": \"2026-05-01\",\n        \"until\": \"2026-05-21\"\n    },\n    \"breakdowns\": [\n        \"age\",\n        \"gender\"\n    ],\n    \"cached\": true\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leads + lead forms

GET/v1/marketing/leads/:id

Fetch one lead by Meta lead_id. Also writes/refreshes the local inbox row.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/leads/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/leads/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/leads/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/marketing/leads?form_id=120000000000000&created_after=2026-05-01

Read lead-gen forms and submissions.

  • ?action=forms&page_id=<page_id> — list lead forms attached to a Page.
  • ?form_id=<form_id>[&created_after=2026-05-01] — pull leads for one form (writes them into the local meta_ad_leads inbox at the same time).
  • ?action=local[&form_id=<form_id>][&since=2026-05-01] — read from the local inbox without touching Graph (the inbox is the destination of the leadgen webhook fan-out).

Minimal query: {"form_id":"120000000000000","created_after":"2026-05-01"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
form_id120000000000000
created_after2026-05-01
curl -X GET 'https://api.endpointr.com/v1/marketing/leads?form_id=120000000000000&created_after=2026-05-01' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/leads?form_id=120000000000000&created_after=2026-05-01', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/leads?form_id=120000000000000&created_after=2026-05-01');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Pages (Facebook)

GET/v1/marketing/pages

List every Facebook Page the token has access to (/me/accounts). Includes the page_access_token for each — that's the value to use when posting to a Page's timeline or attaching the Page to an ad creative.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/pages' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/pages', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/pages');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/marketing/pages/:id

Fetch one Page.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/pages/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/pages/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/pages/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Pixels (datasets + Conversions API)

GET/v1/marketing/pixels/:id

Fetch one pixel.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/pixels/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/pixels/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/pixels/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/marketing/pixels?account_id=%7B%7Bmeta_ad_account_id%7D%7D

List pixels (also called datasets) tied to an ad account.

Required query: account_id.

Minimal query: {"account_id":"{{meta_ad_account_id}}"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
account_id{{meta_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/marketing/pixels?account_id=%7B%7Bmeta_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/pixels?account_id=%7B%7Bmeta_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/pixels?account_id=%7B%7Bmeta_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/marketing/pixels

Send server-side conversion events (Conversions API) for one pixel.

Body: {pixel_id, events:[…], test_event_code?}. Each event is the Meta CAPI event shape — event_name, event_time (Unix seconds), action_source, event_source_url, user_data, custom_data.

PII hashing is automatic. All user_data fields in the documented list (em, ph, fn, ln, ge, db, ct, st, zp, country, external_id) are SHA-256 hashed server-side before transmission. Pass plaintext — already-hashed (64-hex) values are detected and left untouched. Pass test_event_code: 'TEST123' to land deliveries in Events Manager's test mode without affecting live metrics.

Required body: pixel_id, events.

Minimal body: {"pixel_id":"1234567890","events":[{"event_name":"Purchase","event_time":"<unix-seconds>","action_source":"website","event_source_url":"https://example.com/checkout/success","user_data":{"em":"jane@example.com","ph":"+4512345678"},"custom_data":{"currency":"DKK","value":499}}]}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "pixel_id": "1234567890",
    "events": [
        {
            "event_name": "Purchase",
            "event_time": "<unix-seconds>",
            "action_source": "website",
            "event_source_url": "https://example.com/checkout/success",
            "user_data": {
                "em": "jane@example.com",
                "ph": "+4512345678"
            },
            "custom_data": {
                "currency": "DKK",
                "value": 499
            }
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/marketing/pixels' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "pixel_id": "1234567890",
    "events": [
        {
            "event_name": "Purchase",
            "event_time": "<unix-seconds>",
            "action_source": "website",
            "event_source_url": "https://example.com/checkout/success",
            "user_data": {
                "em": "jane@example.com",
                "ph": "+4512345678"
            },
            "custom_data": {
                "currency": "DKK",
                "value": 499
            }
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/pixels', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "pixel_id": "1234567890",
      "events": [
          {
              "event_name": "Purchase",
              "event_time": "<unix-seconds>",
              "action_source": "website",
              "event_source_url": "https://example.com/checkout/success",
              "user_data": {
                  "em": "jane@example.com",
                  "ph": "+4512345678"
              },
              "custom_data": {
                  "currency": "DKK",
                  "value": 499
              }
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/pixels');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"pixel_id\": \"1234567890\",\n    \"events\": [\n        {\n            \"event_name\": \"Purchase\",\n            \"event_time\": \"<unix-seconds>\",\n            \"action_source\": \"website\",\n            \"event_source_url\": \"https://example.com/checkout/success\",\n            \"user_data\": {\n                \"em\": \"jane@example.com\",\n                \"ph\": \"+4512345678\"\n            },\n            \"custom_data\": {\n                \"currency\": \"DKK\",\n                \"value\": 499\n            }\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sync (refresh local mirror)

POST/v1/marketing/sync

Force a refresh from Graph into the local mirror for one ad account.

mode: 'inline' (default) walks every level (campaign → adset → ad → creative) in this request and returns the row counts. Suitable for accounts under ~5k objects.

mode: 'queue' enqueues a tree_sync job in meta_ad_jobs and returns {queued:true, job_id, account_id}. Use for large accounts so the request returns immediately.

Required body: account_id.

Minimal body: {"account_id":"{{meta_ad_account_id}}"}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "account_id": "{{meta_ad_account_id}}",
    "mode": "inline"
}
curl -X POST 'https://api.endpointr.com/v1/marketing/sync' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "account_id": "{{meta_ad_account_id}}",
    "mode": "inline"
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/sync', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "account_id": "{{meta_ad_account_id}}",
      "mode": "inline"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/sync');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"account_id\": \"{{meta_ad_account_id}}\",\n    \"mode\": \"inline\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Webhook subscriptions

GET/v1/marketing/webhook-subscriptions

List app-level webhook subscriptions known to Meta plus the local meta_ad_webhook_subs rows. The local rows map (object, object_id) → customer_id so inbound webhook deliveries are attributed correctly.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/marketing/webhook-subscriptions' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/webhook-subscriptions', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/webhook-subscriptions');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/marketing/webhook-subscriptions

Subscribe to a Meta webhook object. Most common shapes:

Leadgen on a Page:

{object:'page', object_id:'<page-id>', fields:['leadgen','feed'],
 callback_url?:'…', verify_token?:'…'}

Ad account changes:
{object:'ad_account', object_id:'act_…', fields:['adsstatus','spend','disable']}

callback_url defaults to the inbound endpoint on the current host (/v1/webhooks/inbound/meta-ads); verify_token defaults to env META_WEBHOOK_VERIFY_TOKEN.

Meta only allows ONE subscription per (app, object), so calling this twice for the same object updates the existing sub.

Required body: object, fields.

Minimal body: {"object":"page","fields":["leadgen"]}

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "object": "page",
    "object_id": "1234567890",
    "fields": [
        "leadgen"
    ],
    "callback_url": "https://api.endpointr.com/v1/webhooks/inbound/meta-ads"
}
curl -X POST 'https://api.endpointr.com/v1/marketing/webhook-subscriptions' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "object": "page",
    "object_id": "1234567890",
    "fields": [
        "leadgen"
    ],
    "callback_url": "https://api.endpointr.com/v1/webhooks/inbound/meta-ads"
}'
const response = await fetch('https://api.endpointr.com/v1/marketing/webhook-subscriptions', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "object": "page",
      "object_id": "1234567890",
      "fields": [
          "leadgen"
      ],
      "callback_url": "https://api.endpointr.com/v1/webhooks/inbound/meta-ads"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/webhook-subscriptions');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"object\": \"page\",\n    \"object_id\": \"1234567890\",\n    \"fields\": [\n        \"leadgen\"\n    ],\n    \"callback_url\": \"https://api.endpointr.com/v1/webhooks/inbound/meta-ads\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/marketing/webhook-subscriptions/:id

Unsubscribe from a Meta webhook object. :id here is the object name (e.g. page, ad_account) — Meta keys app-level subs by object, not by row id.

_Requires stored credentials: meta-ads (PUT /v1/credentials/meta-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/marketing/webhook-subscriptions/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/marketing/webhook-subscriptions/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/marketing/webhook-subscriptions/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Misc

Log Stream

GET/v1/misc/log?level=info&from=2026-01-01&to=2026-04-21&limit=100

Query logs by level / date range.

Minimal query: {"level":"info","from":"2026-01-01","to":"2026-04-21","limit":"100"}

AuthorizationBearer YOUR_JWT_TOKEN
levelinfo
from2026-01-01
to2026-04-21
limit100
curl -X GET 'https://api.endpointr.com/v1/misc/log?level=info&from=2026-01-01&to=2026-04-21&limit=100' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/misc/log?level=info&from=2026-01-01&to=2026-04-21&limit=100', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/misc/log?level=info&from=2026-01-01&to=2026-04-21&limit=100');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/misc/log

Append a log entry (requires api_log table).

Required body: message.

Minimal body: {"message":"Something happened"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "level": "info",
    "message": "Something happened",
    "context": {
        "user": "alice"
    }
}
curl -X POST 'https://api.endpointr.com/v1/misc/log' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "level": "info",
    "message": "Something happened",
    "context": {
        "user": "alice"
    }
}'
const response = await fetch('https://api.endpointr.com/v1/misc/log', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "level": "info",
      "message": "Something happened",
      "context": {
          "user": "alice"
      }
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/misc/log');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"level\": \"info\",\n    \"message\": \"Something happened\",\n    \"context\": {\n        \"user\": \"alice\"\n    }\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Plagiarism

POST/v1/misc/plagiarism

similar_text comparison with thresholds.

Required body: text1, text2.

Minimal body: {"text1":"Lorem ipsum dolor sit amet.","text2":"Lorem ipsum dolor sit."}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "text1": "Lorem ipsum dolor sit amet.",
    "text2": "Lorem ipsum dolor sit."
}
curl -X POST 'https://api.endpointr.com/v1/misc/plagiarism' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "text1": "Lorem ipsum dolor sit amet.",
    "text2": "Lorem ipsum dolor sit."
}'
const response = await fetch('https://api.endpointr.com/v1/misc/plagiarism', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "text1": "Lorem ipsum dolor sit amet.",
      "text2": "Lorem ipsum dolor sit."
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/misc/plagiarism');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"text1\": \"Lorem ipsum dolor sit amet.\",\n    \"text2\": \"Lorem ipsum dolor sit.\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

OpenProvider

OpenProvider — Customers (handles)

GET/v1/openprovider/op-customers

Takes no parameters. Lists customer handles (first page, up to 100). Each result's handle (e.g. XX123456-XX) is the id the customers get/update/delete tools take, and what a domain registration's owner_handle/admin_handle/tech_handle reference. Filter with the customers query tool.

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/openprovider/op-customers' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-customers', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-customers');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/openprovider/op-customers/:id

Get one customer by its handle (e.g. XX123456-XX) — the id is the handle string, not a number.

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/openprovider/op-customers/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-customers/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-customers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/openprovider/op-customers?pattern=acme

List customer handles. All filters optional. Filters: pattern (matches name/company/handle, wildcard *), email_pattern, company_name_pattern, last_name_pattern, first_name_pattern, handle_pattern, limit, offset.

A customer's handle (e.g. XX123456-XX) is what a domain registration's owner_handle/admin_handle/tech_handle reference.

Minimal query: {"pattern":"acme"}

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

AuthorizationBearer YOUR_JWT_TOKEN
patternacme
curl -X GET 'https://api.endpointr.com/v1/openprovider/op-customers?pattern=acme' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-customers?pattern=acme', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-customers?pattern=acme');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/openprovider/op-customers

Create a customer handle (POST /customers). The response data.handle is the handle you pass as owner_handle when registering a domain.

Required objects: name {first_name, last_name}, address {street, number, zipcode, city, country} (country = ISO-2, e.g. NL), phone {country_code, area_code, subscriber_number}, and email. Add company_name for a company. Some TLDs need extension_additional_data (see OpenProvider's per-TLD requirements).

Minimal:

{"name":{"first_name":"Test","last_name":"Person"},"address":{"street":"Test street","number":"123","zipcode":"1235 XX","city":"Test city","country":"NL"},"phone":{"country_code":"+31","area_code":"111","subscriber_number":"123456"},"email":"test@example.com"}

Required body: name, address, phone, email.

Minimal body: {"name":{"first_name":"Test","last_name":"Person"},"address":{"street":"Test street","number":"123","zipcode":"1235 XX","city":"Test city","country":"NL"},"phone":{"country_code":"+31","area_code":"111","subscriber_number":"123456"},"email":"test@example.com"}

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": {
        "first_name": "Test",
        "last_name": "Person"
    },
    "address": {
        "street": "Test street",
        "number": "123",
        "zipcode": "1235 XX",
        "city": "Test city",
        "country": "NL"
    },
    "phone": {
        "country_code": "+31",
        "area_code": "111",
        "subscriber_number": "123456"
    },
    "email": "test@example.com"
}
curl -X POST 'https://api.endpointr.com/v1/openprovider/op-customers' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": {
        "first_name": "Test",
        "last_name": "Person"
    },
    "address": {
        "street": "Test street",
        "number": "123",
        "zipcode": "1235 XX",
        "city": "Test city",
        "country": "NL"
    },
    "phone": {
        "country_code": "+31",
        "area_code": "111",
        "subscriber_number": "123456"
    },
    "email": "test@example.com"
}'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-customers', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": {
          "first_name": "Test",
          "last_name": "Person"
      },
      "address": {
          "street": "Test street",
          "number": "123",
          "zipcode": "1235 XX",
          "city": "Test city",
          "country": "NL"
      },
      "phone": {
          "country_code": "+31",
          "area_code": "111",
          "subscriber_number": "123456"
      },
      "email": "test@example.com"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-customers');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": {\n        \"first_name\": \"Test\",\n        \"last_name\": \"Person\"\n    },\n    \"address\": {\n        \"street\": \"Test street\",\n        \"number\": \"123\",\n        \"zipcode\": \"1235 XX\",\n        \"city\": \"Test city\",\n        \"country\": \"NL\"\n    },\n    \"phone\": {\n        \"country_code\": \"+31\",\n        \"area_code\": \"111\",\n        \"subscriber_number\": \"123456\"\n    },\n    \"email\": \"test@example.com\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/openprovider/op-customers/:id

Update a customer by its handle (PUT). Send only the fields to change (the name fields cannot be changed — clone a handle instead). e.g. new email, address, phone.

Minimal body: {"email":"new@example.com"}

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "email": "new@example.com"
}
curl -X PUT 'https://api.endpointr.com/v1/openprovider/op-customers/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "email": "new@example.com"
}'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-customers/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "email": "new@example.com"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-customers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"email\": \"new@example.com\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/openprovider/op-customers/:id

Delete a customer by its handle. Only succeeds if no active products (domains, certificates) reference it.

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/openprovider/op-customers/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-customers/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-customers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

OpenProvider — DNS records

GET/v1/openprovider/op-dns-records?name=example.com&type=A

List a zone's DNS records. name is required (the zone/domain, e.g. example.com). Optional filters: type (A|AAAA|CNAME|MX|TXT|…), record_name_pattern, value_pattern, limit, offset.

Required query: name.

Minimal query: {"name":"example.com"}

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

AuthorizationBearer YOUR_JWT_TOKEN
nameexample.com
typeA
curl -X GET 'https://api.endpointr.com/v1/openprovider/op-dns-records?name=example.com&type=A' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-dns-records?name=example.com&type=A', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-dns-records?name=example.com&type=A');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/openprovider/op-dns-records

Append one or more records to a zone (maps to the zone update records.add). Required: domain (the zone/domain) and records — a list of {type, name, value, ttl, prio?} (name is the host part, ""/"@" for apex; prio only for MX/SRV). To EDIT or REMOVE records, use /v1/openprovider/op-zones update instead.

Minimal:

{"domain":"example.com","records":[{"type":"A","name":"www","value":"1.2.3.4","ttl":900}]}

Required body: domain, records.

Minimal body: {"domain":"example.com","records":[{"type":"A","name":"www","value":"1.2.3.4","ttl":900}]}

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "domain": "example.com",
    "records": [
        {
            "type": "A",
            "name": "www",
            "value": "1.2.3.4",
            "ttl": 900
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/openprovider/op-dns-records' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "domain": "example.com",
    "records": [
        {
            "type": "A",
            "name": "www",
            "value": "1.2.3.4",
            "ttl": 900
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-dns-records', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "domain": "example.com",
      "records": [
          {
              "type": "A",
              "name": "www",
              "value": "1.2.3.4",
              "ttl": 900
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-dns-records');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"domain\": \"example.com\",\n    \"records\": [\n        {\n            \"type\": \"A\",\n            \"name\": \"www\",\n            \"value\": \"1.2.3.4\",\n            \"ttl\": 900\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

OpenProvider — DNS zones

GET/v1/openprovider/op-zones

Takes no parameters. Lists DNS zones (first page, up to 100). Each result's name (the full domain, e.g. example.com) is the id the zones get/update/delete tools take. Filter with the zones query tool.

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/openprovider/op-zones' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-zones', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-zones');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/openprovider/op-zones/:id

Get one zone by its name (the full domain, e.g. example.com). Records are included in the response (data.records).

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/openprovider/op-zones/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-zones/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-zones/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/openprovider/op-zones?name_pattern=example.com

List DNS zones. All filters optional. Filters: name_pattern (wildcard *), type (master|slave), with_records (bool), limit, offset.

Minimal query: {"name_pattern":"example.com"}

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

AuthorizationBearer YOUR_JWT_TOKEN
name_patternexample.com
curl -X GET 'https://api.endpointr.com/v1/openprovider/op-zones?name_pattern=example.com' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-zones?name_pattern=example.com', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-zones?name_pattern=example.com');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/openprovider/op-zones

Create a DNS zone. Required: domain {name, extension}. type defaults to master; records is an array of {type, name, value, ttl, prio?} (name is the host part — "" or "@" for the apex; ttl in seconds).

Minimal:

{"domain":{"name":"example","extension":"com"},"type":"master","records":[{"type":"A","name":"www","value":"1.2.3.4","ttl":900},{"type":"A","name":"","value":"1.2.3.4","ttl":900}]}

Required body: domain.

Minimal body: {"domain":{"name":"example","extension":"com"}}

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "domain": {
        "name": "example",
        "extension": "com"
    },
    "type": "master",
    "records": [
        {
            "type": "A",
            "name": "www",
            "value": "1.2.3.4",
            "ttl": 900
        },
        {
            "type": "A",
            "name": "",
            "value": "1.2.3.4",
            "ttl": 900
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/openprovider/op-zones' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "domain": {
        "name": "example",
        "extension": "com"
    },
    "type": "master",
    "records": [
        {
            "type": "A",
            "name": "www",
            "value": "1.2.3.4",
            "ttl": 900
        },
        {
            "type": "A",
            "name": "",
            "value": "1.2.3.4",
            "ttl": 900
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-zones', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "domain": {
          "name": "example",
          "extension": "com"
      },
      "type": "master",
      "records": [
          {
              "type": "A",
              "name": "www",
              "value": "1.2.3.4",
              "ttl": 900
          },
          {
              "type": "A",
              "name": "",
              "value": "1.2.3.4",
              "ttl": 900
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-zones');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"domain\": {\n        \"name\": \"example\",\n        \"extension\": \"com\"\n    },\n    \"type\": \"master\",\n    \"records\": [\n        {\n            \"type\": \"A\",\n            \"name\": \"www\",\n            \"value\": \"1.2.3.4\",\n            \"ttl\": 900\n        },\n        {\n            \"type\": \"A\",\n            \"name\": \"\",\n            \"value\": \"1.2.3.4\",\n            \"ttl\": 900\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/openprovider/op-zones/:id

Edit a zone by its name (the full domain). This is how you EDIT or REMOVE records — records have no id, so you send them grouped by action under records: add, remove, replace, update (each a list of {type, name, value, ttl, prio?}; update items are {original_record, record}). A record is matched by its full tuple. (To just APPEND records, /v1/openprovider/op-dns-records create is simpler.)

Add a TXT and remove an old A:

{"records":{"add":[{"type":"TXT","name":"","value":"v=spf1 -all","ttl":900}],"remove":[{"type":"A","name":"old","value":"9.9.9.9","ttl":900}]}}

Minimal body: {"records":{"add":[{"type":"TXT","name":"","value":"v=spf1 -all","ttl":900}],"remove":[{"type":"A","name":"old","value":"9.9.9.9","ttl":900}]}}

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "records": {
        "add": [
            {
                "type": "TXT",
                "name": "",
                "value": "v=spf1 -all",
                "ttl": 900
            }
        ],
        "remove": [
            {
                "type": "A",
                "name": "old",
                "value": "9.9.9.9",
                "ttl": 900
            }
        ]
    }
}
curl -X PUT 'https://api.endpointr.com/v1/openprovider/op-zones/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "records": {
        "add": [
            {
                "type": "TXT",
                "name": "",
                "value": "v=spf1 -all",
                "ttl": 900
            }
        ],
        "remove": [
            {
                "type": "A",
                "name": "old",
                "value": "9.9.9.9",
                "ttl": 900
            }
        ]
    }
}'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-zones/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "records": {
          "add": [
              {
                  "type": "TXT",
                  "name": "",
                  "value": "v=spf1 -all",
                  "ttl": 900
              }
          ],
          "remove": [
              {
                  "type": "A",
                  "name": "old",
                  "value": "9.9.9.9",
                  "ttl": 900
              }
          ]
      }
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-zones/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"records\": {\n        \"add\": [\n            {\n                \"type\": \"TXT\",\n                \"name\": \"\",\n                \"value\": \"v=spf1 -all\",\n                \"ttl\": 900\n            }\n        ],\n        \"remove\": [\n            {\n                \"type\": \"A\",\n                \"name\": \"old\",\n                \"value\": \"9.9.9.9\",\n                \"ttl\": 900\n            }\n        ]\n    }\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/openprovider/op-zones/:id

Delete a zone by its name (the full domain). Irreversible — the zone stops answering queries.

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/openprovider/op-zones/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-zones/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-zones/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

OpenProvider — Domain check

POST/v1/openprovider/op-domain-check

Check whether one or more domains are available to register, optionally with the price. This is the discovery step before op-domains create.

Required: domains — an array of {name, extension} objects. Optional with_price (bool) to include the registration price per domain.

Each result carries status (free = available, active = taken) and, with with_price, a price block.

Required body: domains.

Minimal body: {"domains":[{"name":"example","extension":"com"}]}

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "domains": [
        {
            "name": "example",
            "extension": "com"
        }
    ],
    "with_price": true
}
curl -X POST 'https://api.endpointr.com/v1/openprovider/op-domain-check' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "domains": [
        {
            "name": "example",
            "extension": "com"
        }
    ],
    "with_price": true
}'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-domain-check', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "domains": [
          {
              "name": "example",
              "extension": "com"
          }
      ],
      "with_price": true
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-domain-check');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"domains\": [\n        {\n            \"name\": \"example\",\n            \"extension\": \"com\"\n        }\n    ],\n    \"with_price\": true\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

OpenProvider — Domains

GET/v1/openprovider/op-domains

Start here — takes no parameters. Lists the account's domains (first page, up to 100, with total). Each result's numeric id is what the domains get/update/delete tools and action:'renew' expect. Need filtering or more pages? Use the domains query tool.

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/openprovider/op-domains' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-domains', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-domains');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/openprovider/op-domains/:id

Get one domain by its numeric id (the id from a list/create response). Returns full domain details: handles, nameservers, status, expiry/renewal dates.

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/openprovider/op-domains/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-domains/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-domains/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/openprovider/op-domains?domain_name_pattern=acme%2A&status=ACT

List domains in the account, newest first. All filters optional; call with {} to list all.

Filters: domain_name_pattern (wildcard * allowed, e.g. acme*), extension (TLD without the dot, e.g. com), status (ACT|REQ|PEN|FAI|DEL), contact_handle, ns_group_pattern, limit (default 100, max 1000), offset.

Auth: vault openprovider.

Minimal query: {"domain_name_pattern":"acme*","status":"ACT"}

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

AuthorizationBearer YOUR_JWT_TOKEN
domain_name_patternacme*
statusACT
curl -X GET 'https://api.endpointr.com/v1/openprovider/op-domains?domain_name_pattern=acme%2A&status=ACT' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-domains?domain_name_pattern=acme%2A&status=ACT', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-domains?domain_name_pattern=acme%2A&status=ACT');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/openprovider/op-domains

Register a domain, or run an action on an existing domain via an action field. Without action, this registers a domain.

Register (no action). Required: domain {name, extension} and owner_handle (a customer handle like XX123456-XX — create one via /v1/openprovider/op-customers). Strongly recommended: period (years, default 1) and name_servers (or ns_group/ns_template_name). admin_handle/tech_handle/billing_handle default to the owner but some TLDs require them explicitly.

Minimal:

{"domain":{"name":"example","extension":"com"},"owner_handle":"XX123456-XX","period":1,"name_servers":[{"name":"ns1.example.com"},{"name":"ns2.example.com"}]}

Renew an existing domain — pass action:'renew' + id (numeric domain id) + period (years):

{"action":"renew","id":123456789,"period":1}

Auth: vault openprovider.

Minimal body: {"domain":{"name":"example","extension":"com"},"owner_handle":"XX123456-XX","period":1,"name_servers":[{"name":"ns1.example.com"},{"name":"ns2.example.com"}]}

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "domain": {
        "name": "example",
        "extension": "com"
    },
    "owner_handle": "XX123456-XX",
    "period": 1,
    "name_servers": [
        {
            "name": "ns1.example.com"
        },
        {
            "name": "ns2.example.com"
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/openprovider/op-domains' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "domain": {
        "name": "example",
        "extension": "com"
    },
    "owner_handle": "XX123456-XX",
    "period": 1,
    "name_servers": [
        {
            "name": "ns1.example.com"
        },
        {
            "name": "ns2.example.com"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-domains', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "domain": {
          "name": "example",
          "extension": "com"
      },
      "owner_handle": "XX123456-XX",
      "period": 1,
      "name_servers": [
          {
              "name": "ns1.example.com"
          },
          {
              "name": "ns2.example.com"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-domains');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"domain\": {\n        \"name\": \"example\",\n        \"extension\": \"com\"\n    },\n    \"owner_handle\": \"XX123456-XX\",\n    \"period\": 1,\n    \"name_servers\": [\n        {\n            \"name\": \"ns1.example.com\"\n        },\n        {\n            \"name\": \"ns2.example.com\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/openprovider/op-domains/:id

Update a domain by its numeric id (PUT). Send only the fields to change — e.g. new contact handles, name_servers/ns_group, autorenew (on|off|default), or is_locked (transfer lock).

Minimal body: {"autorenew":"on","name_servers":[{"name":"ns1.example.com"},{"name":"ns2.example.com"}]}

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "autorenew": "on",
    "name_servers": [
        {
            "name": "ns1.example.com"
        },
        {
            "name": "ns2.example.com"
        }
    ]
}
curl -X PUT 'https://api.endpointr.com/v1/openprovider/op-domains/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "autorenew": "on",
    "name_servers": [
        {
            "name": "ns1.example.com"
        },
        {
            "name": "ns2.example.com"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-domains/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "autorenew": "on",
      "name_servers": [
          {
              "name": "ns1.example.com"
          },
          {
              "name": "ns2.example.com"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-domains/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"autorenew\": \"on\",\n    \"name_servers\": [\n        {\n            \"name\": \"ns1.example.com\"\n        },\n        {\n            \"name\": \"ns2.example.com\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/openprovider/op-domains/:id

Delete a domain by its numeric id (DELETE /domains/{id}).

_Requires stored credentials: openprovider (PUT /v1/credentials/openprovider)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/openprovider/op-domains/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/openprovider/op-domains/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/openprovider/op-domains/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Outreach — Encharge

Encharge — Account info

GET/v1/encharge/encharge-account

Takes no parameters. Account info (peopleCount, timezone, activeServices, …) — the smoke test after storing the api_key.

_Requires stored credentials: encharge (PUT /v1/credentials/encharge)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/encharge/encharge-account' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/encharge/encharge-account', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/encharge/encharge-account');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Encharge — Event webhooks

POST/v1/encharge/encharge-webhooks

Subscribe to Encharge events: {eventType, targetUrl}. eventTypes: newUser, updatedUser, unsubscribedUser, added-tag-<TAG>, removed-tag-<TAG> (e.g. added-tag-signed-up). Keep the returned subscription.id — Encharge has no list-subscriptions endpoint; it's the only handle for delete.

Required body: eventType, targetUrl.

Minimal body: {"eventType":"unsubscribedUser","targetUrl":"https://api.example.com/hooks/encharge"}

_Requires stored credentials: encharge (PUT /v1/credentials/encharge)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "eventType": "unsubscribedUser",
    "targetUrl": "https://api.example.com/hooks/encharge"
}
curl -X POST 'https://api.endpointr.com/v1/encharge/encharge-webhooks' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "eventType": "unsubscribedUser",
    "targetUrl": "https://api.example.com/hooks/encharge"
}'
const response = await fetch('https://api.endpointr.com/v1/encharge/encharge-webhooks', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "eventType": "unsubscribedUser",
      "targetUrl": "https://api.example.com/hooks/encharge"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/encharge/encharge-webhooks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"eventType\": \"unsubscribedUser\",\n    \"targetUrl\": \"https://api.example.com/hooks/encharge\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/encharge/encharge-webhooks/:id

Delete an event subscription by its numeric id (from the create response — no upstream list).

_Requires stored credentials: encharge (PUT /v1/credentials/encharge)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/encharge/encharge-webhooks/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/encharge/encharge-webhooks/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/encharge/encharge-webhooks/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Encharge — People

GET/v1/encharge/encharge-people?email=alice%40example.com

Fetch specific people by identifier: email (or user_id / id), emails (comma-list), or a raw people array of identifier objects. Encharge has no browse-all endpoint — use the segments tools to page an audience.

Minimal query: {"email":"alice@example.com"}

_Requires stored credentials: encharge (PUT /v1/credentials/encharge)._

AuthorizationBearer YOUR_JWT_TOKEN
emailalice@example.com
curl -X GET 'https://api.endpointr.com/v1/encharge/encharge-people?email=alice%40example.com' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/encharge/encharge-people?email=alice%40example.com', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/encharge/encharge-people?email=alice%40example.com');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/encharge/encharge-people

UPSERT people (create or update, matched by email/userId/id): one person object or a people array; extra keys become field values (see the fields tool). Fold {action:'unsubscribe', email} to stop all email to a person. Outreach flow: upsert the person, then add a tag (tags tool) — the Flow triggered by that tag sends the email.

Minimal body: {"email":"alice@example.com","firstName":"Alice","companyName":"Example ApS"}

_Requires stored credentials: encharge (PUT /v1/credentials/encharge)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "email": "alice@example.com",
    "firstName": "Alice",
    "companyName": "Example ApS"
}
curl -X POST 'https://api.endpointr.com/v1/encharge/encharge-people' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "email": "alice@example.com",
    "firstName": "Alice",
    "companyName": "Example ApS"
}'
const response = await fetch('https://api.endpointr.com/v1/encharge/encharge-people', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "email": "alice@example.com",
      "firstName": "Alice",
      "companyName": "Example ApS"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/encharge/encharge-people');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"email\": \"alice@example.com\",\n    \"firstName\": \"Alice\",\n    \"companyName\": \"Example ApS\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/encharge/encharge-people/:id

Archive a person by id (their email or Encharge id). Add ?force=true for a GDPR-compliant full delete.

_Requires stored credentials: encharge (PUT /v1/credentials/encharge)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/encharge/encharge-people/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/encharge/encharge-people/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/encharge/encharge-people/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Encharge — Person fields

GET/v1/encharge/encharge-fields

Takes no parameters. All person fields; each name is the field id used in people upserts and update/delete here.

_Requires stored credentials: encharge (PUT /v1/credentials/encharge)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/encharge/encharge-fields' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/encharge/encharge-fields', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/encharge/encharge-fields');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/encharge/encharge-fields

Create field(s): {name, type: string|number|boolean|integer|any, title?, format?: date|date-time} or a fields array.

Minimal body: {"name":"plan","title":"Plan","type":"string"}

_Requires stored credentials: encharge (PUT /v1/credentials/encharge)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "plan",
    "title": "Plan",
    "type": "string"
}
curl -X POST 'https://api.endpointr.com/v1/encharge/encharge-fields' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "plan",
    "title": "Plan",
    "type": "string"
}'
const response = await fetch('https://api.endpointr.com/v1/encharge/encharge-fields', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "plan",
      "title": "Plan",
      "type": "string"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/encharge/encharge-fields');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"plan\",\n    \"title\": \"Plan\",\n    \"type\": \"string\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/encharge/encharge-fields/:id

Modify a field — id is the field name (from the fields list).

Minimal body: {"title":"Subscription plan"}

_Requires stored credentials: encharge (PUT /v1/credentials/encharge)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "title": "Subscription plan"
}
curl -X PUT 'https://api.endpointr.com/v1/encharge/encharge-fields/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "title": "Subscription plan"
}'
const response = await fetch('https://api.endpointr.com/v1/encharge/encharge-fields/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "title": "Subscription plan"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/encharge/encharge-fields/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"title\": \"Subscription plan\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/encharge/encharge-fields/:id

Delete a field by its name. Removes the property from all people.

_Requires stored credentials: encharge (PUT /v1/credentials/encharge)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/encharge/encharge-fields/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/encharge/encharge-fields/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/encharge/encharge-fields/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Encharge — Segments

GET/v1/encharge/encharge-segments

Takes no parameters. All dynamic segments; each result's numeric id is the segment_id for the people-in-segment query.

_Requires stored credentials: encharge (PUT /v1/credentials/encharge)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/encharge/encharge-segments' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/encharge/encharge-segments', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/encharge/encharge-segments');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/encharge/encharge-segments?segment_id=%7B%7Bencharge_segment_id%7D%7D&limit=50

People in one segment. segment_id required (from the segments list). Optional: limit, offset, attributes (field names to return), sort, order (asc|desc).

Required query: segment_id.

Minimal query: {"segment_id":"{{encharge_segment_id}}"}

_Requires stored credentials: encharge (PUT /v1/credentials/encharge)._

AuthorizationBearer YOUR_JWT_TOKEN
segment_id{{encharge_segment_id}}
limit50
curl -X GET 'https://api.endpointr.com/v1/encharge/encharge-segments?segment_id=%7B%7Bencharge_segment_id%7D%7D&limit=50' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/encharge/encharge-segments?segment_id=%7B%7Bencharge_segment_id%7D%7D&limit=50', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/encharge/encharge-segments?segment_id=%7B%7Bencharge_segment_id%7D%7D&limit=50');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Encharge — Tags (flow triggers)

POST/v1/encharge/encharge-tags

Add tag(s) to a person — the outreach trigger: a Flow triggered by the tag sends the emails. {tag:'newsletter-july', email:'…'}; comma-separate for several tags. Fold {action:'remove', tag, email} to untag. A person's current tags are on their tags field (people query).

Required body: tag.

Minimal body: {"tag":"newsletter-july"}

_Requires stored credentials: encharge (PUT /v1/credentials/encharge)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "tag": "newsletter-july",
    "email": "alice@example.com"
}
curl -X POST 'https://api.endpointr.com/v1/encharge/encharge-tags' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "tag": "newsletter-july",
    "email": "alice@example.com"
}'
const response = await fetch('https://api.endpointr.com/v1/encharge/encharge-tags', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "tag": "newsletter-july",
      "email": "alice@example.com"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/encharge/encharge-tags');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"tag\": \"newsletter-july\",\n    \"email\": \"alice@example.com\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Outreach — SendMails

SendMails — Campaigns (newsletters)

GET/v1/sendmails/sendmails-campaigns

Takes no parameters. Lists all campaigns; each result's uid is the campaign id for get/update/delete/run.

_Requires stored credentials: sendmails (PUT /v1/credentials/sendmails)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendmails/sendmails-campaigns' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendmails/sendmails-campaigns', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendmails/sendmails-campaigns');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/sendmails/sendmails-campaigns/:id

One campaign by uid incl. delivery statistics (sent, opens, clicks, bounces).

_Requires stored credentials: sendmails (PUT /v1/credentials/sendmails)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendmails/sendmails-campaigns/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendmails/sendmails-campaigns/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendmails/sendmails-campaigns/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/sendmails/sendmails-campaigns

Create a newsletter DRAFT — or fold {action:'run'|'pause'|'resume', uid} for the send lifecycle. Draft fields: name, list_uid (from the lists tool), subject, from_email, from_name, reply_to, html (the email body), plain?, track_open/track_click ('yes'/'no'), run_at? ('Y-m-d H:i:s' to schedule). New drafts don't send until you run them.

Required body: name, list_uid, subject.

Minimal body: {"name":"July Newsletter","list_uid":"{{sendmails_list_uid}}","subject":"News for July"}

_Requires stored credentials: sendmails (PUT /v1/credentials/sendmails)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "July Newsletter",
    "list_uid": "{{sendmails_list_uid}}",
    "subject": "News for July",
    "from_email": "news@example.com",
    "from_name": "Example News",
    "reply_to": "news@example.com",
    "html": "<h1>Hello!</h1><p>Our July update…</p>",
    "track_open": "yes",
    "track_click": "yes"
}
curl -X POST 'https://api.endpointr.com/v1/sendmails/sendmails-campaigns' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "July Newsletter",
    "list_uid": "{{sendmails_list_uid}}",
    "subject": "News for July",
    "from_email": "news@example.com",
    "from_name": "Example News",
    "reply_to": "news@example.com",
    "html": "<h1>Hello!</h1><p>Our July update…</p>",
    "track_open": "yes",
    "track_click": "yes"
}'
const response = await fetch('https://api.endpointr.com/v1/sendmails/sendmails-campaigns', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "July Newsletter",
      "list_uid": "{{sendmails_list_uid}}",
      "subject": "News for July",
      "from_email": "news@example.com",
      "from_name": "Example News",
      "reply_to": "news@example.com",
      "html": "<h1>Hello!</h1><p>Our July update…</p>",
      "track_open": "yes",
      "track_click": "yes"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendmails/sendmails-campaigns');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"July Newsletter\",\n    \"list_uid\": \"{{sendmails_list_uid}}\",\n    \"subject\": \"News for July\",\n    \"from_email\": \"news@example.com\",\n    \"from_name\": \"Example News\",\n    \"reply_to\": \"news@example.com\",\n    \"html\": \"<h1>Hello!</h1><p>Our July update…</p>\",\n    \"track_open\": \"yes\",\n    \"track_click\": \"yes\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/sendmails/sendmails-campaigns/:id

Update a draft campaign by uid (PATCH upstream). Only drafts ('new' status) are editable.

Minimal body: {"subject":"Updated subject"}

_Requires stored credentials: sendmails (PUT /v1/credentials/sendmails)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "subject": "Updated subject"
}
curl -X PUT 'https://api.endpointr.com/v1/sendmails/sendmails-campaigns/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "subject": "Updated subject"
}'
const response = await fetch('https://api.endpointr.com/v1/sendmails/sendmails-campaigns/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "subject": "Updated subject"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendmails/sendmails-campaigns/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"subject\": \"Updated subject\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/sendmails/sendmails-campaigns/:id

Delete a campaign by uid.

_Requires stored credentials: sendmails (PUT /v1/credentials/sendmails)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/sendmails/sendmails-campaigns/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendmails/sendmails-campaigns/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendmails/sendmails-campaigns/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

SendMails — Lists

GET/v1/sendmails/sendmails-lists

Takes no parameters — start here. Lists all mail lists; each result's uid is the list_uid the subscribers and campaigns tools need.

_Requires stored credentials: sendmails (PUT /v1/credentials/sendmails)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendmails/sendmails-lists' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendmails/sendmails-lists', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendmails/sendmails-lists');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/sendmails/sendmails-lists/:id

One list by its uid (from the lists list), incl. field definitions.

_Requires stored credentials: sendmails (PUT /v1/credentials/sendmails)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendmails/sendmails-lists/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendmails/sendmails-lists/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendmails/sendmails-lists/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/sendmails/sendmails-lists

Create a mail list — or fold {action:'add_field', uid, type, label, tag} to add a custom subscriber field to an existing list (here tag is the NEW field's uppercase identifier, e.g. COMPANY — distinct from the subscriber tag label field). Create needs name, from_email, from_name and Acelle's contact block (company, address_1, city, zip, country_id, email).

Minimal body: {"name":"Newsletter","from_email":"news@example.com","from_name":"Example News","default_subject":"News from Example","contact":{"company":"Example ApS","address_1":"Street 1","city":"Copenhagen","zip":"2100","country_id":"dk","email":"news@example.com"}}

_Requires stored credentials: sendmails (PUT /v1/credentials/sendmails)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "Newsletter",
    "from_email": "news@example.com",
    "from_name": "Example News",
    "default_subject": "News from Example",
    "contact": {
        "company": "Example ApS",
        "address_1": "Street 1",
        "city": "Copenhagen",
        "zip": "2100",
        "country_id": "dk",
        "email": "news@example.com"
    }
}
curl -X POST 'https://api.endpointr.com/v1/sendmails/sendmails-lists' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "Newsletter",
    "from_email": "news@example.com",
    "from_name": "Example News",
    "default_subject": "News from Example",
    "contact": {
        "company": "Example ApS",
        "address_1": "Street 1",
        "city": "Copenhagen",
        "zip": "2100",
        "country_id": "dk",
        "email": "news@example.com"
    }
}'
const response = await fetch('https://api.endpointr.com/v1/sendmails/sendmails-lists', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "Newsletter",
      "from_email": "news@example.com",
      "from_name": "Example News",
      "default_subject": "News from Example",
      "contact": {
          "company": "Example ApS",
          "address_1": "Street 1",
          "city": "Copenhagen",
          "zip": "2100",
          "country_id": "dk",
          "email": "news@example.com"
      }
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendmails/sendmails-lists');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"Newsletter\",\n    \"from_email\": \"news@example.com\",\n    \"from_name\": \"Example News\",\n    \"default_subject\": \"News from Example\",\n    \"contact\": {\n        \"company\": \"Example ApS\",\n        \"address_1\": \"Street 1\",\n        \"city\": \"Copenhagen\",\n        \"zip\": \"2100\",\n        \"country_id\": \"dk\",\n        \"email\": \"news@example.com\"\n    }\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/sendmails/sendmails-lists/:id

Delete a list by its uid. Irreversible — subscribers on it are removed.

_Requires stored credentials: sendmails (PUT /v1/credentials/sendmails)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/sendmails/sendmails-lists/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendmails/sendmails-lists/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendmails/sendmails-lists/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

SendMails — Subscribers

GET/v1/sendmails/sendmails-subscribers/:id

One subscriber by their uid (from the subscribers query).

_Requires stored credentials: sendmails (PUT /v1/credentials/sendmails)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendmails/sendmails-subscribers/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendmails/sendmails-subscribers/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendmails/sendmails-subscribers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/sendmails/sendmails-subscribers?list_uid=%7B%7Bsendmails_list_uid%7D%7D&per_page=50

List subscribers on a list. list_uid is required — get it from the SendMails lists tool. Optional: per_page, page.

Required query: list_uid.

Minimal query: {"list_uid":"{{sendmails_list_uid}}"}

_Requires stored credentials: sendmails (PUT /v1/credentials/sendmails)._

AuthorizationBearer YOUR_JWT_TOKEN
list_uid{{sendmails_list_uid}}
per_page50
curl -X GET 'https://api.endpointr.com/v1/sendmails/sendmails-subscribers?list_uid=%7B%7Bsendmails_list_uid%7D%7D&per_page=50' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendmails/sendmails-subscribers?list_uid=%7B%7Bsendmails_list_uid%7D%7D&per_page=50', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendmails/sendmails-subscribers?list_uid=%7B%7Bsendmails_list_uid%7D%7D&per_page=50');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/sendmails/sendmails-subscribers

Add a subscriber. Fields are keyed by their Acelle identifier (the uppercase field key, e.g. EMAIL, FIRST_NAME, LAST_NAME, plus any custom fields). Labels go in the built-in tag field — SINGULAR, a comma-separated string like "vip,newsletter" (there is no tags array). Required: list_uid (from the lists tool) + EMAIL. Or fold {action:'subscribe'|'unsubscribe', uid} to flip an existing subscriber's status.

Required body: list_uid, EMAIL.

Minimal body: {"list_uid":"{{sendmails_list_uid}}","EMAIL":"alice@example.com"}

_Requires stored credentials: sendmails (PUT /v1/credentials/sendmails)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "list_uid": "{{sendmails_list_uid}}",
    "EMAIL": "alice@example.com",
    "FIRST_NAME": "Alice",
    "tag": "vip,newsletter"
}
curl -X POST 'https://api.endpointr.com/v1/sendmails/sendmails-subscribers' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "list_uid": "{{sendmails_list_uid}}",
    "EMAIL": "alice@example.com",
    "FIRST_NAME": "Alice",
    "tag": "vip,newsletter"
}'
const response = await fetch('https://api.endpointr.com/v1/sendmails/sendmails-subscribers', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "list_uid": "{{sendmails_list_uid}}",
      "EMAIL": "alice@example.com",
      "FIRST_NAME": "Alice",
      "tag": "vip,newsletter"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendmails/sendmails-subscribers');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"list_uid\": \"{{sendmails_list_uid}}\",\n    \"EMAIL\": \"alice@example.com\",\n    \"FIRST_NAME\": \"Alice\",\n    \"tag\": \"vip,newsletter\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/sendmails/sendmails-subscribers/:id

Update a subscriber's fields by uid (PATCH — writable). Send only the fields you want to change, keyed by their Acelle identifier (EMAIL, FIRST_NAME, …). Set labels via the tag field (SINGULAR, comma-separated string — NOT tags, not an array); a tag you send REPLACES the subscriber's tag string. Deliverability is the status field (subscribed/unsubscribed).

Minimal body: {"FIRST_NAME":"Alice (updated)","tag":"vip,newsletter","status":"subscribed"}

_Requires stored credentials: sendmails (PUT /v1/credentials/sendmails)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "FIRST_NAME": "Alice (updated)",
    "tag": "vip,newsletter",
    "status": "subscribed"
}
curl -X PUT 'https://api.endpointr.com/v1/sendmails/sendmails-subscribers/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "FIRST_NAME": "Alice (updated)",
    "tag": "vip,newsletter",
    "status": "subscribed"
}'
const response = await fetch('https://api.endpointr.com/v1/sendmails/sendmails-subscribers/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "FIRST_NAME": "Alice (updated)",
      "tag": "vip,newsletter",
      "status": "subscribed"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendmails/sendmails-subscribers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"FIRST_NAME\": \"Alice (updated)\",\n    \"tag\": \"vip,newsletter\",\n    \"status\": \"subscribed\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/sendmails/sendmails-subscribers/:id

Delete a subscriber by uid (removes them from the list entirely; prefer {action:'unsubscribe'} to keep the record).

_Requires stored credentials: sendmails (PUT /v1/credentials/sendmails)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/sendmails/sendmails-subscribers/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendmails/sendmails-subscribers/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendmails/sendmails-subscribers/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Outreach — Sendr

Sendr — Analyze website (personas)

POST/v1/sendr/sendr-analyze-website

Analyze a website and generate 2-5 buyer personas (name, tagline, bio, goals, pain points) + an about text. Required: url. Useful input for campaign copy and targeting.

Required body: url.

Minimal body: {"url":"https://example.com"}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "url": "https://example.com"
}
curl -X POST 'https://api.endpointr.com/v1/sendr/sendr-analyze-website' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "url": "https://example.com"
}'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-analyze-website', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "url": "https://example.com"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-analyze-website');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"url\": \"https://example.com\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sendr — Campaigns (sequences, read-only)

GET/v1/sendr/sendr-campaigns

Takes no parameters. Lists outreach campaigns with status (DRAFT|ACTIVE|PAUSED), stepsCount, contactsCount, and sheetId. Campaigns are read-only via the API (built in the Sendr app) — add contacts via the sheet-rows tool using the campaign's sheetId.

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-campaigns' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-campaigns', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-campaigns');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/sendr/sendr-campaigns/:id

One campaign by its numeric id (from the campaigns list).

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-campaigns/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-campaigns/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-campaigns/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/sendr/sendr-campaigns?status=ACTIVE

Filtered campaign list. Optional: search, status (DRAFT|ACTIVE|PAUSED), page, limit (max 100), sort, order.

Minimal query: {"status":"ACTIVE"}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
statusACTIVE
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-campaigns?status=ACTIVE' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-campaigns?status=ACTIVE', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-campaigns?status=ACTIVE');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sendr — Dynamic audio

POST/v1/sendr/sendr-dynamic-audio

Queue personalized-audio generation: targetWord in the source audio is replaced with replacementWord (voice-cloned). Required: audioUrl (public), targetWord, replacementWord. Optional: elevenlabsId, languageCode, webhookUrl. Returns {jobId}.

Required body: audioUrl, targetWord, replacementWord.

Minimal body: {"audioUrl":"https://example.com/audio.mp3","targetWord":"NAME","replacementWord":"Alice"}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "audioUrl": "https://example.com/audio.mp3",
    "targetWord": "NAME",
    "replacementWord": "Alice"
}
curl -X POST 'https://api.endpointr.com/v1/sendr/sendr-dynamic-audio' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "audioUrl": "https://example.com/audio.mp3",
    "targetWord": "NAME",
    "replacementWord": "Alice"
}'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-dynamic-audio', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "audioUrl": "https://example.com/audio.mp3",
      "targetWord": "NAME",
      "replacementWord": "Alice"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-dynamic-audio');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"audioUrl\": \"https://example.com/audio.mp3\",\n    \"targetWord\": \"NAME\",\n    \"replacementWord\": \"Alice\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sendr — Inbox (reply threads)

GET/v1/sendr/sendr-inbox

Takes no parameters. The 25 latest reply threads across channels; each result's id is the thread_id for messages/actions.

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-inbox' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-inbox', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-inbox');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/sendr/sendr-inbox/:id

One thread by its thread_id (from the list/query).

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-inbox/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-inbox/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-inbox/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/sendr/sendr-inbox?status=unread&limit=25

Filtered threads. Optional: search (min 3 chars), status (unread|deleted), channels, campaign_ids (from the inbox-campaigns tool), tag_ids (from the inbox-tags tool), assigned_to_seat_ids, unassigned, starred, cursor, limit (max 100).

Minimal query: {"status":"unread","limit":25}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
statusunread
limit25
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-inbox?status=unread&limit=25' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-inbox?status=unread&limit=25', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-inbox?status=unread&limit=25');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/sendr/sendr-inbox

Act on threads via action: send {thread_id, body} = reply in-thread; read/unread, star/unstar {thread_id}; tag/untag {thread_id, tag_id}; delete/restore {thread_ids:[…]} (bulk, max 100); assign {thread_ids:[…], seat_id} (null unassigns; seat ids via the seat tool).

Minimal body: {"action":"send","thread_id":"{{sendr_thread_id}}","body":"Thanks \u2014 following up with the details now."}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "action": "send",
    "thread_id": "{{sendr_thread_id}}",
    "body": "Thanks — following up with the details now."
}
curl -X POST 'https://api.endpointr.com/v1/sendr/sendr-inbox' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "action": "send",
    "thread_id": "{{sendr_thread_id}}",
    "body": "Thanks — following up with the details now."
}'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-inbox', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "action": "send",
      "thread_id": "{{sendr_thread_id}}",
      "body": "Thanks — following up with the details now."
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-inbox');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"action\": \"send\",\n    \"thread_id\": \"{{sendr_thread_id}}\",\n    \"body\": \"Thanks — following up with the details now.\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sendr — Inbox campaigns

GET/v1/sendr/sendr-inbox-campaigns

Takes no parameters. The campaigns that have inbox threads — the id source for the inbox campaign_ids filter.

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-inbox-campaigns' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-inbox-campaigns', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-inbox-campaigns');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sendr — Inbox messages + attachments

GET/v1/sendr/sendr-inbox-messages?thread_id=%7B%7Bsendr_thread_id%7D%7D&limit=50

A thread's messages. thread_id required (from the inbox list/query); optional cursor, limit. Add message_id for that message's attachment list; add attachment_id too to download it (returned as {base64, mime_type, bytes}). Reply via the inbox tool's 'send' action.

Required query: thread_id.

Minimal query: {"thread_id":"{{sendr_thread_id}}"}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
thread_id{{sendr_thread_id}}
limit50
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-inbox-messages?thread_id=%7B%7Bsendr_thread_id%7D%7D&limit=50' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-inbox-messages?thread_id=%7B%7Bsendr_thread_id%7D%7D&limit=50', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-inbox-messages?thread_id=%7B%7Bsendr_thread_id%7D%7D&limit=50');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sendr — Inbox tags

GET/v1/sendr/sendr-inbox-tags

Takes no parameters. Workspace inbox tags — each numeric id is the tag_id for the inbox tag/untag actions and the tag_ids filter.

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-inbox-tags' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-inbox-tags', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-inbox-tags');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/sendr/sendr-inbox-tags?search=hot

Tags filtered by search.

Minimal query: {"search":"hot"}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
searchhot
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-inbox-tags?search=hot' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-inbox-tags?search=hot', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-inbox-tags?search=hot');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/sendr/sendr-inbox-tags

Create a tag: name (required, max 50) + optional color.

Required body: name.

Minimal body: {"name":"hot-lead"}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "hot-lead",
    "color": "#f43f5e"
}
curl -X POST 'https://api.endpointr.com/v1/sendr/sendr-inbox-tags' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "hot-lead",
    "color": "#f43f5e"
}'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-inbox-tags', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "hot-lead",
      "color": "#f43f5e"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-inbox-tags');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"hot-lead\",\n    \"color\": \"#f43f5e\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/sendr/sendr-inbox-tags/:id

Edit a tag's name/color by its numeric id.

Minimal body: {"name":"warm-lead"}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "warm-lead"
}
curl -X PUT 'https://api.endpointr.com/v1/sendr/sendr-inbox-tags/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "warm-lead"
}'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-inbox-tags/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "warm-lead"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-inbox-tags/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"warm-lead\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/sendr/sendr-inbox-tags/:id

Delete a tag by its numeric id (cascade-removes it from all threads).

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/sendr/sendr-inbox-tags/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-inbox-tags/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-inbox-tags/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sendr — Page templates

GET/v1/sendr/sendr-page-templates

Takes no parameters. Lists your page templates — each id is the templateId the pages tool needs.

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-page-templates' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-page-templates', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-page-templates');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/sendr/sendr-page-templates/:id

A template's variable tags by template id. Use the tags as keys in the pages tool's variablesValues.

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-page-templates/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-page-templates/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-page-templates/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sendr — Personalized pages

GET/v1/sendr/sendr-pages/:id

A generated page's current state by pageId or slug (from the create response): eventStatus (pending|done|failed), generated assets (gifUrl, audioUrl, lipsyncVideo, backgroundScreenshot), processing flags, errorMessage.

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-pages/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-pages/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-pages/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/sendr/sendr-pages

Generate a personalized page from a template. Required: templateId (from the page-templates tool). Optional: variablesValues (keys = the template's variable tags), gifSource (landing-page|video-thumbnail|dynamic-website|linkedin-profile), gifWebsiteUrl (required when gifSource=dynamic-website), videoBackgroundUrl, videoBackgroundType (static|cursor|scroll), gifHyperlinkText, attributes (echoed in webhooks — e.g. {sheet, row}), webhookUrl (inline, THIS page only — use workspace webhooks for a standing subscription). Returns {pageId, pageUrl, pagePreviewUrl, warnings} — poll the get verb with pageId until eventStatus is done|failed.

Required body: templateId.

Minimal body: {"templateId":123}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "templateId": 123,
    "variablesValues": {
        "first_name": "Alice",
        "company": "Example ApS"
    }
}
curl -X POST 'https://api.endpointr.com/v1/sendr/sendr-pages' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "templateId": 123,
    "variablesValues": {
        "first_name": "Alice",
        "company": "Example ApS"
    }
}'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-pages', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "templateId": 123,
      "variablesValues": {
          "first_name": "Alice",
          "company": "Example ApS"
      }
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-pages');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"templateId\": 123,\n    \"variablesValues\": {\n        \"first_name\": \"Alice\",\n        \"company\": \"Example ApS\"\n    }\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sendr — Seat (API user)

GET/v1/sendr/sendr-seat

Takes no parameters. The API user's workspace/seat info — the smoke test after storing the api_key, and the seat-id source for inbox assignment.

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-seat' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-seat', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-seat');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sendr — Sheet columns (field schema)

GET/v1/sendr/sendr-sheet-columns?sheet_id=%7B%7Bsendr_sheet_id%7D%7D

A sheet's columns. sheet_id required (from the sheets list). Read this before adding rows — column names are the keys a row accepts; enrichment columns report run status.

Required query: sheet_id.

Minimal query: {"sheet_id":"{{sendr_sheet_id}}"}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
sheet_id{{sendr_sheet_id}}
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-sheet-columns?sheet_id=%7B%7Bsendr_sheet_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-sheet-columns?sheet_id=%7B%7Bsendr_sheet_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-sheet-columns?sheet_id=%7B%7Bsendr_sheet_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sendr — Sheet rows (add contacts)

POST/v1/sendr/sendr-sheet-rows

How you add a contact to a campaign: add a row to the campaign's sheet. sheet_id required (from the sheets list / a campaign's sheetId); every other key is a cell keyed by column name (see the sheet-columns tool). Returns {rowId, rowNumber}.

Required body: sheet_id.

Minimal body: {"sheet_id":"{{sendr_sheet_id}}"}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "sheet_id": "{{sendr_sheet_id}}",
    "Email": "alice@example.com",
    "First Name": "Alice",
    "Company": "Example ApS"
}
curl -X POST 'https://api.endpointr.com/v1/sendr/sendr-sheet-rows' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "sheet_id": "{{sendr_sheet_id}}",
    "Email": "alice@example.com",
    "First Name": "Alice",
    "Company": "Example ApS"
}'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-sheet-rows', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "sheet_id": "{{sendr_sheet_id}}",
      "Email": "alice@example.com",
      "First Name": "Alice",
      "Company": "Example ApS"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-sheet-rows');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"sheet_id\": \"{{sendr_sheet_id}}\",\n    \"Email\": \"alice@example.com\",\n    \"First Name\": \"Alice\",\n    \"Company\": \"Example ApS\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sendr — Sheets (contact tables)

GET/v1/sendr/sendr-sheets

Takes no parameters. Lists contact sheets; each result's id is the sheet_id the columns and rows tools need, and campaignId links it to a campaign.

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-sheets' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-sheets', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-sheets');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/sendr/sendr-sheets/:id

One sheet by its id (from the sheets list).

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-sheets/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-sheets/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-sheets/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/sendr/sendr-sheets?name=Leads&limit=50

Filtered sheet list. Optional: name, campaignId, offset, limit (20-1000), user, date.

Minimal query: {"name":"Leads","limit":50}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
nameLeads
limit50
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-sheets?name=Leads&limit=50' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-sheets?name=Leads&limit=50', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-sheets?name=Leads&limit=50');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sendr — Video generation

POST/v1/sendr/sendr-video

Queue personalized-video generation (dynamic audio merged into / lip-synced onto a source video). Required: audioUrl, targetWord, replacementWord, videoUrl (all public URLs). Optional: mode (merge|lipsync|video_only, default merge), elevenlabsId, languageCode, pageSlug, webhookUrl. Returns {jobId}.

Required body: audioUrl, targetWord, replacementWord, videoUrl.

Minimal body: {"audioUrl":"https://example.com/audio.mp3","targetWord":"NAME","replacementWord":"Alice","videoUrl":"https://example.com/video.mp4"}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "audioUrl": "https://example.com/audio.mp3",
    "targetWord": "NAME",
    "replacementWord": "Alice",
    "videoUrl": "https://example.com/video.mp4",
    "mode": "lipsync"
}
curl -X POST 'https://api.endpointr.com/v1/sendr/sendr-video' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "audioUrl": "https://example.com/audio.mp3",
    "targetWord": "NAME",
    "replacementWord": "Alice",
    "videoUrl": "https://example.com/video.mp4",
    "mode": "lipsync"
}'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-video', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "audioUrl": "https://example.com/audio.mp3",
      "targetWord": "NAME",
      "replacementWord": "Alice",
      "videoUrl": "https://example.com/video.mp4",
      "mode": "lipsync"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-video');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"audioUrl\": \"https://example.com/audio.mp3\",\n    \"targetWord\": \"NAME\",\n    \"replacementWord\": \"Alice\",\n    \"videoUrl\": \"https://example.com/video.mp4\",\n    \"mode\": \"lipsync\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Sendr — Workspace webhooks

GET/v1/sendr/sendr-webhooks

Takes no parameters. Lists workspace webhooks (page render + engagement events). Sendr keys webhooks by their url — that's the id the folded actions take.

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/sendr/sendr-webhooks' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-webhooks', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-webhooks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/sendr/sendr-webhooks

Create a webhook ({name, url, events?, attributes?}) — or a folded action keyed by url: {action:'update', url, …} (PATCH), {action:'delete', url}, {action:'toggle', url, enabled}, {action:'reveal_secret', url}. Event types: page:pending/done/failed, engagement:page_view/audio_play/video_play/button_click/video_emoji_click/video_comment/meeting_booked.

Minimal body: {"name":"Page events","url":"https://api.example.com/hooks/sendr","events":["page:done","engagement:page_view"]}

_Requires stored credentials: sendr (PUT /v1/credentials/sendr)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "Page events",
    "url": "https://api.example.com/hooks/sendr",
    "events": [
        "page:done",
        "engagement:page_view"
    ]
}
curl -X POST 'https://api.endpointr.com/v1/sendr/sendr-webhooks' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "Page events",
    "url": "https://api.example.com/hooks/sendr",
    "events": [
        "page:done",
        "engagement:page_view"
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/sendr/sendr-webhooks', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "Page events",
      "url": "https://api.example.com/hooks/sendr",
      "events": [
          "page:done",
          "engagement:page_view"
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/sendr/sendr-webhooks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"Page events\",\n    \"url\": \"https://api.example.com/hooks/sendr\",\n    \"events\": [\n        \"page:done\",\n        \"engagement:page_view\"\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads

Reddit Ads — Ad Account History

POST/v1/redditads/reddit-history

Change history for an ad account (who changed what, when) — a query-by-POST endpoint upstream, so it lives on create even though it only reads. Required: ad_account_id. Remaining fields are Reddit's filter body (entity types, date range), forwarded verbatim.

Required body: ad_account_id.

Minimal body: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "ad_account_id": "{{reddit_ad_account_id}}"
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-history' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "ad_account_id": "{{reddit_ad_account_id}}"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-history', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "ad_account_id": "{{reddit_ad_account_id}}"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-history');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"ad_account_id\": \"{{reddit_ad_account_id}}\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Ad Accounts

GET/v1/redditads/reddit-ad-accounts/:id

One ad account by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-ad-accounts/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-ad-accounts/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-ad-accounts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-ad-accounts?business_id=%7B%7Breddit_business_id%7D%7D

List ad accounts under a business. Required: business_id (from the businesses list). Each result's id is the ad_account_id the campaign/ad-group/ad/audience/report tools need. Paginate with page.size/page.token or a returned next_url.

Required query: business_id.

Minimal query: {"business_id":"{{reddit_business_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
business_id{{reddit_business_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-ad-accounts?business_id=%7B%7Breddit_business_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-ad-accounts?business_id=%7B%7Breddit_business_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-ad-accounts?business_id=%7B%7Breddit_business_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-ad-accounts

This is a SEARCH, not creation — Reddit's API cannot create ad accounts. Maps to POST /businesses/{business_id}/ad_accounts/query: a filtered ad-account search. Required: business_id; remaining fields are Reddit's filter body, forwarded verbatim.

Required body: business_id.

Minimal body: {"business_id":"{{reddit_business_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "business_id": "{{reddit_business_id}}"
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-ad-accounts' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "business_id": "{{reddit_business_id}}"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-ad-accounts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "business_id": "{{reddit_business_id}}"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-ad-accounts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"business_id\": \"{{reddit_business_id}}\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/redditads/reddit-ad-accounts/:id

Update ad-account fields (name, time_zone_id, …). PATCH upstream.

Minimal body: {"name":"Acme \u2014 Performance"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "Acme — Performance"
}
curl -X PUT 'https://api.endpointr.com/v1/redditads/reddit-ad-accounts/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "Acme — Performance"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-ad-accounts/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "Acme — Performance"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-ad-accounts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"Acme — Performance\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Ad Groups

GET/v1/redditads/reddit-ad-groups/:id

One ad group by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-ad-groups/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-ad-groups/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-ad-groups/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-ad-groups?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D

List ad groups under an ad account — the id source for ad-group get/update and ad_group_id on ad create. Required: ad_account_id.

Required query: ad_account_id.

Minimal query: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
ad_account_id{{reddit_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-ad-groups?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-ad-groups?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-ad-groups?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-ad-groups

Create an ad group (targeting, bid, schedule, budget live here). Required: ad_account_id; flat fields auto-wrapped as {data:{…}}. Reference the parent campaign_id; bids/budgets in microcurrency; validate targeting values with the reddit-targeting tools first.

Required body: ad_account_id.

Minimal body: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "ad_account_id": "{{reddit_ad_account_id}}",
    "campaign_id": "{{reddit_campaign_id}}",
    "name": "US — broad",
    "configured_status": "PAUSED",
    "bid_type": "CPC",
    "bid_value": 1500000,
    "goal_type": "DAILY_SPEND",
    "goal_value": 10000000
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-ad-groups' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "ad_account_id": "{{reddit_ad_account_id}}",
    "campaign_id": "{{reddit_campaign_id}}",
    "name": "US — broad",
    "configured_status": "PAUSED",
    "bid_type": "CPC",
    "bid_value": 1500000,
    "goal_type": "DAILY_SPEND",
    "goal_value": 10000000
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-ad-groups', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "ad_account_id": "{{reddit_ad_account_id}}",
      "campaign_id": "{{reddit_campaign_id}}",
      "name": "US — broad",
      "configured_status": "PAUSED",
      "bid_type": "CPC",
      "bid_value": 1500000,
      "goal_type": "DAILY_SPEND",
      "goal_value": 10000000
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-ad-groups');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"ad_account_id\": \"{{reddit_ad_account_id}}\",\n    \"campaign_id\": \"{{reddit_campaign_id}}\",\n    \"name\": \"US — broad\",\n    \"configured_status\": \"PAUSED\",\n    \"bid_type\": \"CPC\",\n    \"bid_value\": 1500000,\n    \"goal_type\": \"DAILY_SPEND\",\n    \"goal_value\": 10000000\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/redditads/reddit-ad-groups/:id

Update an ad group (PATCH upstream). Pause/resume/archive via configured_status — no delete.

Minimal body: {"configured_status":"PAUSED"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "configured_status": "PAUSED"
}
curl -X PUT 'https://api.endpointr.com/v1/redditads/reddit-ad-groups/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "configured_status": "PAUSED"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-ad-groups/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "configured_status": "PAUSED"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-ad-groups/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"configured_status\": \"PAUSED\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Ads

GET/v1/redditads/reddit-ads/:id

One ad by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-ads/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-ads/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-ads/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-ads?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D

List ads under an ad account. Required: ad_account_id.

Required query: ad_account_id.

Minimal query: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
ad_account_id{{reddit_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-ads?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-ads?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-ads?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-ads

Create an ad — binds a post to an ad group. Required: ad_account_id; reference ad_group_id + post_id (create the post first via reddit-posts). Flat fields auto-wrapped as {data:{…}}.

Required body: ad_account_id.

Minimal body: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "ad_account_id": "{{reddit_ad_account_id}}",
    "ad_group_id": "{{reddit_ad_group_id}}",
    "post_id": "{{reddit_post_id}}",
    "configured_status": "PAUSED"
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-ads' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "ad_account_id": "{{reddit_ad_account_id}}",
    "ad_group_id": "{{reddit_ad_group_id}}",
    "post_id": "{{reddit_post_id}}",
    "configured_status": "PAUSED"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-ads', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "ad_account_id": "{{reddit_ad_account_id}}",
      "ad_group_id": "{{reddit_ad_group_id}}",
      "post_id": "{{reddit_post_id}}",
      "configured_status": "PAUSED"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-ads');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"ad_account_id\": \"{{reddit_ad_account_id}}\",\n    \"ad_group_id\": \"{{reddit_ad_group_id}}\",\n    \"post_id\": \"{{reddit_post_id}}\",\n    \"configured_status\": \"PAUSED\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/redditads/reddit-ads/:id

Update an ad (PATCH upstream). Pause/resume/archive via configured_status — no delete.

Minimal body: {"configured_status":"PAUSED"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "configured_status": "PAUSED"
}
curl -X PUT 'https://api.endpointr.com/v1/redditads/reddit-ads/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "configured_status": "PAUSED"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-ads/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "configured_status": "PAUSED"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-ads/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"configured_status\": \"PAUSED\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Apps + SKAdNetwork

GET/v1/redditads/reddit-apps?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D

Mobile apps (for app-install campaigns). Required — exactly one of: ad_account_id (list the account's apps) | app_id (+ optional report: last_fired_at (default, MMP signal freshness) | skan_availability (SKAdNetwork)).

Minimal query: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
ad_account_id{{reddit_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-apps?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-apps?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-apps?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Businesses

GET/v1/redditads/reddit-businesses

Takes no parameters — start here. Lists the businesses the member belongs to; each result's id is the business_id the ad-accounts, pixels, profiles and product-catalogs tools need.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-businesses' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-businesses', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-businesses');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-businesses/:id

One business by id (from the businesses list).

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-businesses/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-businesses/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-businesses/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/redditads/reddit-businesses/:id

Update business fields (name, …). PATCH upstream — send only the fields to change.

Minimal body: {"name":"Acme Media"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "Acme Media"
}
curl -X PUT 'https://api.endpointr.com/v1/redditads/reddit-businesses/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "Acme Media"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-businesses/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "Acme Media"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-businesses/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"Acme Media\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Campaigns

GET/v1/redditads/reddit-campaigns/:id

One campaign by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-campaigns/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-campaigns/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-campaigns/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-campaigns?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D

List campaigns under an ad account — the id source for campaign get/update and campaign_id on ad-group create. Required: ad_account_id (from reddit-ad-accounts).

Required query: ad_account_id.

Minimal query: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
ad_account_id{{reddit_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-campaigns?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-campaigns?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-campaigns?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-campaigns

Create a campaign. Required: ad_account_id; flat campaign fields are auto-wrapped as Reddit's {data:{…}} envelope. objective: e.g. TRAFFIC | CONVERSIONS | AWARENESS_AND_REACH | APP_INSTALLS | CATALOG_SALES | LEAD_GENERATION | VIDEO_VIEWS. Start configured_status: PAUSED to review before spending; money fields are in microcurrency (5000000 = 5.00).

Required body: ad_account_id.

Minimal body: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "ad_account_id": "{{reddit_ad_account_id}}",
    "name": "Spring sale — traffic",
    "objective": "TRAFFIC",
    "configured_status": "PAUSED",
    "funding_instrument_id": "{{reddit_funding_instrument_id}}"
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-campaigns' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "ad_account_id": "{{reddit_ad_account_id}}",
    "name": "Spring sale — traffic",
    "objective": "TRAFFIC",
    "configured_status": "PAUSED",
    "funding_instrument_id": "{{reddit_funding_instrument_id}}"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-campaigns', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "ad_account_id": "{{reddit_ad_account_id}}",
      "name": "Spring sale — traffic",
      "objective": "TRAFFIC",
      "configured_status": "PAUSED",
      "funding_instrument_id": "{{reddit_funding_instrument_id}}"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-campaigns');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"ad_account_id\": \"{{reddit_ad_account_id}}\",\n    \"name\": \"Spring sale — traffic\",\n    \"objective\": \"TRAFFIC\",\n    \"configured_status\": \"PAUSED\",\n    \"funding_instrument_id\": \"{{reddit_funding_instrument_id}}\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/redditads/reddit-campaigns/:id

Update a campaign (PATCH upstream — send only the fields to change). Pause with {configured_status:"PAUSED"}, resume with ACTIVE, archive with ARCHIVED — there is no delete.

Minimal body: {"configured_status":"PAUSED"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "configured_status": "PAUSED"
}
curl -X PUT 'https://api.endpointr.com/v1/redditads/reddit-campaigns/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "configured_status": "PAUSED"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-campaigns/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "configured_status": "PAUSED"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-campaigns/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"configured_status\": \"PAUSED\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Catalog Imports

GET/v1/redditads/reddit-catalog-imports?catalog_id=%7B%7Breddit_catalog_id%7D%7D

Catalog import runs + diagnostics. Required — exactly one of: catalog_id (list the catalog's import runs) | import_id (+ optional view: issues (default) | report).

Minimal query: {"catalog_id":"{{reddit_catalog_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
catalog_id{{reddit_catalog_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-catalog-imports?catalog_id=%7B%7Breddit_catalog_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-catalog-imports?catalog_id=%7B%7Breddit_catalog_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-catalog-imports?catalog_id=%7B%7Breddit_catalog_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Creative Assets

GET/v1/redditads/reddit-creative-assets/:id

One creative asset by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-creative-assets/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-creative-assets/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-creative-assets/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-creative-assets?profile_id=%7B%7Breddit_profile_id%7D%7D

List a profile's uploaded creative media (images/video). Required: profile_id.

Required query: profile_id.

Minimal query: {"profile_id":"{{reddit_profile_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
profile_id{{reddit_profile_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-creative-assets?profile_id=%7B%7Breddit_profile_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-creative-assets?profile_id=%7B%7Breddit_profile_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-creative-assets?profile_id=%7B%7Breddit_profile_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Custom Audiences

GET/v1/redditads/reddit-custom-audiences/:id

One custom audience by id (size, status).

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-custom-audiences/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-custom-audiences/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-custom-audiences/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-custom-audiences?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D

List an ad account's custom (customer-list) audiences. Required: ad_account_id.

Required query: ad_account_id.

Minimal query: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
ad_account_id{{reddit_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-custom-audiences?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-custom-audiences?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-custom-audiences?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-custom-audiences

Create an empty custom audience, then fill it via update. Required: ad_account_id.

Required body: ad_account_id.

Minimal body: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "ad_account_id": "{{reddit_ad_account_id}}",
    "name": "Newsletter subscribers",
    "type": "CUSTOMER_LIST"
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-custom-audiences' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "ad_account_id": "{{reddit_ad_account_id}}",
    "name": "Newsletter subscribers",
    "type": "CUSTOMER_LIST"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-custom-audiences', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "ad_account_id": "{{reddit_ad_account_id}}",
      "name": "Newsletter subscribers",
      "type": "CUSTOMER_LIST"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-custom-audiences');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"ad_account_id\": \"{{reddit_ad_account_id}}\",\n    \"name\": \"Newsletter subscribers\",\n    \"type\": \"CUSTOMER_LIST\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/redditads/reddit-custom-audiences/:id

Updates the MEMBER LIST, not the audience fields — the only mutation Reddit exposes (PATCH /custom_audiences/{id}/users). Body: {action_type:"ADD"|"REMOVE", users:[…]} with SHA-256-hashed emails/MAIDs. There is no rename.

Minimal body: {"action_type":"ADD","users":[{"email":"<sha256-hashed email>"}]}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "action_type": "ADD",
    "users": [
        {
            "email": "<sha256-hashed email>"
        }
    ]
}
curl -X PUT 'https://api.endpointr.com/v1/redditads/reddit-custom-audiences/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "action_type": "ADD",
    "users": [
        {
            "email": "<sha256-hashed email>"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-custom-audiences/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "action_type": "ADD",
      "users": [
          {
              "email": "<sha256-hashed email>"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-custom-audiences/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"action_type\": \"ADD\",\n    \"users\": [\n        {\n            \"email\": \"<sha256-hashed email>\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/redditads/reddit-custom-audiences/:id

Delete a custom audience by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/redditads/reddit-custom-audiences/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-custom-audiences/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-custom-audiences/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Data Deletion

GET/v1/redditads/reddit-data-deletion-jobs/:id

Status of a deletion job by id (from create).

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-data-deletion-jobs/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-data-deletion-jobs/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-data-deletion-jobs/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-data-deletion-jobs

Submit an advertising-data deletion job (requires the adsdatadeletion scope). Required: ad_account_id; remaining fields forwarded per Reddit's deletion shape.

Required body: ad_account_id.

Minimal body: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "ad_account_id": "{{reddit_ad_account_id}}"
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-data-deletion-jobs' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "ad_account_id": "{{reddit_ad_account_id}}"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-data-deletion-jobs', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "ad_account_id": "{{reddit_ad_account_id}}"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-data-deletion-jobs');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"ad_account_id\": \"{{reddit_ad_account_id}}\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Forecasting

GET/v1/redditads/reddit-forecasts

Channel-planning reach estimate (GET /channel_planning/reach) — query params forwarded verbatim.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-forecasts' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-forecasts', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-forecasts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-forecasts

Generate a bid suggestion for a prospective ad group (POST /forecasting/bid_suggestions). Fields auto-wrapped as {data:{…}} and forwarded verbatim; returned bids in microcurrency.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
[]
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-forecasts' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '[]'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-forecasts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify([])
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-forecasts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '[]');
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Funding Instruments

GET/v1/redditads/reddit-funding-instruments?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D

Payment sources. Required — exactly one of: ad_account_id (list the account's funding instruments — campaign create needs a funding_instrument_id) | funding_instrument_id (its child allocations).

Minimal query: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
ad_account_id{{reddit_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-funding-instruments?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-funding-instruments?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-funding-instruments?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-funding-instruments

This is a SEARCH, not creation — maps to POST /businesses/{business_id}/funding_instruments/query. Required: business_id; remaining fields are the filter body, forwarded verbatim.

Required body: business_id.

Minimal body: {"business_id":"{{reddit_business_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "business_id": "{{reddit_business_id}}"
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-funding-instruments' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "business_id": "{{reddit_business_id}}"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-funding-instruments', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "business_id": "{{reddit_business_id}}"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-funding-instruments');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"business_id\": \"{{reddit_business_id}}\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Lead Gen Forms

GET/v1/redditads/reddit-lead-gen-forms/:id

One lead gen form by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-lead-gen-forms/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-lead-gen-forms/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-lead-gen-forms/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-lead-gen-forms?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D

List an ad account's lead generation forms. Required: ad_account_id.

Required query: ad_account_id.

Minimal query: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
ad_account_id{{reddit_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-lead-gen-forms?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-lead-gen-forms?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-lead-gen-forms?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-lead-gen-forms

Create a lead gen form (immutable once created — no update/delete upstream). Required: ad_account_id.

Required body: ad_account_id.

Minimal body: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "ad_account_id": "{{reddit_ad_account_id}}",
    "name": "Demo request form"
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-lead-gen-forms' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "ad_account_id": "{{reddit_ad_account_id}}",
    "name": "Demo request form"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-lead-gen-forms', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "ad_account_id": "{{reddit_ad_account_id}}",
      "name": "Demo request form"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-lead-gen-forms');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"ad_account_id\": \"{{reddit_ad_account_id}}\",\n    \"name\": \"Demo request form\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Lookups

GET/v1/redditads/reddit-lookups?resource=time_zones

Parameterless reference lists. Required: resource ∈ industries (business verticals) | time_zones (for time_zone_id fields).

Required query: resource.

Minimal query: {"resource":"time_zones"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
resourcetime_zones
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-lookups?resource=time_zones' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-lookups?resource=time_zones', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-lookups?resource=time_zones');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Me

GET/v1/redditads/reddit-me

Takes no parameters — the smoke test after connecting. The authenticated Reddit member (GET /me).

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-me' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-me', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-me');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Pixels + Conversions (CAPI)

GET/v1/redditads/reddit-pixels?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D

Reddit Pixels. Required — exactly one of: ad_account_id | business_id (list pixels) | pixel_id (its last-fired-at firing diagnostics).

Minimal query: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
ad_account_id{{reddit_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-pixels?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-pixels?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-pixels?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-pixels

Send server-side conversion events (Conversions API) to a pixel — requires the adsconversions scope. Required: pixel_id; the events payload is auto-wrapped as {data:{…}} and forwarded verbatim (event_at, event_type, user, event_metadata per Reddit's CAPI shape).

Required body: pixel_id.

Minimal body: {"pixel_id":"{{reddit_pixel_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "pixel_id": "{{reddit_pixel_id}}",
    "events": [
        {
            "event_at": "2026-01-01T12:00:00Z",
            "event_type": {
                "tracking_type": "Purchase"
            }
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-pixels' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "pixel_id": "{{reddit_pixel_id}}",
    "events": [
        {
            "event_at": "2026-01-01T12:00:00Z",
            "event_type": {
                "tracking_type": "Purchase"
            }
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-pixels', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "pixel_id": "{{reddit_pixel_id}}",
      "events": [
          {
              "event_at": "2026-01-01T12:00:00Z",
              "event_type": {
                  "tracking_type": "Purchase"
              }
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-pixels');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"pixel_id\": \"{{reddit_pixel_id}}\",\n    \"events\": [\n        {\n            \"event_at\": \"2026-01-01T12:00:00Z\",\n            \"event_type\": {\n                \"tracking_type\": \"Purchase\"\n            }\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Posts

GET/v1/redditads/reddit-posts/:id

One post by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-posts/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-posts/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-posts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-posts?profile_id=%7B%7Breddit_profile_id%7D%7D

List a profile's posts — the post_id source for ad create. Required: profile_id (from reddit-profiles).

Required query: profile_id.

Minimal query: {"profile_id":"{{reddit_profile_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
profile_id{{reddit_profile_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-posts?profile_id=%7B%7Breddit_profile_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-posts?profile_id=%7B%7Breddit_profile_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-posts?profile_id=%7B%7Breddit_profile_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-posts

Create the (ad) post an ad promotes. Required: profile_id; flat fields auto-wrapped as {data:{…}} — headline, body, destination_url, media assets (upload via Reddit's creative-assets flow first for image/video posts).

Required body: profile_id.

Minimal body: {"profile_id":"{{reddit_profile_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "profile_id": "{{reddit_profile_id}}",
    "headline": "Meet the new Acme 3000",
    "type": "TEXT"
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-posts' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "profile_id": "{{reddit_profile_id}}",
    "headline": "Meet the new Acme 3000",
    "type": "TEXT"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-posts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "profile_id": "{{reddit_profile_id}}",
      "headline": "Meet the new Acme 3000",
      "type": "TEXT"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-posts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"profile_id\": \"{{reddit_profile_id}}\",\n    \"headline\": \"Meet the new Acme 3000\",\n    \"type\": \"TEXT\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/redditads/reddit-posts/:id

Update a post (PATCH upstream — send only changed fields).

Minimal body: {"headline":"Updated headline"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "headline": "Updated headline"
}
curl -X PUT 'https://api.endpointr.com/v1/redditads/reddit-posts/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "headline": "Updated headline"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-posts/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "headline": "Updated headline"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-posts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"headline\": \"Updated headline\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Product Catalogs

GET/v1/redditads/reddit-product-catalogs/:id

One catalog by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-product-catalogs/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-catalogs/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-catalogs/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-product-catalogs?business_id=%7B%7Breddit_business_id%7D%7D

List a business's product catalogs — the catalog_id source for feeds/sets/products. Required: business_id.

Required query: business_id.

Minimal query: {"business_id":"{{reddit_business_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
business_id{{reddit_business_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-product-catalogs?business_id=%7B%7Breddit_business_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-catalogs?business_id=%7B%7Breddit_business_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-catalogs?business_id=%7B%7Breddit_business_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-product-catalogs

Create a product catalog under a business. Required: business_id.

Required body: business_id.

Minimal body: {"business_id":"{{reddit_business_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "business_id": "{{reddit_business_id}}",
    "name": "Web store catalog"
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-product-catalogs' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "business_id": "{{reddit_business_id}}",
    "name": "Web store catalog"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-catalogs', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "business_id": "{{reddit_business_id}}",
      "name": "Web store catalog"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-catalogs');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"business_id\": \"{{reddit_business_id}}\",\n    \"name\": \"Web store catalog\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/redditads/reddit-product-catalogs/:id

Update a catalog (PATCH upstream).

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
[]
curl -X PUT 'https://api.endpointr.com/v1/redditads/reddit-product-catalogs/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '[]'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-catalogs/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify([])
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-catalogs/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '[]');
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/redditads/reddit-product-catalogs/:id

Delete a catalog by id (its feeds/sets/products go with it).

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/redditads/reddit-product-catalogs/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-catalogs/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-catalogs/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Product Feeds

GET/v1/redditads/reddit-product-feeds/:id

One feed by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-product-feeds/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-feeds/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-feeds/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-product-feeds?catalog_id=%7B%7Breddit_catalog_id%7D%7D

List a catalog's scheduled product feeds. Required: catalog_id (from reddit-product-catalogs).

Required query: catalog_id.

Minimal query: {"catalog_id":"{{reddit_catalog_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
catalog_id{{reddit_catalog_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-product-feeds?catalog_id=%7B%7Breddit_catalog_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-feeds?catalog_id=%7B%7Breddit_catalog_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-feeds?catalog_id=%7B%7Breddit_catalog_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-product-feeds

Create a product feed (scheduled fetch of a product file). Required: catalog_id.

Required body: catalog_id.

Minimal body: {"catalog_id":"{{reddit_catalog_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "catalog_id": "{{reddit_catalog_id}}",
    "name": "Daily feed"
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-product-feeds' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "catalog_id": "{{reddit_catalog_id}}",
    "name": "Daily feed"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-feeds', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "catalog_id": "{{reddit_catalog_id}}",
      "name": "Daily feed"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-feeds');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"catalog_id\": \"{{reddit_catalog_id}}\",\n    \"name\": \"Daily feed\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/redditads/reddit-product-feeds/:id

Update a feed (PATCH upstream).

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
[]
curl -X PUT 'https://api.endpointr.com/v1/redditads/reddit-product-feeds/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '[]'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-feeds/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify([])
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-feeds/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '[]');
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/redditads/reddit-product-feeds/:id

Delete a feed by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/redditads/reddit-product-feeds/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-feeds/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-feeds/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Product Sets

GET/v1/redditads/reddit-product-sets/:id

One product set by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-product-sets/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-sets/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-sets/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-product-sets?catalog_id=%7B%7Breddit_catalog_id%7D%7D

List a catalog's product sets (filtered subsets ads can target). Required: catalog_id.

Required query: catalog_id.

Minimal query: {"catalog_id":"{{reddit_catalog_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
catalog_id{{reddit_catalog_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-product-sets?catalog_id=%7B%7Breddit_catalog_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-sets?catalog_id=%7B%7Breddit_catalog_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-sets?catalog_id=%7B%7Breddit_catalog_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-product-sets

Create a product set in a catalog. Required: catalog_id.

Required body: catalog_id.

Minimal body: {"catalog_id":"{{reddit_catalog_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "catalog_id": "{{reddit_catalog_id}}",
    "name": "Shoes under $100"
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-product-sets' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "catalog_id": "{{reddit_catalog_id}}",
    "name": "Shoes under $100"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-sets', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "catalog_id": "{{reddit_catalog_id}}",
      "name": "Shoes under $100"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-sets');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"catalog_id\": \"{{reddit_catalog_id}}\",\n    \"name\": \"Shoes under \$100\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/redditads/reddit-product-sets/:id

Update a product set (PATCH upstream).

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
[]
curl -X PUT 'https://api.endpointr.com/v1/redditads/reddit-product-sets/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '[]'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-sets/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify([])
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-sets/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '[]');
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/redditads/reddit-product-sets/:id

Delete a product set by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/redditads/reddit-product-sets/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-product-sets/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-product-sets/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Products

GET/v1/redditads/reddit-products?catalog_id=%7B%7Breddit_catalog_id%7D%7D

List products. Required — exactly one of: catalog_id (all products in a catalog) | product_set_id (products in a set).

Minimal query: {"catalog_id":"{{reddit_catalog_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
catalog_id{{reddit_catalog_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-products?catalog_id=%7B%7Breddit_catalog_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-products?catalog_id=%7B%7Breddit_catalog_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-products?catalog_id=%7B%7Breddit_catalog_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-products

Batch-write products — Reddit has no per-product CRUD. Required: catalog_id + products (array of product objects). Default upserts (batch_upsert); fold action:"delete" to batch-delete the listed products instead.

Required body: catalog_id, products.

Minimal body: {"catalog_id":"{{reddit_catalog_id}}","products":[{"id":"sku-123","title":"Acme runner","price":"99.00 USD","link":"https://example.com/p/sku-123"}]}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "catalog_id": "{{reddit_catalog_id}}",
    "products": [
        {
            "id": "sku-123",
            "title": "Acme runner",
            "price": "99.00 USD",
            "link": "https://example.com/p/sku-123"
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-products' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "catalog_id": "{{reddit_catalog_id}}",
    "products": [
        {
            "id": "sku-123",
            "title": "Acme runner",
            "price": "99.00 USD",
            "link": "https://example.com/p/sku-123"
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-products', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "catalog_id": "{{reddit_catalog_id}}",
      "products": [
          {
              "id": "sku-123",
              "title": "Acme runner",
              "price": "99.00 USD",
              "link": "https://example.com/p/sku-123"
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-products');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"catalog_id\": \"{{reddit_catalog_id}}\",\n    \"products\": [\n        {\n            \"id\": \"sku-123\",\n            \"title\": \"Acme runner\",\n            \"price\": \"99.00 USD\",\n            \"link\": \"https://example.com/p/sku-123\"\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Profiles

GET/v1/redditads/reddit-profiles/:id

One profile by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-profiles/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-profiles/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-profiles/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-profiles?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D

List profiles (the posting identities that own posts + creative assets). Required — exactly one of: ad_account_id | business_id. Each result's id is the profile_id the posts/structured-posts/creative-assets tools need.

Minimal query: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
ad_account_id{{reddit_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-profiles?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-profiles?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-profiles?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Reports

POST/v1/redditads/reddit-reports

Performance reporting — a query-by-POST endpoint upstream, so it lives on create even though it only reads. Required: ad_account_id. Report definition fields (auto-wrapped as {data:{…}}): starts_at/ends_at (ISO 8601 UTC), breakdowns (e.g. ["campaign_id","date"]), fields (metrics: spend, impressions, clicks, ctr, ecpm, conversion metrics…), optional filter, time_zone_id. Spend comes back in microcurrency. Paginated (pagination.next_url).

Required body: ad_account_id.

Minimal body: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "ad_account_id": "{{reddit_ad_account_id}}",
    "starts_at": "2026-01-01T00:00:00Z",
    "ends_at": "2026-01-08T00:00:00Z",
    "breakdowns": [
        "campaign_id",
        "date"
    ],
    "fields": [
        "spend",
        "impressions",
        "clicks",
        "ctr"
    ]
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-reports' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "ad_account_id": "{{reddit_ad_account_id}}",
    "starts_at": "2026-01-01T00:00:00Z",
    "ends_at": "2026-01-08T00:00:00Z",
    "breakdowns": [
        "campaign_id",
        "date"
    ],
    "fields": [
        "spend",
        "impressions",
        "clicks",
        "ctr"
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-reports', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "ad_account_id": "{{reddit_ad_account_id}}",
      "starts_at": "2026-01-01T00:00:00Z",
      "ends_at": "2026-01-08T00:00:00Z",
      "breakdowns": [
          "campaign_id",
          "date"
      ],
      "fields": [
          "spend",
          "impressions",
          "clicks",
          "ctr"
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-reports');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"ad_account_id\": \"{{reddit_ad_account_id}}\",\n    \"starts_at\": \"2026-01-01T00:00:00Z\",\n    \"ends_at\": \"2026-01-08T00:00:00Z\",\n    \"breakdowns\": [\n        \"campaign_id\",\n        \"date\"\n    ],\n    \"fields\": [\n        \"spend\",\n        \"impressions\",\n        \"clicks\",\n        \"ctr\"\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Saved Audiences

GET/v1/redditads/reddit-saved-audiences/:id

One saved audience by id.

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-saved-audiences/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-saved-audiences/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-saved-audiences/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-saved-audiences?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D

List an ad account's saved audiences (reusable targeting definitions). Required: ad_account_id.

Required query: ad_account_id.

Minimal query: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
ad_account_id{{reddit_ad_account_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-saved-audiences?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-saved-audiences?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-saved-audiences?ad_account_id=%7B%7Breddit_ad_account_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-saved-audiences

Create a saved audience — a named bundle of targeting (communities, interests, geos, devices; validate values with reddit-targeting first). Required: ad_account_id.

Required body: ad_account_id.

Minimal body: {"ad_account_id":"{{reddit_ad_account_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "ad_account_id": "{{reddit_ad_account_id}}",
    "name": "US mobile gamers"
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-saved-audiences' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "ad_account_id": "{{reddit_ad_account_id}}",
    "name": "US mobile gamers"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-saved-audiences', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "ad_account_id": "{{reddit_ad_account_id}}",
      "name": "US mobile gamers"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-saved-audiences');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"ad_account_id\": \"{{reddit_ad_account_id}}\",\n    \"name\": \"US mobile gamers\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/redditads/reddit-saved-audiences/:id

Update a saved audience (PATCH upstream).

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
[]
curl -X PUT 'https://api.endpointr.com/v1/redditads/reddit-saved-audiences/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '[]'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-saved-audiences/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify([])
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-saved-audiences/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '[]');
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Structured Posts

GET/v1/redditads/reddit-structured-posts/:id

One structured post by id (once the creation job finished).

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-structured-posts/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-structured-posts/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-structured-posts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/redditads/reddit-structured-posts?profile_id=%7B%7Breddit_profile_id%7D%7D

Structured (multi-asset/carousel) posts. Required — exactly one of: profile_id (list the profile's structured posts) | job_id (poll an async creation job from create).

Minimal query: {"profile_id":"{{reddit_profile_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
profile_id{{reddit_profile_id}}
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-structured-posts?profile_id=%7B%7Breddit_profile_id%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-structured-posts?profile_id=%7B%7Breddit_profile_id%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-structured-posts?profile_id=%7B%7Breddit_profile_id%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-structured-posts

Create a structured post — async: submits a creation job and returns a job id; poll it via this tool's query verb with job_id until done. Required: profile_id; flat fields auto-wrapped as {data:{…}}.

Required body: profile_id.

Minimal body: {"profile_id":"{{reddit_profile_id}}"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "profile_id": "{{reddit_profile_id}}"
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-structured-posts' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "profile_id": "{{reddit_profile_id}}"
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-structured-posts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "profile_id": "{{reddit_profile_id}}"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-structured-posts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"profile_id\": \"{{reddit_profile_id}}\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/redditads/reddit-structured-posts/:id

Update a structured post (PATCH upstream).

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
[]
curl -X PUT 'https://api.endpointr.com/v1/redditads/reddit-structured-posts/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '[]'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-structured-posts/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify([])
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-structured-posts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, '[]');
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Reddit Ads — Targeting Reference

GET/v1/redditads/reddit-targeting?resource=communities_search&query=gaming

Targeting reference data — one tool for every GET /targeting/* endpoint. Required: resource ∈ carriers | communities | communities_search | communities_suggestions | devices | geolocations | interests | languages | third_party_audiences. Remaining query params forwarded verbatim (e.g. names for communities, query for communities_search).

Required query: resource.

Minimal query: {"resource":"communities_search"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

AuthorizationBearer YOUR_JWT_TOKEN
resourcecommunities_search
querygaming
curl -X GET 'https://api.endpointr.com/v1/redditads/reddit-targeting?resource=communities_search&query=gaming' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-targeting?resource=communities_search&query=gaming', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-targeting?resource=communities_search&query=gaming');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/redditads/reddit-targeting

Targeting validators/suggesters — one tool for every POST /targeting/* endpoint. Required: action ∈ geolocations_validations | keyword_suggestions | keyword_validations. Remaining fields auto-wrapped as {data:{…}} and forwarded verbatim.

Required body: action.

Minimal body: {"action":"keyword_validations"}

_Requires stored credentials: reddit-ads (PUT /v1/credentials/reddit-ads)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "action": "keyword_validations",
    "keywords": [
        "running shoes"
    ]
}
curl -X POST 'https://api.endpointr.com/v1/redditads/reddit-targeting' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "action": "keyword_validations",
    "keywords": [
        "running shoes"
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/redditads/reddit-targeting', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "action": "keyword_validations",
      "keywords": [
          "running shoes"
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/redditads/reddit-targeting');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"action\": \"keyword_validations\",\n    \"keywords\": [\n        \"running shoes\"\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Rendering

HTML → PDF

POST/v1/rendering/html2-pdf

Render HTML or a URL to PDF via dompdf.

Minimal body: {"html":"<h1>Hello</h1>","paper_size":"A4","orientation":"portrait"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "html": "<h1>Hello</h1>",
    "paper_size": "A4",
    "orientation": "portrait"
}
curl -X POST 'https://api.endpointr.com/v1/rendering/html2-pdf' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "html": "<h1>Hello</h1>",
    "paper_size": "A4",
    "orientation": "portrait"
}'
const response = await fetch('https://api.endpointr.com/v1/rendering/html2-pdf', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "html": "<h1>Hello</h1>",
      "paper_size": "A4",
      "orientation": "portrait"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/rendering/html2-pdf');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"html\": \"<h1>Hello</h1>\",\n    \"paper_size\": \"A4\",\n    \"orientation\": \"portrait\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

PDF page → PNG

POST/v1/rendering/pdf

Render one page of a remote PDF to PNG (requires Imagick extension).

Required body: url.

Minimal body: {"url":"https://example.com/doc.pdf"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "url": "https://example.com/doc.pdf",
    "page": 0
}
curl -X POST 'https://api.endpointr.com/v1/rendering/pdf' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "url": "https://example.com/doc.pdf",
    "page": 0
}'
const response = await fetch('https://api.endpointr.com/v1/rendering/pdf', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "url": "https://example.com/doc.pdf",
      "page": 0
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/rendering/pdf');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"url\": \"https://example.com/doc.pdf\",\n    \"page\": 0\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

PhantomJS

POST/v1/rendering/phantom-js

All actions throw. Documented body shape retained for reference. The website action is superseded by POST /v1/rendering/screenshot.

Minimal body: {"action":"website","url":"https://example.com","width":1280,"height":720}

Note: Stub — PhantomJS is deprecated. For screenshots use POST /v1/rendering/screenshot.

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "action": "website",
    "url": "https://example.com",
    "width": 1280,
    "height": 720
}
curl -X POST 'https://api.endpointr.com/v1/rendering/phantom-js' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "action": "website",
    "url": "https://example.com",
    "width": 1280,
    "height": 720
}'
const response = await fetch('https://api.endpointr.com/v1/rendering/phantom-js', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "action": "website",
      "url": "https://example.com",
      "width": 1280,
      "height": 720
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/rendering/phantom-js');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"action\": \"website\",\n    \"url\": \"https://example.com\",\n    \"width\": 1280,\n    \"height\": 720\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Screenshot / thumbnail

POST/v1/rendering/screenshot

Screenshot a live web page. Built for thumbnails and link previews — the default returns a hosted PNG URL you can drop straight into an <img>.

Requires SCRAPEDO_API_KEY. Rendering is done by scrape.do on a real browser; without the key this returns 501. This is the working replacement for the website action of the dead PhantomJS handler.

Request:
- url (required) — public http/https page. SSRF-validated.
- width / height — viewport in px, 240-3840. Default 1280x720.
- full_page — capture the whole scroll height instead of the viewport. Default false.
- selector — CSS selector to capture just one element. Beats full_page when both are sent.
- devicedesktop (default) | mobile | tablet.
- thumbnail_width — downscale to this width before storing, aspect ratio preserved. Never upscales. Saves a round trip through /v1/graphics/resize.
- wait_ms — extra settle time after load, 0-15000. Use it for pages that animate in.
- geo_code — ISO-2 country for the proxy exit, e.g. dk. Defaults to SCRAPEDO_GEOCODE.
- super — residential/mobile proxy. Default false, deliberately: screenshots already force the rendered tier, and super multiplies the credit cost again. Turn it on only when a target actually blocks you.
- outputurl (default) | base64 | both.

Response. {source_url, format, mime, bytes, width, height, full_page, via} plus url + expires_in + expires_at (for url/both) and image_base64 (for base64/both).

TTL. Hosted images live on the same 24-hour volume as /v1/images/host and are swept hourly, so the URL is good for 24-25 hours. There is no cache — each call re-shoots the page and spends credits. Cache the URL your side.

Errors. 400 — bad input (unknown output/device, out-of-range dimensions, non-public url); validated before anything is billed. 501SCRAPEDO_API_KEY is not set on this deployment. 502 — the render itself failed; the reason is verbatim in error (dead token, exhausted credits, blocked target, selector matched nothing) and scrape.do's own status is in upstream.status. The upstream status is never forwarded as the HTTP status: a dead proxy token returns 401 upstream, and echoing that would read as your endpointr auth having failed.

Other example bodies.

Full-page capture, no hosting:

{"url":"https://example.com","full_page":true,"output":"base64"}

Just the hero block, on a mobile viewport:

{"url":"https://example.com","selector":"#hero","device":"mobile"}

A stubborn page behind Cloudflare, given time to settle:

{"url":"https://example.com","super":true,"wait_ms":3000,"geo_code":"dk"}

Minimal body: {"url":"https://example.com","width":1280,"height":720,"thumbnail_width":400}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "url": "https://example.com",
    "width": 1280,
    "height": 720,
    "thumbnail_width": 400
}
curl -X POST 'https://api.endpointr.com/v1/rendering/screenshot' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "url": "https://example.com",
    "width": 1280,
    "height": 720,
    "thumbnail_width": 400
}'
const response = await fetch('https://api.endpointr.com/v1/rendering/screenshot', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "url": "https://example.com",
      "width": 1280,
      "height": 720,
      "thumbnail_width": 400
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/rendering/screenshot');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"url\": \"https://example.com\",\n    \"width\": 1280,\n    \"height\": 720,\n    \"thumbnail_width\": 400\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Scrapers

Header Markdown

GET/v1/scrapers/header-markdown-extractor

List resources.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/scrapers/header-markdown-extractor' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/scrapers/header-markdown-extractor', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/scrapers/header-markdown-extractor');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/scrapers/header-markdown-extractor?url=https%3A%2F%2Fexample.com

Extract h1-h6 headers from a URL as Markdown.

Required query: url.

Minimal query: {"url":"https://example.com"}

AuthorizationBearer YOUR_JWT_TOKEN
urlhttps://example.com
curl -X GET 'https://api.endpointr.com/v1/scrapers/header-markdown-extractor?url=https%3A%2F%2Fexample.com' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/scrapers/header-markdown-extractor?url=https%3A%2F%2Fexample.com', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/scrapers/header-markdown-extractor?url=https%3A%2F%2Fexample.com');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Search Console

Search Console — Search Analytics

POST/v1/searchconsole/search-analytics

Query search performance for a property — clicks, impressions, CTR, position — sliced by the dimensions you request.

Required: siteUrl (selects the property; pulled into the path), startDate, endDate (both YYYY-MM-DD).

Common optional fields (forwarded verbatim to Search Console): dimensions (query|page|country|device|date|searchAppearance), type (web|image|video|news|discover|googleNews), dimensionFilterGroups, rowLimit (max 25000), startRow, dataState (final|all).

Auth — hybrid (see Sites). Scope: webmasters.readonly.

Other example body — top pages last month with a refresh-triplet:

{
  "refresh_token":"{{google_refresh_token}}",
  "client_id":"{{google_client_id}}",
  "client_secret":"{{google_client_secret}}",
  "siteUrl":"sc-domain:example.com",
  "startDate":"2026-05-01",
  "endDate":"2026-05-31",
  "dimensions":["page"],
  "rowLimit":100
}

Required body: siteUrl, startDate, endDate.

Minimal body: {"siteUrl":"https://example.com/","startDate":"2026-05-01","endDate":"2026-05-28"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "oauth_token": "{{google_oauth_token}}",
    "siteUrl": "https://example.com/",
    "startDate": "2026-05-01",
    "endDate": "2026-05-28",
    "dimensions": [
        "query"
    ],
    "rowLimit": 25
}
curl -X POST 'https://api.endpointr.com/v1/searchconsole/search-analytics' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "oauth_token": "{{google_oauth_token}}",
    "siteUrl": "https://example.com/",
    "startDate": "2026-05-01",
    "endDate": "2026-05-28",
    "dimensions": [
        "query"
    ],
    "rowLimit": 25
}'
const response = await fetch('https://api.endpointr.com/v1/searchconsole/search-analytics', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "oauth_token": "{{google_oauth_token}}",
      "siteUrl": "https://example.com/",
      "startDate": "2026-05-01",
      "endDate": "2026-05-28",
      "dimensions": [
          "query"
      ],
      "rowLimit": 25
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/searchconsole/search-analytics');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"oauth_token\": \"{{google_oauth_token}}\",\n    \"siteUrl\": \"https://example.com/\",\n    \"startDate\": \"2026-05-01\",\n    \"endDate\": \"2026-05-28\",\n    \"dimensions\": [\n        \"query\"\n    ],\n    \"rowLimit\": 25\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Search Console — Sitemaps

GET/v1/searchconsole/sitemaps?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&siteUrl=https%3A%2F%2Fexample.com%2F

List the sitemaps submitted for a property, or fetch one. Required: siteUrl. Add feedpath (the sitemap URL) to fetch a single sitemap's status instead of the list.

Auth — hybrid (see Sites). Scope: webmasters.readonly.

Single-sitemap query:

?oauth_token={{google_oauth_token}}&siteUrl=https://example.com/&feedpath=https://example.com/sitemap.xml

Required query: siteUrl.

Minimal query: {"siteUrl":"https://example.com/"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
siteUrlhttps://example.com/
curl -X GET 'https://api.endpointr.com/v1/searchconsole/sitemaps?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&siteUrl=https%3A%2F%2Fexample.com%2F' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/searchconsole/sitemaps?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&siteUrl=https%3A%2F%2Fexample.com%2F', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/searchconsole/sitemaps?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&siteUrl=https%3A%2F%2Fexample.com%2F');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Search Console — Sites

GET/v1/searchconsole/sites

List the verified sites on the authenticated Google account.

Auth — hybrid. Store creds once via PUT /v1/credentials/google and send nothing; or carry the OAuth bag per-request (?oauth_token=… or the refresh-triplet). Scope: webmasters.readonly.

Response. {data: {siteEntry: [{siteUrl, permissionLevel}, …]}, refreshed_access_token?}.

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/searchconsole/sites' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/searchconsole/sites', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/searchconsole/sites');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/searchconsole/sites?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&siteUrl=https%3A%2F%2Fexample.com%2F

Fetch a single site by siteUrl. siteUrl carries slashes, so it's passed as a query param (not a path segment) — e.g. https://example.com/ or sc-domain:example.com.

Required query: siteUrl.

Minimal query: {"siteUrl":"https://example.com/"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
siteUrlhttps://example.com/
curl -X GET 'https://api.endpointr.com/v1/searchconsole/sites?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&siteUrl=https%3A%2F%2Fexample.com%2F' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/searchconsole/sites?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&siteUrl=https%3A%2F%2Fexample.com%2F', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/searchconsole/sites?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&siteUrl=https%3A%2F%2Fexample.com%2F');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Search Console — URL Inspection

POST/v1/searchconsole/url-inspection

Inspect a single URL's index status on a verified property — coverage verdict, last crawl time, indexing state, mobile usability, rich-results.

Required: inspectionUrl (the page to inspect), siteUrl (the verified property it belongs to). Optional: languageCode (BCP-47, e.g. en-US).

Auth — hybrid (see Sites). Scope: webmasters.readonly.

Required body: inspectionUrl, siteUrl.

Minimal body: {"inspectionUrl":"https://example.com/some-page","siteUrl":"https://example.com/"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "oauth_token": "{{google_oauth_token}}",
    "inspectionUrl": "https://example.com/some-page",
    "siteUrl": "https://example.com/"
}
curl -X POST 'https://api.endpointr.com/v1/searchconsole/url-inspection' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "oauth_token": "{{google_oauth_token}}",
    "inspectionUrl": "https://example.com/some-page",
    "siteUrl": "https://example.com/"
}'
const response = await fetch('https://api.endpointr.com/v1/searchconsole/url-inspection', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "oauth_token": "{{google_oauth_token}}",
      "inspectionUrl": "https://example.com/some-page",
      "siteUrl": "https://example.com/"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/searchconsole/url-inspection');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"oauth_token\": \"{{google_oauth_token}}\",\n    \"inspectionUrl\": \"https://example.com/some-page\",\n    \"siteUrl\": \"https://example.com/\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Social

Leadshark — Automation status

PUT/v1/social/leadshark-automation-status/:id

Flip an automation's status by id (from the automations list) without a full update. Maps to PUT /api/automations/{id}/status.

Required body: status.

Minimal body: {"status":"Paused"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "status": "Paused"
}
curl -X PUT 'https://api.endpointr.com/v1/social/leadshark-automation-status/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "status": "Paused"
}'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-automation-status/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "status": "Paused"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-automation-status/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"status\": \"Paused\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Automation templates

GET/v1/social/leadshark-automation-templates?page=1&limit=20

No params needed (page/limit optional; newest first). Each id is the template_id you reuse as automation.template_id in scheduled-posts (then name/dm_template become optional).

Minimal query: {"page":"1","limit":"20"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
page1
limit20
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-automation-templates?page=1&limit=20' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-automation-templates?page=1&limit=20', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-automation-templates?page=1&limit=20');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Automations

GET/v1/social/leadshark-automations/:id

One automation by id (from the list), including its stats object.

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-automations/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-automations/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-automations/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/social/leadshark-automations?page=1&limit=10

List post-engagement automations (paginate with page, limit; each result carries a stats object). Each id feeds get/update/delete and the automation-status tool.

Minimal query: {"page":"1","limit":"10"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
page1
limit10
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-automations?page=1&limit=10' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-automations?page=1&limit=10', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-automations?page=1&limit=10');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/social/leadshark-automations

Create an automation bound to a LinkedIn post. Required: post_id (the full URN, e.g. urn:li:activity:7150…) + linkedin_post_url — BOTH come from the leadshark-posts tool (item.post_id + item.share_url). Use the full URN exactly (the numeric tail alone won't bind engagement). Optional: name, keywords[], dm_template, auto_connect, auto_like (Pro+/Apex), links_enabled, page_id (needs links_enabled), template_id, follow-up fields. Pass only {name, post_id, linkedin_post_url} for a webhook-only automation (comments stream to your webhooks; no LeadShark actions).

Required body: post_id, linkedin_post_url.

Minimal body: {"post_id":"urn:li:activity:7150123456789012345","linkedin_post_url":"https://www.linkedin.com/posts/..."}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "Guide Giveaway",
    "post_id": "urn:li:activity:7150123456789012345",
    "linkedin_post_url": "https://www.linkedin.com/posts/...",
    "keywords": [
        "interested"
    ],
    "dm_template": "Hi {{firstName}}! Here is the guide: https://example.com/guide"
}
curl -X POST 'https://api.endpointr.com/v1/social/leadshark-automations' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "Guide Giveaway",
    "post_id": "urn:li:activity:7150123456789012345",
    "linkedin_post_url": "https://www.linkedin.com/posts/...",
    "keywords": [
        "interested"
    ],
    "dm_template": "Hi {{firstName}}! Here is the guide: https://example.com/guide"
}'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-automations', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "Guide Giveaway",
      "post_id": "urn:li:activity:7150123456789012345",
      "linkedin_post_url": "https://www.linkedin.com/posts/...",
      "keywords": [
          "interested"
      ],
      "dm_template": "Hi {{firstName}}! Here is the guide: https://example.com/guide"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-automations');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"Guide Giveaway\",\n    \"post_id\": \"urn:li:activity:7150123456789012345\",\n    \"linkedin_post_url\": \"https://www.linkedin.com/posts/...\",\n    \"keywords\": [\n        \"interested\"\n    ],\n    \"dm_template\": \"Hi {{firstName}}! Here is the guide: https://example.com/guide\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/social/leadshark-automations/:id

Update an automation by id (from the list). Send only the fields to change.

Minimal body: {"keywords":["interested","yes"]}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "keywords": [
        "interested",
        "yes"
    ]
}
curl -X PUT 'https://api.endpointr.com/v1/social/leadshark-automations/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "keywords": [
        "interested",
        "yes"
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-automations/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "keywords": [
          "interested",
          "yes"
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-automations/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"keywords\": [\n        \"interested\",\n        \"yes\"\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/social/leadshark-automations/:id

Delete an automation by id.

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/social/leadshark-automations/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-automations/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-automations/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Bookmark tags

GET/v1/social/leadshark-bookmark-tags

Takes no parameters. All bookmark tags — the name/id source for filtering bookmarks.

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-bookmark-tags' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-bookmark-tags', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-bookmark-tags');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Bookmarks

GET/v1/social/leadshark-bookmarks

List saved LinkedIn profile bookmarks (tags + notes). Filter tags come from the leadshark-bookmark-tags tool.

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-bookmarks' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-bookmarks', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-bookmarks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/social/leadshark-bookmarks

Save a bookmark (LinkedIn profile + optional tags/notes).

Minimal body: {"linkedin_url":"https://www.linkedin.com/in/john-doe/","tags":["prospect"],"note":"Met at conf"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "linkedin_url": "https://www.linkedin.com/in/john-doe/",
    "tags": [
        "prospect"
    ],
    "note": "Met at conf"
}
curl -X POST 'https://api.endpointr.com/v1/social/leadshark-bookmarks' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "linkedin_url": "https://www.linkedin.com/in/john-doe/",
    "tags": [
        "prospect"
    ],
    "note": "Met at conf"
}'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-bookmarks', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "linkedin_url": "https://www.linkedin.com/in/john-doe/",
      "tags": [
          "prospect"
      ],
      "note": "Met at conf"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-bookmarks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"linkedin_url\": \"https://www.linkedin.com/in/john-doe/\",\n    \"tags\": [\n        \"prospect\"\n    ],\n    \"note\": \"Met at conf\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/social/leadshark-bookmarks/:id

Delete a bookmark by id (from the list).

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/social/leadshark-bookmarks/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-bookmarks/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-bookmarks/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Dashboard activity

GET/v1/social/leadshark-dashboard-activity?granularity=daily

Activity rollups (comments processed, DMs, connections, replies, leads) for exports. granularity: daily|weekly|monthly|all_time. after/before bound the window (ignored for all_time); optional cutoff returns before/on-after slices.

Minimal query: {"granularity":"daily"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
granularitydaily
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-dashboard-activity?granularity=daily' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-dashboard-activity?granularity=daily', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-dashboard-activity?granularity=daily');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Discover feed (Apex)

GET/v1/social/leadshark-discover

Apex. Fresh lead-magnet posts from the Lead Magnet Radar (public post + creator + engagement counts) — prospect-rich posts to engage.

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-discover' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-discover', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-discover');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Enrich company

GET/v1/social/leadshark-enrich-company?linkedin_url=https%3A%2F%2Fwww.linkedin.com%2Fcompany%2Facme%2F

Enrich a LinkedIn company. Pass the company identifier (company URL / handle), forwarded verbatim.

Minimal query: {"linkedin_url":"https://www.linkedin.com/company/acme/"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
linkedin_urlhttps://www.linkedin.com/company/acme/
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-enrich-company?linkedin_url=https%3A%2F%2Fwww.linkedin.com%2Fcompany%2Facme%2F' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-enrich-company?linkedin_url=https%3A%2F%2Fwww.linkedin.com%2Fcompany%2Facme%2F', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-enrich-company?linkedin_url=https%3A%2F%2Fwww.linkedin.com%2Fcompany%2Facme%2F');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Enrich person

GET/v1/social/leadshark-enrich-person?linkedin_url=https%3A%2F%2Fwww.linkedin.com%2Fin%2Fjohn-doe%2F

Enrich a LinkedIn person (real profile view; soft cap ~200-250/day). Pass the profile identifier Leadshark expects (e.g. linkedin_url / profile URL or username), forwarded verbatim.

Minimal query: {"linkedin_url":"https://www.linkedin.com/in/john-doe/"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
linkedin_urlhttps://www.linkedin.com/in/john-doe/
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-enrich-person?linkedin_url=https%3A%2F%2Fwww.linkedin.com%2Fin%2Fjohn-doe%2F' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-enrich-person?linkedin_url=https%3A%2F%2Fwww.linkedin.com%2Fin%2Fjohn-doe%2F', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-enrich-person?linkedin_url=https%3A%2F%2Fwww.linkedin.com%2Fin%2Fjohn-doe%2F');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Leads

GET/v1/social/leadshark-leads?page=1&limit=250

List leads captured from automations and post engagement (email + ICP score when present). Non-archived individuals only. Page via page/limit and response.pagination.has_more.

Minimal query: {"page":"1","limit":"250"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
page1
limit250
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-leads?page=1&limit=250' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-leads?page=1&limit=250', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-leads?page=1&limit=250');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — LinkedIn Posts

GET/v1/social/leadshark-posts?limit=25&page=1&linkedin_id=&raw=0

List your LinkedIn posts from Leadshark, returning {count, urls[]} (each post's share_url). Pass page + a small limit (e.g. 25) for one fast page — recommended. Omit page to auto-paginate all your posts in small pages (bounded to ~200; truncated:true if there are more). raw=1 returns full post objects (impressions/comments/reactions/reposts/created_at/…). linkedin_id targets another profile — omit it (don't send blank) for your own. Auth: vault leadshark default, OR per-request api_key passthrough.

Minimal query: {"page":"1","limit":"25"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
limit25
page1
linkedin_id
raw0
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-posts?limit=25&page=1&linkedin_id=&raw=0' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-posts?limit=25&page=1&linkedin_id=&raw=0', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-posts?limit=25&page=1&linkedin_id=&raw=0');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/social/leadshark-linkedin-search

Run a LinkedIn people/company search. Body is forwarded verbatim to POST /api/linkedin-search.

Minimal body: {"keywords":"head of marketing","limit":25}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "keywords": "head of marketing",
    "limit": 25
}
curl -X POST 'https://api.endpointr.com/v1/social/leadshark-linkedin-search' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "keywords": "head of marketing",
    "limit": 25
}'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-linkedin-search', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "keywords": "head of marketing",
      "limit": 25
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-linkedin-search');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"keywords\": \"head of marketing\",\n    \"limit\": 25\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Page emails (Pro+)

GET/v1/social/leadshark-page-emails?page_id=abc-123

Pro+. Deduped roll-up of every email captured for one Page across all its links (ideal for CRM export; use link-events for the raw per-link stream). page_id required. Each commenter_id joins to https://linkedin.com/in/{commenter_id}.

Required query: page_id.

Minimal query: {"page_id":"abc-123"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
page_idabc-123
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-page-emails?page_id=abc-123' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-page-emails?page_id=abc-123', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-page-emails?page_id=abc-123');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Page responses (Pro+)

GET/v1/social/leadshark-page-responses?page_id=abc-123

Pro+. One Page's quiz responses. page_id required (supports pagination).

Required query: page_id.

Minimal query: {"page_id":"abc-123"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
page_idabc-123
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-page-responses?page_id=abc-123' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-page-responses?page_id=abc-123', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-page-responses?page_id=abc-123');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Page stats (Pro+)

GET/v1/social/leadshark-page-stats?page_id=abc-123

Pro+. One Page's stats. page_id required (from the leadshark-pages tool).

Required query: page_id.

Minimal query: {"page_id":"abc-123"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
page_idabc-123
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-page-stats?page_id=abc-123' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-page-stats?page_id=abc-123', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-page-stats?page_id=abc-123');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Pages (Pro+)

GET/v1/social/leadshark-pages/:id

One page by id (from the list).

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-pages/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-pages/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-pages/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/social/leadshark-pages

Pro+. List lead-magnet quiz Pages. Each id is the page_id for links / scheduled-post pre-automations and the page-stats/responses/emails tools.

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-pages' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-pages', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-pages');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/social/leadshark-pages

Create a Page. Required: title. Optional status (published), page_title, page_description, questions[] (each with answers mapped to fit|maybe|not). Save the returned page.id.

Required body: title.

Minimal body: {"title":"LinkedIn Growth Playbook"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "title": "LinkedIn Growth Playbook",
    "status": "published",
    "page_title": "Get Your Free Playbook",
    "page_description": "The exact strategies I used",
    "questions": [
        {
            "question_text": "Your biggest challenge?",
            "answers": [
                {
                    "answer_text": "Engagement",
                    "mapped_outcome": "fit"
                },
                {
                    "answer_text": "Just browsing",
                    "mapped_outcome": "not"
                }
            ]
        }
    ]
}
curl -X POST 'https://api.endpointr.com/v1/social/leadshark-pages' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "title": "LinkedIn Growth Playbook",
    "status": "published",
    "page_title": "Get Your Free Playbook",
    "page_description": "The exact strategies I used",
    "questions": [
        {
            "question_text": "Your biggest challenge?",
            "answers": [
                {
                    "answer_text": "Engagement",
                    "mapped_outcome": "fit"
                },
                {
                    "answer_text": "Just browsing",
                    "mapped_outcome": "not"
                }
            ]
        }
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-pages', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "title": "LinkedIn Growth Playbook",
      "status": "published",
      "page_title": "Get Your Free Playbook",
      "page_description": "The exact strategies I used",
      "questions": [
          {
              "question_text": "Your biggest challenge?",
              "answers": [
                  {
                      "answer_text": "Engagement",
                      "mapped_outcome": "fit"
                  },
                  {
                      "answer_text": "Just browsing",
                      "mapped_outcome": "not"
                  }
              ]
          }
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-pages');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"title\": \"LinkedIn Growth Playbook\",\n    \"status\": \"published\",\n    \"page_title\": \"Get Your Free Playbook\",\n    \"page_description\": \"The exact strategies I used\",\n    \"questions\": [\n        {\n            \"question_text\": \"Your biggest challenge?\",\n            \"answers\": [\n                {\n                    \"answer_text\": \"Engagement\",\n                    \"mapped_outcome\": \"fit\"\n                },\n                {\n                    \"answer_text\": \"Just browsing\",\n                    \"mapped_outcome\": \"not\"\n                }\n            ]\n        }\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/social/leadshark-pages/:id

Update a Page by id.

Minimal body: {"page_description":"Updated"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "page_description": "Updated"
}
curl -X PUT 'https://api.endpointr.com/v1/social/leadshark-pages/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "page_description": "Updated"
}'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-pages/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "page_description": "Updated"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-pages/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"page_description\": \"Updated\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/social/leadshark-pages/:id

Delete a Page by id.

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/social/leadshark-pages/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-pages/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-pages/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Post comments

GET/v1/social/leadshark-post-comments?post_id=urn%3Ali%3Aactivity%3A7150123456789012345

Comments on a post (live from LinkedIn; Pro & up). post_id required (LinkedIn activity URN from leadshark-posts). Page with cursor.

Required query: post_id.

Minimal query: {"post_id":"urn:li:activity:7150123456789012345"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
post_idurn:li:activity:7150123456789012345
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-post-comments?post_id=urn%3Ali%3Aactivity%3A7150123456789012345' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-post-comments?post_id=urn%3Ali%3Aactivity%3A7150123456789012345', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-post-comments?post_id=urn%3Ali%3Aactivity%3A7150123456789012345');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Post reactions

GET/v1/social/leadshark-post-reactions?post_id=urn%3Ali%3Aactivity%3A7150123456789012345

Who reacted to a post (live from LinkedIn; Pro & up). post_id required — a LinkedIn activity URN from the leadshark-posts tool (item.post_id) or post-stats social_id. Page with cursor until pagination.has_more is false.

Required query: post_id.

Minimal query: {"post_id":"urn:li:activity:7150123456789012345"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
post_idurn:li:activity:7150123456789012345
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-post-reactions?post_id=urn%3Ali%3Aactivity%3A7150123456789012345' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-post-reactions?post_id=urn%3Ali%3Aactivity%3A7150123456789012345', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-post-reactions?post_id=urn%3Ali%3Aactivity%3A7150123456789012345');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Post reposts

GET/v1/social/leadshark-post-reposts?post_id=urn%3Ali%3Aactivity%3A7150123456789012345

Who reposted a post (live from LinkedIn; Pro & up). post_id required (URN from leadshark-posts). ~10/page via cursor; space calls 1-2s apart.

Required query: post_id.

Minimal query: {"post_id":"urn:li:activity:7150123456789012345"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
post_idurn:li:activity:7150123456789012345
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-post-reposts?post_id=urn%3Ali%3Aactivity%3A7150123456789012345' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-post-reposts?post_id=urn%3Ali%3Aactivity%3A7150123456789012345', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-post-reposts?post_id=urn%3Ali%3Aactivity%3A7150123456789012345');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Post stats

GET/v1/social/leadshark-post-stats?summary=true

Post metrics. List mode is deprecated upstream (prefer the leadshark-posts tool). summary=true returns lifetime totals across your last N posts (Apex); max_posts caps the walk and next_cursor (as cursor) continues if truncated. Each post's social_id (URN) is the post_id the engagement tools take.

Minimal query: {"summary":"true"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
summarytrue
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-post-stats?summary=true' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-post-stats?summary=true', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-post-stats?summary=true');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Profile viewers (Apex)

GET/v1/social/leadshark-profile-viewers

Apex + Premium LinkedIn. Recent profile viewers. Page with cursor. Non-Apex keys 403; non-Premium LinkedIn 403s premium_required.

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-profile-viewers' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-profile-viewers', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-profile-viewers');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Scheduled posts

GET/v1/social/leadshark-scheduled-posts

List scheduled LinkedIn posts. Each id feeds update/delete.

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-scheduled-posts' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-scheduled-posts', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-scheduled-posts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/social/leadshark-scheduled-posts

Schedule a post. Required: content + scheduled_time (ISO 8601; 15 min-90 days out). Optional automation object (pre-automation) creates an automation on publish — it accepts page_id (from leadshark-pages) with links_enabled:true, or template_id (from leadshark-automation-templates). Use multipart/form-data for file attachments.

Required body: content, scheduled_time.

Minimal body: {"content":"Comment PLAYBOOK and I will DM it to you!","scheduled_time":"2026-04-25T14:00:00Z"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "content": "Comment PLAYBOOK and I will DM it to you!",
    "scheduled_time": "2026-04-25T14:00:00Z",
    "automation": {
        "name": "Playbook Giveaway",
        "keywords": [
            "playbook"
        ],
        "dm_template": "Hey {{firstName}}! Here: https://example.com/playbook",
        "links_enabled": true,
        "page_id": "abc-123"
    }
}
curl -X POST 'https://api.endpointr.com/v1/social/leadshark-scheduled-posts' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "content": "Comment PLAYBOOK and I will DM it to you!",
    "scheduled_time": "2026-04-25T14:00:00Z",
    "automation": {
        "name": "Playbook Giveaway",
        "keywords": [
            "playbook"
        ],
        "dm_template": "Hey {{firstName}}! Here: https://example.com/playbook",
        "links_enabled": true,
        "page_id": "abc-123"
    }
}'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-scheduled-posts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "content": "Comment PLAYBOOK and I will DM it to you!",
      "scheduled_time": "2026-04-25T14:00:00Z",
      "automation": {
          "name": "Playbook Giveaway",
          "keywords": [
              "playbook"
          ],
          "dm_template": "Hey {{firstName}}! Here: https://example.com/playbook",
          "links_enabled": true,
          "page_id": "abc-123"
      }
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-scheduled-posts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"content\": \"Comment PLAYBOOK and I will DM it to you!\",\n    \"scheduled_time\": \"2026-04-25T14:00:00Z\",\n    \"automation\": {\n        \"name\": \"Playbook Giveaway\",\n        \"keywords\": [\n            \"playbook\"\n        ],\n        \"dm_template\": \"Hey {{firstName}}! Here: https://example.com/playbook\",\n        \"links_enabled\": true,\n        \"page_id\": \"abc-123\"\n    }\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/social/leadshark-scheduled-posts/:id

Edit a scheduled post by id (from the list). Cannot edit within 15 min of publish, or after it published.

Minimal body: {"content":"Updated copy"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "content": "Updated copy"
}
curl -X PUT 'https://api.endpointr.com/v1/social/leadshark-scheduled-posts/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "content": "Updated copy"
}'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-scheduled-posts/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "content": "Updated copy"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-scheduled-posts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"content\": \"Updated copy\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/social/leadshark-scheduled-posts/:id

Cancel a scheduled post by id.

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/social/leadshark-scheduled-posts/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-scheduled-posts/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-scheduled-posts/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Signal events (Apex)

GET/v1/social/leadshark-signal-events?page=1&type=comment

Apex. Raw engagement signal events. type: comment|reaction|repost|profile_view|lead_magnet_click|dm_sent|comment_reply|connection_accepted|connection_sent|automation_engagement. since/until filter by engagement date; since_captured/until_captured by when we detected it (best for what-is-new-this-week, especially reactions/reposts). total_pages pagination.

Minimal query: {"page":"1","type":"comment"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
page1
typecomment
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-signal-events?page=1&type=comment' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-signal-events?page=1&type=comment', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-signal-events?page=1&type=comment');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Signals / hot leads (Apex)

GET/v1/social/leadshark-signals?page=1

Apex. Your ranked hot leads (deduped, heat-scored roll-up across all tracked posts). total_pages pagination — increment page until page == total_pages.

Minimal query: {"page":"1"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
page1
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-signals?page=1' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-signals?page=1', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-signals?page=1');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/social/leadshark-links

Create a tracking link. automation_id from leadshark-automations; page_id from leadshark-pages routes visitors through that Page.

Minimal body: {"url":"https://example.com/guide","automation_id":"...","page_id":"abc-123"}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "url": "https://example.com/guide",
    "automation_id": "...",
    "page_id": "abc-123"
}
curl -X POST 'https://api.endpointr.com/v1/social/leadshark-links' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "url": "https://example.com/guide",
    "automation_id": "...",
    "page_id": "abc-123"
}'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-links', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "url": "https://example.com/guide",
      "automation_id": "...",
      "page_id": "abc-123"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-links');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"url\": \"https://example.com/guide\",\n    \"automation_id\": \"...\",\n    \"page_id\": \"abc-123\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Webhook test

POST/v1/social/leadshark-webhook-test

Send a test event to a webhook. webhook_id required (from the leadshark-webhooks tool). Maps to POST /api/v1/webhooks/{id}/test.

Required body: webhook_id.

Minimal body: {"webhook_id":"..."}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "webhook_id": "..."
}
curl -X POST 'https://api.endpointr.com/v1/social/leadshark-webhook-test' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "webhook_id": "..."
}'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-webhook-test', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "webhook_id": "..."
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-webhook-test');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"webhook_id\": \"...\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Leadshark — Webhooks

GET/v1/social/leadshark-webhooks/:id

One webhook by id.

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-webhooks/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-webhooks/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-webhooks/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/social/leadshark-webhooks

List webhooks. Each id feeds get/update/delete and the webhook-test tool.

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/social/leadshark-webhooks' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-webhooks', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-webhooks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/social/leadshark-webhooks

Create a webhook (HMAC-SHA256 signed deliveries). event_types: new_comment, email_captured, lead_sent (Pro) + new_profile_visit, new_like (Apex only — non-Apex 403 apex_only_event_types). The signing secret is returned ONCE at creation.

Minimal body: {"target_url":"https://api.example.com/hooks/leadshark","event_types":["new_comment","email_captured"]}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "target_url": "https://api.example.com/hooks/leadshark",
    "event_types": [
        "new_comment",
        "email_captured"
    ]
}
curl -X POST 'https://api.endpointr.com/v1/social/leadshark-webhooks' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "target_url": "https://api.example.com/hooks/leadshark",
    "event_types": [
        "new_comment",
        "email_captured"
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-webhooks', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "target_url": "https://api.example.com/hooks/leadshark",
      "event_types": [
          "new_comment",
          "email_captured"
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-webhooks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"target_url\": \"https://api.example.com/hooks/leadshark\",\n    \"event_types\": [\n        \"new_comment\",\n        \"email_captured\"\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/social/leadshark-webhooks/:id

Update a webhook by id. Pass {rotate_secret:true} to rotate the signing secret (returned once in the response).

Minimal body: {"event_types":["new_comment"]}

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "event_types": [
        "new_comment"
    ]
}
curl -X PUT 'https://api.endpointr.com/v1/social/leadshark-webhooks/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "event_types": [
        "new_comment"
    ]
}'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-webhooks/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "event_types": [
          "new_comment"
      ]
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-webhooks/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"event_types\": [\n        \"new_comment\"\n    ]\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/social/leadshark-webhooks/:id

Delete a webhook by id.

_Requires stored credentials: leadshark (PUT /v1/credentials/leadshark)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/social/leadshark-webhooks/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/social/leadshark-webhooks/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/social/leadshark-webhooks/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Tasks

Google Tasks — Tasklists

GET/v1/tasks/google-tasklists/:id

Fetch a single tasklist by id. Auth in query (?oauth_token=… or refresh triplet).

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tasks/google-tasklists/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tasks/google-tasklists/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tasks/google-tasklists/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/tasks/google-tasklists?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D

List the authenticated Google account's task lists.

Auth — hybrid (vault OR per-request).

Vault mode (recommended). Store creds once via PUT /v1/credentials/google with {client_id, client_secret, refresh_token} (or use the admin UI). Then *omit credentials from the request entirely* — Endpointr will mint a fresh access_token from the stored refresh_token on every call:

# nothing to add to the URL — the vault provides creds

Per-request mode. Carry the OAuth bag on every call. Useful when the credentials live in a client-side store (mobile/browser) rather than the server.

1. Direct access_token (cheapest — skips the token exchange):

?oauth_token={{google_oauth_token}}

2. Refresh-token triplet — Endpointr exchanges it via https://oauth2.googleapis.com/token, uses the access_token for the upstream call, and surfaces the new access_token in data.refreshed_access_token so the client can cache it:

?refresh_token={{google_refresh_token}}&client_id={{google_client_id}}&client_secret={{google_client_secret}}

Resolution order. Per-request oauth_token → per-request refresh-triplet → vault refresh-triplet → 400.

Required OAuth scope: https://www.googleapis.com/auth/tasks.

Response. {data: <google-payload>, refreshed_access_token?: <new>, expires_in?: <seconds>, scope?: <granted>}. Unwrap data to get the upstream Google body.

Minimal query: {"oauth_token":"{{google_oauth_token}}"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
curl -X GET 'https://api.endpointr.com/v1/tasks/google-tasklists?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tasks/google-tasklists?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tasks/google-tasklists?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tasks/google-tasklists

Create a new tasklist. Auth in body alongside the upstream fields.

Other example bodies.

Using refresh-triplet (no fresh access_token on hand):

{
  "refresh_token":"{{google_refresh_token}}",
  "client_id":"{{google_client_id}}",
  "client_secret":"{{google_client_secret}}",
  "title":"My new list"
}

Required body: title.

Minimal body: {"title":"My new list"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "oauth_token": "{{google_oauth_token}}",
    "title": "My new list"
}
curl -X POST 'https://api.endpointr.com/v1/tasks/google-tasklists' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "oauth_token": "{{google_oauth_token}}",
    "title": "My new list"
}'
const response = await fetch('https://api.endpointr.com/v1/tasks/google-tasklists', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "oauth_token": "{{google_oauth_token}}",
      "title": "My new list"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tasks/google-tasklists');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"oauth_token\": \"{{google_oauth_token}}\",\n    \"title\": \"My new list\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/tasks/google-tasklists/:id

Update a tasklist (PATCH upstream). Only the fields you send are touched.

Minimal body: {"oauth_token":"{{google_oauth_token}}","title":"Renamed list"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "oauth_token": "{{google_oauth_token}}",
    "title": "Renamed list"
}
curl -X PUT 'https://api.endpointr.com/v1/tasks/google-tasklists/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "oauth_token": "{{google_oauth_token}}",
    "title": "Renamed list"
}'
const response = await fetch('https://api.endpointr.com/v1/tasks/google-tasklists/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "oauth_token": "{{google_oauth_token}}",
      "title": "Renamed list"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tasks/google-tasklists/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"oauth_token\": \"{{google_oauth_token}}\",\n    \"title\": \"Renamed list\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/tasks/google-tasklists/:id?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D

Delete a tasklist. Auth in query string.

Minimal query: {"oauth_token":"{{google_oauth_token}}"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
curl -X DELETE 'https://api.endpointr.com/v1/tasks/google-tasklists/:id?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tasks/google-tasklists/:id?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tasks/google-tasklists/:id?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Google Tasks — Tasks

GET/v1/tasks/google-tasks/:id?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&tasklist=%40default

Fetch a single task. Required: tasklist + auth in query.

Minimal query: {"oauth_token":"{{google_oauth_token}}","tasklist":"@default"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
tasklist@default
curl -X GET 'https://api.endpointr.com/v1/tasks/google-tasks/:id?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&tasklist=%40default' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tasks/google-tasks/:id?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&tasklist=%40default', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tasks/google-tasks/:id?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&tasklist=%40default');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/tasks/google-tasks?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&tasklist=%40default&showCompleted=false

List tasks within a tasklist. Required: tasklist (use @default for the user's primary list, or any id from /v1/tasks/google-tasklists).

Auth — hybrid. See *Google Tasks — Tasklists* for the full resolution order. Short version: store creds once at /v1/credentials/google and tasklist is the only required field below; or carry the OAuth bag per-request as shown.

Forwarded filter params: showCompleted, showHidden, showDeleted, dueMin, dueMax, completedMin, completedMax, updatedMin, maxResults, pageToken.

Other example queries.

Open tasks only, due in the next 7 days:

?oauth_token={{google_oauth_token}}&tasklist=@default&showCompleted=false&dueMax=2026-05-05T23:59:59.000Z

Next page (cursor from previous response's nextPageToken):

?oauth_token={{google_oauth_token}}&tasklist=@default&pageToken=<from-prev-response>

Required query: tasklist.

Minimal query: {"tasklist":"@default"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
tasklist@default
showCompletedfalse
curl -X GET 'https://api.endpointr.com/v1/tasks/google-tasks?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&tasklist=%40default&showCompleted=false' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tasks/google-tasks?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&tasklist=%40default&showCompleted=false', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tasks/google-tasks?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&tasklist=%40default&showCompleted=false');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tasks/google-tasks

Create a task in a tasklist. Required: tasklist, title.

Optional fields: notes, due (RFC3339 timestamp — Google ignores time-of-day, only the date part counts), status (needsAction | completed), parent (sub-task), previous (insert position).

Other example bodies.

Sub-task under a parent:

{
  "oauth_token":"{{google_oauth_token}}",
  "tasklist":"@default",
  "parent":"<parent-task-id>",
  "title":"Whole milk",
  "notes":"2L"
}

Using the refresh triplet (no fresh access_token):

{
  "refresh_token":"{{google_refresh_token}}",
  "client_id":"{{google_client_id}}",
  "client_secret":"{{google_client_secret}}",
  "tasklist":"@default",
  "title":"Buy milk",
  "due":"2026-05-01T00:00:00.000Z"
}

Required body: tasklist, title.

Minimal body: {"tasklist":"@default","title":"Buy milk"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "oauth_token": "{{google_oauth_token}}",
    "tasklist": "@default",
    "title": "Buy milk",
    "notes": "Whole milk, 2L",
    "due": "2026-05-01T00:00:00.000Z"
}
curl -X POST 'https://api.endpointr.com/v1/tasks/google-tasks' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "oauth_token": "{{google_oauth_token}}",
    "tasklist": "@default",
    "title": "Buy milk",
    "notes": "Whole milk, 2L",
    "due": "2026-05-01T00:00:00.000Z"
}'
const response = await fetch('https://api.endpointr.com/v1/tasks/google-tasks', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "oauth_token": "{{google_oauth_token}}",
      "tasklist": "@default",
      "title": "Buy milk",
      "notes": "Whole milk, 2L",
      "due": "2026-05-01T00:00:00.000Z"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tasks/google-tasks');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"oauth_token\": \"{{google_oauth_token}}\",\n    \"tasklist\": \"@default\",\n    \"title\": \"Buy milk\",\n    \"notes\": \"Whole milk, 2L\",\n    \"due\": \"2026-05-01T00:00:00.000Z\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/tasks/google-tasks/:id

Update a task (PATCH upstream — partial; only sent fields change). Required: tasklist.

Common updates:
- status: "completed" → check it off (Google also stamps completed automatically)
- status: "needsAction" → un-check
- due → reschedule
- title / notes → edit text

Other example bodies.

Un-check a previously completed task:

{"oauth_token":"{{google_oauth_token}}","tasklist":"@default","status":"needsAction"}

Reschedule:

{"oauth_token":"{{google_oauth_token}}","tasklist":"@default","due":"2026-05-08T00:00:00.000Z"}

Required body: tasklist.

Minimal body: {"tasklist":"@default"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "oauth_token": "{{google_oauth_token}}",
    "tasklist": "@default",
    "status": "completed"
}
curl -X PUT 'https://api.endpointr.com/v1/tasks/google-tasks/:id' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "oauth_token": "{{google_oauth_token}}",
    "tasklist": "@default",
    "status": "completed"
}'
const response = await fetch('https://api.endpointr.com/v1/tasks/google-tasks/:id', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "oauth_token": "{{google_oauth_token}}",
      "tasklist": "@default",
      "status": "completed"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tasks/google-tasks/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"oauth_token\": \"{{google_oauth_token}}\",\n    \"tasklist\": \"@default\",\n    \"status\": \"completed\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/tasks/google-tasks/:id?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&tasklist=%40default

Delete a task. Required: tasklist + auth in query.

Minimal query: {"oauth_token":"{{google_oauth_token}}","tasklist":"@default"}

AuthorizationBearer YOUR_JWT_TOKEN
oauth_token{{google_oauth_token}}
tasklist@default
curl -X DELETE 'https://api.endpointr.com/v1/tasks/google-tasks/:id?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&tasklist=%40default' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tasks/google-tasks/:id?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&tasklist=%40default', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tasks/google-tasks/:id?oauth_token=%7B%7Bgoogle_oauth_token%7D%7D&tasklist=%40default');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

TidyCal

TidyCal — Account (me)

GET/v1/tidycal/tidy-cal-me

Your TidyCal account profile (GET /me): name, email, timezone, language, vanity path, currency symbol and lifetime-pro status. No parameters. Multi-account: pass account to choose which stored credential to read.

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tidycal/tidy-cal-me' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-me', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-me');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

TidyCal — Accounts (labels)

GET/v1/tidycal/tidy-cal-accounts

List the TidyCal account labels configured for this customer (stored as tidycal:<label>). Returns {accounts:[…], has_default:bool} — never secrets. Pass one of these labels as the account selector on the other TidyCal tools. This tool takes no account selector.

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tidycal/tidy-cal-accounts' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-accounts', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-accounts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

TidyCal — Booking Types

GET/v1/tidycal/tidy-cal-booking-types?page=1

List your booking types — the scheduling pages you offer (GET /booking-types). Only page is supported. Each type's id is the booking_type_id the bookings + timeslots tools need.

Minimal query: {"page":"1"}

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

AuthorizationBearer YOUR_JWT_TOKEN
page1
curl -X GET 'https://api.endpointr.com/v1/tidycal/tidy-cal-booking-types?page=1' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-booking-types?page=1', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-booking-types?page=1');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tidycal/tidy-cal-booking-types

Create a booking type (POST /booking-types).

Required: title, description (HTML allowed), duration_minutes, url_slug.

Optional: padding_minutes, latest_availability_days, private, max_bookings, booking_availability_interval_minutes, booking_threshold (min notice, minutes), redirect_url, price + payment_platform (stripe|paypal|tidycal, required when price>0) + currency_code.

Minimal (free):

{"title":"30 Minute Meeting","description":"Book a 30 minute meeting with me","duration_minutes":30,"url_slug":"30-minute-meeting"}

Paid:
{"title":"Consult","description":"Paid consult","duration_minutes":60,"url_slug":"consult","price":25,"payment_platform":"stripe","currency_code":"USD"}

Required body: title, description, duration_minutes, url_slug.

Minimal body: {"title":"30 Minute Meeting","description":"Book a 30 minute meeting with me","duration_minutes":30,"url_slug":"30-minute-meeting"}

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "title": "30 Minute Meeting",
    "description": "Book a 30 minute meeting with me",
    "duration_minutes": 30,
    "url_slug": "30-minute-meeting"
}
curl -X POST 'https://api.endpointr.com/v1/tidycal/tidy-cal-booking-types' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "title": "30 Minute Meeting",
    "description": "Book a 30 minute meeting with me",
    "duration_minutes": 30,
    "url_slug": "30-minute-meeting"
}'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-booking-types', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "title": "30 Minute Meeting",
      "description": "Book a 30 minute meeting with me",
      "duration_minutes": 30,
      "url_slug": "30-minute-meeting"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-booking-types');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"title\": \"30 Minute Meeting\",\n    \"description\": \"Book a 30 minute meeting with me\",\n    \"duration_minutes\": 30,\n    \"url_slug\": \"30-minute-meeting\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

TidyCal — Bookings

GET/v1/tidycal/tidy-cal-bookings/:id

Fetch one booking by its numeric {id} (booking type, contact, questions, payment, hosts).

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tidycal/tidy-cal-bookings/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-bookings/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-bookings/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/tidycal/tidy-cal-bookings?starts_at=2026-08-01&ends_at=2026-08-31

List your bookings (GET /bookings). All filters optional.

Filters: starts_at, ends_at (dates), cancelled (true = only cancelled), page, include_teams (true = include team bookings).

List a window:

?starts_at=2026-08-01&ends_at=2026-08-31

Minimal query: {"starts_at":"2026-08-01","ends_at":"2026-08-31"}

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

AuthorizationBearer YOUR_JWT_TOKEN
starts_at2026-08-01
ends_at2026-08-31
curl -X GET 'https://api.endpointr.com/v1/tidycal/tidy-cal-bookings?starts_at=2026-08-01&ends_at=2026-08-31' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-bookings?starts_at=2026-08-01&ends_at=2026-08-31', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-bookings?starts_at=2026-08-01&ends_at=2026-08-31');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tidycal/tidy-cal-bookings

Book a slot on a booking type (POSTs to /booking-types/{booking_type_id}/bookings). booking_type_id is required in the body — get one from the booking-types tool and a valid starts_at from the timeslots tool.

Required: booking_type_id, name, email, timezone, and a time — either starts_at (single) or a bookings array (package/multi-session).

Minimal (single):

{"booking_type_id":12345,"starts_at":"2026-08-13T10:00:00Z","name":"John Doe","email":"john@example.com","timezone":"America/Los_Angeles"}

With answers to booking questions:
{"booking_type_id":12345,"starts_at":"2026-08-13T10:00:00Z","name":"John Doe","email":"john@example.com","timezone":"America/Los_Angeles","booking_questions":[{"booking_type_question_id":1,"answer":"My answer"}]}

Package (multiple sessions):
{"booking_type_id":12345,"name":"John Doe","email":"john@example.com","timezone":"America/Los_Angeles","bookings":[{"starts_at":"2026-08-13T10:00:00Z"},{"starts_at":"2026-08-20T10:00:00Z"}]}

Required body: booking_type_id, name, email, timezone.

Minimal body: {"booking_type_id":12345,"name":"John Doe","email":"john@example.com","timezone":"America/Los_Angeles"}

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "booking_type_id": 12345,
    "starts_at": "2026-08-13T10:00:00Z",
    "name": "John Doe",
    "email": "john@example.com",
    "timezone": "America/Los_Angeles"
}
curl -X POST 'https://api.endpointr.com/v1/tidycal/tidy-cal-bookings' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "booking_type_id": 12345,
    "starts_at": "2026-08-13T10:00:00Z",
    "name": "John Doe",
    "email": "john@example.com",
    "timezone": "America/Los_Angeles"
}'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-bookings', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "booking_type_id": 12345,
      "starts_at": "2026-08-13T10:00:00Z",
      "name": "John Doe",
      "email": "john@example.com",
      "timezone": "America/Los_Angeles"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-bookings');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"booking_type_id\": 12345,\n    \"starts_at\": \"2026-08-13T10:00:00Z\",\n    \"name\": \"John Doe\",\n    \"email\": \"john@example.com\",\n    \"timezone\": \"America/Los_Angeles\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

TidyCal — Cancel booking

POST/v1/tidycal/tidy-cal-booking-cancel

Cancel a booking (PATCH /bookings/{booking_id}/cancel). booking_id is required; reason optional.

Minimal:

{"booking_id":98765}

With a reason:
{"booking_id":98765,"reason":"Client requested cancellation"}

Required body: booking_id.

Minimal body: {"booking_id":98765}

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "booking_id": 98765,
    "reason": "Client requested cancellation"
}
curl -X POST 'https://api.endpointr.com/v1/tidycal/tidy-cal-booking-cancel' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "booking_id": 98765,
    "reason": "Client requested cancellation"
}'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-booking-cancel', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "booking_id": 98765,
      "reason": "Client requested cancellation"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-booking-cancel');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"booking_id\": 98765,\n    \"reason\": \"Client requested cancellation\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

TidyCal — Contacts

GET/v1/tidycal/tidy-cal-contacts?page=1

List your contacts (GET /contacts). Only page is supported.

Minimal query: {"page":"1"}

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

AuthorizationBearer YOUR_JWT_TOKEN
page1
curl -X GET 'https://api.endpointr.com/v1/tidycal/tidy-cal-contacts?page=1' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-contacts?page=1', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-contacts?page=1');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tidycal/tidy-cal-contacts

Create a contact (POST /contacts). Required: name, email. Optional timezone. (TidyCal gates contact creation behind a lifetime subscription — HTTP 402 otherwise.)

Minimal:

{"name":"John Doe","email":"john@example.com"}

Required body: name, email.

Minimal body: {"name":"John Doe","email":"john@example.com"}

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "name": "John Doe",
    "email": "john@example.com",
    "timezone": "America/New_York"
}
curl -X POST 'https://api.endpointr.com/v1/tidycal/tidy-cal-contacts' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "name": "John Doe",
    "email": "john@example.com",
    "timezone": "America/New_York"
}'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-contacts', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "name": "John Doe",
      "email": "john@example.com",
      "timezone": "America/New_York"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-contacts');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"name\": \"John Doe\",\n    \"email\": \"john@example.com\",\n    \"timezone\": \"America/New_York\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

TidyCal — Team booking types

GET/v1/tidycal/tidy-cal-team-booking-types?team_id=42

List a team's booking types (GET /teams/{team_id}/booking-types). team_id is required. Optional page.

Required query: team_id.

Minimal query: {"team_id":"42"}

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

AuthorizationBearer YOUR_JWT_TOKEN
team_id42
curl -X GET 'https://api.endpointr.com/v1/tidycal/tidy-cal-team-booking-types?team_id=42' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-team-booking-types?team_id=42', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-team-booking-types?team_id=42');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tidycal/tidy-cal-team-booking-types

Create a booking type for a team (POST /teams/{team_id}/booking-types). Required: team_id, title, description, duration_minutes, url_slug (the tidy-cal-booking-types fields plus team_id).

Minimal:

{"team_id":42,"title":"Team Intro","description":"Meet the team","duration_minutes":30,"url_slug":"team-intro"}

Required body: team_id, title, description, duration_minutes, url_slug.

Minimal body: {"team_id":42,"title":"Team Intro","description":"Meet the team","duration_minutes":30,"url_slug":"team-intro"}

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "team_id": 42,
    "title": "Team Intro",
    "description": "Meet the team",
    "duration_minutes": 30,
    "url_slug": "team-intro"
}
curl -X POST 'https://api.endpointr.com/v1/tidycal/tidy-cal-team-booking-types' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "team_id": 42,
    "title": "Team Intro",
    "description": "Meet the team",
    "duration_minutes": 30,
    "url_slug": "team-intro"
}'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-team-booking-types', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "team_id": 42,
      "title": "Team Intro",
      "description": "Meet the team",
      "duration_minutes": 30,
      "url_slug": "team-intro"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-team-booking-types');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"team_id\": 42,\n    \"title\": \"Team Intro\",\n    \"description\": \"Meet the team\",\n    \"duration_minutes\": 30,\n    \"url_slug\": \"team-intro\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

TidyCal — Team bookings

GET/v1/tidycal/tidy-cal-team-bookings?team_id=42&start_date=2026-08-01&end_date=2026-08-31

List a team's bookings (GET /teams/{team_id}/bookings). team_id is required. Optional filters: page, start_date, end_date, email, host_id, cancelled (true/false).

Minimal:

?team_id=42

Required query: team_id.

Minimal query: {"team_id":"42"}

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

AuthorizationBearer YOUR_JWT_TOKEN
team_id42
start_date2026-08-01
end_date2026-08-31
curl -X GET 'https://api.endpointr.com/v1/tidycal/tidy-cal-team-bookings?team_id=42&start_date=2026-08-01&end_date=2026-08-31' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-team-bookings?team_id=42&start_date=2026-08-01&end_date=2026-08-31', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-team-bookings?team_id=42&start_date=2026-08-01&end_date=2026-08-31');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

TidyCal — Team users

GET/v1/tidycal/tidy-cal-team-users?team_id=42

List a team's users (GET /teams/{team_id}/users). team_id is required. Optional page.

Required query: team_id.

Minimal query: {"team_id":"42"}

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

AuthorizationBearer YOUR_JWT_TOKEN
team_id42
curl -X GET 'https://api.endpointr.com/v1/tidycal/tidy-cal-team-users?team_id=42' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-team-users?team_id=42', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-team-users?team_id=42');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
POST/v1/tidycal/tidy-cal-team-users

Invite a user to a team by email (POST /teams/{team_id}/users). Required: team_id, email. Optional role_name (admin|user).

Minimal:

{"team_id":42,"email":"user@example.com"}

As an admin:
{"team_id":42,"email":"user@example.com","role_name":"admin"}

Required body: team_id, email.

Minimal body: {"team_id":42,"email":"user@example.com"}

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "team_id": 42,
    "email": "user@example.com",
    "role_name": "user"
}
curl -X POST 'https://api.endpointr.com/v1/tidycal/tidy-cal-team-users' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "team_id": 42,
    "email": "user@example.com",
    "role_name": "user"
}'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-team-users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "team_id": 42,
      "email": "user@example.com",
      "role_name": "user"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-team-users');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"team_id\": 42,\n    \"email\": \"user@example.com\",\n    \"role_name\": \"user\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
DELETE/v1/tidycal/tidy-cal-team-users/:id

Remove a user from a team (DELETE /teams/{team_id}/users/{teamUser}). Id is the composite teamId:teamUserId — over MCP the team can't travel in the query.

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X DELETE 'https://api.endpointr.com/v1/tidycal/tidy-cal-team-users/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-team-users/:id', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-team-users/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

TidyCal — Teams

GET/v1/tidycal/tidy-cal-teams/:id

Fetch one team by its numeric {id}.

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

AuthorizationBearer YOUR_JWT_TOKEN
curl -X GET 'https://api.endpointr.com/v1/tidycal/tidy-cal-teams/:id' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-teams/:id', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-teams/:id');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
GET/v1/tidycal/tidy-cal-teams?page=1

List teams you own or belong to (GET /teams). Only page is supported. Each team's id is used by the team-bookings / team-users / team-booking-types tools.

Minimal query: {"page":"1"}

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

AuthorizationBearer YOUR_JWT_TOKEN
page1
curl -X GET 'https://api.endpointr.com/v1/tidycal/tidy-cal-teams?page=1' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-teams?page=1', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-teams?page=1');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

TidyCal — Timeslots (availability)

GET/v1/tidycal/tidy-cal-timeslots?booking_type_id=12345&starts_at=2026-08-13T00%3A00%3A00Z&ends_at=2026-08-20T00%3A00%3A00Z

Get bookable time slots for a booking type over a UTC range (GET /booking-types/{booking_type_id}/timeslots). Honors your schedule, existing bookings, calendar conflicts and buffers.

Required: booking_type_id, starts_at, ends_at (UTC ISO-8601).

Minimal:

?booking_type_id=12345&starts_at=2026-08-13T00:00:00Z&ends_at=2026-08-20T00:00:00Z

Required query: booking_type_id, starts_at, ends_at.

Minimal query: {"booking_type_id":"12345","starts_at":"2026-08-13T00:00:00Z","ends_at":"2026-08-20T00:00:00Z"}

_Requires stored credentials: tidycal (PUT /v1/credentials/tidycal)._

AuthorizationBearer YOUR_JWT_TOKEN
booking_type_id12345
starts_at2026-08-13T00:00:00Z
ends_at2026-08-20T00:00:00Z
curl -X GET 'https://api.endpointr.com/v1/tidycal/tidy-cal-timeslots?booking_type_id=12345&starts_at=2026-08-13T00%3A00%3A00Z&ends_at=2026-08-20T00%3A00%3A00Z' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN'
const response = await fetch('https://api.endpointr.com/v1/tidycal/tidy-cal-timeslots?booking_type_id=12345&starts_at=2026-08-13T00%3A00%3A00Z&ends_at=2026-08-20T00%3A00%3A00Z', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  }
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/tidycal/tidy-cal-timeslots?booking_type_id=12345&starts_at=2026-08-13T00%3A00%3A00Z&ends_at=2026-08-20T00%3A00%3A00Z');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Verifications

Verify (bulk)

POST/v1/verifications/bulk

Verify many values in one call. Body: {values: [...], kind?, provider?}. Same field semantics as POST /v1/verifications/verify; kind and provider apply to every entry.

Max 1000 values per call. The endpoint iterates the provider's single-call API per value — there is no provider-native batching yet, so 1000 emails ≈ 1000 upstream calls.

Response. {count, results: [...]}. Each result has the same shape as the single endpoint plus index (the position in the input array). Per-row error is set when one value couldn't be verified (e.g. empty string, kind mismatch, provider threw) — the rest of the batch still runs.

Required body: values.

Minimal body: {"values":["jane@acme.com","john@example.com"]}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "values": [
        "jane@acme.com",
        "john@example.com"
    ],
    "provider": "reoon"
}
curl -X POST 'https://api.endpointr.com/v1/verifications/bulk' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "values": [
        "jane@acme.com",
        "john@example.com"
    ],
    "provider": "reoon"
}'
const response = await fetch('https://api.endpointr.com/v1/verifications/bulk', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "values": [
          "jane@acme.com",
          "john@example.com"
      ],
      "provider": "reoon"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/verifications/bulk');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"values\": [\n        \"jane@acme.com\",\n        \"john@example.com\"\n    ],\n    \"provider\": \"reoon\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);

Verify (single)

POST/v1/verifications/verify

Verify a single email or phone number. Body: {value, kind?, provider?}.

value — the address / number to verify. Required.
kindemail | phone. Optional; auto-detected from value (presence of @ ⇒ email).
provider — slug of a configured verifier (e.g. reoon, numverify, neverbounce). Optional.

Provider selection.
- If provider is set: that exact slug is used. 404 when the customer has no stored credentials for it (or it isn't a known verifier).
- If provider is omitted: the customer's verification_priority list (set under /admin/customers/{id} → Verification priority) is walked, and the first slug whose provider supports the kind wins. 404 when no slug in the list supports the requested kind.

Response. {provider, kind, value, status, raw, http_status, endpoint, request_at}. status is the provider's normalised verdict (safe / valid / invalid / risky / disposable / …). raw is the provider's full response body — refer to the provider's own docs for the fields beyond status.

Status codes. 201 ok · 400 bad request / provider/kind mismatch · 401 invalid token · 404 provider not configured / unknown slug / no priority match · 502 provider returned an empty envelope.

Required body: value.

Minimal body: {"value":"jane@acme.com"}

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "value": "jane@acme.com",
    "provider": "reoon"
}
curl -X POST 'https://api.endpointr.com/v1/verifications/verify' \
  -H 'Content-Type: application/json' \
  -H 'Authorization: Bearer YOUR_JWT_TOKEN' \
  -d '{
    "value": "jane@acme.com",
    "provider": "reoon"
}'
const response = await fetch('https://api.endpointr.com/v1/verifications/verify', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_JWT_TOKEN'
  },
  body: JSON.stringify({
      "value": "jane@acme.com",
      "provider": "reoon"
  })
});
const data = await response.json();
$ch = curl_init('https://api.endpointr.com/v1/verifications/verify');
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json',
    'Authorization: Bearer YOUR_JWT_TOKEN',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, "{\n    \"value\": \"jane@acme.com\",\n    \"provider\": \"reoon\"\n}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);