API Integration documentation

Rikochey PHP

Install the PHP client, list websites and forms, submit requests and handle errors.

The PHP client covers the endpoints meant for servers. It requires PHP 8.1 and no dependency: curl is used when available, PHP streams otherwise. The WordPress plugin is built on it.

Installation

Download the ZIP and unzip it in your project, for instance under packages/rikochey-php.

Without Composer, load its autoloader:

require __DIR__.'/packages/rikochey-php/autoload.php';

With Composer, declare the folder as a path repository:

{
    "repositories": [{ "type": "path", "url": "packages/rikochey-php" }],
    "require": { "rikochey/rikochey-php": "^1.0" }
}

Create a client

use Rikochey\Client;

$client = new Client('42|pQ7d…', 'https://api.example.com');

The first argument is the organization's API key — see Obtain and manage a key. The second is the platform address, without /api. Keep the key in your server's configuration, never in a page.

The client creates that key in their panel, where it is shown once, and sends it to you. The library never asks for a password, because the API has no password authentication at all: a key opens that organization's API and nothing else, and it is revoked in one click without disturbing the account.

A dumped client keeps the key to itself, so it does not leak into a log:

print_r($client);   // token => ***

Need a second key — one per integration, so revoking one never takes down the others? Mint it with the one you already hold, through tokens() below.

Websites and forms

foreach ($client->websites()->all() as $website) {
    echo $website->name.' — '.$website->host;
}

$website = $client->websites()->get('019f0000-0000-7000-8000-00000000000a');
$inVienne = $client->websites()->inDepartment('86'); // empty array when none

$forms = $client->websites()->forms($website->id);

echo $forms->scriptTag();                    // <script src="…" defer></script>
echo $forms->find('contact')?->embedTag();   // <div data-rikochey-form="contact"></div>

foreach ($forms as $form) {
    echo $form->name.': '.count($form->fields).' fields';
}

Responses are read-only objects: Website, Department, WebsiteForms, Form, FormField, Token. Dates are DateTimeImmutable.

Languages

Form::$locale is the form's source language, Form::$locales the languages it offers, source first. Form::$name and the FormField texts — label, placeholder, helpText — are in the source language: the script translates them on the page.

Without a language, the tag follows the lang attribute of the page's <html>. Pass one to force it:

use Rikochey\Data\Form;

echo Form::tag('contact', [], 'en');
// <div data-rikochey-form="contact" data-rikochey-lang="en"></div>

echo $forms->find('contact')?->embedTag(['my-form'], 'en');

en-GB is accepted, and so is en_GB, written en-GB in the tag; a value that is not a language code is left out. A language the form does not offer renders it in its source language — see Languages.

Submit a request from a server

use Rikochey\Data\Captcha;

$submission = $client->submissions()->submit($website->id, 'contact', [
    'name' => 'Smith',
    'email' => '[email protected]',
    'message' => 'Hello, I would like a quote.',
    'page_url' => 'https://www.example.com/contact',
]);

echo $submission->message;  // the client's confirmation message
echo $submission->leadId;

Add 'form_locale' => 'en' to the fields to get the messages and the confirmation in one of the form's languages; without it, the website language applies, resolved among the form's languages.

The token replaces the website signature and the origin check, not the captcha: when the website's chain has links, pass the token solved on the page — new Captcha('recaptcha_v3:12', $token) as the fourth argument. See Submit a form.

Following a captcha fallback

When the API finds a chain link unreachable, it refuses the submission with a captcha_fallback error that names the next link and signs a fallback token authorizing it. Solve the captcha again at that link, then replay the submission with both:

use Rikochey\Data\Captcha;
use Rikochey\Exception\AuthorizationException;

try {
    $submission = $client->submissions()->submit($website->id, 'contact', $fields, $captcha);
} catch (AuthorizationException $exception) {
    if ($exception->errorCode !== 'captcha_fallback') {
        throw $exception;
    }

    $exception->nextCaptchaProvider();   // "altcha:7" — the link to solve next
    $exception->captchaFallbackToken();  // "1789459320.4f3b…" — valid two minutes

    $submission = $client->submissions()->submit($website->id, 'contact', $fields, Captcha::afterFallback(
        $exception,
        $tokenSolvedAtThatLink,
    ));
}

Captcha::afterFallback() is shorthand for new Captcha($exception->nextCaptchaProvider(), $token, $exception->captchaFallbackToken()). The client sends the link key and its token as headers, and the fallback token in the captcha_fallback field of the body, which the API strips before recording the request.

Without the fallback token, a third-party link the API did not designate is refused with captcha_provider_not_allowed. Follow one fallback per link, no more — see The captcha.

Manage keys with a key

$created = $client->tokens()->create('CRM integration');   // $created->token, shown once

foreach ($client->tokens()->all() as $token) {
    echo $token->name.' — last used '.($token->lastUsedAt?->format('Y-m-d') ?? 'never');
}

$client->tokens()->revoke($token->id);
$client->tokens()->revokeAll(); // including the token of this client

Errors

Every exception extends Rikochey\Exception\RikocheyException.

Exception When Useful properties
AuthenticationException 401: key unknown, expired or revoked status
AuthorizationException 403: other organization, website disabled, captcha errorCode, nextCaptchaProvider(), captchaFallbackToken()
NotFoundException 404: unknown website, form or token status
ValidationException 422: invalid fields errors(), by field name
RateLimitException 429 — see Rate limiting retryAfter, in seconds
ApiException Any other status — 413 payload_too_large among them — or a response that is not JSON status, errorCode, payload
TransportException No response: DNS, TLS, timeout
use Rikochey\Exception\ValidationException;

try {
    $client->submissions()->submit($website->id, 'contact', $fields);
} catch (ValidationException $exception) {
    foreach ($exception->errors() as $field => $messages) {
        // show $messages next to $field
    }
}

Your own HTTP client

Implement Rikochey\Http\Transport to send requests through your framework — proxy, logging, retries — and pass it as the third argument:

use Rikochey\Http\Request;
use Rikochey\Http\Response;
use Rikochey\Http\Transport;

final class MyTransport implements Transport
{
    public function send(Request $request): Response
    {
        // $request->method, $request->url, $request->headers, $request->body
        return new Response($status, $headers, $body);
    }
}

$client = new Client('42|pQ7d…', 'https://api.example.com', new MyTransport);

A transport returns every response, errors included: the client turns them into exceptions. It throws TransportException only when no response was received.