connectors

object

%Connector

/phlo/resources/connectors/Connector.phlo

Base class for API connectors: credentials, JSON requests, retries, pagination and a normalized result contract

Make one with Connector::make(); it reads its own section from %creds, so keys live in data/creds.ini or in PHLO__Section__key in the environment and never in your code. Every call answers in the same shape, ok with status and data or ok false with error, and nothing is thrown, so test ->ok rather than catching. Raise retries above zero to let GET, HEAD and QUERY back off and try again on 429 and 5xx; writes are never retried, because a repeated POST would book twice. Connector::for($record, 'column') uses the credentials of one record, a tenant or a user, on top of the app's section: the column holds them sealed with seal(), which encrypts with the key in [connectors], and an OAuth connector keeps its access token per record.

apiconnectorhttprestbase
const

Connector :: section

line 9
Defines a section within a Connector, allowing for the organization of related components and functionalities.
void
const

Connector :: api

line 10
Defines a connector for API interactions, allowing seamless communication between different services.
void
method

%Connector -> __construct (?array $config = null)

line 12
Initializes a Connector instance with optional configuration settings. If no configuration is provided, it attempts to load credentials from a predefined section.
if ($config === null){
	$section = static::section
	$creds = $section ? %creds->{$section} : null
	$config = $creds ? (array)$creds->toArray : []
}
$this->config = $config
$this->timeout = 15
$this->retries = 0
static

Connector :: make (?array $config = null):static

line 23
Creates a new instance of the Connector class using the provided configuration.
new static($config)
static

Connector :: for (object $record, string $column = 'connector'):static

line 27
The credentials of one record rather than the app: its column adds its own key or tokens to the
$connector = new static
$connector->config = [...$connector->config, ...static::unseal((string)($record->objData[$column] ?? void))]
$connector->owner = strtr($record::class, us, dash).dash.($record->objData[method_exists($record, 'idColumn') ? $record::idColumn() : 'id'] ?? void)
return $connector
static

Connector :: seal (array $credentials):string

line 35
Credentials for a column, encrypted, so a dump of the database hands out no one's account.
'sealed:'.encrypt(json_encode($credentials), static::sealKey())
static

Connector :: unseal (string $value):array

line 37
if ($value === void) return []
if (!str_starts_with($value, 'sealed:')) return (array)(json_decode($value, true) ?? [])
($plain = decrypt(substr($value, 7), static::sealKey())) === false && error('Connector credentials do not unseal: another key, or a changed value', 500)
return (array)json_decode($plain, true)
static

Connector :: sealKey (bool $required = true):?string

line 46
The key in the [connectors] section of creds. Without it there is nothing to seal with, so a
$key = ($section = %creds->connectors) ? (string)$section->key : void
return $key !== void ? $key : ($required ? error('Sealing credentials needs key in the [connectors] section of creds', 500) : null)
method

%Connector -> base:string

line 51
Accesses the base API for the Connector, allowing interaction with its core functionalities.
static::api
method

%Connector -> headers:array

line 52
This property retrieves the headers associated with the Connector.
[]
static

Connector :: fields:array

line 54
Connector::$fields is a static property that holds an array of field definitions for the Connector class.
[]
method

%Connector -> configured (...$keys):bool

line 56
Checks if all specified keys are configured in the Connector's configuration. Returns true if all keys are present, otherwise returns false.
foreach ($keys AS $key){
	if (($this->config[$key] ?? void) === void) return false
}
return true
method

%Connector -> missing (...$keys):?obj

line 63
Checks if the specified credentials are configured and returns null if they are; otherwise, it triggers a failure indicating which credentials are missing.
return $this->configured(...$keys) ? null : static::fail(static::section.' credentials not configured ('.implode(', ', $keys).')')
static

Connector :: bearer ($token):string

line 67
The Connector::$bearer property holds the authorization header value formatted with a Bearer token for API requests.
'Authorization: Bearer '.$token
static

Connector :: basic ($user, $pass):string

line 68
Generates a Basic Authorization header by encoding the provided username and password in base64 format.
'Authorization: Basic '.base64_encode($user.colon.$pass)
static

Connector :: build (string $method, string $url, ?array $query = null, array $headers = [], mixed $json = null, mixed $form = null):array

line 70
if ($query) $url .= (str_contains($url, qm) ? '&' : qm).http_build_query($query)
$body = null
if ($json !== null){
	$body = is_string($json) ? $json : json_encode($json, jsonFlat)
	$headers[] = 'Content-Type: application/json'
}
elseif ($form !== null){
	$body = is_string($form) ? $form : http_build_query($form)
	$headers[] = 'Content-Type: application/x-www-form-urlencoded'
}
$headers[] = 'Accept: application/json'
return ['method' => strtoupper($method), 'url' => $url, 'headers' => $headers, 'body' => $body]
static

Connector :: ok ($data, int $status = 200):obj

line 85
The Connector::$ok property holds an object with a boolean 'ok' value, a 'status', and 'data' attributes.
obj(ok: true, status: $status, data: $data)
static

Connector :: fail ($error, int $status = 0):obj

line 86
The Connector::$fail property represents a failure state, containing an object with 'ok' set to false, along with the 'status' and 'error' details.
obj(ok: false, status: $status, error: $error)
static

Connector :: errorMessage ($data, string $raw, int $status):string

line 88
Connector::$errorMessage retrieves the error message from a given data object, checking various properties for a string representation of the error, and defaults to a formatted HTTP status message if no error is found.
if (is_object($data)){
	if (isset($data->error->message)) return (string)$data->error->message
	if (isset($data->error) && is_string($data->error)) return $data->error
	if (isset($data->message)) return (string)$data->message
	if (isset($data->errors)){
		$errors = $data->errors
		if (is_string($errors)) return $errors
		if (is_array($errors)) return is_string($errors[0] ?? null) ? $errors[0] : json_encode($errors)
		if (is_object($errors)) return json_encode($errors)
	}
}
return $raw !== void ? $raw : 'HTTP '.$status
static

Connector :: parse ($raw, int $status = 200):obj

line 103
Parses a raw input string into a JSON object, returning a success response or an error message based on the HTTP status code.
$raw = (string)$raw
$data = $raw === void ? null : json_decode($raw)
if ($status < 200 || $status >= 300) return static::fail(static::errorMessage($data, $raw, $status), $status)
return obj(ok: true, status: $status, data: $data ?? $raw)
static

Connector :: retryable ($method, int $status):bool

line 110
in_array($method, ['GET', 'HEAD', 'QUERY']) && ($status === 429 || $status >= 500)
static

