IFDB APIs > putific

The putific API

The putific API provides a way for application programs to add new game listings to IFDB, or update existing listings.

The usual way to create or edit an IFDB listing is through the IFDB Web form interface. The Web form is, of course, designed for direct interaction with a human user. The API provides a programmatic alternative, for situations where an application program has access to its own representation of a game's structured metadata, and wishes to send the data to IFDB without forcing the user to re-type the same data into a Web form.

This API was designed primarily for IF IDE-type tools, such as TADS Workbench or the Inform 7 IDE. These tools create and edit IF game projects, so by their nature they keep track of project metadata. The particular use case we envision for an IDE is a menu command (or something similar) to "Publish to IFDB". This would cause the IDE to send the project metadata to IFDB via this API, in order to create or update the IFDB listing for the game. This would save the user the trouble of re-entering the project metadata via the IFDB web form.

The metadata format used in the putific API is iFiction, the XML format defined by the Treaty of Babel specification. iFiction is the obvious format for our purposes here, since it was was specifically designed for metadata interchange among sites like IFDB and the IF Archive, tools such as an Inform or TADS IDE, client programs that act as IF browsers or jukeboxes, etc. IFDB's own game listing format was designed with the iFiction spec in mind, so an iFiction input file maps to an IFDB listing with excellent fidelity.

Examples

Code examples are available on Github. As of this writing, we have code samples for Python, Node.js, and Bash/Curl.

Note that you may want to test the API by setting up a local developer environment. See the README for more details. However, the dev environment does not support uploading images, so you won't be able to see your cover art if you test locally.

The example scripts are configured to point directly to the real IFDB site. If you create test entries, please clean them up, by visiting their game details page and clicking "Delete This Page" at the bottom of the page.

Basic protocol

The putific API is exposed as an HTTP POST request to a particular URL on the IFDB Web site. The URL is http://ifdb.org/putific.

All parameters are passed as POST data with the HTTP request. As typical for HTTP, there's no session state or context; the entire transaction is carried out as the single POST request.

The parameters to the API are provided in the usual HTTP POST fashion, by encoding them into the content body of the POST as though they were HTML form input field values being sent with a form submission. HTTP defines a number of Content-Type encodings that work with the POST verb, but this API requires a particular format. Specifically, the parameters must be provided as a content body with MIME type "multipart/form-data". This is required because of the need to attach one or more files to the request (the iFiction file, and optionally the Cover Art image file), and this is the standard HTTP way of handling file uploads using HTML forms. This format also handles ordinary scalar data parameters, and can freely mix scalar data values with file uploads.

The server replies to the POST with an XML object describing the result of the request. The contents are described below.

Constructing multipart/form-data requests

The easiest way to construct a POST request meeting the API's requirements is to use a Web client library with support for POST requests with multipart/form-data payloads. If that's not available, constructing the payload manually is fairly straightforward. For the benefit of hand-coders, this section outlines how to construct the payload. For full details, see the formal multipart/form-data specification in RFC 2388.

The overall form of the request body is a series of sections separated by "boundary" lines. A boundary line is just some fairly arbitrary text that's chosen so that it can't be confused with anything in any of the actual content being sent. Specifically, it's a string of characters that doesn't appear as a leading substring of any line of characters occurring in any of the content sections. The usual algorithm for constructing a boundary line is to pick a fairly long sequence of random letters and numbers (10-20 characters, say), then scan the content to verify that the sequence isn't a leading substring of any line of the content items. If the sequence does happen to appear, simply pick another sequence and scan again. Long random sequences are statistically unlikely to occur in the data, so this process should find a suitable boundary string quickly.

To construct a boundary line in the payload, start a line with two hyphens (ASCII 45), followed by the boundary string, followed by a CR-LF (ASCII 10-13) sequence.

The payload begins with a boundary line.

The end of the payload is indicated by a special boundary line that adds two hyphens (ASCII 45) at the end of the boundary string (just before the CR-LF).

Immediately after each boundary line is a Content-Disposition line. There are two forms for this line: one for ordinary data parameters, and one for file attachments.

An ordinary data parameter is identified with a content-disposition line like this:

   Content-Disposition: form-data; name="foo"

