jotform / jotform-api-nodejs Goto Github PK
View Code? Open in Web Editor NEWJotForm API - NodeJS Client
License: GNU General Public License v2.0
JotForm API - NodeJS Client
License: GNU General Public License v2.0
node -v
v7.7.1
npm i jotform --save gives
Failed to compile.
Error in .//jotform//sntp/lib/index.js
Module not found: 'dgram' in /react-app/node_modules/jotform/node_modules/sntp/lib
@ .//jotform//sntp/lib/index.js 3:12-28
Error in .//jotform//sntp/lib/index.js
Module not found: 'dns' in /react-app/node_modules/jotform/node_modules/sntp/lib
@ .//jotform//sntp/lib/index.js 4:10-24
Check out this method of Node.js SDK, it doesn't filter by formID
GET /user/forms
Try yourself.
See, I specify limit=100
but I also specify to filter result by two formIDs
only. So I have to receive just 2 items. But I receive full 100 items. How it can be??
id
filter key doesn't work either
https://api.jotform.com/user/forms?apiKey=...&filter="{"form_id":[827356126,9384673498]}"&limit=100&orderby=created_at
// Encoding URI component doesn't do the job either:
https://api.jotform.com/user/forms?apiKey=&filter="%7B%22form_id%22%3A%5B4065204955%2C5309169199%5D%7D"&limit=100&orderby=created_at
{
responseCode: 200,
resultSet: {
offset: 0,
limit: 100,
orderby: "created_at",
filter: ""{"form_id":[60422050068949,53609169109965]}"", // <==== the filter
count: 100 // <==== HOW IS IT POSSIBLE??? Must be only 2
},
message: "success",
content: [ 100 items here ]
}
Even though the given API is fully authorized, and I am able to use other delete functions of the API, this particular function returns an error saying user lacks authorization.
I have a question like: vehicle_image5
When I read this with the getFormSubmissions api call I get back all the answers. But for the image I get back the data like:
{
"widget_metadata": {
"type": "imagelinks",
"value": [
{
"name": "AC Cobra 6.jpg",
"url": "/widget-uploads/imagepreview/123123123123/123AC_Cobra_665bffac78e450.jpg"
}
]
}
}
but how can I get to this image to download the jpg file while authorized by the API?
I know that the image is at https://files.jotform.com/jotformWidgets/imagepreview/123123123123/123AC_Cobra_665bffac78e450.jpg but of course I have no rights to wget or curl it directly.
Provide a default timeout
or allow it to be specified for request
calls.
TypeError [ERR_INVALID_ARG_TYPE]: The "msecs" argument must be of type number. Received type object
node has several changes to handle socket timeout and agent here nodejs/node@949e885#diff-5f7fb0850412c6be189faeddea6c5359R180. This impacts jotform as the default value for timeout is null
sending request with timeout: added fix the issue
Your system throws an error when requesting getFormQuestions
:
if(response.statusCode != 200){
^
TypeError: Cannot read property 'statusCode' of undefined
at Request._callback (C:\Users\user\node\app\node_modules\jotform\index.js:42:24)
at self.callback (C:\Users\user\node\app\node_modules\jotform\node_modules\request\index.js:148:22)
at emitOne (events.js:96:13)
at Request.emit (events.js:188:7)
at ClientRequest.self.clientErrorHandler (C:\Users\user\node\app\node_modules\jotform\node_modules\request\index.js:257:10)
....
Usecase:
const jf = require("jotform");
jf.options({
apiKey: api_key,
debug: debug,
});
jf.getForms({
offset: 0,
limit: 300, // I set only 300, and response has just 150.
filter: {}
})
.then(forms => {
// forms is an array with > 100 entries
// I iterate it and make a request for every formId to "getFormQuestions(form.id)"
return Promise.all( forms.map(form => {
return jf.getFormQuestions(form.id)
.then(formQ => {
return {
form: form,
formQ: formQ
}
});
}));
})
.then(resp => {
console.log(resp);
})
.fail(err => {
console.log(err);
});
What error is this? How to get rid of it and get responses without errors?
I followed the instructions from the readme to import the Jotform class using esm imports
node version 18.19.0
import Jotform from 'jotform'
const client = new Jotform('mytoken')
TypeError: Jotform is not a constructor
Changing to
import JotformPackage from 'jotform'
const Jotform = JotformPackage.default
const client = new Jotform('mytoken')
solves the problem
Could you please implement a formId
filter for GET /user/forms endpoint?
And please don't limit it 29 units as you usually like to do with other filters. Let it be not less than 100, though the more the better. Example:
https://api.jotform.com/user/forms?filter={"ids": ["FormID1","FormID2","FormID3",... and not less than 100]}
With that filter we could avoid looping GET /form/{id} endpoint.
It is notorious that your servers are very weak when query something from your servers with a loop.
For instance, when requesting only 10 form questions (GET /form/id/questions
) in a loop, your servers may respond longer than 2 minutes, or throw error, or just freeze.
Example. We want to avoid doing this. Your servers throw errors very often when looped.
let promises = formIds.map(id => {
let p = new Promise((success, fail) => {
request('https://api.jotform.com/form/' + id + '?apikey={...}', function (error, response) {
if (error !== null) {
fail(error);
} else {
success(response.body);
}
});
});
return p;
});
Promise.all(promises);
While waiting for the filter we will use this approach. So please be aware that we will query your servers with loops very intensively.
CODE:
const jf = require("JotForm");
jf.options({
debug: true,
apiKey: 'b***93**2ca31***c995**79fe******'
});
jf.getFormSubmissions('90**3054***661')
.then(function(r){
})
.fail(function(e){
});
ERROR:
GET to URL: http://api.jotform.com/user?apiKey=b***93**2ca31***c995**79fe******
internal/validators.js:126
throw new ERR_INVALID_ARG_TYPE(name, 'number', value);
^
TypeError [ERR_INVALID_ARG_TYPE]: The "timeout" argument must be of type number. Received null
at validateNumber (internal/validators.js:126:11)
at getTimerDuration (internal/timers.js:376:3)
at new ClientRequest (_http_client.js:170:20)
at Object.request (http.js:47:10)
at Request.start (C:\Users\Mateus\Desktop\Área de Trabalho\BOT Discord\JotFormBOT\node_modules\request\index.js:594:30)
at Request.end (C:\Users\Mateus\Desktop\Área de Trabalho\BOT Discord\JotFormBOT\node_modules\request\index.js:1186:28)
at C:\Users\Mateus\Desktop\Área de Trabalho\BOT Discord\JotFormBOT\node_modules\request\index.js:436:12
at processTicksAndRejections (internal/process/task_queues.js:79:11) {
code: 'ERR_INVALID_ARG_TYPE'
}
What's going on with your servers? I constantly get this error when requesting you API. Constantly means 3-4 times for every 5 requests. It is not possible at all to use your API.
{
Error: connect ETIMEDOUT 104.23.128.7:443
at Object.exports._errnoException (util.js:1012:11)
at exports._exceptionWithHostPort (util.js:1035:20)
at TCPConnectWrap.afterConnect [as oncomplete] (net.js:1080:14)
code: 'ETIMEDOUT',
errno: 'ETIMEDOUT',
syscall: 'connect',
address: '104.23.128.7',
port: 443
}
IP address lookup reveals that that IP is connected with your organisation.
What error is this?
Why can't your servers serve my requests?
Can you fix it?
How to make requests while you will fix your issues?
If you add a new question using Jotform API without name or order:
Could you please elaborate which module should be installed and used, jotform-api-nodejs
or jotform
?
Read here http://api.jotform.com/docs/:
NodeJS Client Library
npm install jotform-api-nodejs
Now read your README:
Installation
npm install jotform
And they both can be installed, and have different versions. This is from package.json
after installation:
"jotform": "0.0.8",
"jotform-api-nodejs": "0.0.2",
How can it be? Which one should be used?
What error is this?
I run jotform.getForms({})
with Nodejs SDK and constantly get this error.
C:\Users\user\app\node_modules\jotform\index.js:102
(fullText !== undefined ? "&fullText=" + fullText : "") +
^
ReferenceError: fullText is not defined
at Object.exports.getForms (C:\Users\user\app\node_modules\jotform\index.js:102:14)
at Object.<anonymous> (C:\Users\user\app\routes\routes.js:20:4)
at Module._compile (module.js:541:32)
at Object.Module._extensions..js (module.js:550:10)
at Module.load (module.js:458:32)
at tryModuleLoad (module.js:417:12)
at Function.Module._load (module.js:409:3)
at Module.require (module.js:468:17)
at require (internal/module.js:20:19)
at Object.<anonymous> (C:\Users\user\app\app.js:25:14)
fullText
option to the method or don't - the error is throw anyhow.jotform.getForms({ limit: 10 })
nor jotform.getForms({ limit: 10, fullText: 'foo'} )
works. Error it thrown in both cases.getForms
method of Node SDK. There is no ANY mention of fullText
for getForms
method in the docs. Then why did you include it in the code?Your docs:
You code in getForms
method:
Guys, you collect money for your service. Every month! Be plz serious about the service you offer!
Earlier today you claimed here #16 that issues were fixed.
Now, six hours later the issue is still not fixed.
I want to test new features on real live data and I can't do it. When will you fix it?
I can't set the API to EU Protected mode.
Running npm audit
in a project with Jotform installed returns this result amongst others:
node_modules/tunnel-agent
request 2.2.6 - 2.86.0
Depends on vulnerable versions of form-data
Depends on vulnerable versions of hawk
Depends on vulnerable versions of http-signature
Depends on vulnerable versions of mime
Depends on vulnerable versions of qs
Depends on vulnerable versions of tunnel-agent
node_modules/request
jotform *
Depends on vulnerable versions of request
node_modules/jotform
The main issue here is that Jotform depends on a vulnerable version of request
Since I had to write these anyway, I'm sharing TypeScript typings for this package in case somebody needs them. It would be also a neat base for potential TypeScript migration in the future.
They are far from being ideal; I was not able to find a definite documentation for each and every call, so this is my "best guess" so that I have ANYTHING to work with.
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unused-vars */
declare module 'jotform' {
type ControlOptions = string; // optionone|optiontwo|optionthree…
type ControlValues = string; // valueone|valuetwo|valuethree…
type EnableDisableFlag = 'enable' | 'disable';
type EnableDisableUppercaseFlag = 'Enable' | 'Disable';
type HintOptions = string; // optionone,optiontwo,optionthree…
type HorizontalAlign = 'Auto' | 'Center' | 'Left' | 'Right' | string; // TODO: Add all possible values
type ISODate = string;
type NumberLikeString = `${number}`;
type Special =
| 'Canadian Provinces'
| 'Countries'
| 'Days'
| 'Gender'
| 'Last 100 Years'
| 'Months'
| 'None'
| 'Time Zones'
| 'US States Abbr'
| 'US States';
type StringifiedJson<T> = string;
type Url = string;
type Validation =
| 'Alphabetic'
| 'AlphaNumeric'
| 'Currency'
| 'Cyrillic'
| 'Email'
| 'Numeric'
| 'Url'
| 'None';
type VerticalAlign = 'Top' | 'Middle' | 'Bottom' | string; // TODO: Add all possible values
type Weekday = 'Sunday' | 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday';
type WidgetType = 'native' | string; // TODO: Add all possible values
type YesNoFlag = 'Yes' | 'No';
type ZeroOneFlag = '0' | '1';
export type Options = {
apiKey?: string;
debug?: boolean;
url?: string;
version?: string;
timeout?: number;
};
type Filter = Record<string, any>;
type Query = {
direction?: 'ASC' | 'DESC';
filter?: Filter;
limit?: number;
offset?: number;
orderby?: any;
};
export type User = {
username: string;
name: string;
email: string;
website: Url | null;
time_zone: string;
account_type: Url;
status: 'ACTIVE' | string; // TODO: Add all possible values
created_at: ISODate;
updated_at: ISODate | null;
region: 'EMEA' | string; // TODO: Add all possible values
is_verified: ZeroOneFlag;
usage: Url;
euOnly: ZeroOneFlag;
new_users_campaign: string;
loginToGetSubmissions: ZeroOneFlag;
loginToViewUploadedFiles: ZeroOneFlag;
loginToViewSubmissionRSS: ZeroOneFlag;
showJotFormPowered: ZeroOneFlag;
defaultTheme: string;
submission_inbox_group: string;
avatarUrl: Url;
formListingsTestUser: ZeroOneFlag;
company_logo: Url;
company: string;
tablesIntroVideoShown: YesNoFlag;
disableViewLimits: boolean;
allowBoards: boolean;
allowDigest: boolean;
allowPrefills: boolean;
allowWorkflowFeatures: boolean;
allowAutoDeleteSubmissions: boolean;
teamsBetaUser: ZeroOneFlag;
paymentNewProductManagement: boolean;
allowEncryptionV2: boolean;
};
export type Usage = {
submissions: string;
ssl_submissions: string;
payments: string;
uploads: string;
total_submissions: string;
signed_documents: string;
pdf_attachment_submissions: string;
mobile_submissions: string;
views: string;
api: number;
form_count: string;
};
export type Form<K extends string = string> = {
id: K;
username: string;
title: string;
height: NumberLikeString;
status: 'ENABLED' | 'DELETED' | string; // TODO: Add all possible values
created_at: ISODate;
updated_at: ISODate | null;
last_submission: string | null;
new: ZeroOneFlag;
count: NumberLikeString;
type: 'LEGACY' | 'CARD';
favorite: ZeroOneFlag;
archived: ZeroOneFlag;
url: Url;
};
export type FormQuestionBase<K extends string = string> = {
hidden?: YesNoFlag;
name: string;
order: NumberLikeString;
protected?: YesNoFlag;
qid: K;
text: string;
type: string;
};
export type FormQuestionControlBase<K extends string = string> = FormQuestionBase<K> & {
labelAlign: HorizontalAlign;
readonly?: YesNoFlag;
required?: YesNoFlag;
subLabel?: string;
useCalculations?: YesNoFlag;
};
export type FormQuestionComplexBase<K extends string = string> = FormQuestionBase<K> & {
labelAlign: HorizontalAlign;
readonly?: YesNoFlag;
required?: YesNoFlag;
};
export type FormQuestionAddress<K extends string = string> = FormQuestionComplexBase<K> & {
compoundHint: HintOptions;
countryDropdownDefaultText: string;
customCountryDropdown: YesNoFlag;
customCountryOptions: string;
customStateOptions?: '';
description?: string;
hasAutocomplete: YesNoFlag;
hasGeolocation: YesNoFlag;
requiredInputs: ControlOptions;
selectedCountry: string;
states: 'americanStates' | 'textbox';
subfields: ControlOptions;
sublabels: {
addr_line1: string;
addr_line2: string;
city: string;
state: string;
postal: string;
country: string;
};
type: 'control_address';
};
export type FormQuestionButton<K extends string = string> = FormQuestionBase<K> & {
buttonAlign: HorizontalAlign;
buttonStyle?: 'None' | string; // TODO: Add all possible values
clear?: YesNoFlag;
clearText?: string;
encryptIcon?: YesNoFlag;
print?: YesNoFlag;
printText?: string;
type: 'control_button';
};
export type FormQuestionCheckbox<K extends string = string> = FormQuestionControlBase<K> & {
allowOther?: YesNoFlag;
calcValues?: ControlValues;
description?: string;
maxSelection?: NumberLikeString | '';
minSelection?: NumberLikeString | '';
options: ControlOptions;
otherText?: string;
selected?: string;
shuffle?: YesNoFlag;
special?: Special;
spreadCols?: NumberLikeString;
type: 'control_checkbox';
};
export type FormQuestionDatetime<K extends string = string> = FormQuestionComplexBase<K> & {
ageVerification: YesNoFlag;
allowTime: YesNoFlag;
autoCalendar: YesNoFlag;
dateSeparator: '-' | '/' | '.';
days: ControlOptions; // Localized names of days
description?: string;
defaultDate: 'none' | 'current' | `custom_${string}-${string}-${string}`;
defaultTime: 'none' | 'current' | `custom_${string}:${string}`;
format: string;
limitDate: StringifiedJson<{
days: {
monday: boolean;
tuesday: boolean;
wednesday: boolean;
thursday: boolean;
friday: boolean;
saturday: boolean;
sunday: boolean;
};
future: true;
past: boolean;
custom: boolean;
ranges: boolean;
start: ISODate | '';
end: ISODate | '';
}>;
liteMode: YesNoFlag;
minAge: NumberLikeString;
months: ControlOptions; // Localized names of months
onlyFuture: YesNoFlag;
startWeekOn: Weekday;
step: NumberLikeString;
sublabels: {
day: string;
month: string;
year: string;
last: string;
hour: string;
minutes: string;
litemode: string;
};
timeFormat: 'AM/PM' | '24 Hour';
today: string; // Localized "Today"
validateLiteDate: YesNoFlag;
type: 'control_datetime';
};
export type FormQuestionDivider<K extends string = string> = FormQuestionBase<K> & {
color: string;
height: NumberLikeString;
lineStyle: 'solid' | 'dotted' | 'dashed';
margin: NumberLikeString;
protected?: never;
spaceAbove: NumberLikeString;
spaceBelow: NumberLikeString;
type: 'control_divider';
};
export type FormQuestionDropdown<K extends string = string> = FormQuestionControlBase<K> & {
autoFixed?: YesNoFlag;
calcValues?: ControlValues;
description?: string;
emptyText?: string;
multipleSelections?: YesNoFlag;
options: ControlOptions;
searchText?: string;
selected?: string;
shuffle?: YesNoFlag;
size?: NumberLikeString;
special?: Special;
type: 'control_dropdown';
visibleOptions?: NumberLikeString;
width?: NumberLikeString;
};
export type FormQuestionEmail<K extends string = string> = FormQuestionControlBase<K> & {
allowCustomDomains: YesNoFlag;
allowedDomains: string;
autoFixed?: YesNoFlag;
confirmation: YesNoFlag;
confirmationHint: string;
confirmationSublabel: string;
defaultValue: string;
description?: string;
disallowFree: YesNoFlag;
domainCheck: YesNoFlag;
hint: string;
maxsize: NumberLikeString | '';
shrink?: YesNoFlag;
size?: NumberLikeString;
validation?: Extract<Validation, 'Email'>;
verificationCode: YesNoFlag;
type: 'control_email';
};
export type FormQuestionFullname<K extends string = string> = FormQuestionComplexBase<K> & {
compoundHint: HintOptions;
description?: string;
middle: YesNoFlag;
prefix: YesNoFlag;
prefixChoices: ControlOptions;
sublabels: {
prefix: string;
first: string;
middle: string;
last: string;
suffix: string;
};
suffix: YesNoFlag;
type: 'control_fullname';
};
export type FormQuestionHead<K extends string = string> = FormQuestionBase<K> & {
alt?: string;
headerImage?: string;
headerType: 'Default' | 'Large' | 'Small';
imageAlign: HorizontalAlign;
nextButtonText?: string;
previousButtonText?: string;
protected?: never;
showQuestionCount?: YesNoFlag;
subHeader: string;
textAlign: HorizontalAlign;
type: 'control_head';
verticalTextAlign: VerticalAlign;
width?: string;
};
export type FormQuestionHidden<K extends string = string> = FormQuestionBase<K> & {
browserInfo: YesNoFlag;
defaultValue: string;
type: 'control_hidden';
labelAlign: HorizontalAlign;
selectedField: string;
widgetType: WidgetType;
};
export type FormQuestionImage<K extends string = string> = FormQuestionBase<K> & {
align?: HorizontalAlign;
alt: string;
description?: string;
height: NumberLikeString;
labelText: string;
link: Url | '';
protected?: never;
src: Url;
target: '_blank' | '_self' | '_parent' | '_top';
type: 'control_image';
width: NumberLikeString;
};
export type FormQuestionNumber<K extends string = string> = FormQuestionControlBase<K> & {
autoFixed: YesNoFlag;
defaultValue: string;
description?: string;
hint: string;
maxsize: NumberLikeString | '';
maxValue: NumberLikeString | '';
minValue: NumberLikeString | '';
shrink?: YesNoFlag;
size?: NumberLikeString;
type: 'control_number';
};
export type FormQuestionPagebreak<K extends string = string> = FormQuestionBase<K> & {
autoNext: YesNoFlag;
backText: string;
backVisi: 'Visible' | 'Hidden';
buttonStyle: 'None' | string; // TODO: Add all possible values
nextText: string;
nextVisi: 'Visible' | 'Hidden';
pageInfo: string;
type: 'control_pagebreak';
};
export type FormQuestionPhone<K extends string = string> = FormQuestionComplexBase<K> & {
compoundHint: HintOptions;
countryCode: YesNoFlag;
description?: string;
inputMask: EnableDisableFlag;
inputMaskValue: string;
sublabels: {
country: string;
area: string;
phone: string;
full: string;
masked: string;
};
shrink?: YesNoFlag;
size?: NumberLikeString;
type: 'control_phone';
};
export type FormQuestionRadio<K extends string = string> = FormQuestionControlBase<K> & {
allowOther?: YesNoFlag;
calcValues?: ControlValues;
description?: string;
options: ControlOptions;
otherText?: string;
selected?: string;
shuffle?: YesNoFlag;
special?: Special;
spreadCols?: NumberLikeString;
type: 'control_radio';
};
export type FormQuestionText<K extends string = string> = FormQuestionBase<K> & {
type: 'control_text';
};
export type FormQuestionTextbox<K extends string = string> = FormQuestionControlBase<K> & {
autoFixed?: YesNoFlag;
defaultValue?: string;
description?: string;
hint?: string;
inputTextMask?: string;
maxsize?: NumberLikeString | '';
minsize?: NumberLikeString | '';
shrink?: YesNoFlag;
size?: NumberLikeString;
type: 'control_textbox';
validation?: Validation;
};
type FormQuestionTextareaV1<K extends string = string> = FormQuestionControlBase<K> & {
cols: NumberLikeString;
maxsize?: NumberLikeString | '';
rows: NumberLikeString;
type: 'control_textarea';
};
type FormQuestionTextareaV2<K extends string = string> = FormQuestionControlBase<K> & {
autoFixed: YesNoFlag;
cols: NumberLikeString; // Height in pixels, NOT rows
defaultValue: string;
description?: string;
entryLimit: string;
entryLimitMin: string;
hint: string;
maxsize?: NumberLikeString | '';
mde: YesNoFlag;
rows: NumberLikeString; // Height in pixels, NOT rows
type: 'control_textarea';
validation?: Validation;
wysiwyg: EnableDisableUppercaseFlag;
};
export type FormQuestionTextarea<K extends string = string> =
| FormQuestionTextareaV1<K>
| FormQuestionTextareaV2<K>;
export type FormQuestion<K extends string = string> =
| FormQuestionAddress<K>
| FormQuestionButton<K>
| FormQuestionCheckbox<K>
| FormQuestionDatetime<K>
| FormQuestionDivider<K>
| FormQuestionDropdown<K>
| FormQuestionEmail<K>
| FormQuestionFullname<K>
| FormQuestionHead<K>
| FormQuestionHidden<K>
| FormQuestionImage<K>
| FormQuestionNumber<K>
| FormQuestionPagebreak<K>
| FormQuestionPhone<K>
| FormQuestionRadio<K>
| FormQuestionText<K>
| FormQuestionTextbox<K>
| FormQuestionTextarea<K>;
export type FormQuestions = { [K in string]: FormQuestion<K> };
type SubmissionAnswerBase<T> = Omit<T, 'answer'> & {
name: string;
order: NumberLikeString;
text: string;
type: string;
answer?: any;
};
type SubmissionAnswerAddress = SubmissionAnswerBase<{
answer?: {
addr_line1?: string;
addr_line2?: string;
city?: string;
state?: string;
postal?: string;
country?: string;
};
prettyFormat?: string;
}>;
type SubmissionAnswerButton = SubmissionAnswerBase<{ answer?: never }>;
type SubmissionAnswerCheckbox = SubmissionAnswerBase<{ answer?: string[] }>;
type SubmissionAnswerDatetime = SubmissionAnswerBase<{ answer?: any }>; // TODO
type SubmissionAnswerDivider = SubmissionAnswerBase<{ answer?: never }>;
type SubmissionAnswerDropdown = SubmissionAnswerBase<{ answer?: string }>;
type SubmissionAnswerEmail = SubmissionAnswerBase<{ answer?: string }>;
type SubmissionAnswerFullname = SubmissionAnswerBase<{
answer?: {
prefix?: string;
first?: string;
middle?: string;
last?: string;
suffix?: string;
};
prettyFormat?: string;
}>;
type SubmissionAnswerHead = SubmissionAnswerBase<{ answer?: never }>;
type SubmissionAnswerImage = SubmissionAnswerBase<{ answer?: never }>;
type SubmissionAnswerNumber = SubmissionAnswerBase<{ answer?: any }>; // TODO
type SubmissionAnswerPhone = SubmissionAnswerBase<{
answer?: {
country: string;
area: string;
phone: string;
full: string;
masked: string;
};
prettyFormat?: string;
}>;
type SubmissionAnswerRadio = SubmissionAnswerBase<{ answer?: string }>;
type SubmissionAnswerText = SubmissionAnswerBase<{ answer?: never }>;
type SubmissionAnswerTextbox = SubmissionAnswerBase<{ answer?: string }>;
type SubmissionAnswerTextarea = SubmissionAnswerBase<{ answer?: string }>;
type SubmissionAnswer =
| SubmissionAnswerButton
| SubmissionAnswerCheckbox
| SubmissionAnswerDatetime
| SubmissionAnswerDivider
| SubmissionAnswerDropdown
| SubmissionAnswerEmail
| SubmissionAnswerFullname
| SubmissionAnswerHead
| SubmissionAnswerImage
| SubmissionAnswerNumber
| SubmissionAnswerPhone
| SubmissionAnswerRadio
| SubmissionAnswerText
| SubmissionAnswerTextbox
| SubmissionAnswerTextarea;
export type Submission<K1 extends string = string, K2 extends string = string> = {
id: K2;
form_id: K1;
ip: string;
created_at: ISODate;
status: 'ACTIVE' | 'CUSTOM' | 'DELETED' | string; // TODO: Add all possible values
new: ZeroOneFlag;
flag: ZeroOneFlag;
notes: string;
updated_at: ISODate | null;
answers: {
[K in string]: SubmissionAnswer;
};
};
export type Subuser = Record<string, any>; // TODO
export type Folder<K extends string = string> = {
id: K;
path: string;
owner: string;
name: string;
parent: string;
color: string;
forms: Form[];
subfolders: Folder[];
};
export type FolderRoot = {
id: string;
path: string;
owner: string;
name: string;
parent: 'root';
color: string;
forms: {
[K in string]: Form<K>;
};
subfolders: Folder[];
};
export type Report<K extends string = string> = Record<string, any>; // TODO
type FolderLayoutNode = {
[K in string]: {
nodes: FolderLayoutNode;
};
};
export type Settings = {
username: string;
name: string;
email: string;
website: string | null;
time_zone: string;
account_type: Url;
status: 'ACTIVE' | string; // TODO: Add all possible values
created_at: ISODate;
updated_at: ISODate | null;
region: 'EMEA' | string; // TODO: Add all possible values
is_verified: ZeroOneFlag;
usage: Url;
euOnly: ZeroOneFlag;
new_users_campaign: string;
loginToGetSubmissions: ZeroOneFlag;
loginToViewUploadedFiles: ZeroOneFlag;
loginToViewSubmissionRSS: ZeroOneFlag;
showJotFormPowered: ZeroOneFlag;
defaultTheme: string;
submission_inbox_group: string;
avatarUrl: Url;
formListingsTestUser: ZeroOneFlag;
company_logo: Url;
company: string;
tablesIntroVideoShown: YesNoFlag;
folderLayout: StringifiedJson<FolderLayoutNode>;
disableViewLimits: boolean;
};
export type HistoryEntry = Record<string, any>; // TODO
export type FormFile<K1 extends string = string, K2 extends string = string> = {
id: K2;
name: string;
type: string;
size: string;
username: string;
form_id: K1;
submission_id: string;
uploaded: string;
date: string;
url: Url;
};
export type FormWebhook = string;
type ConditionActionBase = {
id: string;
isError: boolean;
};
export type ConditionVisibilityActionSingle = ConditionActionBase & {
visibility: 'Show' | 'Hide' | 'Require' | 'Unrequire';
field: string;
};
export type ConditionVisibilityActionMultiple = ConditionActionBase & {
visibility: 'ShowMultiple' | 'HideMultiple' | 'RequireMultiple' | 'UnrequireMultiple';
fields: string[];
};
export type ConditionVisibilityAction =
| ConditionVisibilityActionSingle
| ConditionVisibilityActionMultiple;
export type ConditionCalculationAction = ConditionActionBase & {
equation: string;
resultField: string;
};
export type ConditionAction = ConditionVisibilityAction | ConditionCalculationAction;
export type ConditionTerm = {
id: string;
field: string;
operator: 'contains' | 'notContains' | 'equals' | 'notEquals' | 'isEmpty' | 'isFilled';
value: string;
isError: boolean;
};
type Condition = {
action: StringifiedJson<ConditionAction[]>;
disabled?: ZeroOneFlag;
id: string;
index: string;
link: 'Any' | 'All';
priority: NumberLikeString;
terms: StringifiedJson<ConditionTerm[]>;
type: 'field' | 'require' | string; // TODO: Add all possible values
};
type Email = {
body: string;
branding21Email: ZeroOneFlag | '';
dirty: string;
from: 'default' | string;
hideEmptyFields: ZeroOneFlag;
html: ZeroOneFlag;
lastQuestionID: string;
name: string;
pdfattachment: string;
replyTo: string;
sendOnEdit: ZeroOneFlag;
sendOnSubmit: ZeroOneFlag;
subject: string;
to: string;
type: 'notification' | string; // TODO: Add all possible values
uniqueID: string;
uploadAttachment: string;
};
export type SubmissionSetting = Record<string, any>; // TODO
export type Integrations = Record<string, any> & {
webhooks?: {
endpoints: StringifiedJson<string[]>;
};
}; // TODO
type FormPropertiesBase<K extends string = string> = {
activeRedirect?: 'thanktext' | 'thankurl';
alignment: VerticalAlign;
conditions?: Condition[];
defaultAutoResponderEmailAssigned: YesNoFlag;
defaultEmailAssigned: YesNoFlag;
defaultTheme: string;
emails: Email[];
formOwnerAccountType: 'ENTERPRISE' | 'FREE' | string; // TODO: Add all possible values
formOwnerName: string;
hash: string;
height: NumberLikeString;
id: K;
injectCSS: string;
integrations: Integrations | never[];
isEUForm: ZeroOneFlag;
isHIPAA: ZeroOneFlag;
lastQuestionID: string;
owner: string;
pagetitle: string;
pageTitleChanged: YesNoFlag;
slug: string;
status: 'ENABLED' | string; // TODO: Add all possible values
styleJSON: string;
submissionSettings: SubmissionSetting[];
thanktext: string;
thankurl?: Url;
thankYouIconSrc: Url;
thankYouImageSrc: Url;
thankYouPageLayout: 'smallImageUp' | string; // TODO: Add all possible values
themeID: string;
title: string;
type: 'LEGACY' | string; // TODO: Add all possible values
};
export type FormPropertiesV3<K extends string = string> = FormPropertiesBase<K> & {
allowFormFillerToContact: YesNoFlag;
assigneeSubmissionPermission: 'submitOnly' | string; // TODO: Add all possible values
assignLinkExpireDate: string;
assignLinkExpireTimezone: string;
assignSlug: string;
datetimeMigrationIncludesBirthDate: ZeroOneFlag;
datetimeMigrationStatus: 'done' | string; // TODO: Add all possible values
defaultTheme: 'v1';
isOrganizationSettingModalClosed: ZeroOneFlag;
};
type FormStrings = Record<string, string>;
export type FormPropertiesV4<K extends string = string> = FormPropertiesBase<K> & {
allowSubmissionEdit: YesNoFlag;
background: string;
cardThemeID: string;
clearFieldOnHide: EnableDisableFlag;
creationLanguage: string;
defaultTheme: 'v2';
errorNavigation: YesNoFlag;
expireDate: 'No Limit' | any; // TODO: Add all possible values
font: string;
fontcolor: string;
fontsize: NumberLikeString;
formStrings: [FormStrings];
formStringsChanged: YesNoFlag;
formType: 'legacyForm' | string; // TODO: Add all possible values
formWidth: NumberLikeString;
hideEmptySubmissionFields: YesNoFlag;
hideMailEmptyFields: EnableDisableFlag;
hideNonInputSubmissionFields: YesNoFlag;
hideSubmissionHeader: YesNoFlag;
highlightLine: 'Enabled' | string; // TODO: Add all possible values
isEncrypted: YesNoFlag;
labelWidth: NumberLikeString;
limitSubmission: 'No Limit' | any; // TODO: Add all possible values
lineSpacing: NumberLikeString;
messageOfLimitedForm: string;
mobileGoButton: 'enable' | string; // TODO: Add all possible values
newPaymentUIForNewCreatedForms: YesNoFlag;
optioncolor: string;
pageColor: string;
preventCloningForm: YesNoFlag;
responsive: YesNoFlag;
sendpostdata: YesNoFlag;
showJotFormLogo: YesNoFlag;
showProgressBar: EnableDisableFlag;
styles: string;
submissionHeaders: string;
submitError: 'jumpToFirstError' | string; // TODO: Add all possible values
thankYouDownloadPDF: YesNoFlag;
thankYouEditSubmission: YesNoFlag;
thankYouFillAgain: YesNoFlag;
thankYouSelectedPDFs: YesNoFlag;
unique: 'None' | string; // TODO: Add all possible values
uniqueField: '<Field Id>' | string; // TODO: Add all possible values
usesNewPDF: YesNoFlag;
v4: '1';
};
export type FormProperties<K extends string = string> = FormPropertiesV3<K> | FormPropertiesV4<K>;
export function options(options: Options): void;
export function getUser(): Promise<User>;
export function getUsage(): Promise<Usage>;
export function getForms(query?: Query): Promise<Form[]>;
export function getSubmissions(
query?: Query & {
fullText?: any;
nocache?: any;
},
): Promise<Submission[]>;
export function getSubusers(): Promise<Subuser[]>;
export function getFolders(): Promise<FolderRoot | 'You do not have any folders'>;
export function getReports(): Promise<Report[]>;
export function getSettings(): Promise<Settings>;
export function getHistory(query?: {
action?: any;
date?: any;
sortBy?: any;
startDate?: any;
endDate?: any;
}): Promise<HistoryEntry[]>;
export function getForm<K extends string>(formID: K): Promise<Form<K>>;
export function getFormQuestions(formID: string): Promise<FormQuestions>;
export function getFormQuestion<K extends string>(
formID: string,
questionID: K,
): Promise<FormQuestion<K>>;
export function getFormSubmissions<K extends string>(
formID: K,
query?: Query,
): Promise<Submission<K>[]>;
export function createFormSubmission<K extends string>(
formID: K,
submission: {
submission: Record<string, any>;
},
): Promise<{ submissionID: string, URL: string }>;
export function createFormSubmissions(
formID: string,
submissions: Record<string, any>[],
): Promise<{ submissionID: string, URL: string }[]>;
export function getFormFiles<K extends string>(formID: K): Promise<FormFile<K>[]>;
export function getFormWebhooks(formID: string): Promise<Record<string, FormWebhook>>;
export function createFormWebhook(
formID: string,
webhookURL: string,
): Promise<Record<string, FormWebhook>>;
export function deleteFormWebhook(formID: string, webhookID: string): Promise<any>;
export function getSubmission<K extends string>(submissionID: K): Promise<Submission<string, K>>;
export function editSubmission<K extends string>(
submissionID: K,
submission: Partial<Submission<string, K>>,
): Promise<{ submissionID: K; URL: Url }>;
export function deleteSubmission(submissionID: string): Promise<any>;
export function getReport<K extends string>(reportID: K): Promise<Report<K>>;
export function getFolder<K extends string>(folderID: K): Promise<Folder<K>>;
export function createForm(form: Partial<Form>): Promise<Form>;
export function createForms(forms: Partial<Form>[]): Promise<any>;
export function deleteForm(formID: string): Promise<any>;
export function cloneForm(formID: string): Promise<Form>;
export function addFormQuestion(
formID: string,
question: Partial<FormQuestion>,
): Promise<FormQuestion>;
export function addFormQuestions(
formID: string,
questions: Partial<FormQuestion>[],
): Promise<any>;
export function deleteFormQuestion(formID: string, questionID: string): Promise<any>;
export function getFormProperties<K extends string>(formID: K): Promise<FormProperties<K>>;
export function addFormProperty(formID: string, property: any): Promise<any>;
export function addFormProperties(formID: string, properties: any): Promise<any>;
export function getFormPropertyByKey(formID: string, propertyKey: string): Promise<any>;
}
I was calling getFormSubmissions()
and seeing debug output, but my process exited immediately without waiting for the promise. After much frustration I accessed the API URL outputted by the debug info through my browser, and found the following error:
{"responseCode":301,"location":"https:\/\/eu-api.jotform.com\/form\/REDACTED\/submissions","message":"Your account is in EU Safe mode. Please use \"eu-api.jotform.com\" endpoint to get your results."}
I was handling errors correctly, so I didn't understand why I was not getting an exception (through the "fail" handler). I checked the code and came across the following in the sendRequest
function of your index.js
file (line 41):
request(options, function(err, response, body){
if(err){
deferred.reject(err);
return;
}
if(response.statusCode == 200 && body.responseCode == 200){
deferred.resolve(body.content);
}
if(response.statusCode != 200){
deferred.reject(new Error(body.message));
}
});
If the request comes back with a response.statusCode == 200, but there's a different response code in the JSON body, the deferred is neither resolved nor rejected. The code assumes that the HTTP status code will match the API response code, but to be sure you should check
if(response.statusCode != 200 || body.responseCode != 200)
you probably also should initialize your Error with body.message
only when there's a body, and with a library error otherwise... if the HTTP response fails it's not guaranteed you'll have a JSON body.
Your through and through broken API just make me crazy!
Method GET /user/submissions
.
First, formIDs
filter:
Why do you say it in your docs if it is not true?
You can also use
formIDs
commands to get more advanced filtering
Example: {"formIDs":["your-form-id","your-form-id#2"]}
Guys, it is not true. Filter doesn't work. Try to pass in at least 100 ids, just 100, not to say 1000. Then either delete your lie from your docs or edit that max number of items is about 25. I didn't check exact number, it is your job to test your code, we pay you for your service!
Second, form_ids:ne
filter, not equal.
Exactly the same thing with it. Doesn't work for more then 29.
Could you please repair these filters?
I use Node.js library.
I want to fetch 1000 forms. By your API it is max. I provide limit
filter but it doesn't work. Only default 100 is returned (for any limit number, not only 1000):
jf.getForms({
limit: 1000 // filter
})
.then(function(forms) {
console.log(forms.length); // 100, not 1000
})
.fail(function(e) {
});
I use jotform
, not jotform-api-nodejs
. See the difference: #4
How to make filter work?
var jotform = require("jotform")
jf.options({ // oops - there's no `jf`! =)
The jotform node module made use of "node-uuid" in the past for generating uuids. This package has been deprecated (https://www.npmjs.com/package/node-uuid) in favor of "uuid" (https://www.npmjs.com/package/uuid). Deprecation warnings related to jotforms use of the "node-uuid" are mucking up test/process logs and should be swapped out for "uuid".
Your stuff doesn't work. POST request returns success but it not success, nothing updated!
I have such a form field:
123: {
text: "Status",
type: "control_checkbox",
answer: [
"Status 1"
],
prettyFormat: "Status 1"
},
I need to update it with this array ["Status 2", "Status 3", "Status 4"]
How do I do it?
Code:
var request = require("request");
var options = { method: 'POST',
url: 'https://api.jotform.com/submission/12345',
qs: { apikey: '...' },
headers:
{ 'cache-control': 'no-cache',
'content-type': 'application/x-www-form-urlencoded',
apikey: '...',
accept: 'application/json' },
form: { 'submission[123]': '["Status 2", "Status 3", "Status 4"]' } };
request(options, function (error, response, body) {
if (error) throw new Error(error);
console.log(body);
});
UPD:
Your API doesn't consider array.
When I pass this array ["Status 2", "Status 3", "Status 4"]
your API for some reason takes only first item of the array. Why?? What do you do with the rest of the items? Why they disappeared?
123: {
text: "Status",
type: "control_checkbox",
answer: [
"Status 2"
],
prettyFormat: "Status 2"
},
Please provide us a working example on HOW TO UPDATE type: "control_checkbox"
WITH ARRAY using your API?
In @wojtekmaj/jotform
, a fork I maintain, you can get forms belonging to team like so:
jotform.getForm(formId, { 'jf-team-id': teamId })
In jotform
1.0.1, you can only get forms by ID:
client.form.get(formId)
which (kind of understandably), fails to fetch the form.
If formIDs
array is, say about 100 items, created_at:{operator}
filters are not applied (docs).
Example.
If I remove formIDs
option, oh my goodness, the API suddenly start working and returns as expected with created_at:
applied.
If I specify formIDs
, like in an example below, created_at:operators
are not applied and number of returned submissions is as many as limit
option says.
jf.getSubmissions({
offset: '0',
limit : '100',
filter: {
"created_at:gte": moment().set({hour: 0, minutes: 0, seconds: 0}).format('YYYY-MM-DD HH:mm:ss'),
"created_at:lt": moment().add(1, 'day').set({hour: 0, minutes: 0, seconds: 0}).format('YYYY-MM-DD HH:mm:ss'),
formIDs: ["formID1", "formID2", ...say 100 items]
}
})
When may we expect the fix?
As always: "sorry, we cannot fix it quickly"?
What is your best practices for bulk requests?
For example, a request for form/questions
for more than 100 forms (with Promise.all
, of course) never finishes and trows error:
if(response.statusCode != 200){
^
TypeError: Cannot read property 'statusCode' of undefined
at Request._callback (C:\Users\user\node\app\node_modules\jotform\index.js:42:24)
at self.callback (C:\Users\user\node\app\node_modules\jotform\node_modules\request\index.js:148:22)
at emitOne (events.js:96:13)
at Request.emit (events.js:188:7)
at ClientRequest.self.clientErrorHandler (C:\Users\user\node\app\node_modules\jotform\node_modules\request\index.js:257:10)
....
Here is a use case.
I need to create a response like this [ { form: {}, formQuestions: {} }, {...}, {...}, ... ]
So I use two requests: 1) getForms
and 2) getFormQuestions
in Promise.all
for every form.id
.
It works only for negligeable getForms
limit
s. I tested for limit: 10
and it is still ok. But for limit: 300
and if result forms.length
is > 100, it fails. The error is thrown.
const jf = require("jotform");
jf.options({
apiKey: api_key,
debug: debug,
});
jf.getForms({
offset: 0,
limit: 300, // I set only 300, and response has just 150.
filter: {}
})
.then(forms => {
// forms is an array with > 100 entries
// I iterate it and make a request for every formId to "getFormQuestions(form.id)"
return Promise.all( forms.map(form => {
return jf.getFormQuestions(form.id)
.then(formQ => {
return {
form: form,
formQ: formQ
}
});
}));
})
.then(resp => {
console.log(resp);
})
.fail(err => {
console.log(err);
});
So what is your best practice to perform such requests?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.