Connector :: backoff (int $attempt, $response):int

line 112
The Connector::$backoff property calculates the backoff time in microseconds based on the 'retry-after' header from the response, with a maximum limit of 30 seconds or a default value multiplied by the attempt count.
$after = (int)($response->headers['retry-after'] ?? 0)
return $after > 0 ? min($after, 30) * 1000000 : 200000 * $attempt
method

%Connector -> dispatch (array $req):obj

line 121
Retries only what retryable() allows, and waits as long as the server asked for.
$method = $req['method']
$body = $req['body']
$attempt = 0
$response = null
while (true){
	try {
		if ($method === 'GET') $raw = HTTP($req['url'], $req['headers'], cookies: false, timeout: $this->timeout, response: $response)
		elseif ($method === 'DELETE') $raw = HTTP($req['url'], $req['headers'], DELETE: true, cookies: false, timeout: $this->timeout, response: $response)
		elseif ($method === 'PUT') $raw = HTTP($req['url'], $req['headers'], PUT: $body ?? void, cookies: false, timeout: $this->timeout, response: $response)
		elseif ($method === 'PATCH') $raw = HTTP($req['url'], $req['headers'], PATCH: $body ?? void, cookies: false, timeout: $this->timeout, response: $response)
		elseif ($method === 'QUERY') $raw = HTTP($req['url'], $req['headers'], QUERY: $body ?? void, cookies: false, timeout: $this->timeout, response: $response)
		else $raw = HTTP($req['url'], $req['headers'], POST: $body ?? void, cookies: false, timeout: $this->timeout, response: $response)
	}
	catch (\Throwable $e){
		return static::fail($e->getMessage(), 0)
	}
	$status = $response->status ?? 0
	if (($status >= 200 && $status < 300) || !static::retryable($method, $status) || $attempt >= $this->retries) break
	usleep(static::backoff(++$attempt, $response))
}
$result = static::parse($raw, $status)
$result->headers = $response->headers ?? []
return $result
method

%Connector -> request (string $method, string $url, ?array $query = null, array $headers = [], mixed $json = null, mixed $form = null):obj

line 147
Sends an HTTP request using the specified method, URL, query parameters, headers, and optional JSON or form data.
if (!str_starts_with($url, 'http')) $url = rtrim((string)$this->base, slash).slash.ltrim($url, slash)
$headers = array_merge((array)$this->headers, $headers)
return $this->dispatch(static::build($method, $url, $query, $headers, $json, $form))
method

%Connector -> get (string $url, ?array $query = null, array $headers = []):obj

line 153
Sends a GET request to the specified URL with optional query parameters and headers.
$this->request('GET', $url, query: $query, headers: $headers)
method

%Connector -> post (string $url, mixed $json = null, array $headers = []):obj

line 154
Sends a POST request to the specified URL with optional JSON data and headers.
$this->request('POST', $url, headers: $headers, json: $json)
method

%Connector -> put (string $url, mixed $json = null, array $headers = []):obj

line 155
Sends a PUT request to the specified URL with optional JSON data and headers.
$this->request('PUT', $url, headers: $headers, json: $json)
method

%Connector -> patch (string $url, mixed $json = null, array $headers = []):obj

line 156
Sends a PATCH request to the specified URL with optional JSON data and headers.
$this->request('PATCH', $url, headers: $headers, json: $json)
method

%Connector -> query (string $url, mixed $json = null, array $headers = []):obj

line 157
$this->request('QUERY', $url, headers: $headers, json: $json)
method

%Connector -> del (string $url, array $headers = []):obj

line 158
Sends a DELETE request to the specified URL with optional headers.
$this->request('DELETE', $url, headers: $headers)
method

%Connector -> form (string $url, array $fields, array $headers = []):obj

line 159
Sends a POST request to the specified URL with the given form fields and headers.
$this->request('POST', $url, headers: $headers, form: $fields)
method

%Connector -> paginate (string $url, callable $extract, ?array $query = null, string $param = 'page', int $start = 1, int $max = 0):array

line 161
Fetches paginated data from a specified URL, extracting items using a provided callable and accumulating them until a maximum count is reached or no more data is available.
$items = []
$page = $start
while (true){
	$res = $this->get($url, ($query ?? []) + [$param => $page])
	if (!$res->ok) break
	$batch = $extract($res->data)
	if (!$batch) break
	foreach ($batch AS $item) $items[] = $item
	if ($max && count($items) >= $max) break
	$page++
}
return $items
object

%EBoekhouden

/phlo/resources/connectors/finance/EBoekhouden.phlo

e-Boekhouden.nl connector: session auth from an API token, relations and sales invoices

Authenticates with a session rather than a token per call: the first call trades api_token for one, later calls in the same request reuse it, and a new request starts over. Sessions are limited, so gather the work you need per request instead of making a connector per call.

eboekhoudenaccountinginvoicesrelationsconnector
const

EBoekhouden :: section

line 10
'EBoekhouden'
const

EBoekhouden :: api

line 11
'https://api.e-boekhouden.nl/v1'
prop

%EBoekhouden -> sessionToken:string

line 13
void
method

%EBoekhouden -> headers:array

line 15
$this->sessionToken !== void ? ['Authorization: '.$this->sessionToken] : []
static

EBoekhouden :: fields:array

line 17
arr(
	section: 'EBoekhouden',
	config: arr(source: 'Source label shown in the e-Boekhouden audit trail (optional)'),
	secret: arr(api_token: 'API token (Beheer > Instellingen > API)'),
)
method

%EBoekhouden -> session:obj

line 26
Returns the session token, fetching one on first use.
if ($m = $this->missing('api_token')) return $m
if ($this->sessionToken !== void) return static::ok($this->sessionToken)
$res = $this->post('session', ['accessToken' => trim((string)$this->config['api_token']), 'source' => (string)($this->config['source'] ?? 'Phlo')])
if (!$res->ok) return $res
$token = (string)($res->data->token ?? void)
if ($token === void) return static::fail('e-Boekhouden session token missing in response', $res->status)
$this->sessionToken = $token
return static::ok($token)
method

%EBoekhouden -> guard:?obj

line 37
$res = $this->session
return $res->ok ? null : $res
method

%EBoekhouden -> relations (array $query = []):obj

line 42
if ($m = $this->guard) return $m
return $this->get('relation', $query)
method

%EBoekhouden -> createRelation (array $relation):obj

line 47
if ($m = $this->guard) return $m
return $this->post('relation', $relation)
method

%EBoekhouden -> invoices (array $query = []):obj