A file attachment has a similar content-disposition line, but adds a "filename=" parameter. It also must be followed by two additional header lines giving the MIME type of the file attachment and the transfer encoding:

   Content-Disposition: form-data; name="coverart"; filename="mypic.jpg"
   Content-type: image/jpeg
   Content-transfer-encoding: binary

The content-type should be text/xml for the iFiction attachment, and image/jpeg, image/png, or image/gif for the cover art image.

For this API, always use the 8bit encoding for text files (including UTF-8), and the binary encoding for image files. Don't use base64 or other text-encapsulated formats for image files, as the Apache/php combination we're using doesn't seem to decode these properly.

Following the content-disposition line (and the additional content headers for file attachments), add a blank line (i.e., another CR-LF pair), followed immediately by the content data for the section. For an ordinary parameter item, this is simply the plain text of the parameter value. For a file, this is the contents of the file. Since we transfer binary files with the "binary" transfer encoding, you should simply copy a binary file into the payload byte for byte.

The end of the section's content is indicated by a CR-LF pair followed immediately by a boundary line. The CR-LF just before the boundary is considered to be part of the boundary, so it must be present in all cases, including binary files.

Once the payload is constructed, it's simply sent to the server with an HTTP POST verb and a Content-Type header like so:

   Content-Type: multipart/form-data; boundary="asdfjkl"

Note that the boundary value specified in the POST content-type header does not include the "--" prefix that appears with each boundary line within the content body.

Parameter names and values

username: the IFDB username for the account under which the listing will be created. Ordinary text parameter; required.

password: the user's IFDB password. Ordinary text parameter; required.

ifiction: the iFiction XML file upload. Use content-type text/xml. The file name and suffix aren't important; the server ignores these and always tries to parse this file as XML regardless of the filename. See the external links section for details on the XML format. File upload parameter; required.

links: the external link list. This is an XML attachment, like the iFiction file. Use content-type text/xml. The file name and suffix are ignored; this is always treated as an XML file. File upload parameter; optional.

coverart: the Cover Art image for the game listing. If present, this must be a valid JPEG, PNG, or GIF file. There's an upper limit on the file size, which is currently 256K bytes; uploading an image over the limit will cause an error reply. It's not necessary for the client detect which image format is being submitted or to validate the image, since the server does this itself, and ignores the filename, extension, and content-type. So, for example, if you submit a JPEG image as a file named FOO.GIF with an image/png content type, the server will still know it's a JPEG. If you try submit a Word document with the same name and type, the server will reject it as an invalid image. If you happen to know the image type, you should specify correct content-type (image/jpeg, image/png, or image/gif), since it's conceivable that the API could become stricter in the future about the content-type header matching the actual submitted data. If you don't know the image type, use image/x-unknown; again, in the event the server needs to become stricter about matching types, this will tell the server that the client isn't claiming to know the type. File upload parameter; optional.

imageCopyrightStatus: the copyright status of the image; IFDB records this in the database with the image. The parameter value must match one of the following strings:

The default is "unspecified". Ordinary text parameter; optional.

imageCopyright: the copyright message for the image, as a text string (e.g., "Copyright 2009 Bob Smith"). The value can be any text. If this omitted or empty, and the copyright status is not "public domain" or "unspecified", a default copyright message will be generated of the form "Copyright (year) by (author)", where "year" is the current year and "author" is the author name from the iFiction record. Ordinary text parameter; optional.

requireIFID: This parameter can be supplied with the value "no" to override the usual rule that a TUID/IFID must be provided in the iFiction record. This should never be used by an IF IDE that's submitting an author's own game project for publication. Any IF IDE that's using this API should conform to the Babel specification at least as far as generating a proper IFID for each game project, so there should never be a need for an IDE to submit a game without an IFID. Instead, this parameter is specifically intended for clients importing information on old commercial games for which no copy of the executable is available: without an executable, it's not even possible to construct the MD5-based IFID that Babel specifies for legacy games.

