Skip to content

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.

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 curl are indistinguishable, and Origin and Referer are 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 @AuraEnabled ExperienceEngine controller over the platform’s session-bound, CSRF-protected transport.

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.

  1. Create a dedicated integration user on the Minimum Access - Salesforce profile, never a person.
  2. Assign the packaged Sliick Forms Integration permission 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 no Form_Submission__c access at all.
  3. 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.
  4. 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.
FlowWho it is forWhat you supply
Client CredentialsYour own backend calling your own orgConsumer key and secret, plus a Run As user set on the same Policies screen. That user is the identity every call runs as
JWT BearerSliick Edge, when you use hosted public formsNo 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.

All are deployed under the sliick namespace and all require an Authorization: Bearer <token> header.

MethodRoutePurpose
GET or POST/services/apexrest/sliick/experience/*Fetch a form’s manifest bundle
POST/services/apexrest/sliick/submitStage a submission
POST/services/apexrest/sliick/uploadSecure file intake
POST/services/apexrest/sliick/resumeMint 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>"}.

/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.
CodeMeaning
200Manifest served
201 ReceivedSubmission staged synchronously. The body carries submissionId
202 AcceptedSubmission published to the async path. No submissionId: track it by your own correlationId
400Invalid_Request or Validation_Failed. The errors array names the failing fields
401Signature_Invalid, or a required signature was missing
403Form_Closed, or, if the whole route 403s, the caller does not hold that handler class
404Form_Not_Found: no visible definition with that external ID
500Server 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.

A 403 means something different at each stage. Work down this list:

  1. 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.
  2. 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 Integration set, not a clone of it.
  3. A 403 carrying {"status": "Form_Closed"}. Authentication worked. The form is paused, outside its schedule window, or at its response cap.
  4. A 404 on /experience for a form you can see in the builder. You are calling as the org’s designated edge integration user, and that identity gets 404 for any form without Allow on your public Sliick Edge address ticked. Use a different user, or tick the box.