line 52
if ($m = $this->guard) return $m
return $this->get('invoice', $query)
method

%EBoekhouden -> createInvoice (array $invoice):obj

line 57
if ($m = $this->guard) return $m
return $this->post('invoice', $invoice)
object

%ExactOnline

/phlo/resources/connectors/finance/ExactOnline.phlo

Exact Online connector (OAuth2): read sales invoices and accounts, create sales invoices

The division number sits in the base URL, so a connector speaks to exactly one administration and a second administration needs a second connector with its own config. Exact rotates the refresh token on every refresh, which is why the token store locks; running two apps on one refresh token logs both out.

exactexactonlineaccountinginvoicesoauthconnector
const

ExactOnline :: section

line 10
Defines a section within the ExactOnline resource, allowing for structured data management.
'ExactOnline'
const

ExactOnline :: tokenUrl

line 11
Returns the URL for obtaining an OAuth2 token from ExactOnline.
'https://start.exactonline.nl/api/oauth2/token'
method

%ExactOnline -> base:string

line 13
Constructs the base URL for the ExactOnline API using the specified division from the configuration.
'https://start.exactonline.nl/api/v1/'.($this->config['division'] ?? void)
static

ExactOnline :: fields:array

line 15
Defines the fields required for integrating with ExactOnline, including configuration for OAuth credentials and scopes.
arr(
	section: 'ExactOnline',
	config: arr(division: 'Division (administration) number'),
	secret: arr(
		client_id: 'OAuth client ID',
		client_secret: 'OAuth client secret',
		refresh_token: 'OAuth refresh token (managed and rotated after first authorization)',
	),
	scopes: 'OAuth2 authorization code flow; token endpoint refreshes automatically',
)
method

%ExactOnline -> guard:?obj

line 26
Checks for the presence of required parameters 'division', 'client_id', 'client_secret', and 'refresh_token'.
$this->missing('division', 'client_id', 'client_secret', 'refresh_token')
method

%ExactOnline -> invoices (array $query = []):obj

line 28
Retrieves a list of sales invoices from ExactOnline based on the provided query parameters.
if ($m = $this->guard) return $m
return $this->get('salesinvoice/SalesInvoices', $query)
method

%ExactOnline -> accounts (array $query = []):obj

line 33
Retrieves account information from ExactOnline using the specified query parameters.
if ($m = $this->guard) return $m
return $this->get('crm/Accounts', $query)
method

%ExactOnline -> createInvoice (array $invoice):obj

line 38
Creates a new invoice in ExactOnline using the provided invoice data.
if ($m = $this->guard) return $m
return $this->post('salesinvoice/SalesInvoices', $invoice)
object

%GoogleCalendar

/phlo/resources/connectors/cloud/GoogleCalendar.phlo

Google Calendar connector (OAuth2): read events and create events

Shares the Google section with Sheets, so one refresh token has to carry both scopes. calendarId defaults to primary, which is the mailbox of the account that authorized, not a shared agenda; give a calendar address for those. Google returns times as RFC3339 with an offset, so keep the timezone rather than casting to a local timestamp.

googlecalendareventsoauthconnector
const

GoogleCalendar :: section

line 10
Defines a section within a Google Calendar resource, allowing for the organization of events and details.
'Google'
const

GoogleCalendar :: tokenUrl

line 11
This is the URL endpoint used to obtain an access token from Google Calendar's OAuth 2.0 authorization server.
'https://oauth2.googleapis.com/token'
method

%GoogleCalendar -> base:string

line 13
This defines the base URL for accessing the Google Calendar API version 3.
'https://www.googleapis.com/calendar/v3'
static

GoogleCalendar :: fields:array

line 15
GoogleCalendar::$fields provides configuration details for accessing Google Calendar, including OAuth credentials and required scopes.
arr(
	section: 'Google',
	secret: arr(
		client_id: 'OAuth client ID',
		client_secret: 'OAuth client secret',
		refresh_token: 'OAuth refresh token (scopes: calendar, spreadsheets)',
	),
	scopes: 'https://www.googleapis.com/auth/calendar, https://www.googleapis.com/auth/spreadsheets',
)
method

%GoogleCalendar -> guard:?obj

line 25
Checks for the presence of required credentials: client_id, client_secret, and refresh_token.
$this->missing('client_id', 'client_secret', 'refresh_token')
method

%GoogleCalendar -> events (string $calendarId = 'primary', array $query = []):obj

line 27
Retrieves events from a specified Google Calendar using the provided calendar ID and optional query parameters.
if ($m = $this->guard) return $m
return $this->get('calendars/'.rawurlencode($calendarId).'/events', $query)
method

%GoogleCalendar -> createEvent (array $event, string $calendarId = 'primary'):obj

line 32
Creates a new event in the specified Google Calendar using the provided event details.
if ($m = $this->guard) return $m
return $this->post('calendars/'.rawurlencode($calendarId).'/events', $event)
object

%GoogleSheets

/phlo/resources/connectors/cloud/GoogleSheets.phlo

Google Sheets connector (OAuth2): read ranges and append rows

Shares the Google section with Calendar, so one refresh token has to carry both scopes. A range is A1 notation including the tab name, e.g. Sheet1!A:D. append() writes with USER_ENTERED, so the sheet parses what you send just as a typist would and a leading zero or a date-like string is reinterpreted; pass RAW when the value has to stay untouched.

googlesheetsspreadsheetoauthconnector
const

GoogleSheets :: section

line 10
Defines a section for organizing data within a Google Sheets resource.
'Google'
const

GoogleSheets :: tokenUrl

line 11
This is the URL endpoint for obtaining OAuth 2.0 tokens from Google Sheets.
'https://oauth2.googleapis.com/token'
method

%GoogleSheets -> base:string

line 13
This item represents a base URL for accessing the Google Sheets API, specifically the endpoint for interacting with spreadsheets.
'https://sheets.googleapis.com/v4/spreadsheets'
static

GoogleSheets :: fields:array

line 15
Defines the configuration fields for Google Sheets integration, including OAuth credentials and required scopes.
arr(
	section: 'Google',
	secret: arr(
		client_id: 'OAuth client ID',
		client_secret: 'OAuth client secret',
		refresh_token: 'OAuth refresh token (scopes: calendar, spreadsheets)',
	),
	scopes: 'https://www.googleapis.com/auth/spreadsheets',
)
method

%GoogleSheets -> guard:?obj

line 25
Checks for the presence of required credentials: client_id, client_secret, and refresh_token.
$this->missing('client_id', 'client_secret', 'refresh_token')
method