When this parameter is included with the value "no", the API is only capable of creating a new game record, and requires the title of the game to be unique (that is, if there's an existing game with the same title, ignoring upper/lower case differences, the new listing will be rejected). We discourage using this parameter except as a last resort; in particular, if you could compute the MD5 IFID for the game (because you have a copy of the executable, or could legally get one), but you just haven't done so, the inconvenience of computing the MD5 IFID is a poor excuse for using this override. Providing an IFID (even if it's just an MD5 hash) is highly desirable because it helps reduce the chances that someone else will create a redundant listing for the same game.

Ordinary text parameter; optional.

lastversion: This parameter represents the current version number of the page. This number is shown in the footer of the HTML page ("This is version N of this page") or in the XML version of the page in the <pageversion> field.

This parameter is required only when editing an IFDB listing, and only when another IFDB user has most recently edited the listing. When creating a new listing, or when editing a listing that you previously edited, the parameter is not required.

iFiction records

The iFiction file included in the request must be syntactically valid XML. The API does not validate against any XML schema, so you can use custom tags beyond what's in the Babel spec (to store development system-specific extensions to the basic Babel metadata, for example). The file must be encoded in the UTF-8 character set, although be aware that IFDB will convert content data to ISO-8859-1 (Latin-1), so there could be loss of fidelity for characters outside of the 8859-1 subset of Unicode.

As in the Babel spec, the file must not contain any entity markups beyond the primitive XML set, namely &lt; for <, &gt; for >, and &amp; for &. HTML markups are not allowed within field values, except for the markup <br/> to represent a paragraph break in the <description> item.

The following Babel fields are used for the corresponding IFDB fields:

In addition to the fields listed above, the game's current version number is inferred from the releases/attached/release/version item, if present, otherwise from the first releases/history/release/version item in order of appearance in the file. If none of those items are present, the API uses the TADS-specific item tads2/version, if present.

Required fields: The iFiction file must minimally include title, and author, as well as a unique identifier, which is either an IFDB TUID or the game's IFID(s). (The identifier requirement can be overridden with the requireIFID parameter, though, as described above.) All other items are optional.

External links

The request can optionally provide a list of external links (including downloadable files) to add to the game listing. You will usually want to include at least a link to the downloadable story file, but you can also include any desired supplemental files (such as hints, maps, instructions, walk-throughs, or "feelies"). These are listed on the game's IFDB page in the "External Links" section.

Note that IFDB doesn't host game downloads. It only links to them. The External Links section is simply a list of URLs to files on other servers; you don't attach any of the actual game files to the request, just pointers to them.

The external links list is specified via an XML file, attached to the request via the links parameter. The XML content is formatted as follows:

   <downloads xmlns="http://ifdb.org/api/xmlns">
      <links>
        link 1
        link 2
        ...
      </links>
   </downloads>

Each link item has this format:

   <link>
      <url>URL to the file</url>
      <title>Title of the file</title>
      <desc>Description of the file</desc>
      <isGame/>
      <pending/>
      <format>File format ID</format>
      <os>Operating system ID and version</os>
      <compression>File format ID for compression type</compression>
      <compressedPrimary> </compressedPrimary>
   </link>

You should always include a URL, title, and format. The OS and OS version are required for application program files, and shouldn't be included for file types. The other fields are optional.

The URL is the full, absolute URL for the file, starting with "http://". For files hosted on the IF Archive, always provide the URL of the main IF Archive server, not a mirror. IFDB has special recognition of IF Archive URLs. In particular, each user can set a preferred Archive mirror, and IFDB will automatically rewrite IF Archive URLs to point instead to the selected mirror when that user is viewing a game's page. If you specify a URL that points to a mirror site, IFDB won't be able to make this preference adjustment.

The title is the displayed title of the file, which serves as the hyperlinked text. It's friendliest to users to use a descriptive name, such as "Story File" or "Installer", but many IFDB links just use the root filename (i.e., the last part of the URL, stripped of any path prefix).

The description is additional descriptive text to be displayed with the link. This is plain text (no HTML markup).

isGame should be included if this link points to a playable version of the game, such as a story file or an executable application, or it's a compressed file that contains a playable version of the game. For example, <isGame/> should be included for a ZIP file containing a Z-Machine story file for the game. Omit this for supplemental files (documentation, walk-throughs, etc).

pending should be included if the provided URL isn't a working link yet, but is expected to be working soon. This is designed especially for use with IF Archive uploads. When you upload a file to the Archive, you can generally predict the final URL for the file based on the Archive's directory layout conventions. However, the file won't actually appear at this URL immediately after uploading it, since the Archive administrators review uploads before moving them to their final directory location. This process usually takes a few days. This is inconvenient for the IDE "publish" command that the putific API is designed for: the publisher will want to perform the IF Archive upload and IFDB page creation in a single process, but creating the IFDB page right after the IF Archive upload means that either you have to put up with a broken link for a few days, or make the user come back and fill in the link manually when it's actually working. Neither option is very good. This is where the "pending" flag comes in. The flag tells IFDB that the link isn't good yet, so IFDB will hide the link from the game's home page. It also tells IFDB that the link will be good soon, though. IFDB acts on this by periodically testing the link to see if it works. As soon as the link starts working, IFDB will remove the "pending" flag and show the download link with the listing.

format gives the file's format. This is specified as an "External ID" code for the file type, which you can find on the File Formats page.

os gives the file's operating system and version. This should be specified only for binary machine-code executable application files, such as Windows .EXE files or Unix binaries. Don't include this for Virtual Machine "story file" types such as z-code or TADS, since these files are portable to multiple machines. Don't include it for document types (PDF, Word, ASCII text, GUE maps, etc), since these files are also not inherently tied to a single type of machine.

The operating system is specified as a two part External ID for the OS. The first part is the external ID for the OS itself, and the second is the OS version. The two parts are separated by a period '.'. You can find the list of versions for each OS by going to the Operating Systems page, then clicking on the OS you want to look up. The OS page has a list of "Version & Download Adviser entries". Each version shows the External ID string for the OS/version combination. For example, Windows 95's External ID is "Windows.95".

IFDB's basic assumption for OS versioning is that applications for version N of a given OS will also run on version N+1. You should therefore always specify the earliest OS version that the executable will run on; IFDB will automatically offer it to people who are using later versions of the same OS. (When our backward compatibility assumption fails to hold, we simply treat the newer, incompatible OS version as a whole new operating system. That's why Mac OS is in the list twice: once as Mac OS 7-9, and separately as Mac OS X.)

Important: for a compressed file, such as a ZIP file, specify the format and OS for the contents of the compressed file, rather than the compression format itself. IFDB considers the compression to be just a wrapper; what's important is what's inside. For an archive format like ZIP or Tar that can group multiple files together, specify the format of the "primary" file - see compressedPrimary below for details.

compression gives the file format for a compressed file, such as a ZIP or .tar.gz file. Specify this only when there is indeed some kind of compression or wrapper format. For our purposes, we call any sort of archive wrapper format a "compression" format, even if there's no actual data compression. For example, you'd specify "tar" here for a .tar file, even though Tar doesn't involve any data compression.

As we just mentioned, the format entry for a compressed file gives the format of the contents rather than of the compression wrapper; so the compression element is where the compression wrapper format is specified. The value here is an External ID taken from the File Formats list.

compressedPrimary gives the filename of the "primary" file within a compressed or archive file. This is the name of the primary file after extraction. If there's a directory path, use the path relative to the container, since you obviously can't guess at the absolute directory structure where a user will eventually extract the file. Use URL/Unix-style "/" paths if there's a path prefix. For formats like Tar or ZIP that actually store filenames within the wrapper, use the stored filename. For formats like GZIP where the extracted filename is derived from the compressed file's name, specify the name that the extraction tool conventionally derives, assuming that the user will use the filename from the URL for the local copy.

Which file is "primary"? For a format like GZIP that only wraps a single file, the primary file is obviously just the wrapped file. For multi-file wrappers like ZIP or Tar, it's up to you to pick the primary file, but there's one special consideration: if there's a playable version of the game within the archive, such as a story file or executable, you should always designate that as the primary file. This allows the "Play Online" button to play the the game given the compressed file. If there's no playable game file, pick whichever file is the most important, or whichever one the user would most directly want to open or view. For example, for a ZIP containing an HTML page and a bunch of GIFs and JPEGs displayed on the page, the HTML page would presumably be the primary.

If there's no good basis for designating a particular file as primary, you can omit simply omit this element. Note that you should still set a valid file type for the <format> element. Use the file type of the majority of the contents if possible, otherwise you can just use the generic "document" type as a last resort. For a collection of source files, for example, you'd specify the format as "text".

Restrictions

If the submitted TUID/IFID refers to an existing IFDB listing, the effect is to update the existing listing, as though using the standard IFDB listing editor Web interface. Updating a listing is allowed only if the submitting user is the same as the last user who edited the page. If another user last edited the page, an edit request is rejected. This is because we have to assume that we have a three-way merge conflict: some other user has edited the on-line listing, and meanwhile the current user has edited her private, off-line copy of the iFiction data. The current user now intends to update the on-line listing with her updated iFiction data, but we can't assume that she knows about the other user's edits to the on-line copy. We thus reject the POST update request, and instead require the current user to go to the IFDB Web-based listing editor and edit the live on-line data manually. The current user can still decide to go ahead with the updates, but by requiring her to manually edit the live on-line data, we ensure that she at least sees the other user's changes before overwriting them.

If the submitted iFiction record contains multiple IFIDs, and any of the IFIDs are already present on IFDB in existing listings, the IFIDs in the iFiction record must all refer to the same game. It's fairly obvious why this must be the case: a given IFID refers to exactly one game; so a group of IFIDs purporting to refer to the same game (by being grouped together in a single iFiction record) can't also refer to any other games. Therefore, if the IFIDs in the iFiction record refer to two or more existing IFDB listings, the request is rejected with an error. Note, however, that it's fine for there to be a mix of IFIDs that exist on IFDB and IFIDs that don't: this would be the case if the updated listing is adding IFIDs to an existing listing. The restriction is simply that all IFIDs in the iFiction record that already exist on IFDB must all point to the same listing.

The external links specified through the API are purely additive. When the API is used to update an existing listing, the external links in the request are merged with the external links from the existing record. The merging is by URL:

There's one exception to the replacement rule for a matching URL. If the link in the API request is marked as "pending", and the old link with the same URL is not marked pending, the pending flag in the request is ignored. The rationale is that the non-pending status in the existing record presumably means that the link already works for an older version of the same game, and that what's really "pending" in the new request is the replacement of that file with the new version. So even though the request client thinks of the link as pending, it's not actually a broken link; it's only pending in that the version update isn't complete yet. It seems better to keep the existing link to the old game in the interim, so that users viewing the page will find something to download, even if it's not necessarily fully up to date yet.

The reason that the external links are additive is that the links presented through the request API are presumably the files published directly by the author of the game, but existing links might refer to other "unofficial" files created by third parties, such as hints or walk-throughs. We don't want to delete those third-party files just because the author is updating her published files. So, we simply retain any existing links without changes when they're not mentioned in the request. In the simplest case, when the request doesn't include any external links, this means that we make no changes at all to the existing links.

Reply format

The server replies to the request with an XML object describing the result. The response has this tag structure for a successful transaction:

   <putific xmlns="http://ifdb.org/api/xmlns">
      <ok/>
      <ACTION-NAME/>
      <tuid>TUID (IFDB's internal ID) of the listing</tuid>
      <viewUrl>URL of page for viewing the listing</viewUrl>
      <editUrl>URL of page for editing the listing</editUrl>
   </putific>

ACTION-NAME is one of the following:

The TUID is the internal IFDB identifier for the game listing that was created or updated by the request.

The viewUrl is an http URL to the main IFDB page for the listing. The editUrl is a URL to reach a user interface for editing the listing manually.

If an error occurs, the reply has this structure:

   <putific>
      <error>
        <code>error code</code>
        <message>error message text</message>
        <detail>
          <field>field ID</field>
          <desc>description of the field</desc>
          <error>field-specific error message</detail>
        </detail>
      </error>
   </putific>

The error code is a computer-friendly identifier for the error, from the following list:

The error message text is human-readable text describing the error condition. The "detail" items are included when the error is due to an invalid value for one of the submitted iFiction fields. The field ID is the XPath description of the iFiction element containing the error, without the top-level "story" tag; for example, the "author" field is identified as "bibliographic/author". The field description is a human-readable description of the field, for display to users. The field-specific error message is a human-readable message describing the value error. The server validates all of the fields before returning an error and adds a "detail" item for each error, so there could be multiple "detail" items in the reply.

When an error is returned, the server will not have saved any portion of the updates; the whole create/update operation fails atomically. This means that no further client action is required to clean up after the failed request, and also that it's safe to simply try again by posting a new request (which the user will probably want to do if the error involved a data validation problem that can be corrected by editing the source data).