Meridian handbook
For agentsRecipes

Claims

How an agent lets a business claim its listing on engine-dmo, seeding an organisation for each listing, writing the claim link that has no helper, reading the verification mail, and verifying the form from a script.

engine-dmo

This feature is part of the DMO bundle (engine-dmo).

A claim is how a business with no portal account takes control of its entry: someone fills in a form, proves their email address, and staff approve. The engine draws the whole flow under /members/claim/. Your work is to give each listing an organisation and to link to the form. This recipe follows the DMO directory, and its examples use the locale code en: replace it with your source locale.

sequenceDiagram
  participant C as Claimant
  participant E as Engine
  participant S as Staff
  C->>E: GET /members/claim/(organisation id)
  C->>E: POST the form
  E-->>C: Mail with a confirm link
  C->>E: Follow the link: pending becomes verified
  S->>E: Approve in the admin
  E-->>C: Mail with a portal invitation

Seed an organisation for each listing

A claim is always of an existing organisation; it never makes one. So a listing can only be claimed if it has an organisation, and a directory of unclaimed businesses seeds one per listing. A name is enough:

# seed/organisations/ferry-cafe.yaml
name: The Ferry Cafe
# seed/documents/listing/ferry-cafe.yaml
organisation: ferry-cafe
locales:
  en:
    title: The Ferry Cafe
    slug: ferry-cafe
    summary: Tea and cake by the old ferry steps.
    location: { lat: 51.8120, lon: -2.7150, precision: rooftop }
    primaryTag: { id: eat-drink.cafe, type: tag }
    category: eat-drink
    area: { id: monmouth, type: area }
publish: [en]

organisation: is the organisation’s file name, and sits at the top level, not under locales:. Give an email or website where you know them: they are what the automatic signals compare the claimant’s address with. See Organisations and people for the file’s other keys. An organisation with no membership holds the none tier in member_editable.

There is no helper for this address, which makes it the one exception to the rule that links come from helpers (Rules). Write it out in the listing’s template:

{# in templates/_shared/listing/entry.html, inside the article #}
{% if entry.organisation %}
<p><a href="/members/claim/{{ entry.organisation.id }}">{{ t('listing.claim') }}</a></p>
{% endif %}
# config/strings/en.yaml (add to the file's strings:)
strings:
  listing.claim: Is this your business?

check accepts this href as it is, with the variable after a fixed path. Use the site’s portal path in place of /members if server.portal_path or the site’s portal.yaml moves it. entry.organisation is set only for the page’s own entry, so this link belongs on the entry page and cannot go on a card in a list. The form answers not found if auth.member.registration is invite_only, and the portal must not be mode: "off".

Verify

engine-dmo check
engine-dmo seed verify
engine-dmo seed reset --confirm && engine-dmo seed apply
engine-dmo render /places/ferry-cafe | grep -o '/members/claim/[^"]*'

The last prints the link. The form itself is the portal’s, so render cannot show it. With engine-dmo dev up running:

curl -s -o claim.html -w '%{http_code}\n' http://localhost:8080/members/claim/<organisation id>
TS=$(grep -o 'name="_ts" value="[^"]*"' claim.html | cut -d'"' -f4)
curl -s -o /dev/null -w '%{http_code}\n' -X POST --data-urlencode "_ts=$TS" \
  -d 'name=Sam&email=sam@ferrycafe.example&role=Owner' http://localhost:8080/members/claim/<organisation id>
engine-dmo crm claims list --status pending

Both answer 200 and the claim is listed as pending. To carry it on you need the mail, whose confirm link is /members/claim/verify/<token> on the address you posted to (Read the mail). Following it makes the claim verified and puts it in the admin’s Claims queue, where a person decides it. If you cannot read mail or sign in as staff, say the rest is the person’s.

The form’s challenge differs from an enquiry form’s. It is in the page’s _ts field, with no fetch and no minimum fill time. Its proof of work is a decimal number in _pow that makes sha256(_ts + number) start with 16 zero bits, about 65,000 tries, where a declared form asks for 8. It is optional here too: a claim with no proof is accepted, and is only never approved automatically.

See Claims for the signals, the score, the review queue and the settings.