%GoogleSheets -> values ($spreadsheetId, string $range):obj

line 27
Retrieves the values from a specified range in a Google Sheets spreadsheet identified by the given spreadsheet ID.
if ($m = $this->guard) return $m
return $this->get($spreadsheetId.'/values/'.rawurlencode($range))
method

%GoogleSheets -> append ($spreadsheetId, string $range, array $rows, string $valueInputOption = 'USER_ENTERED'):obj

line 32
Appends rows of data to a specified range in a Google Sheets spreadsheet, using the provided value input option.
if ($m = $this->guard) return $m
return $this->post($spreadsheetId.'/values/'.rawurlencode($range).':append?valueInputOption='.$valueInputOption, ['values' => $rows])
object

%Lightspeed

/phlo/resources/connectors/shops/Lightspeed.phlo

Lightspeed Retail (V3) connector: read customers and sales, create customers

Retail V3, so cluster_id names the account and the key and secret authenticate. Lightspeed rate limits per account with a leaky bucket, so raise retries rather than firing calls in a loop. findCustomer() picks its search field from what you pass: an address with an at sign searches on email, anything else on phone.

lightspeedwebshopretailposcustomersconnector
const

Lightspeed :: section

line 10
Defines a section in a Lightspeed application, allowing for the organization and management of content within that section.
'Lightspeed'
method

%Lightspeed -> base:string

line 12
Lightspeed->base constructs the base URL for accessing the Lightspeed API, incorporating the cluster ID from the configuration if available.
'https://api.lightspeedapp.com/API/V3/Account/'.($this->config['cluster_id'] ?? void)
method

%Lightspeed -> headers:array

line 14
This method retrieves the headers required for Lightspeed API authentication using the provided API key and secret from the configuration.
[static::basic($this->config['api_key'] ?? void, $this->config['api_secret'] ?? void)]
static

Lightspeed :: fields:array

line 16
Lightspeed::$fields defines the configuration fields required for connecting to the Lightspeed API, including cluster ID, language, API key, API secret, and access scopes.
arr(
	section: 'Lightspeed',
	config: arr(
		cluster_id: 'Account / cluster ID',
		language: 'Language (optional, default nl)',
	),
	secret: arr(
		api_key: 'API key',
		api_secret: 'API secret',
	),
	scopes: 'Customer read/write, Sale read',
)
method

%Lightspeed -> customers (array $query = []):obj

line 29
Retrieves customer data from the Lightspeed API using the provided query parameters.
if ($m = $this->missing('cluster_id', 'api_key', 'api_secret')) return $m
return $this->get('Customer.json', $query)
method

%Lightspeed -> findCustomer ($participant):obj

line 34
Finds a customer by their email or phone number, depending on the format of the provided participant identifier.
if ($m = $this->missing('cluster_id', 'api_key', 'api_secret')) return $m
$field = str_contains((string)$participant, '@') ? 'Email' : 'Phone'
return $this->get('Customer.json', [$field => $participant, 'limit' => 1])
method

%Lightspeed -> customer ($id):obj

line 40
Retrieves customer information from the Lightspeed API using the specified customer ID.
if ($m = $this->missing('cluster_id', 'api_key', 'api_secret')) return $m
return $this->get('Customer/'.$id.'.json')
method

%Lightspeed -> sales (array $query = []):obj

line 45
Retrieves sales data from the API using the provided query parameters, ensuring that required credentials are present.
if ($m = $this->missing('cluster_id', 'api_key', 'api_secret')) return $m
return $this->get('Sale.json', $query)
method

%Lightspeed -> createCustomer (array $customer):obj

line 50
Creates a new customer by sending the provided customer data to the Lightspeed API, ensuring that required parameters are present.
if ($m = $this->missing('cluster_id', 'api_key', 'api_secret')) return $m
return $this->post('Customer.json', $customer)
object

%MessageBird

/phlo/resources/connectors/chat/MessageBird.phlo

MessageBird connector: send SMS

Needs access_key and an originator, the sender shown to the recipient; some countries refuse an alphanumeric originator, so a real number is the safer choice. Pass an array as the recipient to send one message to several numbers at once.

messagebirdsmsmessagingconnector
const

MessageBird :: section

line 10
Defines a section for MessageBird within the Phlo application.
'MessageBird'
method

%MessageBird -> base:string

line 12
This defines the base URL for the MessageBird API, which is used for sending and receiving messages.
'https://rest.messagebird.com'
method

%MessageBird -> headers:array

line 14
This retrieves the headers required for authenticating requests to the MessageBird API using the provided access key.
['Authorization: AccessKey '.($this->config['access_key'] ?? void)]
static

MessageBird :: fields:array

line 16
Defines the fields for the MessageBird configuration, including the originator and access key.
arr(
	section: 'MessageBird',
	config: arr(originator: 'Originator (sender number or name)'),
	secret: arr(access_key: 'Access key'),
)
static

MessageBird :: errorMessage ($data, string $raw, int $status):string

line 22
Retrieves the error message from the MessageBird API response, if available; otherwise, it falls back to the parent class's error message method.
if (is_object($data) && isset($data->errors[0]->description)) return (string)$data->errors[0]->description
return parent::errorMessage($data, $raw, $status)
method

%MessageBird -> sms ($to, $body, array $extra = []):obj

line 27
Sends an SMS message to the specified recipient(s) using the MessageBird API, requiring an access key and originator configuration.
if ($m = $this->missing('access_key', 'originator')) return $m
$recipients = is_array($to) ? $to : [$to]
return $this->post('messages', ['originator' => $this->config['originator'], 'recipients' => $recipients, 'body' => $body] + $extra)
object

%MicrosoftGraph

/phlo/resources/connectors/cloud/MicrosoftGraph.phlo

Microsoft Graph connector (app-only client credentials): read users and calendars, send mail, create events

This is the app-only flow: the token belongs to the registration, not to a person, so it needs admin-consented application permissions and every call names the mailbox it acts on. Tokens are cached in APCu for their lifetime, so a run without APCu fetches one per request. Set mailbox to spare yourself passing a user each time.

microsoftgraphoffice365calendarmailconnector
const

MicrosoftGraph :: section

line 10
This function interacts with the Microsoft Graph API to manage sections within a resource.
'Microsoft'
method

%MicrosoftGraph -> base:string

line 12
This defines the base URL for accessing the Microsoft Graph API, allowing interaction with Microsoft services and resources.
'https://graph.microsoft.com/v1.0'
method

%MicrosoftGraph -> headers:array

line 14
This item retrieves the headers required for authentication with Microsoft Graph using a bearer token.
[static::bearer((string)$this->token)]
static

