# <a name="main-title"></a> Idealist API documentation ## <a name="target-audience"></a> Target Audience This documentation is aimed at software developers wishing to implement programmatic posting to Idealist.org on behalf of approved organizations. ## <a name="overview"></a> Overview Idealist provides an API that allows an approved US-based organization to programmatically publish job listings using job pack credits purchased through the web application. There is no ability to programmatically purchase credits at this time. The API is mostly a JSON-based REST API. An API sandbox has been made available to test API calls. Please see below. This page documents creating and removing listings. To consume listings previously posted on Idealist.org please see the <a href="/listings-api">Listings API page</a>. ## <a name="requirements"></a> Requirements To post on Idealist.org: - You must have an approved, US-based organization on Idealist.org. - In order to post jobs, your organization must have purchased a job pack and must have at least one unused job credit to use the API. - You must contact the Idealist support staff to obtain a production API token: <support@idealist.org>. ## <a name="support"></a> Support For technical questions regarding the API during the implementation phase, contact <api@idealist.org>. For support afterward, contact <support@idealist.org>. ## <a name="sandbox"></a> Sandbox You can use the sandbox to test all of the API endpoints described below. You do not need to have an approved US-based organization on Idealist.org or to have received an API token to use the sandbox. Data older than one week is removed periodically. Don't expect your data to be preserved. The Sandbox URL is `https://api-sandbox.idealist.org`. The production URL is `https://www.idealist.org` (replace `api-sandbox` with `www`). ### <a name="create-a-test-organization"></a> Create a test organization The first step to use the sandbox is to programmatically create a test organization. Make a POST request with an empty body: ``` curl -X POST https://api-sandbox.idealist.org/api/v1/sandbox/organizations ``` Note: this is the only API call that does not require authentication. Sample response: ```json { "orgId": "9e11d62224ab48c79432fae431397620", "billingAddressId": "8acdb4a5d6fd4b2c80012b0066a5510d", "apiToken": "4de322caa0744fc38ff82e100963d933" } ``` The new test organization will have 3 job credits, allowing you to post 3 jobs and to make sure that your code correctly handles not having enough credits to post a new job. Feel free to create as many test organizations as you need. The `orgId`, `billingAddressId` field values will be necessary when creating a job. The `apiToken` will be necessary to authenticate to all of the endpoint calls. When making sandbox endpoint calls, please make sure that you are connecting to `https://api-sandbox.idealist.org` ## <a name="quick-start"></a> Quick Start To publish a new job on Idealist.org: - check that your organization has enough credits - fetch the list of billing addresses - create the job ### <a name="org-has-enough-credits"></a> 1. Check that the organization has enough credits Request: ``` curl -H "Accept: application/json" --user APITOKEN: https://HOST/api/v1/organizations/ORGID/credits ``` where: - **APITOKEN** is the API token - **HOST** is either `www.idealist.org` to post jobs on the production idealist.org site, or `api-sandbox.idealist.org` to simulate posting in the sandbox - **ORGID** is the organization's ID Notes: - all of the API endpoints require "Basic" authentification -- you must pass the API token as the username and no password. In the `curl` example above, this is why you see `APITOKEN:` , i.e. the value of the API token followed by a `:` and no password. - you can find the organization's ID on the production `idealist.org` site by accessing the organization's dashboard (through the upper right menu) and copying the ID from the URL. All organization dashboard URLs follow the `https://www.idealist.org/admin/ORGID/` pattern. The 32 character long UUID in the URL is the organization's ID. Sample response: ``` { "credits": { "JOB": 3 } } ``` ### <a name="get-list-billing-addresses"></a> 2. Get list of existing billing addresses Now that we've checked that the organization has enough credits to post a job, get the list of existing billing addresses. After posting the job, an invoice will be created with this billing address. Note that billing addresses cannot be created using the API and must be added/edited/removed through the idealist.org web UI. Request: ``` curl -H "Accept: application/json" --user APITOKEN: https://HOST/api/v1/organizations/ORGID/billing-addresses ``` where: - **APITOKEN** is the API token - **HOST** is either `www.idealist.org` to post jobs on the production idealist.org site, or `api-idealist.idealist.org` to simulate posting in the sandbox - **ORGID** is the organization's ID Sample response: ``` { "billingAddresses": [{ "id": "2e292e6f2bef4bff853a26a560463f13", "firstName": "John", "lastName": "Doe", "title": null, "phone": null, "email": "john.doe@example.com", "line1": "123 Fake Street", "line2": null, "city": "New York", "state": "NY", "zipcode": "10001", "country": "US" }], "page": 0, "perPage": 20, "count": 1, "lastPage": 0 } ``` ### <a name="post-the-job"></a> 3. Post the job ``` curl -H "Accept: application/json" \ -H "Content-Type: application/json" \ --user APITOKEN: \ -X POST \ -d ' { "billingAddressId": "BILLINGADDRESSID", "name": "Job Name", "description": "<p>Job description</p>", "locale": "en", "locationHidden": false, "addressLine1": "123 Fake Street", "addressLine2": "Suite 543", "addressCity": "New York", "addressState": "New York", "addressStateCode": "NY", "addressCountry": "US", "addressZipcode": "10001", "latitude": "40.7481561", "longitude": "-73.9895051", "applyEmail": "test@example.com", "professionalLevel": "PROFESSIONAL", "education": "HIGH_SCHOOL", "isFullTime": true, "isTemporary": false, "isContract": false, "locationType": "ONSITE" } ' \ https://HOST/api/v1/organizations/ORGID/jobs ``` where: - **APITOKEN** is the API token - **BILLINGADDRESSID** is the billing address ID (found when getting the list of existing billing addresses) - **HOST** is either `www.idealist.org` to post jobs on the production idealist.org site, or `api-sandbox.idealist.org` to simulate posting in the sandbox - **ORGID** is the organization's ID Sample response: ``` { "job": { "id": "eec0def7e0a8452eb0d9278b7a2b41cb" }, "invoice": { "id": "c3cb8584e4a14ab1ae126d694f432a38", "url": "https://www.idealist.org/invoices/c3cb8584e4a14ab1ae126d694f432a38" } } ``` You've created a new job! 🙌 ## <a name="authentication"></a> Authentication To authenticate all API endpoint requests, you must obtain a production API token from the Idealist support staff -- <support@idealist.org> All sandbox API calls must use the `apiToken` value returned when creating a test organization. You must use the API token as the `username` for Basic HTTP authentication. Use an empty string for the `password`. Most HTTP libraries come with Basic authentication. For example, using the Python library `requests` (<http://python-requests.org>): ```python import requests API_TOKEN = '9e11d62224ab48c79432fae431397620' ORG_ID = 'eec0def7e0a8452eb0d9278b7a2b41cb' BASE_URL = 'https://www.idealist.org/api/v1' url = '{base_url}/organizations/{org_id}/credits'.format( base_url=BASE_URL, org_id=ORG_ID, ) headers = { 'Accept': 'application/json', } response = requests.get(url, auth=(API_TOKEN, ''), headers=headers) credits_left = response.json()['credits']['JOB'] print(credits_left) ``` For more information: <https://en.wikipedia.org/wiki/Basic_access_authentication>. ## <a name="status-codes"></a> API Reference - Status Codes The API will return the following HTTP status codes: - 200 OK - 201 Created - job posted - 302 Found - if you are being redirected to /login, make sure that you are passing `Accept: application/json` - 400 Bad Request - check that your JSON is valid and that you are providing all required fields (the JSON response should include an explanation of the error) - 401 Unauthorized - check your API token and the host name (`www.idealist.org` for production or `api-sandbox.idealist.org` for sandbox) - 402 Payment Required - organization has no remaining job credits to post. Job Pack credits must be purchased using the web application interface to post more jobs. If you are using the sandbox, you can create a new test organization to post more jobs. - 403 Forbidden - you tried to do something you were not allowed to. Authentication went fine (we know who you are and your token is valid), but the action you are trying to do is forbidden. E.g. you tried to edit a deleted job. - 404 Not Found - check the organization ID ## <a name="rate-limiting"></a> Throttling / Rate Limiting There's currently no implementation of any rate limiting. However, since some requests can't be processed in parallel (e.g. posting a job uses a job credit, and a single credit can only be used once), it's expected that requests are sent sequentially and not in parallel. That is: you should wait to have received a response before sending the next request. ## <a name="endpoints"></a> API Reference - Endpoints ### <a name="credits"></a> Credits `GET /api/v1/organizations/{org_id}/credits` Sample response: ```json { "credits": { "JOB": 3 } } ``` ### <a name="billing-addresses"></a> Billing Addresses `GET /api/v1/organizations/{org_id}/billing-addresses` Sample response: ```json { "billingAddresses": [{ "id": "2e292e6f2bef4bff853a26a560463f13", "firstName": "John", "lastName": "Doe", "title": null, "phone": null, "email": "john.doe@example.com", "line1": "123 Fake Street", "line2": null, "city": "New York", "state": "NY", "zipcode": "10001", "country": "US" }], "page": 0, "perPage": 20, "count": 1, "lastPage": 0 } ``` You can specify the page in the url like so: `GET /api/v1/organizations/{org_id}/billing-addresses?page=1` Pages are 0-indexed. ### <a name="jobs"></a> Jobs #### <a name="create-a-job"></a> Create a Job `POST /api/v1/organizations/{org_id}/jobs` Sample request body: ```json { "billingAddressId": "2e292e6f2bef4bff853a26a560463f13", "name": "Job Name", "description": "<p>Job description</p>", "locale": "en", "jobFunctions": ["COMMUNICATIONS", "COMMUNITY_ORGANIZING"], "locationHidden": false, "addressLine1": "123 Fake Street", "addressLine2": "Suite 543", "addressCity": "New York", "addressState": "New York", "addressStateCode": "NY", "addressCountry": "US", "addressZipcode": "10001", "latitude": "40.7481561", "longitude": "-73.9895051", "applyEmail": "test@example.com", "professionalLevel": "PROFESSIONAL", "education": "HIGH_SCHOOL", "isFullTime": true, "isTemporary": false, "isContract": false, "locationType": "REMOTE", "remoteZone": "CITY", "remoteCountry": null } ``` ##### <a name="create-job-fields-required"></a> Create Job Fields - Required `name` (string) the title of the job listing. Max length: 1000 characters. `description` (string) HTML markup. Max length: 25000 characters. `latitude` and `longitude` (decimal, positive or negative) example: latitude `40.7125465` longitude `-74.0055648` for New York City. Allows us to show the listing to users looking for jobs around that point. `addressCity` (string) is a human readable city name, like `Pittsburg`. Max length: 255 characters. `addressState` (string) is a human readable state, like `California`. Max length: 100 characters. `addressStateCode` (string) is the standard state abbreviation, like `FL`. Max length: 100 characters. `addressCountry` (string) is the two letter ISO 3166-1 alpha-2 code for the country, like `US`. One of `applyEmail`, `applyUrl` or `applyText` is required. You may provide values for 1, 2 or all 3 fields. - if provided, `applyEmail` (string) must be a valid email address. Max length: 255 characters. - if provided, `applyURL` (string) must be a valid URL. Max length: 2000 characters. - if provided, `applyText` (string) must contain valid HTML markup. Max length: 25000 characters. `professionalLevel` (string) possible values are: `NONE`, `ENTRY_LEVEL`, `PROFESSIONAL`, `MANAGERIAL`, `EXECUTIVE`, `DIRECTOR` `education` (string) possible values are: `NO_REQUIREMENT`, `HIGH_SCHOOL`, `TWO_YEAR_DEGREE`, `FOUR_YEAR_DEGREE`, `MASTERS_DEGREE`, `JD`, `MD`, `PHD`, `OTHER` `billingAddressId` (string) the billing address ID to use on the invoice. ##### <a name="create-job-fields-optional"></a> Create Job Fields - Optional `jobFunctions` (array of strings) List of the job functions for this job. Maximum of 5. See [Job Functions](#job-functions) below for the API request to get the list of possible job functions. `locationHidden` (boolean) when set to `true`, only the city, state and country are shown on the site as the job's location. When set to `false`, `addressLine1` must be provided and will be shown on the site (as well as `addressLine2`, if provided). Default: `false` `addressLine1` (string) required if `locationHidden` is `false`. Max length: 255 characters. `addressLine2` (string) example: `"Floor 11"`. Max length: 255 characters. `addressZipcode` (string) zip code or postal code. Max length: 100 characters. `benefits` (string) HTML markup. Max length: 25000 characters. `salaryMinimum` (integer | float | string) minimum yearly salary in US dollars. example: `20000`, `"20000.00"`, `20000.00`. `salaryMaximum` (integer | float | string) maximum yearly salary in US dollars. example: `30000`, `"30000.00"`, `30000.00`. `salaryCurrency` (string) Three letter currency code. example: `"USD"`, `"EUR"`, `"CAD"`, `"GBP"`. `salaryPeriod` (string) The salary specified (min/max) is for the period: `"YEAR"`, `"MONTH"`, `"WEEK"`, `"DAY"`, `"HOUR"`. `salaryDetails` (string) HTML markup. Max length: 2500 characters. `languageProficiency` (string) HTML markup. Max length: 25000 characters. `locale` (string) One of `"en"` (English), `"es"` (Spanish), `"pt"` (Portuguese). The language used in the listing. `applicationDeadline` (ISO date) example: `"2017-07-15"` `startDate` (ISO date) example: `"2017-07-15"` `endDate` (ISO date) example: `"2017-07-15"`. If `startDate` is specified, must be after `startDate`. `isFullTime` (boolean) default: `false` `isTemporary` (boolean) default: `false` `isContract` (boolean) default: `false` `locationType` (string) Required. One of `"ONSITE"`, `"REMOTE"`, `"HYBRID"`. If not `"ONSITE"`, then `remoteZone` is required. If `HYBRID`, then `remoteZone` should be `CITY` or `STATE`, and will be converted to `CITY` if it's `COUNTRY` or `WORLD`. `remoteZone` (string) Where a remote job can be performed from. One of `"CITY"`, `"STATE"`, `"COUNTRY"`, or `"WORLD"` (default) if `locationType` is not `"ONSITE"`. If set to `"COUNTRY"`, then `remoteCountry` is required. If set to `"STATE", then `remoteState` is required. `remoteState`: (string) The US state for a STATE zone remote job; for a STATE zone remote job. Use the last two letters of the ISO 3166-2:US code for the state, like `"NY"`. `remoteCountry`: (string) The country for a COUNTRY zone remote job; for a STATE zone remote job, this should be set to US or will be set automatically (otherwise `null`). Use the two letter ISO 3166-1 alpha-2 code for the country, like `"US"`. `remoteOk` (boolean) <strong>This is deprecated and you should prefer to use </strong>`locationType`. Whether the job is permanently remote. ##### <a name="create-job-fields-recruiters"></a> Create Job Fields - Available to recruiter organizations only Organizations of the type "Recruiter" can post job listings either on behalf of other organizations (i.e., jobs posted by recruiters can have any organization's name attached to them) and "anonymously" (i.e., posted jobs will not show any organization's name) On the `idealist.org` production site, these fields can only be set by organizations of type "Recruiter". On the `api-sandbox.idealist.org` sandbox, all test organizations are allowed to set these fields. Note: If you wish to change the organization type of an organization in production, contact us. Fields: `hasOrgNameOverride` (boolean) whether to post on behalf of another organization `orgNameOverride` (string) if `hasOrgNameOverride` is `true`, this is the name of the organization that the job is posted on behalf of (leave `null` to post job with no organization name, i.e. "anonymously"). Max length: 1000 characters. #### <a name="create-a-job-continued"></a> Create a job (continued) If the post is successful, the server will respond with HTTP status `201`. Sample response: ```json { "job": { "id": "eec0def7e0a8452eb0d9278b7a2b41cb" }, "invoice": { "id": "c3cb8584e4a14ab1ae126d694f432a38", "url": "https://www.idealist.org/invoices/c3cb8584e4a14ab1ae126d694f432a38" } } ``` Note: on the API sandbox, fetching the invoice URL (returned as part of the response after creating a job -- see above) will return a 404. Invoice URLs on the production site will work. On the API sandbox, the `job.id` value (returned as part of the response -- see above) can be used to fetch the job using the [Get Job](#get-job) call, just as on the production site. #### <a name="get-job"></a> Get Job `GET /api/v1/organizations/{org_id}/jobs/{job_id}` Sample response: ```json { "job": { "id": "eec0def7e0a8452eb0d9278b7a2b41cb", "created": "2017-07-05T22:44:25.640039Z", "updated": "2017-07-05T22:44:25.640039Z", "status": ["PUBLISHED"], "name": "Job Name", "hasOrgNameOverride": false, "orgNameOverride": null, "description": "<p>Job description</p>", "locale": "en", "expires": "2017-08-04T22:44:25.639246Z", "creator": null, "deleted": null, "isPublished": true, "firstPublished": "2017-07-05T22:44:25.640039Z", "locationHidden": true, "addressLine1": null, "addressLine2": "line2", "addressCity": "city", "addressState": "New York", "addressStateCode": "NY", "addressZipcode": "10001", "addressCountry": "US", "latitude": 40.748156, "longitude": -73.989505, "applyEmail": "test@example.com", "applyUrl": null, "applyText": null, "benefits": null, "salaryMinimum": null, "salaryMaximum": null, "salaryDetails": null, "professionalLevel": "PROFESSIONAL", "languageProficiency": null, "education": "HIGH_SCHOOL", "applicationDeadline": null, "startDate": null, "isFullTime": true, "isTemporary": false, "isContract": false, "locationType": "REMOTE", "remoteZone": "CITY", "remoteCountry": null, "url": { "en": "https://www.idealist.org/en/eec0def7e0a8452eb0d9278b7a2b41cb-example", "es": "https://www.idealist.org/es/eec0def7e0a8452eb0d9278b7a2b41cb-ejemplo" } } } ``` #### <a name="edit-job"></a> Edit Job `PUT /api/v1/organizations/{org_id}/jobs/{job_id}` Sample request body: ```json { "name": "Job Name", "description": "<p>Job description</p>", "locale": "en", "jobFunctions": ["COMMUNICATIONS", "COMMUNITY_ORGANIZING"], "applyEmail": "test@example.com", "professionalLevel": "PROFESSIONAL", "education": "HIGH_SCHOOL", "isFullTime": true, "isTemporary": false, "isContract": false, "locationType": "ONSITE" } ``` The fields are the same as when creating a job, except that those field are not taken into account: - `locationHidden` - `addressLine1` - `addressLine2` - `addressCity` - `addressState` - `addressStateCode` - `addressCountry` - `addressZipcode` - `latitude` - `longitude` Note that you can edit jobs through the API only if the job has been published and is not archived or deleted. #### <a name="delete-job"></a> Delete Job `DELETE /api/v1/organizations/{org_id}/jobs/{job_id}` You should receive a `204 No Content` response. If you `GET` a deleted job, you will see that the `deleted` attribute is now a datetime string instead of `null`. For example: ``` { "job": { // ... "deleted": "2017-10-10T20:49:50.162464Z", // ... } } ``` #### <a name="hide-job"></a> Hide Job `POST /api/v1/organizations/{org_id}/jobs/{job_id}/hide` You can hide a published job by posting to the above URL. It's not possible to hide a job that is not currently published. #### <a name="unhide-job"></a> Unhide Job `POST /api/v1/organizations/{org_id}/jobs/{job_id}/unhide` You can unhide a hidden job by posting to the above URL. It's not possible to unhide a job that is not currently hidden. If a job has been hidden but would then be unpublished for other reasons (deleted, or expired), it's no longer possible to unhide the it. ### <a name="job-functions"></a> Job Functions `GET /api/v1/job-functions?lang={en|es}` Returns all the possible job functions. You have to pass the language in the URL, otherwise a 404 will be returned. You should query this list to make sure you have the lastest list of possible job functions. Sample response: ```json { "jobFunctions": [{ "id": "ACCOUNTING_FINANCE", "text": "Accounting & Finance" }, { "id": "ADMINISTRATIVE", "text": "Administrative" }, { "id": "ADVOCACY", "text": "Advocacy" }, // ... truncated { "id": "WRITING_EDITORIAL", "text": "Writing/Editorial" }] } ``` ### <a name="note-on-html-sanitization"></a> Note on HTML sanitization The HTML strings are sanitized for security and styling reasons. The following tags are currently allowed: - `h1` - `h2` - `h3` - `h4` - `h5` - `h6` - `ul` - `ol` - `li` - `p` - `br` - `a` - `strong` - `em` - `u` - `b` - `s` - `span` - `div` - `sub` - `sup` ## <a name="changelog"></a> Changelog - 2024-06-20 Add endDate. - 2024-06-20 Add salaryCurrency and salaryPeriod. Update salaryMinimum and salaryMaximum type. - 2024-01-24 Remove `remoteTemporary` and `remoteTemporaryCovid` fields and remove `REMOTE_TEMPORARY` as option from `locationType` as positions are no longer expected to be temporarily remote. - 2022-10-10 Add `locationType`. Deprecate `remoteOk` and `remoteTemporary`. - 2022-03-29 Add `locale`. - 2022-02-24 Update docs for `professionalLevel` (add `DIRECTOR`). - 2022-02-24 Update validation of "remote" fields to match changes made to production. - 2021-11-11 Update docs to note `remoteOk` and `remoteTemporaryCovid` are mutually exclusive. - 2021-11-03 Updated examples to use `remoteTemporary` instead of `remoteTemporaryCovid`. - 2021-10-08 Added `remoteZone`, `remoteCountry`, and `remoteTemporaryCovid` to api examples. - 2021-10-05 Added `remoteTemporary` in favor of `remoteTemporaryCovid` since we will support positions that are temporarily remote post-covid. - 2021-03-04 Updated to include `remoteZone`, `remoteCountry` and `remoteTemporaryCovid`. - 2019-09-26 Remove API for volunteer opportunities. - 2018-10-17 Add Note on HTML sanitization section. - 2018-06-20 Updated introduction and support email address and instructions. - 2018-06-16 Add documentation about volunteer opportunities new `addressDescription` field. - 2017-12-07 Add documentation about volunteer opportunities and hiding-unhiding listings. - 2017-10-09 Add missing documentation for deleting a job. - 2017-10-01 Add job functions and the possibility to edit and delete a job. Add max length of fields. - 2017-07-27 More information on production API launch date, more info on `orgNameOverride`. - 2017-07-20 Copy fixes and clarifications. - 2017-07-19 Initial version.