endpointr / docs

Endpointr API

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

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)

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.

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 read-only measurement APIs — Search Console
and Google Analytics 4 (Data + Admin) — 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).

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):
- ga4-accounts list = accounts the user can access.
- ga4-properties query = properties under an account (requires ?account=);
get one by numeric id.
- ga4-data-streams query = streams under a property (requires ?property=).

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)
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 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 (v24, REST) relay exposed through
endpointr, scoped to read-only reporting via GAQL (Google Ads Query
Language) — no campaign/ad mutation in this connector. Each tool mirrors
one REST endpoint under /v1/googleads/*. Tool names follow:
endpointr_googleads_<resource>_<verb>
where verb is one of: list (GET collection), create (POST).

Typical flow: accessible-customers list (discover which Ads accounts you
can reach) -> reports create (run a GAQL query against one of them).

Resources:
- accessible-customers list = every Ads account (customer_id) the
authenticated OAuth grant can access directly, no
id needed. An empty result with a valid grant
usually means the account is only reachable
through a manager (MCC) — pass login_customer_id
set to that manager and query its
customer_client resource via reports instead.
- reports create = run a raw GAQL SELECT via
GoogleAdsService.search against one account.
REQUIRES customer_id (10 digits, dashes okay)
+ query (a GAQL string, forwarded verbatim).
pageSize/pageToken page through results (up
to 10,000 rows/page). Cost/bid fields are in
MICROS (1,000,000 micros = 1 currency unit).
Example query: "SELECT campaign.id,
campaign.name, metrics.clicks,
metrics.impressions, metrics.cost_micros FROM
campaign WHERE segments.date DURING LAST_7_DAYS".

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 / query string. The create
tool accepts an open body object — the schema lists documented fields,
but the full GAQL/search request shape is supported.

claude mcp add googleads --transport http https://googleads.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 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?, <custom TAGs>}).
Fold {action:'subscribe'|'unsubscribe', uid} to flip status.
update by uid (PATCH); 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-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

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);

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=.

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.

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 — Data Streams

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);

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);

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);

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. 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 (list_uid + EMAIL + any field TAGs like FIRST_NAME) — 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"
}
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"
}'
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"
  })
});
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}");
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
PUT/v1/sendmails/sendmails-subscribers/:id

Update a subscriber's fields by uid (PATCH upstream).

Minimal body: {"FIRST_NAME":"Alice (updated)"}

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

Content-Typeapplication/json
AuthorizationBearer YOUR_JWT_TOKEN
{
    "FIRST_NAME": "Alice (updated)"
}
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)"
}'
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)"
  })
});
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}");
$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);

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.

Minimal body: {"action":"website","url":"https://example.com","width":1280,"height":720}

Note: Stub — PhantomJS is deprecated. Use Playwright externally.

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);

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 — 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);

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);