MicrosoftGraph :: fields:array

line 16
Defines the configuration fields required to connect to Microsoft Graph, including tenant ID, client ID, mailbox, client secret, and application permissions.
arr(
	section: 'Microsoft',
	config: arr(
		tenant_id: 'Azure AD tenant ID',
		client_id: 'App registration client ID',
		mailbox: 'Default mailbox / user UPN for calendar and mail (optional)',
	),
	secret: arr(client_secret: 'App registration client secret'),
	scopes: 'Application permissions: User.Read.All, Calendars.ReadWrite, Mail.Send',
)
prop

%MicrosoftGraph -> token:string

line 27
Retrieves an access token from Microsoft Graph using the fetchToken method.
$this->fetchToken()
method

%MicrosoftGraph -> fetchToken:string

line 33
Caches the app-only token in APCu until a minute before it expires.
$tenant = $this->config['tenant_id'] ?? void
$id = $this->config['client_id'] ?? void
$secret = $this->config['client_secret'] ?? void
if ($tenant === void || $id === void || $secret === void) return void
$key = 'phlo:graph:'.$tenant.colon.$id
if (function_exists('apcu_fetch')){
	$cached = apcu_fetch($key)
	if ($cached) return $cached
}
$res = $this->dispatch(static::build('POST', 'https://login.microsoftonline.com/'.$tenant.'/oauth2/v2.0/token', null, [], null, ['grant_type' => 'client_credentials', 'client_id' => $id, 'client_secret' => $secret, 'scope' => 'https://graph.microsoft.com/.default']))
if (!$res->ok) return void
$token = $res->data->access_token ?? void
$expires = (int)($res->data->expires_in ?? 3600)
if ($token !== void && function_exists('apcu_store')) apcu_store($key, $token, max(60, $expires - 60))
return $token
method

%MicrosoftGraph -> mailbox ($user = null):string

line 51
Retrieves the mailbox associated with a specified user, or defaults to a configured mailbox if no user is provided.
$user ?? ($this->config['mailbox'] ?? void)
method

%MicrosoftGraph -> users (array $query = []):obj

line 53
Retrieves a list of users from Microsoft Graph based on the provided query parameters.
if ($m = $this->missing('tenant_id', 'client_id', 'client_secret')) return $m
return $this->get('users', $query)
method

%MicrosoftGraph -> user ($id):obj

line 58
Fetches user information from Microsoft Graph using the specified user ID.
if ($m = $this->missing('tenant_id', 'client_id', 'client_secret')) return $m
return $this->get('users/'.rawurlencode((string)$id))
method

%MicrosoftGraph -> events ($user = null, array $query = []):obj

line 63
Fetches events from a specified user's Microsoft mailbox using the Microsoft Graph API.
if ($m = $this->missing('tenant_id', 'client_id', 'client_secret')) return $m
$mailbox = $this->mailbox($user)
if ($mailbox === void) return static::fail('Microsoft mailbox required')
return $this->get('users/'.rawurlencode((string)$mailbox).'/events', $query)
method

%MicrosoftGraph -> sendMail ($message, $user = null, bool $save = true):obj

line 70
Sends an email message using the Microsoft Graph API, optionally specifying a user and whether to save the message to the Sent Items folder.
if ($m = $this->missing('tenant_id', 'client_id', 'client_secret')) return $m
$mailbox = $this->mailbox($user)
if ($mailbox === void) return static::fail('Microsoft mailbox required')
return $this->post('users/'.rawurlencode((string)$mailbox).'/sendMail', ['message' => $message, 'saveToSentItems' => $save])
method

%MicrosoftGraph -> createEvent (array $event, $user = null):obj

line 77
Creates a new event in the specified user's Microsoft calendar using the provided event details.
if ($m = $this->missing('tenant_id', 'client_id', 'client_secret')) return $m
$mailbox = $this->mailbox($user)
if ($mailbox === void) return static::fail('Microsoft mailbox required')
return $this->post('users/'.rawurlencode((string)$mailbox).'/events', $event)
object

%Moneybird

/phlo/resources/connectors/finance/Moneybird.phlo

Moneybird connector: read contacts and invoices, create sales invoices

Needs an administration_id and a personal access token with rights for contacts and invoices. findContact() searches on one match, so use it to look up rather than to list. An invoice is created as a draft: sending or booking it is a separate step in Moneybird.

moneybirdaccountinginvoicescontactsconnector
const

Moneybird :: section

line 10
Defines a section within the Moneybird integration for organizing related functionalities.
'Moneybird'
method

%Moneybird -> base:string

line 12
This retrieves the base URL for the Moneybird API, incorporating the administration ID from the configuration if available.
'https://moneybird.com/api/v2/'.($this->config['administration_id'] ?? void)
method

%Moneybird -> headers:array

line 14
Returns the headers required for authentication with the Moneybird API, using a bearer token derived from the access token in the configuration.
[static::bearer($this->config['access_token'] ?? void)]
static

Moneybird :: fields:array

line 16
Defines the fields for the Moneybird integration, including the administration ID and a personal access token for accessing contacts and invoices.
arr(
	section: 'Moneybird',
	config: arr(administration_id: 'Administration ID'),
	secret: arr(access_token: 'Personal access token with read/write for contacts and invoices'),
)
method

%Moneybird -> contacts (array $query = []):obj

line 22
Retrieves a list of contacts from Moneybird based on the provided query parameters.
if ($m = $this->missing('administration_id', 'access_token')) return $m
return $this->get('contacts.json', $query)
method

%Moneybird -> findContact ($query):obj

line 27
Finds a contact in Moneybird based on the provided query, returning the first result found.
if ($m = $this->missing('administration_id', 'access_token')) return $m
return $this->get('contacts.json', ['query' => $query, 'per_page' => 1])
method

%Moneybird -> contact ($id):obj

line 32
Retrieves the contact information associated with the specified ID from Moneybird, requiring an administration ID and access token.
if ($m = $this->missing('administration_id', 'access_token')) return $m
return $this->get('contacts/'.$id.'.json')
method

%Moneybird -> invoices (array $query = []):obj

line 37
Fetches invoices from Moneybird based on the provided query parameters, requiring an 'administration_id' and 'access_token'.
if ($m = $this->missing('administration_id', 'access_token')) return $m
return $this->get('sales_invoices.json', $query)
method

%Moneybird -> createContact (array $contact):obj

line 42
Creates a new contact in Moneybird using the provided contact details. It requires an administration ID and access token to function properly.
if ($m = $this->missing('administration_id', 'access_token')) return $m
return $this->post('contacts.json', ['contact' => $contact])
method

