Finish connecting a Drupal site with the emailed code
Signed with the site key, like connect/verify. The code lives at most
300 seconds and allows three attempts: a wrong code answers 400
DRUPAL_CODE_INVALID, and once three have failed, 410
DRUPAL_CODE_EXHAUSTED. A correct code is claimed once and connects the
site, with the same answer and the same binding as connect/verify.
An emailed code proves the billing inbox, not the site: it may connect a new site, never take over a connected one (409 DRUPAL_SITE_ALREADY_CONNECTED). It answers 503 DRUPAL_EMAIL_CODE_UNAVAILABLE while codes cannot be checked.
Request Body
application/json
uuid^[0-9]{6}$The site key's Ed25519 signature, base64url without padding, over the key-sorted canonical JSON of the body without signature.
^[A-Za-z0-9_-]{86}$Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/v1/drupal/connect/code" \ -H "Content-Type: application/json" \ -d '{ "protocol": "decionis-drupal-connect/1", "purpose": "code", "connect_id": "a1b4c748-916a-48f3-ac78-f681f542c9c8", "code": "string", "signature": "string" }'{ "status": "connected", "org_id": "a40f5d1f-d889-42e9-94ea-b9b33585fc6b", "api_key": "string", "mode": "shadow", "entitlement": null}Prove a Drupal site by its challenge, or have a code emailed POST
Signed with the site key: `signature` is the Ed25519 signature, in base64url, over the key-sorted canonical JSON of the body without `signature`. For a reachable site, Decionis fetches `https://<site>/.well-known/decionis-challenge` over HTTPS with no redirects, a five-second budget and a 16 KB cap. The site answers a JSON object with `protocol` (`decionis-drupal-connect/1`), `purpose` (`challenge`), `connect_id`, `site_url`, `challenge`, and `signature`: the site key's signature over the rest, signed the same way. A match claims the challenge, once, and connects the site (200). A site that answers without a valid proof is refused (403 DRUPAL_CHALLENGE_FAILED) and is not offered a code. For a site Decionis cannot reach, or that gives no answer at all, a 6-digit code is emailed to the billing address instead (202); finish with `connect/code`. Until a code sender is configured, that answers 503 DRUPAL_EMAIL_CODE_UNAVAILABLE. Connecting binds the site to one Decionis organization for good, creating it the first time, and answers that organization's least-privilege key (role MEMBER, scopes decision:read and decision:write) exactly once: only its hash is kept. Proving a connected site again issues a new key and revokes the one it had. The site starts in Shadow. The challenge is single-use. A second verify of a used challenge answers 409 DRUPAL_CHALLENGE_ALREADY_USED, and a connect that has connected, failed or sent a code answers 409 DRUPAL_CONNECT_NOT_PENDING: start a new one. When Decionis refuses to bind the site, the answer carries that status with UPSTREAM_UNAVAILABLE.
Read a connected Drupal site's mode POST
Authenticated with the key the site received when it connected. Every Drupal site runs in Shadow today and `entitlement` is always null: no enforcement entitlement exists for Drupal workspaces yet. An unknown organization and a wrong key get the same 401. Read-only; safe to repeat.

