The Sliick Forms REST API
This page is a developer reference. It names Apex classes, permission sets and HTTP contracts, and it is for someone building a server-to-server integration against Sliick Forms. Admins configuring a form or a public site do not need it: see Choosing how to host a form instead.
What the API is for
Section titled “What the API is for”Sliick Forms exposes four Apex REST routes for authenticated integrations: fetch a form’s manifest, stage a submission, upload a file, and mint a save-and-continue token. They are the surface for a server-to-server client with an identity of its own.
They are not a guest surface, and no packaged permission set grants them to a guest. Apex REST is anonymous HTTP: a request from a browser and one from
curlare indistinguishable, andOriginandRefererare set by the caller. Granting these classes to a site’s shared guest identity would publish submit, upload and resume to the open internet. A browser on an Experience Cloud page uses the Sliick Experience Form component instead, which calls the@AuraEnabledExperienceEnginecontroller over the platform’s session-bound, CSRF-protected transport.
Becoming an authenticated caller
Section titled “Becoming an authenticated caller”The package ships an External Client App called Sliick Forms Integration, already registered by the install: there is no consumer key, secret or callback URL for you to create. It does not work until an admin edits its Policies screen, and that is the reason a first attempt fails.
- Create a dedicated integration user on the Minimum Access - Salesforce profile, never a person.
- Assign the packaged
Sliick Forms Integrationpermission set to that user, and do not clone it: an upgrade updates a packaged set in place and can never add a grant to your copy. The set carries API Enabled, Apex class access to the four handlers plus the inbound provisioning callback, read-only access to form definitions and elements, and Create on both staging platform events. It grants noForm_Submission__caccess at all. - Setup, External Client App Manager, Sliick Forms Integration, Policies, Edit. Set Permitted Users to Admin approved users are pre-authorized and pre-authorise the integration user’s own profile or a permission set that user holds.
- Enable the grant flow you intend to use, on the same screen. Enabling a flow and pre-authorising a user are separate settings and you need both.
| Flow | Who it is for | What you supply |
|---|---|---|
| Client Credentials | Your own backend calling your own org | Consumer key and secret, plus a Run As user set on the same Policies screen. That user is the identity every call runs as |
| JWT Bearer | Sliick Edge, when you use hosted public forms | No key material: Sliick holds the private key. You still enable the flow and name the integration user as its subject |
JWT Bearer against the packaged app is Sliick’s transport, not yours. The certificate an assertion is verified against lives in Dev-Hub-owned settings you cannot edit, so you cannot sign an assertion the packaged app will accept. To use JWT Bearer for your own backend, create your own External Client App in your org and point it at the same routes: the handlers are global and do not care which app the token came from.
Revoking access is one step: unassign the permission set from the user, or deactivate the user. Both take effect on the next call.
The four routes
Section titled “The four routes”All are deployed under the sliick namespace and all require an Authorization: Bearer <token> header.
| Method | Route | Purpose |
|---|---|---|
GET or POST | /services/apexrest/sliick/experience/* | Fetch a form’s manifest bundle |
POST | /services/apexrest/sliick/submit | Stage a submission |
POST | /services/apexrest/sliick/upload | Secure file intake |
POST | /services/apexrest/sliick/resume | Mint a save-and-continue token |
formExternalId may be supplied in the path, as a query parameter, or in a POST body.
A fifth route, POST /services/apexrest/sliick/callback, exists only so a hosted-edge connection can deliver credentials to the org. It refuses anything that is not a correctly signed delivery for that org, and you never call it.
/upload takes formExternalId, fileName and base64, and answers 201 with {"success": true, "uploadRef": "…", "fileName": "…", "errors": []}. Put the uploadRef in the submission payload for that field.
/resume takes formExternalId and state, where state is {"values": {…}, "rows": {…}, "page": 0}, and answers 201 with {"success": true, "resumeRef": "<token>"}.
Signed requests for edge-served forms
Section titled “Signed requests for edge-served forms”/submit, /upload and /resume refuse an unsigned request with 401 and Missing X-Sliick-Signature header when the form they name is served on your Sliick Edge address, meaning Allow on your public Sliick Edge address is ticked on that form.
- The rule is per form, not per org. A form that is not on your edge address is unaffected, even in an org that uses the edge for other forms.
- You cannot produce the signature yourself. It is computed over an org-held secret in a protected custom setting, invisible to subscribers by design. Send that traffic through Sliick Edge, which signs on your behalf and is also where rate limiting and bot filtering run.
- Unaffected: forms not on your edge address, orgs with no edge connection, Experience Cloud forms, and
/experience, which has never verified signatures. - A signature you do send is always checked, whatever the form’s setting.
Status codes
Section titled “Status codes”| Code | Meaning |
|---|---|
200 | Manifest served |
201 Received | Submission staged synchronously. The body carries submissionId |
202 Accepted | Submission published to the async path. No submissionId: track it by your own correlationId |
400 | Invalid_Request or Validation_Failed. The errors array names the failing fields |
401 | Signature_Invalid, or a required signature was missing |
403 | Form_Closed, or, if the whole route 403s, the caller does not hold that handler class |
404 | Form_Not_Found: no visible definition with that external ID |
500 | Server fault, or Validation_Unavailable where the server could not load the form’s elements to validate against. Retry with backoff rather than treating it as a rejection of the payload |
Which path you get is decided by your identity, not by a parameter. A caller with Create on Form_Submission__c takes the synchronous path and gets 201 with an ID; a caller without it, which includes every packaged permission set, takes the asynchronous path and gets 202. Treat 202 as the normal success case and do not depend on receiving a submissionId.
Reading a 403
Section titled “Reading a 403”A 403 means something different at each stage. Work down this list:
- The token request is refused. Nothing of ours was called yet. Almost always the Permitted Users setting, or a user whose profile or permission set was never pre-authorised. Check the user you actually authenticate as: for Client Credentials that is the Run As user.
- The whole route 403s, with no JSON body of ours. The identity behind the token does not hold that handler’s Apex class. Assign the packaged
Sliick Forms Integrationset, not a clone of it. - A 403 carrying
{"status": "Form_Closed"}. Authentication worked. The form is paused, outside its schedule window, or at its response cap. - A
404on/experiencefor a form you can see in the builder. You are calling as the org’s designated edge integration user, and that identity gets404for any form without Allow on your public Sliick Edge address ticked. Use a different user, or tick the box.