%Moneybird -> createInvoice (array $invoice):obj

line 47
Creates a new invoice in Moneybird using the provided invoice data.
if ($m = $this->missing('administration_id', 'access_token')) return $m
return $this->post('sales_invoices.json', ['sales_invoice' => $invoice])
object

%OAuthConnector

/phlo/resources/connectors/OAuthConnector.phlo

Base class for OAuth2 connectors: stored, auto-refreshed bearer access tokens via TokenStore, on the OAuth2 primitive

Use this instead of Connector when the provider hands out access tokens that expire. Give it client_id, client_secret and a refresh_token and TokenStore keeps the access token and renews it in time; a subclass only names its section and tokenUrl.

oauthoauth2connectorbasetokenrefresh
const

OAuthConnector :: tokenUrl

line 10
Specifies the URL for obtaining an OAuth token.
void
method

%OAuthConnector -> oauthKey:string

line 13
One access token per record when the connector came from for().
static::section.(($owner = $this->owner) ? dash.$owner : void)
prop

%OAuthConnector -> token:?string

line 15
Retrieves the OAuth token using the provided credentials and refresh token from the TokenStore.
TokenStore::access($this->oauthKey, static::tokenUrl, $this->config['client_id'] ?? void, $this->config['client_secret'] ?? void, ['refresh_token' => $this->config['refresh_token'] ?? null])
method

%OAuthConnector -> headers:array

line 17
Returns the headers required for OAuth authentication, including a bearer token derived from the instance's token.
[static::bearer((string)$this->token)]
method

%OAuthConnector -> authed:bool

line 19
Checks if the OAuth token is authenticated by verifying that it is not void.
(string)$this->token !== void
object

%PDOK

/phlo/resources/connectors/geo/PDOK.phlo

PDOK connector: a Dutch address from a postcode and house number, out of the government's open register

address('1012RJ', 147) gives the street, city, municipality, province and coordinates, or null when the register does not know it. It needs no key and no account. An addition goes in the third argument, as the register writes it: 147, A. Call it from your own route and cache what people ask often; the register is a public service, not yours to hammer.

pdokaddresspostcodenetherlandsgeoconnector
const

PDOK :: api

line 10
'https://api.pdok.nl/bzk/locatieserver/search/v3_1'
method

%PDOK -> address (string $postcode, int|string $number, string $addition = void):?obj

line 12
$postcode = strtoupper(preg_replace('/\s+/', void, $postcode))
$house = $number.$addition
if (!preg_match('/^\d{4}[A-Z]{2}$/', $postcode) || !preg_match('/^\d+[A-Za-z0-9-]*$/', $house)) return null
$answer = $this->get('free', ['q' => "postcode:$postcode and huis_nlt:$house", 'fq' => 'type:adres', 'rows' => 1])
$doc = $answer->ok ? ($answer->data->response->docs[0] ?? null) : null
if (!$doc) return null
[$lng, $lat] = preg_match('/POINT\(([\d.]+) ([\d.]+)\)/', (string)($doc->centroide_ll ?? void), $point) ? [(float)$point[1], (float)$point[2]] : [null, null]
return obj(street: $doc->straatnaam, number: $doc->huis_nlt, postcode: $doc->postcode, city: $doc->woonplaatsnaam, municipality: $doc->gemeentenaam, province: $doc->provincienaam, lat: $lat, lng: $lng, label: $doc->weergavenaam)
object

%Resend

/phlo/resources/connectors/chat/Resend.phlo

Resend connector: send transactional email via the HTTP API

Needs api_key and a from_email on a domain you verified with Resend, otherwise the send is refused. send() takes HTML; leave it out for a plain text mail and add anything else Resend accepts through extra.

resendemailtransactionalmessagingconnector
const

Resend :: section

line 10
Resend::section allows you to define a section in a Resend resource for organizing related content.
'Resend'
method

%Resend -> base:string

line 12
Sets the base URL for the Resend API.
'https://api.resend.com'
method

%Resend -> headers:array

line 14
Sets the headers for the Resend request using the provided API key from the configuration.
[static::bearer($this->config['api_key'] ?? void)]
static

Resend :: fields:array

line 16
Defines the fields for the Resend configuration, including the default sender email and the API key.
arr(
	section: 'Resend',
	config: arr(from_email: 'Default sender, e.g. "App <noreply@yourdomain.com>"'),
	secret: arr(api_key: 'API key (re_...)'),
)
method

%Resend -> send ($to, $subject, $html = void, array $extra = []):obj

line 22
Sends an email to the specified recipient(s) with the given subject and optional HTML content, using the configured sender email address.
if ($m = $this->missing('api_key', 'from_email')) return $m
$email = ['from' => $this->config['from_email'], 'to' => is_array($to) ? $to : [$to], 'subject' => $subject]
if ($html !== void) $email['html'] = $html
return $this->post('emails', $email + $extra)
object

%Shopify

/phlo/resources/connectors/shops/Shopify.phlo

Shopify Admin API connector: read customers, orders and products; create draft orders and products; update inventory

Needs shop_domain including myshopify.com and an admin access token; api_version defaults to 2026-10 and a Shopify version is supported for about a year, after which Shopify answers with the oldest one it still supports, so set the one you tested against rather than leaning on the default. This connector speaks the REST Admin API, which Shopify calls legacy; its product writes are deprecated there since 2024-04. setInventory() writes an absolute level, not a difference, so read the current one first when you mean to add. A draft order is not an order until it is completed in Shopify.

shopifywebshopecommerceordersproductsconnector
const

Shopify :: section

line 10
Defines a section in a Shopify theme, allowing for customizable content and layout within the store.
'Shopify'
method

%Shopify -> base:string

line 12
'https://'.($this->config['shop_domain'] ?? void).'/admin/api/'.($this->config['api_version'] ?? '2026-10')
method

%Shopify -> headers:array

line 14
This retrieves the headers required for Shopify API authentication, including the access token from the configuration.
['X-Shopify-Access-Token: '.($this->config['access_token'] ?? void)]
static

Shopify :: fields:array

line 16
arr(
	section: 'Shopify',
	config: arr(
		shop_domain: 'Shop domain, e.g. your-store.myshopify.com',
		api_version: 'Admin API version (optional, default 2026-10)',
	),
	secret: arr(access_token: 'Admin API access token (shpat_...)'),
	scopes: 'read_customers, read_orders, read_products, write_draft_orders, write_inventory',
)
method

%Shopify -> customers (array $query = []):obj

line 26
Fetches a list of customers from Shopify using the provided query parameters.
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->get('customers.json', $query)
method

%Shopify -> searchCustomers ($query, int $limit = 10):obj

line 31
Searches for customers in Shopify based on a query string and an optional limit on the number of results returned.
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->get('customers/search.json', ['query' => $query, 'limit' => $limit])
method

%Shopify -> customer ($id):obj

line 36
Retrieves customer information from Shopify using the specified customer ID.
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->get('customers/'.$id.'.json')
method

%Shopify -> orders (array $query = []):obj

line 41
Fetches a list of orders from Shopify based on the provided query parameters.
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->get('orders.json', $query)
method

%Shopify -> products (array $query = []):obj

line 46
Fetches a list of products from Shopify using the provided query parameters.
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->get('products.json', $query)
method

%Shopify -> createDraftOrder (array $order):obj

line 51
Creates a draft order in Shopify using the provided order details.
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->post('draft_orders.json', ['draft_order' => $order])
method

%Shopify -> createProduct (array $product):obj

line 56
Creates a new product in Shopify using the provided product details.
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->post('products.json', ['product' => $product])
method

%Shopify -> setInventory ($inventoryItemId, $locationId, int $available):obj

line 61
Sets the available inventory level for a specific inventory item at a given location in Shopify.
if ($m = $this->missing('shop_domain', 'access_token')) return $m
return $this->post('inventory_levels/set.json', ['inventory_item_id' => $inventoryItemId, 'location_id' => $locationId, 'available' => $available])
object

%Slack

/phlo/resources/connectors/chat/Slack.phlo

Slack connector: post messages, read channel history and list channels

Needs a bot token, and the bot has to be invited into the channel it posts in, else the call comes back ok false with not_in_channel. Slack answers HTTP 200 even when it refuses, so read the outcome from the result, never from the status.

slackmessagingchatconnector
const

Slack :: section

line 10
Creates a section block in a Slack message, allowing for the inclusion of text and other elements.
'Slack'
const

Slack :: api

line 11
This item provides access to the Slack API, allowing integration with Slack services and functionalities.
'https://slack.com/api'
method

%Slack -> headers:array

line 13
Returns the headers required for authentication when making requests to the Slack API using a bearer token.
[static::bearer($this->config['bot_token'] ?? void)]
static

Slack :: fields:array

line 15
Defines the configuration fields for integrating with Slack, including the bot token, signing secret, and required scopes.
arr(
	section: 'Slack',
	secret: arr(
		bot_token: 'Bot user OAuth token (xoxb-...)',
		signing_secret: 'Signing secret for inbound webhook verification (optional)',
	),
	scopes: 'chat:write, channels:history, channels:read',
)
method

%Slack -> result (obj $res):obj

line 24
Processes the result from a Slack API call, returning the response if successful or a failure message if an error occurred.
if (!$res->ok) return $res
$data = $res->data
if (is_object($data) && ($data->ok ?? null) === false) return static::fail($data->error ?? 'Slack API error', $res->status)
return $res
method

%Slack -> send ($channel, $text, array $extra = []):obj

line 31
Sends a message to a specified Slack channel using the Slack API. It requires a configured bot token and accepts additional parameters through an array.
if (!$this->configured('bot_token')) return static::fail('Slack bot_token not configured')
return $this->result($this->post('chat.postMessage', ['channel' => $channel, 'text' => $text] + $extra))
method

%Slack -> history ($channel, int $limit = 20):obj

line 36
Fetches the message history from a specified Slack channel, with an optional limit on the number of messages returned.
if (!$this->configured('bot_token')) return static::fail('Slack bot_token not configured')
return $this->result($this->get('conversations.history', ['channel' => $channel, 'limit' => $limit]))
method

%Slack -> channels (int $limit = 100, string $types = 'public_channel'):obj

line 41
Retrieves a list of channels from Slack, with optional parameters to limit the number of channels and specify the types of channels to return.
if (!$this->configured('bot_token')) return static::fail('Slack bot_token not configured')
return $this->result($this->get('conversations.list', ['limit' => $limit, 'types' => $types]))
object

%Telegram

/phlo/resources/connectors/chat/Telegram.phlo

Telegram Bot API connector: send messages, photos and documents; poll updates

Needs a bot token from BotFather and a chat_id, and a person has to have written to the bot before it can write to them. Like Slack, the API answers 200 on refusal, which the connector already translates into ok false. photo() and document() take a URL or an existing file_id, not raw bytes.

telegrambotmessagingchatconnector
const

Telegram :: section

line 10
Defines a section within a Telegram resource, allowing for organized grouping of related content.
'Telegram'
method

%Telegram -> base:string

line 12
Constructs the base URL for the Telegram Bot API using the provided bot token from the configuration.
'https://api.telegram.org/bot'.($this->config['bot_token'] ?? void)
static

Telegram :: fields:array

line 14
Defines the fields related to Telegram configuration, including the bot token and optional webhook secret for verification.
arr(
	section: 'Telegram',
	secret: arr(
		bot_token: 'Bot token from BotFather',
		webhook_secret: 'Webhook secret token for inbound verification (optional)',
	),
	help: 'Create a bot via BotFather and store its token. The chat_id is the recipient or chat to message.',
)
method

%Telegram -> result (obj $res):obj

line 23
Processes the result from a Telegram API response, returning the response if successful or a failure message if an error is detected.
if (!$res->ok) return $res
$data = $res->data
if (is_object($data) && ($data->ok ?? null) === false) return static::fail($data->description ?? 'Telegram API error', $res->status)
return $res
method

%Telegram -> send ($chatId, $text, array $extra = []):obj

line 30
Sends a message to a specified Telegram chat using the provided chat ID and text, with optional extra parameters.
if ($m = $this->missing('bot_token')) return $m
return $this->result($this->post('sendMessage', ['chat_id' => $chatId, 'text' => $text] + $extra))
method

%Telegram -> photo ($chatId, $photo, $caption = void, array $extra = []):obj

line 35
Sends a photo to a specified Telegram chat with an optional caption and additional parameters.
if ($m = $this->missing('bot_token')) return $m
$payload = ['chat_id' => $chatId, 'photo' => $photo]
if ($caption !== void) $payload['caption'] = $caption
return $this->result($this->post('sendPhoto', $payload + $extra))
method

%Telegram -> document ($chatId, $document, $caption = void, array $extra = []):obj

line 42
Sends a document to a specified chat on Telegram, optionally including a caption and additional parameters.
if ($m = $this->missing('bot_token')) return $m
$payload = ['chat_id' => $chatId, 'document' => $document]
if ($caption !== void) $payload['caption'] = $caption
return $this->result($this->post('sendDocument', $payload + $extra))
method

%Telegram -> updates (int $offset = 0, int $limit = 100):obj

line 49
Retrieves updates from the Telegram bot API, allowing for pagination through the 'offset' and 'limit' parameters.
if ($m = $this->missing('bot_token')) return $m
return $this->result($this->get('getUpdates', ['offset' => $offset, 'limit' => $limit]))
object

%TokenStore

/phlo/resources/connectors/TokenStore.phlo

Persisted OAuth2 token store with automatic refresh via the OAuth2 resource

Tokens are kept as one file per key under data/tokens with 0600 rights, encrypted when [connectors] has a key, so a refresh survives a restart and every worker shares it. A refresh takes an exclusive lock, which matters with providers that rotate the refresh token: without it the losing request is left holding a dead one.

oauthoauth2tokenrefreshstorecredentials
static

TokenStore :: path ($key):string

line 9
The TokenStore::$path property defines the file path for storing token data, formatted as a JSON file based on a sanitized version of the provided key.
data.'tokens/'.preg_replace('/[^a-z0-9_.-]+/i', us, (string)$key).'.json'
static

TokenStore :: read ($key):array

line 11
$file = static::path($key)
if (!is_file($file)) return []
@chmod(data.'tokens', 0700)
@chmod($file, 0600)
$token = json_read($file, true)
return is_string($token) && class_exists('Connector') ? Connector::unseal($token) : (array)$token
static

TokenStore :: write ($key, array $token):void

line 20
$dir = data.'tokens'
is_dir($dir) || mkdir($dir, 0700, true)
@chmod($dir, 0700)
$file = static::path($key)
// Sealed when the [connectors] key is there, so a copy of data/ holds no live token.
json_write($file, class_exists('Connector') && Connector::sealKey(false) ? Connector::seal($token) : $token)
@chmod($file, 0600)
static

TokenStore :: valid (array $token):bool

line 30
Checks if the access token is valid by ensuring it exists and has not expired, allowing for a 30-second buffer before expiration.
($token['access_token'] ?? void) !== void && (int)($token['expires_at'] ?? 0) > time() + 30
static

TokenStore :: store ($res, $refresh):array

line 32
Stores the access token, refresh token, and expiration time for authentication purposes.
$token = [
	'access_token' => $res['access_token'],
	'refresh_token' => $res['refresh_token'] ?? $refresh,
	'expires_at' => time() + (int)($res['expires_in'] ?? 3600),
]
return $token
static

TokenStore :: lock ($key)

line 44
Takes an exclusive lock around one key's refresh cycle.
$dir = data.'tokens'
is_dir($dir) || mkdir($dir, 0700, true)
$lock = @fopen(static::path($key).'.lock', 'c')
if (!$lock || !flock($lock, LOCK_EX)){
	$lock && fclose($lock)
	return null
}
return $lock
static

TokenStore :: access ($key, $tokenUrl, $clientId, $clientSecret, array $seed = []):?string

line 55
Retrieves the access token from the TokenStore, refreshing it if necessary using the refresh token.
$token = static::read($key)
if (static::valid($token)) return $token['access_token']
$lock = static::lock($key)
if (!$lock) return null
try {
	$token = static::read($key)
	if (!($token['refresh_token'] ?? null) && ($seed['refresh_token'] ?? null)){
		$token = ['refresh_token' => $seed['refresh_token']]
		static::write($key, $token)
	}
	if (static::valid($token)) return $token['access_token']
	$refresh = $token['refresh_token'] ?? null
	if (!$refresh || !$tokenUrl || !$clientId) return null
	$res = OAuth2::refresh($tokenUrl, $clientId, $clientSecret, $refresh)
	if (!($res['access_token'] ?? null)) return null
	$token = static::store($res, $refresh)
	static::write($key, $token)
	return $token['access_token']
} finally {
	flock($lock, LOCK_UN)
	fclose($lock)
}
object

%Twilio

/phlo/resources/connectors/chat/Twilio.phlo

Twilio connector: send SMS and read message status

Needs account_sid and auth_token, plus either a from_number in E.164 or a messaging_service_sid; without one of the two the send is refused before it leaves. Twilio speaks form encoding rather than JSON, which sms() handles, so pass extra fields in Twilio's own capitalized names. A returned sid says it was accepted, not delivered; message() tells you what became of it.

twiliosmsmessagingconnector
const

Twilio :: section

line 10
Defines a section for Twilio integration within a Phlo application.
'Twilio'
method

%Twilio -> base:string

line 12
Constructs the base URL for accessing the Twilio API using the account SID from the configuration.
'https://api.twilio.com/2010-04-01/Accounts/'.($this->config['account_sid'] ?? void)
method

%Twilio -> headers:array

line 14
Retrieves the headers required for Twilio API requests using the provided account SID and auth token from the configuration.
[static::basic($this->config['account_sid'] ?? void, $this->config['auth_token'] ?? void)]
static

Twilio :: fields:array

line 16
Defines the fields for configuring Twilio integration, including account SID, sender number, and optional messaging service SID, along with the authentication token.
arr(
	section: 'Twilio',
	config: arr(
		account_sid: 'Account SID (ACxxxx)',
		from_number: 'Sender number in E.164, e.g. +31600000000 (use this or messaging_service_sid)',
		messaging_service_sid: 'Messaging Service SID (optional alternative to from_number)',
	),
	secret: arr(auth_token: 'Auth token'),
)
method

%Twilio -> sms ($to, $body, array $extra = []):obj

line 26
Sends an SMS message using Twilio's API, requiring either a from_number or messaging_service_sid from the configuration.
if ($m = $this->missing('account_sid', 'auth_token')) return $m
$from = $this->config['from_number'] ?? void
$service = $this->config['messaging_service_sid'] ?? void
if ($from === void && $service === void) return static::fail('Twilio from_number or messaging_service_sid required')
$fields = ['To' => $to, 'Body' => $body] + $extra
if ($service !== void) $fields['MessagingServiceSid'] = $service
else $fields['From'] = $from
return $this->form('Messages.json', $fields)
method

%Twilio -> message ($sid):obj

line 37
Retrieves a message from Twilio using the specified message SID.
if ($m = $this->missing('account_sid', 'auth_token')) return $m
return $this->get('Messages/'.$sid.'.json')

Last updated on 30 October 2026

We use essential cookies to make this site work. With your permission we also use analytics to improve the site.